[Tooling update] health_checks

* Added:
- get_cinder_db_versions: Retrieve cinder db release codename
- get_glance_db_versions: Retrieve glance db release codename
- get_heat_db_versions: Retrieve heat db release codename
- get_neutron_db_versions: Retrieve neutron db release codename
- get_keystone_db_versions: Retrieve keystone db release codename
- get_nova_db_versions: Retrieve nova db release codename
- list_db_versions: Retrieve openstack DB release codenames

* Extra:
- fixed kitchen test (metadata import)
- fixed salt grain import: https://github.com/saltstack/salt/pull/56094
- Schema validator is not enabled. Should be added in next patch.

Change-Id: Iad318511ec6ee3d0c98c503cf66fdcf3ad146427
Prod-Related: PROD-34978
diff --git a/_modules/health_checks.py b/_modules/health_checks.py
index 97d1703..fab223c 100644
--- a/_modules/health_checks.py
+++ b/_modules/health_checks.py
@@ -23,6 +23,215 @@
 except ImportError:
     from yaml import Loader, Dumper
 
+db_ver_map=yaml.load("""
+kilo:
+  cinder: 41
+  glance: 41
+  heat: 62
+  keystone: 67
+  neutron: [ kilo ]
+  nova:
+    api_db: 2
+    db: 280
+liberty:
+  cinder: 60
+  glance: 42
+  heat: 65
+  keystone: 75
+  neutron:
+  - 1b4c6e320f79
+  - 26c371498592
+  - 599c6a226151
+  - 45f955889773
+  - 1c844d1677f7
+  - 52c5312f6baf
+  - 9859ac9c136
+  - 8675309a5c4f
+  - 48153cb5f051
+  - 31337ec0ffee
+  - 34af2b5c5a59
+  - 354db87e3225
+  - 11926bcfe72d
+  - 5498d17be016
+  - 4af11ca47297
+  - 2e5352a0ad4d
+  - 2a16083502f3
+  - 4ffceebfada
+  - 30018084ec99
+  nova:
+    api_db: 3
+    db: 302
+mitaka:
+  cinder: 72
+  glance: 44
+  heat: 71
+  keystone: 97
+  neutron:
+  - 15be73214821
+  - dce3ec7a25c9
+  - 659bf3d90664
+  - 19f26505c74f
+  - 0e66c5227a8a
+  - ec7fcfbf72ee
+  - 32e5974ada25
+  - 3894bccad37f
+  - c3a73f615e4
+  - 13cfb89f881a
+  - 1df244e556f5
+  - 2f9e956e7532
+  - 15e43b934f81
+  - 59cb5b6cf4d
+  - b4caf27aae4
+  - 31ed664953e6
+  - 8a6d8bdae39
+  - c6c112992c9
+  - 2b4c2465d44b
+  - 5ffceebfada
+  - 1b294093239c
+  - 4ffceebfcdc
+  - e3278ee65050
+  nova:
+    api_db: 7
+    db: 319
+newton:
+  cinder: 79
+  glance: 44
+  heat: 73
+  keystone:
+    contract: 1
+    data: 4
+    db: 109
+    expand: 1
+  neutron:
+  - 030a959ceafa
+  - 67daae611b6e
+  - a5648cfeeadf
+  - a963b38d82f4
+  - 6b461a21bcfc
+  - 0f5bef0f87d4
+  - d3435b514502
+  - 5cd92597d11d
+  - 3d0e74aa7d37
+  - 5abc0278ca73
+  - 30107ab6a3ee
+  - 45f8dd33480b
+  - c415aab1c048
+  - 2e0d7a8a1586
+  - 5c85685d616d
+  - a8b517cff8ab
+  - a84ccf28f06a
+  - 7d9d8eeec6ad
+  - 7bbb25278f53
+  - 89ab9a816d70
+  - 8fd3918ef6f4
+  - c879c5e1ee90
+  - b67e765a3524
+  - 3b935b28e7a0
+  - b12a3ef66e62
+  - 4bcd4df1f426
+  - 97c25b0d2353
+  nova:
+    api_db: 22
+    db: 334
+ocata:
+  cinder: 96
+  glance: 45
+  heat: 79
+  keystone:
+    contract: 1
+    data: 16
+    db: 109
+    expand: 1
+  neutron:
+  - a9c43481023c
+  - 929c968efe70
+  nova:
+    api_db: 31
+    db: 347
+pike:
+  cinder: 105
+  glance: 45
+  heat: 80
+  keystone:
+    contract: 1
+    data: 24
+    db: 109
+    expand: 1
+  neutron:
+  - 62c781cb6192
+  - 2b42d90729da
+  - 7d32f979895f
+  - 349b6fd605a6
+  - 804a3c76314c
+  - c8c222d42aa9
+  nova:
+    api_db: 45
+    db: 362
+queens:
+  cinder: 117
+  glance: 45
+  heat: 85
+  keystone:
+    contract: 1
+    data: 44
+    db: 109
+    expand: 1
+  neutron:
+  - 594422d373ee
+  nova:
+    api_db: 52
+    db: 378
+rocky:
+  cinder: 123
+  glance: 45
+  heat: 86
+  keystone:
+    contract: 1
+    data: 52
+    db: 109
+    expand: 1
+  neutron:
+  - 61663558142c
+  - 867d39095bf4
+  nova:
+    api_db: 61
+    db: 390
+stein:
+  cinder: 128
+  glance: 45
+  heat: 86
+  keystone:
+    contract: 1
+    data: 61
+    db: 109
+    expand: 1
+  neutron:
+  - 0ff9e3881597
+  - 195176fb410d
+  - d72db3e25539
+  - fb0167bd9639
+  - 9bfad3f1e780
+  - cada2437bf41
+  nova:
+    api_db: 62
+    db: 391
+train:
+  cinder: 132
+  glance: 45
+  heat: 86
+  keystone:
+    contract: 1
+    data: 71
+    db: 109
+    expand: 1
+  neutron:
+  - 63fd95af7dcd
+  - c613d0b82681
+  nova:
+    api_db: 67
+    db: 402
+""")
+
 default_vrouter_info_map = yaml.load("""
 ContrailConfig:
 - deleted
@@ -1956,3 +2165,291 @@
             namespaces.append(netns)
 
     return namespaces
+
+
+def _load_mysql_module():
+
+    # Check if module is loaded
+    # It can be loaded by parent function
+    # In case of direct funtction call, we load it
+    mod_not_loaded = False
+    try:
+        dir(MySQLdb)
+    except:
+        # Not loaded. Trying to load
+        mod_not_loaded = True
+
+    if mod_not_loaded:
+        try:
+            import MySQLdb
+        except:
+            logger.error("Python library MySQLdb could not be loaded. Install it first")
+            __context__['retcode'] = 2
+            return False
+
+    return MySQLdb
+
+
+def get_keystone_db_versions(db_host, db_user, db_pass, db_name="keystone"):
+
+    ''' Return dict of keystone DB versions '''
+
+    MySQLdb = _load_mysql_module()
+    if not MySQLdb:
+        return MySQLdb
+
+    keystone_versions = {}
+    db = MySQLdb.connect(db_host, db_user, db_pass, db_name)
+    cursor = db.cursor()
+
+    cursor.execute("select `version` from `migrate_version` where `repository_id` like 'keystone'")
+    if cursor.rowcount == 1:
+        keystone_db_ver = cursor.fetchone()[0]
+    else:
+        keystone_db_ver = 0
+
+    if keystone_db_ver > 0:
+        cursor.execute("select `version` from `migrate_version` where `repository_id` like 'keystone_data_migrate'")
+        if cursor.rowcount == 1:
+            keystone_data_ver = cursor.fetchone()[0]
+        else:
+            keystone_data_ver = 0
+    db.close()
+
+    keystone_versions['db'] = keystone_db_ver
+    keystone_versions['data_db'] = keystone_data_ver
+    for release in db_ver_map:
+        keystone_obj = db_ver_map[release]["keystone"]
+        if isinstance(keystone_obj, dict):
+            keystone_db_map = int(keystone_obj["db"])
+            keystone_data_map = int(keystone_obj["data"])
+            if keystone_db_map == keystone_db_ver and keystone_data_map == keystone_data_ver:
+                keystone_versions['os_release'] = release
+        else:
+            keystone_db_map = int(keystone_obj)
+            if keystone_db_map == keystone_db_ver:
+                keystone_versions['os_release'] = release
+
+    return keystone_versions
+
+
+def get_glance_db_versions(db_host, db_user, db_pass, db_name="glance"):
+
+    ''' Return dict of glance DB versions '''
+
+    MySQLdb = _load_mysql_module()
+    if not MySQLdb:
+        return MySQLdb
+
+    glance_versions = {}
+    db = MySQLdb.connect(db_host, db_user, db_pass, db_name)
+    cursor = db.cursor()
+
+    glance_release = "unknown"
+    cursor.execute("select `version` from `migrate_version` where `repository_id` like 'Glance Migrations'")
+    if cursor.rowcount == 1:
+        glance_db_ver = cursor.fetchone()[0]
+        for release in db_ver_map:
+            glance_obj = db_ver_map[release]["glance"]
+            glance_db_map = int(glance_obj)
+            if glance_db_map == glance_db_ver:
+                glance_release = release
+    else:
+        glance_db_ver = 0
+    db.close()
+
+    db = MySQLdb.connect(db_host, db_user, db_pass, db_name)
+    cursor = db.cursor()
+    try:
+        cursor.execute("select `version_num` from `alembic_version`")
+    except:
+        pass
+    if cursor.rowcount == 1:
+        glance_release = cursor.fetchone()[0]
+        for release in db_ver_map:
+            if release in glance_release:
+                glance_db_ver = db_ver_map[release]["glance"]
+                glance_release = "%s (alembic)" % release
+    db.close()
+
+    glance_versions['db'] = glance_db_ver
+    glance_versions['os_release'] = glance_release
+
+    return glance_versions
+
+
+def get_cinder_db_versions(db_host, db_user, db_pass, db_name="cinder"):
+
+    ''' Return dict of cinder DB versions '''
+
+    MySQLdb = _load_mysql_module()
+    if not MySQLdb:
+        return MySQLdb
+
+    cinder_versions = {}
+    db = MySQLdb.connect(db_host, db_user, db_pass, db_name)
+    cursor = db.cursor()
+
+    cursor.execute("select `version` from `migrate_version` where `repository_id` like 'cinder'")
+    if cursor.rowcount == 1:
+        cinder_db_ver = cursor.fetchone()[0]
+    else:
+        cinder_db_ver = 0
+    db.close()
+
+    cinder_release = ""
+    for release in db_ver_map:
+        cinder_obj = db_ver_map[release]["cinder"]
+        cinder_db_map = int(cinder_obj)
+        if cinder_db_map == cinder_db_ver:
+            cinder_release = release
+
+    cinder_versions['db'] = cinder_db_ver
+    cinder_versions['os_release'] = cinder_release
+
+    return cinder_versions
+
+
+def get_heat_db_versions(db_host, db_user, db_pass, db_name="heat"):
+
+    ''' Return dict of heat DB versions '''
+
+    MySQLdb = _load_mysql_module()
+    if not MySQLdb:
+        return MySQLdb
+
+    heat_versions = {}
+    db = MySQLdb.connect(db_host, db_user, db_pass, db_name)
+    cursor = db.cursor()
+
+    cursor.execute("select `version` from `migrate_version` where `repository_id` like 'heat'")
+    if cursor.rowcount == 1:
+        heat_db_ver = cursor.fetchone()[0]
+    else:
+        heat_db_ver = 0
+    db.close()
+
+    heat_release = ""
+    for release in db_ver_map:
+        heat_obj = db_ver_map[release]["heat"]
+        heat_db_map = int(heat_obj)
+        if heat_db_map == heat_db_ver:
+            heat_release = release
+
+    heat_versions['db'] = heat_db_ver
+    heat_versions['os_release'] = heat_release
+
+    return heat_versions
+
+
+def get_neutron_db_versions(db_host, db_user, db_pass, db_name="neutron"):
+
+    ''' Return dict of neutron DB versions '''
+
+    MySQLdb = _load_mysql_module()
+    if not MySQLdb:
+        return MySQLdb
+
+    neutron_versions = {}
+    db = MySQLdb.connect(db_host, db_user, db_pass, db_name)
+    cursor = db.cursor()
+
+    try:
+        cursor.execute("select `version_num` from `alembic_version`")
+        neutron_db_versions_raw = cursor.fetchall()
+        neutron_db_versions = []
+        for el in neutron_db_versions_raw:
+            neutron_db_versions.append(el[0])
+    except:
+        neutron_db_versions = []
+
+    db.close()
+
+    neutron_release = "unknown (no marker found)"
+    for release in db_ver_map:
+        for commit_id in neutron_db_versions:
+            if commit_id in db_ver_map[release]["neutron"]:
+                neutron_release = release
+
+    neutron_versions['db_versions'] = neutron_db_versions
+    neutron_versions['os_release'] = neutron_release
+
+    return neutron_versions
+
+
+def get_nova_db_versions(db_host, db_user, db_pass, db_name="nova", db_api_name="nova_api", db_api_pass=""):
+
+    ''' Return dict of nova DB versions '''
+
+    MySQLdb = _load_mysql_module()
+    if not MySQLdb:
+        return MySQLdb
+
+    if not db_api_pass:
+        db_api_pass = db_pass
+
+    nova_versions = {}
+    db = MySQLdb.connect(db_host, db_user, db_pass, db_name)
+    cursor = db.cursor()
+
+    cursor.execute("select `version` from `migrate_version` where `repository_id` like 'nova'")
+    if cursor.rowcount == 1:
+        nova_db_ver = cursor.fetchone()[0]
+    else:
+        nova_db_ver = 0
+    db.close()
+
+    db = MySQLdb.connect(db_host, db_user, db_pass, db_api_name)
+    cursor = db.cursor()
+    if nova_db_ver > 0:
+        cursor.execute("select `version` from `migrate_version` where `repository_id` like 'nova_api'")
+        if cursor.rowcount == 1:
+            nova_apidb_ver = cursor.fetchone()[0]
+        else:
+            nova_apidb_ver = 0
+    db.close()
+
+    nova_versions['db'] = nova_db_ver
+    nova_versions['api_db'] = nova_apidb_ver
+    for release in db_ver_map:
+        nova_obj = db_ver_map[release]["nova"]
+        if isinstance(nova_obj, dict):
+            nova_db_map = int(nova_obj["db"])
+            nova_apidb_map = int(nova_obj["api_db"])
+            if nova_db_map == nova_db_ver and nova_apidb_map == nova_apidb_ver:
+                nova_versions['os_release'] = release
+        else:
+            nova_db_map = int(nova_obj)
+            if nova_db_map == nova_db_ver:
+                nova_versions['os_release'] = release
+
+    return nova_versions
+
+
+def list_db_versions():
+
+    ''' Retrieve openstack DB release codenames '''
+
+    db_host = str(__salt__['pillar.get']('_param:openstack_database_address'))
+    cinder_db_pass = str(__salt__['pillar.get']('_param:mysql_cinder_password'))
+    glance_db_pass = str(__salt__['pillar.get']('_param:mysql_glance_password'))
+    heat_db_pass = str(__salt__['pillar.get']('_param:mysql_heat_password'))
+    keystone_db_pass = str(__salt__['pillar.get']('_param:mysql_keystone_password'))
+    neutron_db_pass = str(__salt__['pillar.get']('_param:mysql_neutron_password'))
+    nova_db_pass = str(__salt__['pillar.get']('_param:mysql_nova_password'))
+    cinder_db_user = str(__salt__['pillar.get']('_param:mysql_cinder_username'))
+    glance_db_user = str(__salt__['pillar.get']('_param:mysql_glance_username'))
+    heat_db_user = str(__salt__['pillar.get']('_param:mysql_heat_username'))
+    keystone_db_user = str(__salt__['pillar.get']('_param:mysql_keystone_username'))
+    neutron_db_user = str(__salt__['pillar.get']('_param:mysql_neutron_username'))
+    nova_db_user = str(__salt__['pillar.get']('_param:mysql_nova_username'))
+
+    os_db_releases = {}
+    os_db_releases['cinder'] = get_cinder_db_versions(db_host, cinder_db_user, cinder_db_pass)
+    os_db_releases['glance'] = get_glance_db_versions(db_host, glance_db_user, glance_db_pass)
+    os_db_releases['heat'] = get_heat_db_versions(db_host, heat_db_user, heat_db_pass)
+    os_db_releases['neutron'] = get_neutron_db_versions(db_host, neutron_db_user, neutron_db_pass)
+    os_db_releases['keystone'] = get_keystone_db_versions(db_host, keystone_db_user, keystone_db_pass)
+    os_db_releases['nova'] = get_nova_db_versions(db_host, nova_db_user, nova_db_pass)
+
+    return os_db_releases