Initial commit

Change-Id: I680e0343e1d37b185d334c0133ebb155ce7de9be
diff --git a/_modules/runtest/__init__.py b/_modules/runtest/__init__.py
new file mode 100644
index 0000000..f95e556
--- /dev/null
+++ b/_modules/runtest/__init__.py
@@ -0,0 +1,141 @@
+# -*- coding: utf-8 -*-
+'''
+'''
+
+import jinja2
+import logging
+import re
+import requests
+
+import pkg_resources
+
+import salt.pillar
+
+from runtest import tempest_sections
+from runtest.utils import requirements
+
+log = logging.getLogger(__name__)
+
+__virtualname__ = 'runtest'
+
+
+def __virtual__():
+    return __virtualname__
+
+
+CFG_TEMPLATE = """
+{%- for section,options in config.items() -%}
+[{{ section }}]
+{%- for opt,val in options.items() %}
+{{ opt }} = {{ val }}
+{%- endfor %}
+
+{% endfor %}
+"""
+
+REQUIREMENTS_LINK='https://raw.githubusercontent.com/openstack/requirements'
+
+def _get_pillar_for_live_minions(timeout=5, gather_job_timeout=15):
+    client = salt.client.get_local_client(__opts__['conf_file'])
+    pillars = client.cmd('*', 'pillar.items', timeout=timeout,
+                         gather_job_timeout=gather_job_timeout)
+
+    return pillars
+
+def generate_tempest_config(dst, *args, **kwargs):
+
+    pillars = _get_pillar_for_live_minions()
+    this_node_pillar = __opts__['pillar']
+    runtest_opts =  this_node_pillar.get(__virtualname__, {}).get('tempest', {})
+    config = {}
+
+    for ts in tempest_sections.SECTIONS:
+        ts_inst = ts(pillars, runtest_opts)
+        config[ts_inst.name] = {}
+        opts = {}
+        for opt in ts_inst.options:
+            val = getattr(ts_inst, opt)
+            if val is None:
+                val = runtest_opts.get(ts_inst.name, {}).get(opt, None)
+
+            if val is not None:
+                opts[opt] = val
+
+        config[ts_inst.name] = opts
+
+    data = jinja2.Environment().from_string(CFG_TEMPLATE).render(config=config)
+    with open(dst, 'w') as cfg_file:
+        cfg_file.write(data)
+
+    return config
+
+def check_global_requirements(openstack_version=None, url=None):
+    """Check if our installed requirements are satisfied with upstream.
+    """
+    if openstack_version is None:
+      openstack_version = __salt__['pillar.get']('runtest:openstack_version')
+    if url is None:
+      url = '%s/%s/global-requirements.txt' % (REQUIREMENTS_LINK, openstack_version)
+
+    requirements_content = requests.get(url).text
+    pkgs = requirements.get_installed_distributions()
+
+    req_deps = {}
+    freq = {}
+    for pkg in pkgs:
+      for line in requirements_content.splitlines():
+        if re.match(r'^%s[ ><!=]' % pkg.project_name, line):
+            req_cond = line.split('#')[0].strip()
+            try:
+                log.info("Checking requirement %(req_name)s, requirement"
+                         " %(req)s".format(req_name=pkg.project_name,
+                                           req=req_cond))
+                pkg_resources.working_set.require(req_cond)
+            except pkg_resources.VersionConflict as ve:
+                log.warning("Requirement %(req_name)s not satisfied. Required "
+                            "version %(req_cond)s. Installed version "
+                            "%(inst_version)s. Exception %(exc)s".format(
+                                req_name=pkg.project_name, req_cond=line,
+                                inst_version=pkg.version, exc=ve))
+                freq[pkg.project_name] = {'installed_version': pkg.version,
+                                          'requirement': line}
+            except Exception as ex: {'error': ex,
+                                     'requirement': line,
+                                     'installed_version': pkg.version}
+    return freq
+
+def check_upper_constraints(openstack_version=None, url=None):
+    """Check if our installed requirements are satisfied with upstream upper constraints.
+    """
+    if openstack_version is None:
+      openstack_version = __salt__['pillar.get']('runtest:openstack_version')
+    if url is None:
+      url = '%s/%s/upper-constraints.txt' % (REQUIREMENTS_LINK, openstack_version)
+
+    constraints_content = requests.get(url).text
+    pkgs = requirements.get_installed_distributions()
+
+    req_deps = {}
+    freq = {}
+    for pkg in pkgs:
+      for line in constraints_content.splitlines():
+        if re.match(r'^%s[ ><!=]' % pkg.project_name, line):
+            req_cond = line.split('#')[0].strip()
+            try:
+                log.info("Checking requirement %(req_name)s, requirement"
+                         " %(req)s".format(req_name=pkg.project_name,
+                                           req=req_cond))
+                pkg_resources.working_set.require(req_cond)
+            except pkg_resources.VersionConflict as ve:
+                log.warning("Requirement %(req_name)s not satisfied. Required "
+                            "version %(req_cond)s. Installed version "
+                            "%(inst_version)s. Exception %(exc)s".format(
+                                req_name=pkg.project_name, req_cond=line,
+                                inst_version=pkg.version, exc=ve))
+                freq[pkg.project_name] = {'installed_version': pkg.version,
+                                          'requirement': line,
+                                          'error': 've'}
+            except Exception as ex: {'error': ex,
+                                     'requirement': line,
+                                     'installed_version': pkg.version}
+    return freq
diff --git a/_modules/runtest/conditions.py b/_modules/runtest/conditions.py
new file mode 100644
index 0000000..05fe24e
--- /dev/null
+++ b/_modules/runtest/conditions.py
@@ -0,0 +1,51 @@
+import jsonpath_rw as jsonpath
+import operator
+
+
+class SimpleCondition(object):
+    op = None
+
+    def check(self, value, expected):
+        return self.op(value, expected)
+
+
+class EqCondition(SimpleCondition):
+    op = operator.eq
+
+OPERATORS = {
+  'eq': EqCondition,
+}
+
+class BaseRule(object):
+
+    def __init__(self, field, op, val, multiple='first'):
+        self.field = field
+        self.op = op
+        self.value = val
+        self.multiple = multiple
+
+    def check(self, pillar):
+        """Check if condition match in the passed pillar.
+
+        :param pillar: pillar data to check for condition in.
+        :return: True if condition match, False otherwise.
+        """
+
+        res = False
+        count = 0
+        for match in jsonpath.parse(self.field).find(pillar):
+            cond_ext = OPERATORS[self.op]()
+            res = cond_ext.check(match.value, self.value)
+            if (self.multiple == 'first' or
+                (self.multiple == 'all' and not res) or
+                (self.multiple == 'any' and res)):
+                    break
+            elif self.multiple == 'multiple' and res:
+                count += 1
+                if count > 1:
+                    return True
+
+        if not res or self.multiple == 'multiple':
+            return False
+
+        return True
diff --git a/_modules/runtest/tempest_sections/__init__.py b/_modules/runtest/tempest_sections/__init__.py
new file mode 100644
index 0000000..bfa5c8f
--- /dev/null
+++ b/_modules/runtest/tempest_sections/__init__.py
@@ -0,0 +1,57 @@
+
+import auth
+
+import baremetal
+import baremetal_feature_enabled
+import compute
+import compute_feature_enabled
+import debug
+import default
+import dns
+import dns_feature_enabled
+import heat_plugin
+import identity
+import identity_feature_enabled
+import image
+import image_feature_enabled
+import network
+import network_feature_enabled
+import object_storage
+import object_storage_feature_enabled
+import orchestration
+import oslo_concurrency
+import scenario
+import service_clients
+import service_available
+import validation
+import volume
+import volume_feature_enabled
+
+SECTIONS = [
+    auth.Auth,
+    baremetal.Baremetal,
+    baremetal_feature_enabled.BaremetalFeatureEnabled,
+    compute.Compute,
+    compute_feature_enabled.ComputeFeatureEnabled,
+    debug.Debug,
+    default.Default,
+    dns.Dns,
+    dns_feature_enabled.DnsFeatureEnabled,
+    heat_plugin.HeatPlugin,
+    identity.Identity,
+    identity_feature_enabled.IdentityFeatureEnabled,
+    image.Image,
+    image_feature_enabled.ImageFeatureEnabled,
+    network.Network,
+    network_feature_enabled.NetworkFeatureEnabled,
+    object_storage.ObjectStorage,
+    object_storage_feature_enabled.ObjectStorageFeatureEnabled,
+    orchestration.Orchestration,
+    oslo_concurrency.OsloConcurrency,
+    scenario.Scenario,
+    service_clients.ServiceClients,
+    service_available.ServiceAvailable,
+    validation.Validation,
+    volume.Volume,
+    volume_feature_enabled.VolumeFeatureEnabled,
+]
diff --git a/_modules/runtest/tempest_sections/auth.py b/_modules/runtest/tempest_sections/auth.py
new file mode 100644
index 0000000..83a5748
--- /dev/null
+++ b/_modules/runtest/tempest_sections/auth.py
@@ -0,0 +1,62 @@
+
+from runtest import conditions
+from runtest.tempest_sections import base_section
+
+class Auth(base_section.BaseSection):
+
+    name = "auth"
+    options = [
+        'admin_domain_name',
+        'admin_password',
+        'admin_project_name',
+        'admin_username',
+        'create_isolated_networks',
+        'default_credentials_domain_name',
+        'tempest_roles',
+        'test_accounts_file',
+        'use_dynamic_credentials',
+    ]
+
+
+    @property
+    def admin_domain_name(self):
+        pass
+
+    @property
+    def admin_password(self):
+        c = conditions.BaseRule('keystone.server.enabled', 'eq', True)
+        return self.get_item_when_condition_match(
+            'keystone.server.admin_password', c)
+
+    @property
+    def admin_project_name(self):
+        c = conditions.BaseRule('keystone.server.enabled', 'eq', True)
+        return self.get_item_when_condition_match(
+            'keystone.server.admin_tenant', c)
+
+    @property
+    def admin_username(self):
+        c = conditions.BaseRule('keystone.server.enabled', 'eq', True)
+        return self.get_item_when_condition_match(
+            'keystone.server.admin_name', c)
+
+    @property
+    def create_isolated_networks(self):
+        pass
+
+    @property
+    def default_credentials_domain_name(self):
+        pass
+
+    @property
+    def tempest_roles(self):
+        pass
+
+    @property
+    def test_accounts_file(self):
+        pass
+
+    @property
+    def use_dynamic_credentials(self):
+        pass
+
diff --git a/_modules/runtest/tempest_sections/baremetal.py b/_modules/runtest/tempest_sections/baremetal.py
new file mode 100644
index 0000000..d93c210
--- /dev/null
+++ b/_modules/runtest/tempest_sections/baremetal.py
@@ -0,0 +1,99 @@
+
+import base_section
+
+class Baremetal(base_section.BaseSection):
+
+    name = "baremetal"
+    options = [
+        'active_timeout',
+        'adjusted_root_disk_size_gb',
+        'association_timeout',
+        'catalog_type',
+        'deploywait_timeout',
+        'driver',
+        'enabled_drivers',
+        'enabled_hardware_types',
+        'endpoint_type',
+        'max_microversion',
+        'min_microversion',
+        'partition_image_ref',
+        'power_timeout',
+        'unprovision_timeout',
+        'use_provision_network',
+        'whole_disk_image_checksum',
+        'whole_disk_image_ref',
+        'whole_disk_image_url',
+    ]
+
+
+    @property
+    def active_timeout(self):
+        pass
+
+    @property
+    def adjusted_root_disk_size_gb(self):
+        pass
+
+    @property
+    def association_timeout(self):
+        pass
+
+    @property
+    def catalog_type(self):
+        pass
+
+    @property
+    def deploywait_timeout(self):
+        pass
+
+    @property
+    def driver(self):
+        pass
+
+    @property
+    def enabled_drivers(self):
+        pass
+
+    @property
+    def enabled_hardware_types(self):
+        pass
+
+    @property
+    def endpoint_type(self):
+        pass
+
+    @property
+    def max_microversion(self):
+        pass
+
+    @property
+    def min_microversion(self):
+        pass
+
+    @property
+    def partition_image_ref(self):
+        pass
+
+    @property
+    def power_timeout(self):
+        pass
+
+    @property
+    def unprovision_timeout(self):
+        pass
+
+    @property
+    def use_provision_network(self):
+        pass
+
+    @property
+    def whole_disk_image_checksum(self):
+        pass
+
+    @property
+    def whole_disk_image_ref(self):
+        pass
+
+    @property
+    def whole_disk_image_url(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/baremetal_feature_enabled.py b/_modules/runtest/tempest_sections/baremetal_feature_enabled.py
new file mode 100644
index 0000000..3f83520
--- /dev/null
+++ b/_modules/runtest/tempest_sections/baremetal_feature_enabled.py
@@ -0,0 +1,14 @@
+
+import base_section
+
+class BaremetalFeatureEnabled(base_section.BaseSection):
+
+    name = "baremetal_feature_enabled"
+    options = [
+        'ipxe_enabled',
+    ]
+
+
+    @property
+    def ipxe_enabled(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/base_section.py b/_modules/runtest/tempest_sections/base_section.py
new file mode 100644
index 0000000..867386f
--- /dev/null
+++ b/_modules/runtest/tempest_sections/base_section.py
@@ -0,0 +1,68 @@
+
+import abc
+import jsonpath_rw as jsonpath
+
+import salt
+
+from runtest import conditions
+
+class BaseSection(object):
+
+    def __init__(self, pillar, runtest_opts):
+        super(BaseSection, self).__init__()
+        self.pillar = pillar
+        self.runtest_opts = runtest_opts
+        self.salt_client = salt.client.get_local_client()
+
+    @abc.abstractproperty
+    def name(self):
+        """"""
+
+    @abc.abstractproperty
+    def options():
+        """"""
+
+    def get_item_when_condition_match(self, item_path, condition):
+        """Get specified item from pillar when condition match.
+        """
+
+        for node, npillar in self.pillar.items():
+            for match in jsonpath.parse(condition.field).find(npillar):
+                if condition.op == 'eq':
+                    if match.value == condition.value:
+                        res = jsonpath.parse(item_path).find(npillar)
+                        if res:
+                            return res[0].value
+
+    def get_nodes_where_condition_match(self, condition):
+        """ Return a list of nodes that have pillar that matches condition.
+        """
+
+        res = []
+        for node, npillar in self.pillar.items():
+            if condition.check(npillar):
+                res.append(node)
+        return res
+
+    def authenticated_openstack_module_call(self, target, module, *args, **kwargs):
+        """Calls specified openstack module from admin keystone user.
+        """
+        auth_profile = {}
+        ks = conditions.BaseRule(field='keystone.server.enabled', op='eq', val=True)
+        auth_profile['connection_password'] = self.get_item_when_condition_match(
+            'keystone.server.admin_password', ks)
+        auth_profile['connection_user'] = self.get_item_when_condition_match(
+            'keystone.server.admin_name', ks)
+        auth_profile['connection_tenant'] = self.get_item_when_condition_match(
+            'keystone.server.admin_tenant', ks)
+        auth_profile['connection_region_name'] = self.get_item_when_condition_match(
+            'keystone.server.region', ks)
+        address = self.get_item_when_condition_match(
+            'keystone.server.bind.public_address', ks)
+        port = self.get_item_when_condition_match('keystone.server.bind.public_port', ks)
+        auth_profile['connection_auth_url'] = "http://{}:{}/v2.0".format(address, port)
+
+        kwargs.update(auth_profile)
+
+        return self.salt_client.cmd(target, 'neutronng.list_networks', timeout=5,
+                                    gather_job_timeout=15, arg=args, kwarg=kwargs)
diff --git a/_modules/runtest/tempest_sections/compute.py b/_modules/runtest/tempest_sections/compute.py
new file mode 100644
index 0000000..4d9ee33
--- /dev/null
+++ b/_modules/runtest/tempest_sections/compute.py
@@ -0,0 +1,94 @@
+
+import base_section
+
+class Compute(base_section.BaseSection):
+
+    name = "compute"
+    options = [
+        'build_interval',
+        'build_timeout',
+        'catalog_type',
+        'endpoint_type',
+        'fixed_network_name',
+        'flavor_ref',
+        'flavor_ref_alt',
+        'hypervisor_type',
+        'image_ref',
+        'image_ref_alt',
+        'max_microversion',
+        'min_compute_nodes',
+        'min_microversion',
+        'ready_wait',
+        'region',
+        'shelved_offload_time',
+        'volume_device_name',
+    ]
+
+
+    @property
+    def build_interval(self):
+        pass
+
+    @property
+    def build_timeout(self):
+        pass
+
+    @property
+    def catalog_type(self):
+        pass
+
+    @property
+    def endpoint_type(self):
+        pass
+
+    @property
+    def fixed_network_name(self):
+        pass
+
+    @property
+    def flavor_ref(self):
+        pass
+
+    @property
+    def flavor_ref_alt(self):
+        pass
+
+    @property
+    def hypervisor_type(self):
+        pass
+
+    @property
+    def image_ref(self):
+        pass
+
+    @property
+    def image_ref_alt(self):
+        pass
+
+    @property
+    def max_microversion(self):
+        pass
+
+    @property
+    def min_compute_nodes(self):
+        pass
+
+    @property
+    def min_microversion(self):
+        pass
+
+    @property
+    def ready_wait(self):
+        pass
+
+    @property
+    def region(self):
+        pass
+
+    @property
+    def shelved_offload_time(self):
+        pass
+
+    @property
+    def volume_device_name(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/compute_feature_enabled.py b/_modules/runtest/tempest_sections/compute_feature_enabled.py
new file mode 100644
index 0000000..6f787f9
--- /dev/null
+++ b/_modules/runtest/tempest_sections/compute_feature_enabled.py
@@ -0,0 +1,156 @@
+
+import base_section
+
+from runtest import conditions
+
+class ComputeFeatureEnabled(base_section.BaseSection):
+
+    name = "compute-feature-enabled"
+    options = [
+        'api_extensions',
+        'attach_encrypted_volume',
+        'block_migrate_cinder_iscsi',
+        'block_migration_for_live_migration',
+        'change_password',
+        'cold_migration',
+        'config_drive',
+        'console_output',
+        'disk_config',
+        'enable_instance_password',
+        'interface_attach',
+        'live_migrate_back_and_forth',
+        'live_migration',
+        'metadata_service',
+        'nova_cert',
+        'pause',
+        'personality',
+        'rdp_console',
+        'rescue',
+        'resize',
+        'scheduler_available_filters',
+        'serial_console',
+        'shelve',
+        'snapshot',
+        'spice_console',
+        'suspend',
+        'swap_volume',
+        'vnc_console',
+    ]
+
+
+    @property
+    def api_extensions(self):
+        pass
+
+    @property
+    def attach_encrypted_volume(self):
+        pass
+
+    @property
+    def block_migrate_cinder_iscsi(self):
+        pass
+
+    @property
+    def block_migration_for_live_migration(self):
+        pass
+
+    @property
+    def change_password(self):
+        pass
+
+    @property
+    def cold_migration(self):
+        pass
+
+    @property
+    def config_drive(self):
+        pass
+
+    @property
+    def console_output(self):
+        pass
+
+    @property
+    def disk_config(self):
+        pass
+
+    @property
+    def enable_instance_password(self):
+        pass
+
+    @property
+    def interface_attach(self):
+        pass
+
+    @property
+    def live_migrate_back_and_forth(self):
+        pass
+
+    @property
+    def live_migration(self):
+        return conditions.BaseRule('*.nova.compute.enabled', 'eq', True,
+                                   multiple='multiple').check(self.pillar)
+
+    @property
+    def metadata_service(self):
+        pass
+
+    @property
+    def nova_cert(self):
+        pass
+
+    @property
+    def pause(self):
+        pass
+
+    @property
+    def personality(self):
+        pass
+
+    @property
+    def rdp_console(self):
+        pass
+
+    @property
+    def rescue(self):
+        pass
+
+    @property
+    def resize(self):
+        # NOTE(vsaienko) allow_resize_to_same_host is hardcoded to True in
+        # nova formula, update when value is configurable.
+        res = conditions.BaseRule('*.nova.compute.enabled', 'eq', True,
+                            multiple='any').check(self.pillar)
+        return res
+
+    @property
+    def scheduler_available_filters(self):
+        pass
+
+    @property
+    def serial_console(self):
+        pass
+
+    @property
+    def shelve(self):
+        pass
+
+    @property
+    def snapshot(self):
+        pass
+
+    @property
+    def spice_console(self):
+        pass
+
+    @property
+    def suspend(self):
+        pass
+
+    @property
+    def swap_volume(self):
+        pass
+
+    @property
+    def vnc_console(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/debug.py b/_modules/runtest/tempest_sections/debug.py
new file mode 100644
index 0000000..21d4154
--- /dev/null
+++ b/_modules/runtest/tempest_sections/debug.py
@@ -0,0 +1,14 @@
+
+import base_section
+
+class Debug(base_section.BaseSection):
+
+    name = "debug"
+    options = [
+        'trace_requests',
+    ]
+
+
+    @property
+    def trace_requests(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/default.py b/_modules/runtest/tempest_sections/default.py
new file mode 100644
index 0000000..0c2761f
--- /dev/null
+++ b/_modules/runtest/tempest_sections/default.py
@@ -0,0 +1,139 @@
+
+import base_section
+
+class Default(base_section.BaseSection):
+
+    name = "DEFAULT"
+    options = [
+        'debug',
+        'log_config_append',
+        'log_date_format',
+        'log_file',
+        'log_dir',
+        'watch_log_file',
+        'use_syslog',
+        'use_journal',
+        'syslog_log_facility',
+        'use_json',
+        'use_stderr',
+        'logging_context_format_string',
+        'logging_default_format_string',
+        'logging_debug_format_suffix',
+        'logging_exception_prefix',
+        'logging_user_identity_format',
+        'default_log_levels',
+        'publish_errors',
+        'instance_format',
+        'instance_uuid_format',
+        'rate_limit_interval',
+        'rate_limit_burst',
+        'rate_limit_except_level',
+        'fatal_deprecations',
+        'resources_prefix',
+        'pause_teardown',
+    ]
+
+
+    @property
+    def debug(self):
+        pass
+
+    @property
+    def log_config_append(self):
+        pass
+
+    @property
+    def log_date_format(self):
+        pass
+
+    @property
+    def log_file(self):
+        pass
+
+    @property
+    def log_dir(self):
+        pass
+
+    @property
+    def watch_log_file(self):
+        pass
+
+    @property
+    def use_syslog(self):
+        pass
+
+    @property
+    def use_journal(self):
+        pass
+
+    @property
+    def syslog_log_facility(self):
+        pass
+
+    @property
+    def use_json(self):
+        pass
+
+    @property
+    def use_stderr(self):
+        pass
+
+    @property
+    def logging_context_format_string(self):
+        pass
+
+    @property
+    def logging_default_format_string(self):
+        pass
+
+    @property
+    def logging_debug_format_suffix(self):
+        pass
+
+    @property
+    def logging_exception_prefix(self):
+        pass
+
+    @property
+    def logging_user_identity_format(self):
+        pass
+
+    @property
+    def default_log_levels(self):
+        pass
+
+    @property
+    def publish_errors(self):
+        pass
+
+    @property
+    def instance_format(self):
+        pass
+
+    @property
+    def instance_uuid_format(self):
+        pass
+
+    @property
+    def rate_limit_interval(self):
+        pass
+
+    @property
+    def rate_limit_burst(self):
+        pass
+
+    @property
+    def rate_limit_except_level(self):
+        pass
+
+    @property
+    def fatal_deprecations(self):
+        pass
+
+    @property
+    def resources_prefix(self):
+        pass
+
+    @property
+    def pause_teardown(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/dns.py b/_modules/runtest/tempest_sections/dns.py
new file mode 100644
index 0000000..9b18b6a
--- /dev/null
+++ b/_modules/runtest/tempest_sections/dns.py
@@ -0,0 +1,44 @@
+
+import base_section
+
+class Dns(base_section.BaseSection):
+
+    name = "dns"
+    options = [
+        'build_interval',
+        'build_timeout',
+        'catalog_type',
+        'endpoint_type',
+        'min_ttl',
+        'nameservers',
+        'query_timeout',
+    ]
+
+
+    @property
+    def build_interval(self):
+        pass
+
+    @property
+    def build_timeout(self):
+        pass
+
+    @property
+    def catalog_type(self):
+        pass
+
+    @property
+    def endpoint_type(self):
+        pass
+
+    @property
+    def min_ttl(self):
+        pass
+
+    @property
+    def nameservers(self):
+        pass
+
+    @property
+    def query_timeout(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/dns_feature_enabled.py b/_modules/runtest/tempest_sections/dns_feature_enabled.py
new file mode 100644
index 0000000..b3fb061
--- /dev/null
+++ b/_modules/runtest/tempest_sections/dns_feature_enabled.py
@@ -0,0 +1,44 @@
+
+import base_section
+
+class DnsFeatureEnabled(base_section.BaseSection):
+
+    name = "dns_feature_enabled"
+    options = [
+        'api_admin',
+        'api_v1',
+        'api_v1_servers',
+        'api_v2',
+        'api_v2_quotas',
+        'api_v2_root_recordsets',
+        'bug_1573141_fixed',
+    ]
+
+
+    @property
+    def api_admin(self):
+        pass
+
+    @property
+    def api_v1(self):
+        pass
+
+    @property
+    def api_v1_servers(self):
+        pass
+
+    @property
+    def api_v2(self):
+        pass
+
+    @property
+    def api_v2_quotas(self):
+        pass
+
+    @property
+    def api_v2_root_recordsets(self):
+        pass
+
+    @property
+    def bug_1573141_fixed(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/heat_plugin.py b/_modules/runtest/tempest_sections/heat_plugin.py
new file mode 100644
index 0000000..0f2e16f
--- /dev/null
+++ b/_modules/runtest/tempest_sections/heat_plugin.py
@@ -0,0 +1,224 @@
+
+import base_section
+
+class HeatPlugin(base_section.BaseSection):
+
+    name = "heat_plugin"
+    options = [
+        'admin_password',
+        'admin_project_name',
+        'admin_username',
+        'auth_url',
+        'auth_version',
+        'boot_config_env',
+        'build_interval',
+        'build_timeout',
+        'ca_file',
+        'catalog_type',
+        'connectivity_timeout',
+        'convergence_engine_enabled',
+        'disable_ssl_certificate_validation',
+        'fixed_network_name',
+        'fixed_subnet_name',
+        'floating_network_name',
+        'heat_config_notify_script',
+        'image_ref',
+        'instance_type',
+        'ip_version_for_ssh',
+        'keypair_name',
+        'minimal_image_ref',
+        'minimal_instance_type',
+        'network_for_ssh',
+        'password',
+        'project_domain_id',
+        'project_domain_name',
+        'project_name',
+        'region',
+        'sighup_config_edit_retries',
+        'sighup_timeout',
+        'skip_functional_test_list',
+        'skip_functional_tests',
+        'skip_scenario_test_list',
+        'skip_scenario_tests',
+        'skip_test_stack_action_list',
+        'ssh_channel_timeout',
+        'ssh_timeout',
+        'tenant_network_mask_bits',
+        'user_domain_id',
+        'user_domain_name',
+        'username',
+        'volume_size',
+    ]
+
+
+    @property
+    def admin_password(self):
+        pass
+
+    @property
+    def admin_project_name(self):
+        pass
+
+    @property
+    def admin_username(self):
+        pass
+
+    @property
+    def auth_url(self):
+        pass
+
+    @property
+    def auth_version(self):
+        pass
+
+    @property
+    def boot_config_env(self):
+        pass
+
+    @property
+    def build_interval(self):
+        pass
+
+    @property
+    def build_timeout(self):
+        pass
+
+    @property
+    def ca_file(self):
+        pass
+
+    @property
+    def catalog_type(self):
+        pass
+
+    @property
+    def connectivity_timeout(self):
+        pass
+
+    @property
+    def convergence_engine_enabled(self):
+        pass
+
+    @property
+    def disable_ssl_certificate_validation(self):
+        pass
+
+    @property
+    def fixed_network_name(self):
+        pass
+
+    @property
+    def fixed_subnet_name(self):
+        pass
+
+    @property
+    def floating_network_name(self):
+        pass
+
+    @property
+    def heat_config_notify_script(self):
+        pass
+
+    @property
+    def image_ref(self):
+        pass
+
+    @property
+    def instance_type(self):
+        pass
+
+    @property
+    def ip_version_for_ssh(self):
+        pass
+
+    @property
+    def keypair_name(self):
+        pass
+
+    @property
+    def minimal_image_ref(self):
+        pass
+
+    @property
+    def minimal_instance_type(self):
+        pass
+
+    @property
+    def network_for_ssh(self):
+        pass
+
+    @property
+    def password(self):
+        pass
+
+    @property
+    def project_domain_id(self):
+        pass
+
+    @property
+    def project_domain_name(self):
+        pass
+
+    @property
+    def project_name(self):
+        pass
+
+    @property
+    def region(self):
+        pass
+
+    @property
+    def sighup_config_edit_retries(self):
+        pass
+
+    @property
+    def sighup_timeout(self):
+        pass
+
+    @property
+    def skip_functional_test_list(self):
+        pass
+
+    @property
+    def skip_functional_tests(self):
+        pass
+
+    @property
+    def skip_scenario_test_list(self):
+        pass
+
+    @property
+    def skip_scenario_tests(self):
+        pass
+
+    @property
+    def skip_test_stack_action_list(self):
+        pass
+
+    @property
+    def ssh_channel_timeout(self):
+        pass
+
+    @property
+    def ssh_timeout(self):
+        pass
+
+    @property
+    def tenant_network_mask_bits(self):
+        pass
+
+    @property
+    def user_domain_id(self):
+        pass
+
+    @property
+    def user_domain_name(self):
+        pass
+
+    @property
+    def username(self):
+        pass
+
+    @property
+    def volume_size(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/identity.py b/_modules/runtest/tempest_sections/identity.py
new file mode 100644
index 0000000..a658a43
--- /dev/null
+++ b/_modules/runtest/tempest_sections/identity.py
@@ -0,0 +1,105 @@
+
+import base_section
+
+from runtest import conditions
+
+class Identity(base_section.BaseSection):
+
+    name = "identity"
+    options = [
+        'admin_domain_scope',
+        'admin_role',
+        'auth_version',
+        'ca_certificates_file',
+        'catalog_type',
+        'default_domain_id',
+        'disable_ssl_certificate_validation',
+        'region',
+        'uri',
+        'uri_v3',
+        'user_lockout_duration',
+        'user_lockout_failure_attempts',
+        'user_unique_last_password_count',
+        'v2_admin_endpoint_type',
+        'v2_public_endpoint_type',
+        'v3_endpoint_type',
+    ]
+
+
+    @property
+    def admin_domain_scope(self):
+        pass
+
+    @property
+    def admin_role(self):
+        pass
+
+    @property
+    def auth_version(self):
+        return 'v3'
+
+    @property
+    def ca_certificates_file(self):
+        c = conditions.BaseRule('keystone.server.enabled', 'eq', True)
+        return self.get_item_when_condition_match(
+            'keystone.server.cacert', c)
+
+    @property
+    def catalog_type(self):
+        pass
+
+    @property
+    def default_domain_id(self):
+        pass
+
+    @property
+    def disable_ssl_certificate_validation(self):
+        pass
+
+    @property
+    def region(self):
+        c = conditions.BaseRule('keystone.server.enabled', 'eq', True)
+        return self.get_item_when_condition_match(
+            'keystone.server.region', c)
+
+    @property
+    def uri(self):
+        c = conditions.BaseRule('keystone.server.enabled', 'eq', True)
+        vip = self.get_item_when_condition_match(
+            '_param.cluster_vip_address', c)
+        port = self.get_item_when_condition_match(
+            'keystone.server.bind.private_port', c)
+        return "http://{}:{}/v2.0".format(vip, port)
+
+    @property
+    def uri_v3(self):
+        c = conditions.BaseRule('keystone.server.enabled', 'eq', True)
+        vip = self.get_item_when_condition_match(
+            '_param.cluster_vip_address', c)
+        port = self.get_item_when_condition_match(
+            'keystone.server.bind.private_port', c)
+        return "http://{}:{}/v3".format(vip, port)
+
+    @property
+    def user_lockout_duration(self):
+        pass
+
+    @property
+    def user_lockout_failure_attempts(self):
+        pass
+
+    @property
+    def user_unique_last_password_count(self):
+        pass
+
+    @property
+    def v2_admin_endpoint_type(self):
+        pass
+
+    @property
+    def v2_public_endpoint_type(self):
+        pass
+
+    @property
+    def v3_endpoint_type(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/identity_feature_enabled.py b/_modules/runtest/tempest_sections/identity_feature_enabled.py
new file mode 100644
index 0000000..166bebd
--- /dev/null
+++ b/_modules/runtest/tempest_sections/identity_feature_enabled.py
@@ -0,0 +1,49 @@
+
+import base_section
+
+class IdentityFeatureEnabled(base_section.BaseSection):
+
+    name = "identity-feature-enabled"
+    options = [
+        'api_extensions',
+        'api_v2',
+        'api_v2_admin',
+        'api_v3',
+        'domain_specific_drivers',
+        'forbid_global_implied_dsr',
+        'security_compliance',
+        'trust',
+    ]
+
+
+    @property
+    def api_extensions(self):
+        pass
+
+    @property
+    def api_v2(self):
+        pass
+
+    @property
+    def api_v2_admin(self):
+        pass
+
+    @property
+    def api_v3(self):
+        pass
+
+    @property
+    def domain_specific_drivers(self):
+        pass
+
+    @property
+    def forbid_global_implied_dsr(self):
+        pass
+
+    @property
+    def security_compliance(self):
+        pass
+
+    @property
+    def trust(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/image.py b/_modules/runtest/tempest_sections/image.py
new file mode 100644
index 0000000..6d9913e
--- /dev/null
+++ b/_modules/runtest/tempest_sections/image.py
@@ -0,0 +1,49 @@
+
+import base_section
+
+class Image(base_section.BaseSection):
+
+    name = "image"
+    options = [
+        'build_interval',
+        'build_timeout',
+        'catalog_type',
+        'container_formats',
+        'disk_formats',
+        'endpoint_type',
+        'http_image',
+        'region',
+    ]
+
+
+    @property
+    def build_interval(self):
+        pass
+
+    @property
+    def build_timeout(self):
+        pass
+
+    @property
+    def catalog_type(self):
+        pass
+
+    @property
+    def container_formats(self):
+        pass
+
+    @property
+    def disk_formats(self):
+        pass
+
+    @property
+    def endpoint_type(self):
+        pass
+
+    @property
+    def http_image(self):
+        pass
+
+    @property
+    def region(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/image_feature_enabled.py b/_modules/runtest/tempest_sections/image_feature_enabled.py
new file mode 100644
index 0000000..3bc05fb
--- /dev/null
+++ b/_modules/runtest/tempest_sections/image_feature_enabled.py
@@ -0,0 +1,36 @@
+
+import base_section
+
+from runtest import conditions
+
+class ImageFeatureEnabled(base_section.BaseSection):
+
+    name = "image-feature-enabled"
+    options = [
+        'api_v1',
+        'api_v2',
+        'deactivate_image',
+    ]
+
+
+    @property
+    def api_v1(self):
+        ge = conditions.BaseRule('*.glance.server.enabled', 'eq', True,
+                                 multiple='any').check(self.pillar)
+        if not ge:
+            return
+        c = conditions.BaseRule('glance.server.enabled', 'eq', True)
+        gv = self.get_item_when_condition_match(
+            'glance.server.version', c)
+        # starting from Ocata glance_v1 is disabled
+        if gv in ['juno', 'kilo', 'liberty', 'mitaka', 'newton']:
+           return True
+        return False
+
+    @property
+    def api_v2(self):
+        return True
+
+    @property
+    def deactivate_image(self):
+        return True
diff --git a/_modules/runtest/tempest_sections/network.py b/_modules/runtest/tempest_sections/network.py
new file mode 100644
index 0000000..1fb61b3
--- /dev/null
+++ b/_modules/runtest/tempest_sections/network.py
@@ -0,0 +1,112 @@
+
+import base_section
+
+from runtest import conditions
+
+class Network(base_section.BaseSection):
+
+    name = "network"
+    options = [
+        'build_interval',
+        'build_timeout',
+        'catalog_type',
+        'default_network',
+        'dns_servers',
+        'endpoint_type',
+        'floating_network_name',
+        'port_vnic_type',
+        'project_network_cidr',
+        'project_network_mask_bits',
+        'project_network_v6_cidr',
+        'project_network_v6_mask_bits',
+        'project_networks_reachable',
+        'public_network_id',
+        'public_router_id',
+        'region',
+        'shared_physical_network',
+    ]
+
+
+    @property
+    def build_interval(self):
+        pass
+
+    @property
+    def build_timeout(self):
+        pass
+
+    @property
+    def catalog_type(self):
+        pass
+
+    @property
+    def default_network(self):
+        pass
+
+    @property
+    def dns_servers(self):
+        pass
+
+    @property
+    def endpoint_type(self):
+        pass
+
+    @property
+    def floating_network_name(self):
+        pass
+
+    @property
+    def port_vnic_type(self):
+        pass
+
+    @property
+    def project_network_cidr(self):
+        pass
+
+    @property
+    def project_network_mask_bits(self):
+        pass
+
+    @property
+    def project_network_v6_cidr(self):
+        pass
+
+    @property
+    def project_network_v6_mask_bits(self):
+        pass
+
+    @property
+    def project_networks_reachable(self):
+        pass
+
+    @property
+    def public_network_id(self):
+        c = conditions.BaseRule(field='keystone.client.enabled', op='eq',
+                                val=True)
+        nodes = self.get_nodes_where_condition_match(c)
+        network_name = self.runtest_opts.get(
+            'convert_to_uuid', {}).get('public_network_id')
+
+        if not network_name:
+          return
+
+        res = self.authenticated_openstack_module_call(
+            nodes[0], 'neutronng.list_netowkrs')[nodes[0]]['networks']
+        networks = [n['id'] for n in res if n['name'] == network_name]
+
+        if len(networks) != 1:
+            raise Exception("Error getting networks: {}".format(networks))
+
+        return networks[0]
+
+    @property
+    def public_router_id(self):
+        pass
+
+    @property
+    def region(self):
+        pass
+
+    @property
+    def shared_physical_network(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/network_feature_enabled.py b/_modules/runtest/tempest_sections/network_feature_enabled.py
new file mode 100644
index 0000000..957c288
--- /dev/null
+++ b/_modules/runtest/tempest_sections/network_feature_enabled.py
@@ -0,0 +1,50 @@
+
+import base_section
+
+from runtest import conditions
+
+class NetworkFeatureEnabled(base_section.BaseSection):
+
+    name = "network-feature-enabled"
+    options = [
+        'api_extensions',
+        'floating_ips',
+        'ipv6',
+        'ipv6_subnet_attributes',
+        'port_admin_state_change',
+        'port_security',
+    ]
+
+
+    @property
+    def api_extensions(self):
+        # We will get this when running
+        # tox -evenv -- tempest verify-config -uro tempest_config_file
+        pass
+
+    @property
+    def floating_ips(self):
+        pass
+
+    @property
+    def ipv6(self):
+        pass
+
+    @property
+    def ipv6_subnet_attributes(self):
+        pass
+
+    @property
+    def port_admin_state_change(self):
+        pass
+
+    @property
+    def port_security(self):
+        c = conditions.BaseRule('neutron.server.enabled', 'eq', True)
+        ext = self.get_item_when_condition_match(
+            'neutron.server.backend.extension', c)
+
+        if 'port_security' in ext:
+            return ext['port_security'].get('enabled', False)
+
+        return True
diff --git a/_modules/runtest/tempest_sections/object_storage.py b/_modules/runtest/tempest_sections/object_storage.py
new file mode 100644
index 0000000..f5fb9f1
--- /dev/null
+++ b/_modules/runtest/tempest_sections/object_storage.py
@@ -0,0 +1,54 @@
+
+import base_section
+
+class ObjectStorage(base_section.BaseSection):
+
+    name = "object-storage"
+    options = [
+        'catalog_type',
+        'cluster_name',
+        'container_sync_interval',
+        'container_sync_timeout',
+        'endpoint_type',
+        'operator_role',
+        'realm_name',
+        'region',
+        'reseller_admin_role',
+    ]
+
+
+    @property
+    def catalog_type(self):
+        pass
+
+    @property
+    def cluster_name(self):
+        pass
+
+    @property
+    def container_sync_interval(self):
+        pass
+
+    @property
+    def container_sync_timeout(self):
+        pass
+
+    @property
+    def endpoint_type(self):
+        pass
+
+    @property
+    def operator_role(self):
+        pass
+
+    @property
+    def realm_name(self):
+        pass
+
+    @property
+    def region(self):
+        pass
+
+    @property
+    def reseller_admin_role(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/object_storage_feature_enabled.py b/_modules/runtest/tempest_sections/object_storage_feature_enabled.py
new file mode 100644
index 0000000..879ea43
--- /dev/null
+++ b/_modules/runtest/tempest_sections/object_storage_feature_enabled.py
@@ -0,0 +1,29 @@
+
+import base_section
+
+class ObjectStorageFeatureEnabled(base_section.BaseSection):
+
+    name = "object-storage-feature-enabled"
+    options = [
+        'container_sync',
+        'discoverability',
+        'discoverable_apis',
+        'object_versioning',
+    ]
+
+
+    @property
+    def container_sync(self):
+        pass
+
+    @property
+    def discoverability(self):
+        pass
+
+    @property
+    def discoverable_apis(self):
+        pass
+
+    @property
+    def object_versioning(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/orchestration.py b/_modules/runtest/tempest_sections/orchestration.py
new file mode 100644
index 0000000..7bd3374
--- /dev/null
+++ b/_modules/runtest/tempest_sections/orchestration.py
@@ -0,0 +1,59 @@
+
+import base_section
+
+class Orchestration(base_section.BaseSection):
+
+    name = "orchestration"
+    options = [
+        'build_interval',
+        'build_timeout',
+        'catalog_type',
+        'endpoint_type',
+        'instance_type',
+        'keypair_name',
+        'max_resources_per_stack',
+        'max_template_size',
+        'region',
+        'stack_owner_role',
+    ]
+
+
+    @property
+    def build_interval(self):
+        pass
+
+    @property
+    def build_timeout(self):
+        pass
+
+    @property
+    def catalog_type(self):
+        pass
+
+    @property
+    def endpoint_type(self):
+        pass
+
+    @property
+    def instance_type(self):
+        pass
+
+    @property
+    def keypair_name(self):
+        pass
+
+    @property
+    def max_resources_per_stack(self):
+        pass
+
+    @property
+    def max_template_size(self):
+        pass
+
+    @property
+    def region(self):
+        pass
+
+    @property
+    def stack_owner_role(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/oslo_concurrency.py b/_modules/runtest/tempest_sections/oslo_concurrency.py
new file mode 100644
index 0000000..c69bd04
--- /dev/null
+++ b/_modules/runtest/tempest_sections/oslo_concurrency.py
@@ -0,0 +1,19 @@
+
+import base_section
+
+class OsloConcurrency(base_section.BaseSection):
+
+    name = "oslo_concurrency"
+    options = [
+        'disable_process_locking',
+        'lock_path',
+    ]
+
+
+    @property
+    def disable_process_locking(self):
+        pass
+
+    @property
+    def lock_path(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/scenario.py b/_modules/runtest/tempest_sections/scenario.py
new file mode 100644
index 0000000..57459a3
--- /dev/null
+++ b/_modules/runtest/tempest_sections/scenario.py
@@ -0,0 +1,54 @@
+
+import base_section
+
+class Scenario(base_section.BaseSection):
+
+    name = "scenario"
+    options = [
+        'aki_img_file',
+        'ami_img_file',
+        'ari_img_file',
+        'dhcp_client',
+        'img_container_format',
+        'img_dir',
+        'img_disk_format',
+        'img_file',
+        'img_properties',
+    ]
+
+
+    @property
+    def aki_img_file(self):
+        pass
+
+    @property
+    def ami_img_file(self):
+        pass
+
+    @property
+    def ari_img_file(self):
+        pass
+
+    @property
+    def dhcp_client(self):
+        pass
+
+    @property
+    def img_container_format(self):
+        pass
+
+    @property
+    def img_dir(self):
+        pass
+
+    @property
+    def img_disk_format(self):
+        pass
+
+    @property
+    def img_file(self):
+        pass
+
+    @property
+    def img_properties(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/service_available.py b/_modules/runtest/tempest_sections/service_available.py
new file mode 100644
index 0000000..e8106d4
--- /dev/null
+++ b/_modules/runtest/tempest_sections/service_available.py
@@ -0,0 +1,78 @@
+
+import base_section
+
+class ServiceAvailable(base_section.BaseSection):
+
+    name = "service_available"
+    options = [
+        'cinder',
+        'designate',
+        'glance',
+        'heat',
+        'ironic',
+        'neutron',
+        'nova',
+        'sahara',
+        'swift',
+    ]
+
+
+
+    def _is_service_enabled(self, service):
+        """Check if service is enabled in specific environment.
+
+        We assume service is enabled when API for this serivce is
+        enabled at least on one node in the cloud.
+
+        :param service:
+        :param pillars:
+        """
+
+        for node, p in self.pillar.items():
+            p_service = p.get(service)
+            if p_service:
+                p_api = (p_service.get('api') or
+                         p_service.get('controller') or
+                         p_service.get('server'))
+
+                if p_api:
+                    if p_api.get('enabled'):
+                        return True
+        return False
+
+    @property
+    def cinder(self):
+        pass
+
+    @property
+    def designate(self):
+        return self._is_service_enabled('designate')
+
+    @property
+    def glance(self):
+        return self._is_service_enabled('glance')
+
+    @property
+    def heat(self):
+        return self._is_service_enabled('heat')
+
+    @property
+    def ironic(self):
+        return self._is_service_enabled('ironic')
+
+    @property
+    def neutron(self):
+        return self._is_service_enabled('neutron')
+
+    @property
+    def nova(self):
+        return self._is_service_enabled('nova')
+
+    @property
+    def sahara(self):
+        return self._is_service_enabled('sahara')
+
+    @property
+    def swift(self):
+        return self._is_service_enabled('swift')
+
diff --git a/_modules/runtest/tempest_sections/service_clients.py b/_modules/runtest/tempest_sections/service_clients.py
new file mode 100644
index 0000000..21878f1
--- /dev/null
+++ b/_modules/runtest/tempest_sections/service_clients.py
@@ -0,0 +1,19 @@
+
+import base_section
+
+class ServiceClients(base_section.BaseSection):
+
+    name = "service-clients"
+    options = [
+        'http_timeout',
+        'proxy_url',
+    ]
+
+
+    @property
+    def http_timeout(self):
+        pass
+
+    @property
+    def proxy_url(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/validation.py b/_modules/runtest/tempest_sections/validation.py
new file mode 100644
index 0000000..9adf570
--- /dev/null
+++ b/_modules/runtest/tempest_sections/validation.py
@@ -0,0 +1,89 @@
+
+import base_section
+
+class Validation(base_section.BaseSection):
+
+    name = "validation"
+    options = [
+        'auth_method',
+        'connect_method',
+        'connect_timeout',
+        'floating_ip_range',
+        'image_ssh_password',
+        'image_ssh_user',
+        'ip_version_for_ssh',
+        'network_for_ssh',
+        'ping_count',
+        'ping_size',
+        'ping_timeout',
+        'run_validation',
+        'security_group',
+        'security_group_rules',
+        'ssh_shell_prologue',
+        'ssh_timeout',
+    ]
+
+
+    @property
+    def auth_method(self):
+        pass
+
+    @property
+    def connect_method(self):
+        pass
+
+    @property
+    def connect_timeout(self):
+        pass
+
+    @property
+    def floating_ip_range(self):
+        pass
+
+    @property
+    def image_ssh_password(self):
+        pass
+
+    @property
+    def image_ssh_user(self):
+        pass
+
+    @property
+    def ip_version_for_ssh(self):
+        pass
+
+    @property
+    def network_for_ssh(self):
+        pass
+
+    @property
+    def ping_count(self):
+        pass
+
+    @property
+    def ping_size(self):
+        pass
+
+    @property
+    def ping_timeout(self):
+        pass
+
+    @property
+    def run_validation(self):
+        pass
+
+    @property
+    def security_group(self):
+        pass
+
+    @property
+    def security_group_rules(self):
+        pass
+
+    @property
+    def ssh_shell_prologue(self):
+        pass
+
+    @property
+    def ssh_timeout(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/volume.py b/_modules/runtest/tempest_sections/volume.py
new file mode 100644
index 0000000..4df5fbb
--- /dev/null
+++ b/_modules/runtest/tempest_sections/volume.py
@@ -0,0 +1,79 @@
+
+import base_section
+
+class Volume(base_section.BaseSection):
+
+    name = "volume"
+    options = [
+        'backend_names',
+        'build_interval',
+        'build_timeout',
+        'catalog_type',
+        'disk_format',
+        'endpoint_type',
+        'manage_snapshot_ref',
+        'manage_volume_ref',
+        'max_microversion',
+        'min_microversion',
+        'region',
+        'storage_protocol',
+        'vendor_name',
+        'volume_size',
+    ]
+
+
+    @property
+    def backend_names(self):
+        pass
+
+    @property
+    def build_interval(self):
+        pass
+
+    @property
+    def build_timeout(self):
+        pass
+
+    @property
+    def catalog_type(self):
+        pass
+
+    @property
+    def disk_format(self):
+        pass
+
+    @property
+    def endpoint_type(self):
+        pass
+
+    @property
+    def manage_snapshot_ref(self):
+        pass
+
+    @property
+    def manage_volume_ref(self):
+        pass
+
+    @property
+    def max_microversion(self):
+        pass
+
+    @property
+    def min_microversion(self):
+        pass
+
+    @property
+    def region(self):
+        pass
+
+    @property
+    def storage_protocol(self):
+        pass
+
+    @property
+    def vendor_name(self):
+        pass
+
+    @property
+    def volume_size(self):
+        pass
diff --git a/_modules/runtest/tempest_sections/volume_feature_enabled.py b/_modules/runtest/tempest_sections/volume_feature_enabled.py
new file mode 100644
index 0000000..22a754c
--- /dev/null
+++ b/_modules/runtest/tempest_sections/volume_feature_enabled.py
@@ -0,0 +1,64 @@
+
+import base_section
+
+class VolumeFeatureEnabled(base_section.BaseSection):
+
+    name = "volume-feature-enabled"
+    options = [
+        'api_extensions',
+        'api_v1',
+        'api_v2',
+        'api_v3',
+        'backup',
+        'clone',
+        'extend_attached_volume',
+        'manage_snapshot',
+        'manage_volume',
+        'multi_backend',
+        'snapshot',
+    ]
+
+
+    @property
+    def api_extensions(self):
+        pass
+
+    @property
+    def api_v1(self):
+        pass
+
+    @property
+    def api_v2(self):
+        pass
+
+    @property
+    def api_v3(self):
+        pass
+
+    @property
+    def backup(self):
+        pass
+
+    @property
+    def clone(self):
+        pass
+
+    @property
+    def extend_attached_volume(self):
+        pass
+
+    @property
+    def manage_snapshot(self):
+        pass
+
+    @property
+    def manage_volume(self):
+        pass
+
+    @property
+    def multi_backend(self):
+        pass
+
+    @property
+    def snapshot(self):
+        pass
diff --git a/_modules/runtest/utils/__init__.py b/_modules/runtest/utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/_modules/runtest/utils/__init__.py
diff --git a/_modules/runtest/utils/requirements.py b/_modules/runtest/utils/requirements.py
new file mode 100644
index 0000000..b57cf5b
--- /dev/null
+++ b/_modules/runtest/utils/requirements.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+'''
+'''
+
+
+import sys
+import pkg_resources
+
+def get_installed_distributions():
+    """ Returns list of pkg Distribution objects found is sys.path
+    """
+
+    pkgs = []
+    for p in sys.path:
+      for d in pkg_resources.find_distributions(p):
+        pkgs.append(d)
+
+    return pkgs