Adapt Tempest to the new template
authorjose.lausuch <jose.lausuch@ericsson.com>
Fri, 16 Dec 2016 12:52:33 +0000 (13:52 +0100)
committerjose.lausuch <jose.lausuch@ericsson.com>
Mon, 19 Dec 2016 23:37:38 +0000 (00:37 +0100)
JIRA: FUNCTEST-542

Change-Id: I5576ce5c1aecd67ad97999f412e8c049c20d8e94
Signed-off-by: jose.lausuch <jose.lausuch@ericsson.com>
functest/ci/testcases.yaml
functest/opnfv_tests/openstack/tempest/__init__.py [new file with mode: 0644]
functest/opnfv_tests/openstack/tempest/conf_utils.py [new file with mode: 0644]
functest/opnfv_tests/openstack/tempest/tempest.py [new file with mode: 0644]

index dadfa2a..6f57c70 100755 (executable)
@@ -67,6 +67,9 @@ tiers:
                 dependencies:
                     installer: ''
                     scenario: ''
+                run:
+                    module: 'functest.opnfv_tests.openstack.tempest.tempest'
+                    class: 'TempestSmokeSerial'
             -
                 name: rally_sanity
                 criteria: 'success_rate == 100%'
@@ -243,6 +246,9 @@ tiers:
                 dependencies:
                     installer: '(fuel)|(compass)'
                     scenario: 'multisite'
+                run:
+                    module: 'functest.opnfv_tests.openstack.tempest.tempest'
+                    class: 'TempestMultisite'
             -
                 name: odl-sfc
                 criteria: 'status == "PASS"'
@@ -294,6 +300,9 @@ tiers:
                 dependencies:
                     installer: ''
                     scenario: ''
+                run:
+                    module: 'functest.opnfv_tests.openstack.tempest.tempest'
+                    class: 'TempestFullParallel'
 
             -
                 name: rally_full
