Runtest manager

Change-Id: Iadb11e7a9d4af3a8dea803d23fc487cb3647b168
diff --git a/tcp_tests/helpers/utils.py b/tcp_tests/helpers/utils.py
index 46bf9c8..f2311d4 100644
--- a/tcp_tests/helpers/utils.py
+++ b/tcp_tests/helpers/utils.py
@@ -281,8 +281,13 @@
                 return {node.tag: loader.construct_scalar(node)}
 
         yaml.add_multi_constructor("!", multi_constructor)
-        with self.__get_file() as file_obj:
-            self.__documents = [x for x in yaml.load_all(file_obj)]
+        with self.__get_file(mode="a+") as file_obj:
+            file_obj.seek(0)
+            self.__documents = [x for x in yaml.load_all(file_obj)] or [{}, ]
+            # try:
+            #     self.__documents = [x for x in yaml.load_all(file_obj)]
+            # except IOError:
+            #     self.__documents[self.__document_id] = {}
             return self.__documents[self.__document_id]
 
     def write_content(self, content=None):
diff --git a/tcp_tests/managers/runtestmanager.py b/tcp_tests/managers/runtestmanager.py
new file mode 100644
index 0000000..9ed4a9c
--- /dev/null
+++ b/tcp_tests/managers/runtestmanager.py
@@ -0,0 +1,301 @@
+#    Copyright 2018 Mirantis, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import os
+import json
+
+from devops.helpers import helpers
+
+from tcp_tests import logger
+from tcp_tests import settings
+
+
+LOG = logger.logger
+
+TEMPEST_CFG_DIR = '/tmp/test'
+
+CONFIG = {
+    'classes': ['service.runtest.tempest'],
+    'parameters': {
+        '_param': {
+            'runtest_tempest_cfg_dir': TEMPEST_CFG_DIR,
+            'runtest_tempest_cfg_name': 'tempest.conf',
+            'runtest_tempest_public_net': 'net04_ext',
+            'tempest_test_target': 'gtw01*'
+        },
+        'neutron': {
+            'client': {
+                'enabled': True
+            }
+        },
+        'runtest': {
+            'enabled': True,
+            'keystonerc_node': 'ctl01*',
+            'tempest': {
+                'enabled': True,
+                'cfg_dir': '${_param:runtest_tempest_cfg_dir}',
+                'cfg_name': '${_param:runtest_tempest_cfg_name}',
+                'DEFAULT': {
+                    'log_file': 'tempest.log'
+                },
+                'compute': {
+                    'build_timeout': 600,
+                    'max_microversion': 2.53,
+                    'min_compute_nodes': 2,
+                    'min_microversion': 2.1,
+                    'volume_device_name': 'vdc'
+                },
+                'convert_to_uuid': {
+                    'network': {
+                        'public_network_id':
+                        '${_param:runtest_tempest_public_net}'
+                    }
+                },
+                'dns_feature_enabled': {
+                    'api_admin': False,
+                    'api_v1': False,
+                    'api_v2': True,
+                    'api_v2_quotas': True,
+                    'api_v2_root_recordsets': True,
+                    'bug_1573141_fixed': True
+                },
+                'heat_plugin': {
+                    'floating_network_name':
+                    '${_param:runtest_tempest_public_net}'
+                },
+                'network': {
+                    'floating_network_name':
+                    '${_param:runtest_tempest_public_net}'
+                },
+                'share': {
+                    'backend_names': 'lvm',
+                    'capability_create_share_from_snapshot_support': True,
+                    'capability_snapshot_support': True,
+                    'default_share_type_name': 'default',
+                    'enable_ip_rules_for_protocols': 'nfs',
+                    'enable_user_rules_for_protocols': 'cifs',
+                    'max_api_microversion': 2.4,
+                    'min_api_microversion': 2.0,
+                    'run_driver_assisted_migration_tests': False,
+                    'run_host_assisted_migration_tests': True,
+                    'run_manage_unmanage_snapshot_tests': False,
+                    'run_manage_unmanage_tests': False,
+                    'run_migration_with_preserve_snapshots_tests': False,
+                    'run_mount_snapshot_tests': True,
+                    'run_quota_tests': True,
+                    'run_replication_tests': False,
+                    'run_revert_to_snapshot_tests': True,
+                    'run_share_group_tests': False,
+                    'run_shrink_tests': False,
+                    'run_snapshot_tests': True,
+                    'share_creation_retry_number': 2,
+                    'suppress_errors_in_cleanup': True
+                }}}}}
+
+
+class RuntestManager(object):
+    """Helper manager for execution tempest via runtest-formula"""
+
+    image_name = settings.TEMPEST_IMAGE
+    image_version = settings.TEMPEST_IMAGE_VERSION
+    container_name = 'run-tempest-ci'
+    master_host = "cfg01"
+    master_tgt = "{}*".format(master_host)
+    class_name = "runtest"
+    run_cmd = '/bin/bash -c "run-tempest"'
+
+    def __init__(self, underlay, salt_api, cluster_name, domain_name,
+                 tempest_threads, tempest_exclude_test_args,
+                 tempest_pattern=settings.TEMPEST_PATTERN,
+                 run_cmd=None, target='gtw01'):
+        self.underlay = underlay
+        self.__salt_api = salt_api
+        self.target = target
+        self.cluster_name = cluster_name
+        self.domain_name = domain_name
+        self.tempest_threads = tempest_threads
+        self.tempest_exclude_test_args = tempest_exclude_test_args
+        self.tempest_pattern = tempest_pattern
+        self.run_cmd = run_cmd or self.run_cmd
+
+    @property
+    def salt_api(self):
+        return self.__salt_api
+
+    def install_python_lib(self):
+        return self.salt_api.local(
+            "{}*".format(self.target),
+            'pip.install', 'docker'), None
+
+    def install_formula(self):
+        return self.salt_api.local(
+            self.master_tgt,
+            'pkg.install', 'salt-formula-runtest'), None
+
+    def create_networks(self):
+        return self.salt_api.enforce_state(self.master_tgt, 'neutron.client')
+
+    def create_flavors(self):
+        return self.salt_api.enforce_state(self.master_tgt, 'nova.client')
+
+    def create_cirros(self):
+        return self.salt_api.enforce_state(self.master_tgt, 'glance.client')
+
+    def generate_config(self):
+        return self.salt_api.enforce_state(self.master_tgt, 'runtest')
+
+    def fetch_arficats(self, username=None):
+        target_name = next(node_name for node_name
+                           in self.underlay.node_names() if
+                           self.target in node_name)
+        with self.underlay.remote(node_name=target_name, username=None) as tgt:
+            tgt.download(
+                destination="{cfg_dir}/report_*.xml".format(cfg_dir=TEMPEST_CFG_DIR),  # noqa
+                target="{}".format(os.environ.get("PWD")))
+
+    def store_runtest_model(self, config=CONFIG):
+        master_name = next(node_name for node_name
+                           in self.underlay.node_names() if
+                           self.master_host in node_name)
+        with self.underlay.yaml_editor(
+                file_path="/srv/salt/reclass/classes/cluster/"
+                          "{cluster_name}/infra/"
+                          "{class_name}.yml".format(
+                              cluster_name=self.cluster_name,
+                              class_name=self.class_name),
+                node_name=master_name) as editor:
+            editor.content = config
+
+        with self.underlay.yaml_editor(
+                file_path="/srv/salt/reclass/nodes/_generated/"
+                          "cfg01.{domain_name}.yml".format(
+                              domain_name=self.domain_name),
+                node_name=master_name) as editor:
+            editor.content['classes'].append(
+                'cluster.{cluster_name}.infra.{class_name}'.format(
+                    cluster_name=self.cluster_name,
+                    class_name=self.class_name))
+
+        self.salt_api.local('*', 'saltutil.refresh_pillar')
+        self.salt_api.local('*', 'saltutil.sync_all')
+
+    def save_runtime_logs(self, logs=None, inspect=None):
+        if logs:
+            with open("{path}/{target}_tempest_run.log".format(
+                    path=settings.LOGS_DIR, target=self.target), 'w') as f:
+                LOG.info("Save tempest console log")
+                container_log = logs
+                f.write(container_log)
+
+        if inspect:
+            with open("{path}/{target}_tempest_container_info.json".format(
+                    path=settings.LOGS_DIR, target=self.target), 'w') as f:
+                LOG.info("Save tempest containes inspect data")
+
+                container_inspect = json.dumps(inspect,
+                                               indent=4, sort_keys=True)
+                f.write(container_inspect)
+
+    def prepare(self):
+        self.store_runtest_model()
+        res = self.install_formula()
+        LOG.info(json.dumps(res, indent=4))
+        res = self.install_python_lib()
+        LOG.info(json.dumps(res, indent=4))
+        res = self.create_networks()
+        LOG.info(json.dumps(res, indent=4))
+        res = self.create_flavors()
+        LOG.info(json.dumps(res, indent=4))
+        res = self.create_cirros()
+        LOG.info(json.dumps(res, indent=4))
+        res = self.generate_config()
+        LOG.info(json.dumps(res, indent=4))
+
+    def run_tempest(self, timeout=600):
+        tgt = "{}*".format(self.target)
+        params = {
+            "name": self.container_name,
+            "image": self.image_name,
+            "environment": {
+                "ARGS": "-r {tempest_pattern} -w "
+                        "{tempest_threads} "
+                        "{tempest_exclude_test_args}".format(
+                            tempest_pattern=self.tempest_pattern,
+                            tempest_threads=self.tempest_threads,
+                            tempest_exclude_test_args=self.tempest_exclude_test_args)  # noqa
+            },
+            "binds": [
+                "{cfg_dir}/tempest.conf:/etc/tempest/tempest.conf".format(cfg_dir=TEMPEST_CFG_DIR),  # noqa
+                "/tmp/:/tmp/",
+                "{cfg_dir}:/root/tempest".format(cfg_dir=TEMPEST_CFG_DIR),
+                "/etc/ssl/certs/:/etc/ssl/certs/"
+            ],
+            "auto_remove": False,
+            "cmd": self.run_cmd
+        }
+
+        res = self.salt_api.local(tgt, 'dockerng.pull', self.image_name)
+        LOG.info("Tempest image has beed pulled- \n{}".format(
+            json.dumps(res, indent=4)))
+
+        res = self.salt_api.local(tgt, 'dockerng.create', kwargs=params)
+        LOG.info("Tempest container has been created - \n{}".format(
+            json.dumps(res, indent=4)))
+
+        res = self.salt_api.local(tgt, 'dockerng.start', self.container_name)
+        LOG.info("Tempest container has been started - \n{}".format(
+            json.dumps(res, indent=4)))
+
+        def wait_status(s):
+            inspect_res = self.salt_api.local(tgt,
+                                              'dockerng.inspect',
+                                              self.container_name)
+            if 'return' in inspect_res:
+                inspect = inspect_res['return']
+                inspect = inspect[0]
+                inspect = next(inspect.iteritems())[1]
+                status = inspect['State']['Status']
+
+                return status.lower() == s.lower()
+
+            return False
+
+        helpers.wait(lambda: wait_status('exited'),
+                     timeout=timeout,
+                     timeout_msg=('Tempest run didnt finished '
+                                  'in {}'.format(timeout)))
+
+        inspect_res = self.salt_api.local(tgt,
+                                          'dockerng.inspect',
+                                          self.container_name)
+        inspect = inspect_res['return'][0]
+        inspect = next(inspect.iteritems())[1]
+        if inspect['State']['ExitCode'] != 0:
+            LOG.error("Tempest running failed")
+        LOG.info("Tempest tests have been finished - \n{}".format(
+            json.dumps(res, indent=4)))
+
+        logs_res = self.salt_api.local(tgt,
+                                       'dockerng.logs',
+                                       self.container_name)
+        logs = logs_res['return'][0]
+        logs = next(logs.iteritems())[1]
+        LOG.info("Tempest result - \n{}".format(logs))
+
+        res = self.salt_api.local(tgt, 'dockerng.rm', self.container_name)
+        LOG.info("Tempest container was removed".format(
+            json.dumps(res, indent=4)))
+
+        return {'inspect': inspect,
+                'logs': logs}
diff --git a/tcp_tests/settings.py b/tcp_tests/settings.py
index 24c6c0b..d45cd13 100644
--- a/tcp_tests/settings.py
+++ b/tcp_tests/settings.py
@@ -60,3 +60,10 @@
 PATTERN = os.environ.get('PATTERN', None)
 RUN_TEMPEST = get_var_as_bool('RUN_TEMPEST', False)
 RUN_SL_TESTS = get_var_as_bool('RUN_SL_TESTS', False)
+
+TEMPEST_IMAGE = os.environ.get(
+    'TEMPEST_IMAGE',
+    'docker-prod-virtual.docker.mirantis.net/mirantis/cicd/ci-tempest')  # noqa
+TEMPEST_IMAGE_VERSION = os.environ.get('TEMPEST_IMAGE_VERSION', 'latest')
+TEMPEST_PATTERN = os.environ.get('TEMPEST_PATTERN', 'tempest')
+TEMPEST_TIMEOUT = int(os.environ.get('TEMPEST_TIMEOUT', 1800))
diff --git a/tcp_tests/templates/virtual-mcp-pike-ovs/openstack.yaml b/tcp_tests/templates/virtual-mcp-pike-ovs/openstack.yaml
index 3f15080..187ea59 100644
--- a/tcp_tests/templates/virtual-mcp-pike-ovs/openstack.yaml
+++ b/tcp_tests/templates/virtual-mcp-pike-ovs/openstack.yaml
@@ -357,4 +357,3 @@
   retry: {count: 1, delay: 5}
   skip_fail: false
 
-{{ SHARED.RUN_NEW_TEMPEST() }}
diff --git a/tcp_tests/tests/system/test_install_mcp_ovs_pike.py b/tcp_tests/tests/system/test_install_mcp_ovs_pike.py
index 53dc1c1..c5138f0 100644
--- a/tcp_tests/tests/system/test_install_mcp_ovs_pike.py
+++ b/tcp_tests/tests/system/test_install_mcp_ovs_pike.py
@@ -14,6 +14,8 @@
 
 import pytest
 