diff --git a/functest/opnfv_tests/openstack/tempest/__init__.py b/functest/opnfv_tests/openstack/tempest/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/functest/opnfv_tests/openstack/tempest/conf_utils.py b/functest/opnfv_tests/openstack/tempest/conf_utils.py
new file mode 100644 (file)
index 0000000..38b97e7
--- /dev/null
@@ -0,0 +1,192 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2015 All rights reserved
+# This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+import ConfigParser
+import os
+import re
+import shutil
+
+import functest.utils.functest_constants as ft_constants
+import functest.utils.functest_utils as ft_utils
+import opnfv.utils.constants as releng_constants
+
+
+IMAGE_ID_ALT = None
+FLAVOR_ID_ALT = None
+REPO_PATH = ft_constants.FUNCTEST_REPO_DIR
+GLANCE_IMAGE_PATH = os.path.join(ft_constants.FUNCTEST_DATA_DIR,
+                                 ft_constants.GLANCE_IMAGE_FILENAME)
+TEMPEST_TEST_LIST_DIR = ft_constants.TEMPEST_TEST_LIST_DIR
+TEMPEST_RESULTS_DIR = os.path.join(ft_constants.FUNCTEST_RESULTS_DIR,
+                                   'tempest')
+TEMPEST_CUSTOM = os.path.join(REPO_PATH, TEMPEST_TEST_LIST_DIR,
+                              'test_list.txt')
+TEMPEST_BLACKLIST = os.path.join(REPO_PATH, TEMPEST_TEST_LIST_DIR,
+                                 'blacklist.txt')
+TEMPEST_DEFCORE = os.path.join(REPO_PATH, TEMPEST_TEST_LIST_DIR,
+                               'defcore_req.txt')
+TEMPEST_RAW_LIST = os.path.join(TEMPEST_RESULTS_DIR, 'test_raw_list.txt')
+TEMPEST_LIST = os.path.join(TEMPEST_RESULTS_DIR, 'test_list.txt')
+
+CI_INSTALLER_TYPE = ft_constants.CI_INSTALLER_TYPE
+CI_INSTALLER_IP = ft_constants.CI_INSTALLER_IP
+
+
+def configure_tempest(logger, deployment_dir, IMAGE_ID=None, FLAVOR_ID=None):
+    """
+    Add/update needed parameters into tempest.conf file generated by Rally
+    """
+    tempest_conf_file = deployment_dir + "/tempest.conf"
+    if os.path.isfile(tempest_conf_file):
+        logger.debug("Deleting old tempest.conf file...")
+        os.remove(tempest_conf_file)
+
+    logger.debug("Generating new tempest.conf file...")
+    cmd = "rally verify genconfig"
+    ft_utils.execute_command(cmd)
+
+    logger.debug("Finding tempest.conf file...")
+    if not os.path.isfile(tempest_conf_file):
+        logger.error("Tempest configuration file %s NOT found."
+                     % tempest_conf_file)
+        return releng_constants.EXIT_RUN_ERROR
+
+    logger.debug("Updating selected tempest.conf parameters...")
+    config = ConfigParser.RawConfigParser()
+    config.read(tempest_conf_file)
+    config.set(
+        'compute',
+        'fixed_network_name',
+        ft_constants.TEMPEST_PRIVATE_NET_NAME)
+    if ft_constants.TEMPEST_USE_CUSTOM_IMAGES:
+        if IMAGE_ID is not None:
+            config.set('compute', 'image_ref', IMAGE_ID)
+        if IMAGE_ID_ALT is not None:
+            config.set('compute', 'image_ref_alt', IMAGE_ID_ALT)
+    if ft_constants.TEMPEST_USE_CUSTOM_FLAVORS:
+        if FLAVOR_ID is not None:
+            config.set('compute', 'flavor_ref', FLAVOR_ID)
+        if FLAVOR_ID_ALT is not None:
+            config.set('compute', 'flavor_ref_alt', FLAVOR_ID_ALT)
+    config.set('identity', 'tenant_name', ft_constants.TEMPEST_TENANT_NAME)
+    config.set('identity', 'username', ft_constants.TEMPEST_USER_NAME)
+    config.set('identity', 'password', ft_constants.TEMPEST_USER_PASSWORD)
+    config.set(
+        'validation', 'ssh_timeout', ft_constants.TEMPEST_SSH_TIMEOUT)
+
+    if ft_constants.OS_ENDPOINT_TYPE is not None:
+        services_list = ['compute',
+                         'volume',
+                         'image',
+                         'network',
+                         'data-processing',
+                         'object-storage',
+                         'orchestration']
+        sections = config.sections()
+        for service in services_list:
+            if service not in sections:
+                config.add_section(service)
+            config.set(service, 'endpoint_type',
+                       ft_constants.OS_ENDPOINT_TYPE)
+
+    with open(tempest_conf_file, 'wb') as config_file:
+        config.write(config_file)
+
+    # Copy tempest.conf to /home/opnfv/functest/results/tempest/
+    shutil.copyfile(
+        tempest_conf_file, TEMPEST_RESULTS_DIR + '/tempest.conf')
+
+    return releng_constants.EXIT_OK
+
+
+def configure_tempest_multisite(logger, deployment_dir):
+    """
+    Add/update needed parameters into tempest.conf file generated by Rally
+    """
+    logger.debug("configure the tempest")
+    configure_tempest(logger, deployment_dir)
+
+    logger.debug("Finding tempest.conf file...")
+    tempest_conf_old = os.path.join(deployment_dir, '/tempest.conf')
+    if not os.path.isfile(tempest_conf_old):
+        logger.error("Tempest configuration file %s NOT found."
+                     % tempest_conf_old)
+        return releng_constants.EXIT_RUN_ERROR
+
+    # Copy tempest.conf to /home/opnfv/functest/results/tempest/
+    cur_path = os.path.split(os.path.realpath(__file__))[0]
+    tempest_conf_file = os.path.join(cur_path, '/tempest_multisite.conf')
+    shutil.copyfile(tempest_conf_old, tempest_conf_file)
+
+    logger.debug("Updating selected tempest.conf parameters...")
+    config = ConfigParser.RawConfigParser()
+    config.read(tempest_conf_file)
+
+    config.set('service_available', 'kingbird', 'true')
+    cmd = ("openstack endpoint show kingbird | grep publicurl |"
+           "awk '{print $4}' | awk -F '/' '{print $4}'")
+    kingbird_api_version = os.popen(cmd).read()
+    if CI_INSTALLER_TYPE == 'fuel':
+        # For MOS based setup, the service is accessible
+        # via bind host
+        kingbird_conf_path = "/etc/kingbird/kingbird.conf"
+        installer_type = CI_INSTALLER_TYPE
+        installer_ip = CI_INSTALLER_IP
+        installer_username = ft_utils.get_functest_config(
+            "multisite." + installer_type +
+            "_environment.installer_username")
+        installer_password = ft_utils.get_functest_config(
+            "multisite." + installer_type +
+            "_environment.installer_password")
+
+        ssh_options = ("-o UserKnownHostsFile=/dev/null -o "
+                       "StrictHostKeyChecking=no")
+
+        # Get the controller IP from the fuel node
+        cmd = 'sshpass -p %s ssh 2>/dev/null %s %s@%s \
+                \'fuel node --env 1| grep controller | grep "True\|  1" \
+                | awk -F\| "{print \$5}"\'' % (installer_password,
+                                               ssh_options,
+                                               installer_username,
+                                               installer_ip)
+        multisite_controller_ip = "".join(os.popen(cmd).read().split())
+
+        # Login to controller and get bind host details
+        cmd = 'sshpass -p %s ssh 2>/dev/null  %s %s@%s "ssh %s \\" \
+            grep -e "^bind_" %s  \\""' % (installer_password,
+                                          ssh_options,
+                                          installer_username,
+                                          installer_ip,
+                                          multisite_controller_ip,
+                                          kingbird_conf_path)
+        bind_details = os.popen(cmd).read()
+        bind_details = "".join(bind_details.split())
+        # Extract port number from the bind details
+        bind_port = re.findall(r"\D(\d{4})", bind_details)[0]
+        # Extract ip address from the bind details
+        bind_host = re.findall(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}",
+                               bind_details)[0]
+        kingbird_endpoint_url = "http://%s:%s/" % (bind_host, bind_port)
+    else:
+        cmd = "openstack endpoint show kingbird | grep publicurl |\
+               awk '{print $4}' | awk -F '/' '{print $3}'"
+        kingbird_endpoint_url = os.popen(cmd).read()
+
+    try:
+        config.add_section("kingbird")
+    except Exception:
+        logger.info('kingbird section exist')
+    config.set('kingbird', 'endpoint_type', 'publicURL')
+    config.set('kingbird', 'TIME_TO_SYNC', '20')
+    config.set('kingbird', 'endpoint_url', kingbird_endpoint_url)
+    config.set('kingbird', 'api_version', kingbird_api_version)
+    with open(tempest_conf_file, 'wb') as config_file:
+        config.write(config_file)
+
+    return releng_constants.EXIT_OK
diff --git a/functest/opnfv_tests/openstack/tempest/tempest.py b/functest/opnfv_tests/openstack/tempest/tempest.py
new file mode 100644 (file)
index 0000000..ec0ca76
--- /dev/null
@@ -0,0 +1,316 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2015 All rights reserved
+# This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+import os
+import re
+import shutil
+import subprocess
+import time
+
+import yaml
+
+import conf_utils
+import functest.core.testcase_base as testcase_base
+import functest.utils.functest_logger as ft_logger
+import functest.utils.functest_utils as ft_utils
+import functest.utils.openstack_utils as os_utils
+import functest.utils.functest_constants as ft_constants
+import opnfv.utils.constants as releng_constants
+
+""" logging configuration """
+logger = ft_logger.Logger("Tempest").getLogger()
+
+
+class TempestCommon(testcase_base.TestcaseBase):
+
+    def __init__(self):
+        self.case_name = ""
+        self.MODE = ""
+        self.OPTION = ""
+        self.FLAVOR_ID = None
+        self.IMAGE_ID = None
+        self.DEPLOYMENT_DIR = ft_utils.get_deployment_dir()
+
+    def read_file(self, filename):
+        with open(filename) as src:
+            return [line.strip() for line in src.readlines()]
+
+    def create_tempest_resources(self):
+        keystone_client = os_utils.get_keystone_client()
+
+        logger.debug("Creating tenant and user for Tempest suite")
+        tenant_id = os_utils.create_tenant(
+            keystone_client,
+            ft_constants.TEMPEST_TENANT_NAME,
+            ft_constants.TEMPEST_TENANT_DESCRIPTION)
+        if not tenant_id:
+            logger.error("Error : Failed to create %s tenant"
+                         % ft_constants.TEMPEST_TENANT_NAME)
+
+        user_id = os_utils.create_user(keystone_client,
+                                       ft_constants.TEMPEST_USER_NAME,
+                                       ft_constants.TEMPEST_USER_PASSWORD,
+                                       None, tenant_id)
+        if not user_id:
+            logger.error("Error : Failed to create %s user" %
+                         ft_constants.TEMPEST_USER_NAME)
+
+        logger.debug("Creating private network for Tempest suite")
+        network_dic = \
+            os_utils.create_shared_network_full(
+                ft_constants.TEMPEST_PRIVATE_NET_NAME,
+                ft_constants.TEMPEST_PRIVATE_SUBNET_NAME,
+                ft_constants.TEMPEST_ROUTER_NAME,
+                ft_constants.TEMPEST_PRIVATE_SUBNET_CIDR)
+        if not network_dic:
+            return releng_constants.EXIT_RUN_ERROR
+
+        if ft_constants.TEMPEST_USE_CUSTOM_IMAGES:
+            # adding alternative image should be trivial should we need it
+            logger.debug("Creating image for Tempest suite")
+            _, self.IMAGE_ID = os_utils.get_or_create_image(
+                ft_constants.GLANCE_IMAGE_NAME, conf_utils.GLANCE_IMAGE_PATH,
+                ft_constants.GLANCE_IMAGE_FORMAT)
+            if not self.IMAGE_ID:
+                return releng_constants.EXIT_RUN_ERROR
+
+        if ft_constants.TEMPEST_USE_CUSTOM_FLAVORS:
+            # adding alternative flavor should be trivial should we need it
+            logger.debug("Creating flavor for Tempest suite")
+            _, self.FLAVOR_ID = os_utils.get_or_create_flavor(
+                ft_constants.FLAVOR_NAME,
+                ft_constants.FLAVOR_RAM,
+                ft_constants.FLAVOR_DISK,
+                ft_constants.FLAVOR_VCPUS)
+            if not self.FLAVOR_ID:
+                return releng_constants.EXIT_RUN_ERROR
+
+        return releng_constants.EXIT_OK
+
+    def generate_test_list(self, DEPLOYMENT_DIR):
+        logger.debug("Generating test case list...")
+        if self.MODE == 'defcore':
+            shutil.copyfile(
+                conf_utils.TEMPEST_DEFCORE, conf_utils.TEMPEST_RAW_LIST)
+        elif self.MODE == 'custom':
+            if os.path.isfile(conf_utils.TEMPEST_CUSTOM):
+                shutil.copyfile(
+                    conf_utils.TEMPEST_CUSTOM, conf_utils.TEMPEST_RAW_LIST)
+            else:
+                logger.error("Tempest test list file %s NOT found."
+                             % conf_utils.TEMPEST_CUSTOM)
+                return releng_constants.EXIT_RUN_ERROR
+        else:
+            if self.MODE == 'smoke':
+                testr_mode = "smoke"
+            elif self.MODE == 'feature_multisite':
+                testr_mode = " | grep -i kingbird "
+            elif self.MODE == 'full':
+                testr_mode = ""
+            else:
+                testr_mode = 'tempest.api.' + self.MODE
+            cmd = ("cd " + DEPLOYMENT_DIR + ";" + "testr list-tests " +
+                   testr_mode + ">" + conf_utils.TEMPEST_RAW_LIST + ";cd")
+            ft_utils.execute_command(cmd)
+
+        return releng_constants.EXIT_OK
+
+    def apply_tempest_blacklist(self):
+        logger.debug("Applying tempest blacklist...")
+        cases_file = self.read_file(conf_utils.TEMPEST_RAW_LIST)
+        result_file = open(conf_utils.TEMPEST_LIST, 'w')
+        black_tests = []
+        try:
+            installer_type = ft_constants.CI_INSTALLER_TYPE
+            deploy_scenario = ft_constants.CI_SCENARIO
+            if (bool(installer_type) * bool(deploy_scenario)):
+                # if INSTALLER_TYPE and DEPLOY_SCENARIO are set we read the
+                # file
+                black_list_file = open(conf_utils.TEMPEST_BLACKLIST)
+                black_list_yaml = yaml.safe_load(black_list_file)
+                black_list_file.close()
+                for item in black_list_yaml:
+                    scenarios = item['scenarios']
+                    installers = item['installers']
+                    if (deploy_scenario in scenarios and
+                            installer_type in installers):
+                        tests = item['tests']
+                        for test in tests:
+                            black_tests.append(test)
+                        break
+        except:
+            black_tests = []
+            logger.debug("Tempest blacklist file does not exist.")
+
+        for cases_line in cases_file:
+            for black_tests_line in black_tests:
+                if black_tests_line in cases_line:
+                    break
+            else:
+                result_file.write(str(cases_line) + '\n')
+        result_file.close()
+        return releng_constants.EXIT_OK
+
+    def run(self):
+        if not os.path.exists(conf_utils.TEMPEST_RESULTS_DIR):
+            os.makedirs(conf_utils.TEMPEST_RESULTS_DIR)
+
+        # Pre-configuration
+        res = self.create_tempest_resources()
+        if res != releng_constants.EXIT_OK:
+            return res
+
+        res = conf_utils.configure_tempest(logger,
+                                           self.DEPLOYMENT_DIR,
+                                           self.IMAGE_ID,
+                                           self.FLAVOR_ID)
+        if res != releng_constants.EXIT_OK:
+            return res
+
+        res = self.generate_test_list(self.DEPLOYMENT_DIR)
+        if res != releng_constants.EXIT_OK:
+            return res
+
+        res = self.apply_tempest_blacklist()
+        if res != releng_constants.EXIT_OK:
+            return res
+
+        self.OPTION += (" --tests-file %s " % conf_utils.TEMPEST_LIST)
+
+        cmd_line = "rally verify start " + self.OPTION + " --system-wide"
+        logger.info("Starting Tempest test suite: '%s'." % cmd_line)
+
+        header = ("Tempest environment:\n"
+                  "  Installer: %s\n  Scenario: %s\n  Node: %s\n  Date: %s\n" %
+                  (ft_constants.CI_INSTALLER_TYPE,
+                   ft_constants.CI_SCENARIO,
+                   ft_constants.CI_NODE,
+                   time.strftime("%a %b %d %H:%M:%S %Z %Y")))
+
+        f_stdout = open(conf_utils.TEMPEST_RESULTS_DIR + "/tempest.log", 'w+')
+        f_stderr = open(
+            conf_utils.TEMPEST_RESULTS_DIR + "/tempest-error.log", 'w+')
+        f_env = open(conf_utils.TEMPEST_RESULTS_DIR + "/environment.log", 'w+')
+        f_env.write(header)
+
+        # subprocess.call(cmd_line, shell=True,
+        # stdout=f_stdout, stderr=f_stderr)
+        p = subprocess.Popen(
+            cmd_line, shell=True,
+            stdout=subprocess.PIPE,
+            stderr=f_stderr,
+            bufsize=1)
+
+        with p.stdout:
+            for line in iter(p.stdout.readline, b''):
+                if re.search("\} tempest\.", line):
+                    logger.info(line.replace('\n', ''))
+                f_stdout.write(line)
+        p.wait()
+
+        f_stdout.close()
+        f_stderr.close()
+        f_env.close()
+
+        cmd_line = "rally verify show"
+        output = ""
+        p = subprocess.Popen(cmd_line,
+                             shell=True,
+                             stdout=subprocess.PIPE,
+                             stderr=subprocess.PIPE)
+        for line in p.stdout:
+            if re.search("Tests\:", line):
+                break
+            output += line
+        logger.info(output)
+
+        cmd_line = "rally verify list"
+        cmd = os.popen(cmd_line)
+        output = (((cmd.read()).splitlines()[-2]).replace(" ", "")).split("|")
+        # Format:
+        # | UUID | Deployment UUID | smoke | tests | failures | Created at |
+        # Duration | Status  |
+        num_tests = output[4]
+        num_failures = output[5]
+        duration = output[7]
+        # Compute duration (lets assume it does not take more than 60 min)
+        dur_min = int(duration.split(':')[1])
+        dur_sec_float = float(duration.split(':')[2])
+        dur_sec_int = int(round(dur_sec_float, 0))
+        dur_sec_int = dur_sec_int + 60 * dur_min
+
+        try:
+            diff = (int(num_tests) - int(num_failures))
+            success_rate = 100 * diff / int(num_tests)
+        except:
+            success_rate = 0
+
+        if 'smoke' in self.MODE:
+            self.CASE_NAME = 'tempest_smoke_serial'
+        elif 'feature' in self.MODE:
+            self.CASE_NAME = self.MODE.replace(
+                "feature_", "")
+        else:
+            self.CASE_NAME = 'tempest_full_parallel'
+
+        status = ft_utils.check_success_rate(
+            self.CASE_NAME, success_rate)
+        logger.info("Tempest %s success_rate is %s%%, is marked as %s"
+                    % (self.CASE_NAME, success_rate, status))
+
+        if status == "PASS":
+            return releng_constants.EXIT_OK
+        else:
+            return releng_constants.EXIT_RUN_ERROR
+
+
+class TempestSmokeSerial(TempestCommon):
+
+    def __init__(self):
+        TempestCommon.__init__(self)
+        self.case_name = "tempest_smoke_serial"
+        self.MODE = "smoke"
+        self.OPTION = "--concur 1"
+
+
+class TempestSmokeParallel(TempestCommon):
+
+    def __init__(self):
+        TempestCommon.__init__(self)
+        self.case_name = "tempest_smoke_parallel"
+        self.MODE = "smoke"
+        self.OPTION = ""
+
+
+class TempestFullParallel(TempestCommon):
+
+    def __init__(self):
+        TempestCommon.__init__(self)
+        self.case_name = "tempest_full_parallel"
+        self.MODE = "full"
+
+
+class TempestMultisite(TempestCommon):
+
+    def __init__(self):
+        TempestCommon.__init__(self)
+        self.case_name = "multisite"
+        self.MODE = "feature_multisite"
+        self.OPTION = "--concur 1"
+        conf_utils.configure_tempest_multisite(logger, self.DEPLOYMENT_DIR)
+
+
+class TempestCustom(TempestCommon):
+
+    def __init__(self, mode, option):
+        TempestCommon.__init__(self)
+        self.case_name = "tempest_custom"
+        self.MODE = mode
+        self.OPTION = option