+from tcp_tests.managers.runtestmanager import RuntestManager
+
 from tcp_tests import logger
 from tcp_tests import settings
 
@@ -29,7 +31,9 @@
     @pytest.mark.pike_ovs
     def test_mcp_pike_ovs_install(self, underlay,
                                   openstack_deployed,
-                                  openstack_actions):
+                                  openstack_actions,
+                                  salt_actions,
+                                  config):
         """Test for deploying an mcp environment and check it
         Scenario:
         1. Prepare salt on hosts
@@ -39,12 +43,30 @@
 
         """
         openstack_actions._salt.local(
-                tgt='*', fun='cmd.run',
-                args='service ntp stop; ntpd -gq; service ntp start')
+            tgt='*', fun='cmd.run',
+            args='service ntp stop; ntpd -gq; service ntp start')
 
         if settings.RUN_TEMPEST:
-            openstack_actions.run_tempest(pattern=settings.PATTERN)
-            openstack_actions.download_tempest_report()
+            tempest_threads = 2
+            tempest_exclude_test_args = ''
+            tempest_pattern = settings.TEMPEST_PATTERN
+            cluster_name = settings.LAB_CONFIG_NAME
+            tempest_timeout = settings.TEMPEST_TIMEOUT
+            domain_name = "{}.local".format(cluster_name)
+            target = 'gtw01'
+            runtest = RuntestManager(
+                underlay, salt_actions,
+                cluster_name=cluster_name,
+                domain_name=domain_name,
+                tempest_threads=tempest_threads,
+                tempest_exclude_test_args=tempest_exclude_test_args,
+                tempest_pattern=tempest_pattern,
+                target=target)
+            runtest.prepare()
+            test_res = runtest.run_tempest(tempest_timeout)
+            runtest.fetch_arficats(username='root')
+            runtest.save_runtime_logs(**test_res)
+
         LOG.info("*************** DONE **************")
 
     @pytest.mark.grab_versions
@@ -177,8 +199,8 @@
 
         """
         openstack_actions._salt.local(
-                tgt='*', fun='cmd.run',
-                args='service ntp stop; ntpd -gq; service ntp start')
+            tgt='*', fun='cmd.run',
+            args='service ntp stop; ntpd -gq; service ntp start')
 
         if settings.RUN_TEMPEST:
             openstack_actions.run_tempest(pattern=settings.PATTERN)
@@ -246,8 +268,8 @@
 
         """
         openstack_actions._salt.local(
-                tgt='*', fun='cmd.run',
-                args='service ntp stop; ntpd -gq; service ntp start')
+            tgt='*', fun='cmd.run',
+            args='service ntp stop; ntpd -gq; service ntp start')
 
         registry = 'docker-dev-local.docker.mirantis.net/mirantis/networking'
         name = 'rally-tempest-net-features:latest'