X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=functest%2Fopnfv_tests%2Fopenstack%2Frally%2Frally.py;h=6dfdabec8288cd6002d11bea92da9948c25da8a5;hb=5853dd1ef3c522a975f9685250b1b3e85588f738;hp=16699a0191fffd19da86076c723ecb97094c8ac9;hpb=267fb6d355660423488a80f463656fc2b0052b06;p=functest.git diff --git a/functest/opnfv_tests/openstack/rally/rally.py b/functest/opnfv_tests/openstack/rally/rally.py index 16699a019..6dfdabec8 100644 --- a/functest/opnfv_tests/openstack/rally/rally.py +++ b/functest/opnfv_tests/openstack/rally/rally.py @@ -11,52 +11,41 @@ """Rally testcases implementation.""" from __future__ import division +from __future__ import print_function +import fileinput import json import logging import os import re +import shutil import subprocess import time -import uuid -import os_client_config import pkg_resources import prettytable +from ruamel.yaml import YAML +from six.moves import configparser from xtesting.core import testcase -from xtesting.energy import energy import yaml -from functest.opnfv_tests.openstack.tempest import conf_utils +from functest.core import singlevm from functest.utils import config -from functest.utils import functest_utils from functest.utils import env LOGGER = logging.getLogger(__name__) -class RallyBase(testcase.TestCase): +class RallyBase(singlevm.VmReady2): """Base class form Rally testcases implementation.""" - # pylint: disable=too-many-instance-attributes + # pylint: disable=too-many-instance-attributes, too-many-public-methods TESTS = ['authenticate', 'glance', 'cinder', 'gnocchi', 'heat', - 'keystone', 'neutron', 'nova', 'quotas', 'vm', 'all'] - GLANCE_IMAGE_NAME = getattr(config.CONF, 'openstack_image_name') - GLANCE_IMAGE_FILENAME = getattr(config.CONF, 'openstack_image_file_name') - GLANCE_IMAGE_PATH = os.path.join(getattr( - config.CONF, 'dir_functest_images'), GLANCE_IMAGE_FILENAME) - GLANCE_IMAGE_FORMAT = getattr(config.CONF, 'openstack_image_disk_format') - GLANCE_IMAGE_EXTRA_PROPERTIES = getattr( - config.CONF, 'image_properties', {}) - FLAVOR_NAME = getattr(config.CONF, 'rally_flavor_name') - FLAVOR_ALT_NAME = getattr(config.CONF, 'rally_flavor_alt_name') - FLAVOR_RAM = 512 - FLAVOR_RAM_ALT = 1024 - FLAVOR_EXTRA_SPECS = getattr(config.CONF, 'flavor_extra_specs', {}) - if FLAVOR_EXTRA_SPECS: - FLAVOR_RAM = 1024 - FLAVOR_RAM_ALT = 2048 + 'keystone', 'neutron', 'nova', 'quotas'] + RALLY_CONF_PATH = "/etc/rally/rally.conf" + RALLY_AARCH64_PATCH_PATH = pkg_resources.resource_filename( + 'functest', 'ci/rally_aarch64_patch.conf') RALLY_DIR = pkg_resources.resource_filename( 'functest', 'opnfv_tests/openstack/rally') RALLY_SCENARIO_DIR = pkg_resources.resource_filename( @@ -69,49 +58,55 @@ class RallyBase(testcase.TestCase): TENANTS_AMOUNT = 3 ITERATIONS_AMOUNT = 10 CONCURRENCY = 4 - RESULTS_DIR = os.path.join(getattr(config.CONF, 'dir_results'), 'rally') - BLACKLIST_FILE = os.path.join(RALLY_DIR, "blacklist.txt") - TEMP_DIR = os.path.join(RALLY_DIR, "var") + VOLUME_VERSION = 3 + VOLUME_SERVICE_TYPE = "volumev3" + BLACKLIST_FILE = os.path.join(RALLY_DIR, "blacklist.yaml") + TASK_DIR = os.path.join(getattr(config.CONF, 'dir_rally_data'), 'task') + TEMP_DIR = os.path.join(TASK_DIR, 'var') - RALLY_PRIVATE_NET_NAME = getattr(config.CONF, 'rally_network_name') - RALLY_PRIVATE_SUBNET_NAME = str(getattr(config.CONF, 'rally_subnet_name')) - RALLY_PRIVATE_SUBNET_CIDR = getattr(config.CONF, 'rally_subnet_cidr') - RALLY_ROUTER_NAME = getattr(config.CONF, 'rally_router_name') + visibility = 'public' + shared_network = True def __init__(self, **kwargs): """Initialize RallyBase object.""" super(RallyBase, self).__init__(**kwargs) - self.cloud = os_client_config.make_shade() - self.guid = '-' + str(uuid.uuid4()) + assert self.orig_cloud + assert self.project + if self.orig_cloud.get_role("admin"): + role_name = "admin" + elif self.orig_cloud.get_role("Admin"): + role_name = "Admin" + else: + raise Exception("Cannot detect neither admin nor Admin") + self.orig_cloud.grant_role( + role_name, user=self.project.user.id, + project=self.project.project.id, + domain=self.project.domain.id) + self.results_dir = os.path.join( + getattr(config.CONF, 'dir_results'), self.case_name) + self.task_file = '' self.creators = [] - self.mode = '' self.summary = [] self.scenario_dir = '' - self.image_name = None - self.ext_net = None - self.flavor_name = None - self.flavor_alt_name = None self.smoke = None - self.test_name = None self.start_time = None self.result = None self.details = None self.compute_cnt = 0 - self.image = None - self.flavor = None self.flavor_alt = None - self.network = None - self.subnet = None - self.router = None + self.tests = [] + self.run_cmd = '' + self.network_extensions = [] + self.services = [] - def _build_task_args(self, test_file_name): + def build_task_args(self, test_name): """Build arguments for the Rally task.""" - task_args = {'service_list': [test_file_name]} - task_args['image_name'] = str(self.image_name) - task_args['flavor_name'] = str(self.flavor_name) - task_args['flavor_alt_name'] = str(self.flavor_alt_name) - task_args['glance_image_location'] = str(self.GLANCE_IMAGE_PATH) - task_args['glance_image_format'] = str(self.GLANCE_IMAGE_FORMAT) + task_args = {'service_list': [test_name]} + task_args['image_name'] = str(self.image.name) + task_args['flavor_name'] = str(self.flavor.name) + task_args['flavor_alt_name'] = str(self.flavor_alt.name) + task_args['glance_image_location'] = str(self.filename) + task_args['glance_image_format'] = str(self.image_format) task_args['tmpl_dir'] = str(self.TEMPLATE_DIR) task_args['sup_dir'] = str(self.SUPPORT_DIR) task_args['users_amount'] = self.USERS_AMOUNT @@ -120,6 +115,9 @@ class RallyBase(testcase.TestCase): task_args['iterations'] = self.ITERATIONS_AMOUNT task_args['concurrency'] = self.CONCURRENCY task_args['smoke'] = self.smoke + task_args['volume_version'] = self.VOLUME_VERSION + task_args['volume_service_type'] = self.VOLUME_SERVICE_TYPE + task_args['block_migration'] = env.get("BLOCK_MIGRATION").lower() if self.ext_net: task_args['floating_network'] = str(self.ext_net.name) @@ -153,9 +151,84 @@ class RallyBase(testcase.TestCase): if not os.path.exists(self.TEMP_DIR): os.makedirs(self.TEMP_DIR) - self._apply_blacklist(scenario_file_name, test_file_name) + self.apply_blacklist(scenario_file_name, test_file_name) return test_file_name + @staticmethod + def get_verifier_deployment_id(): + """ + Returns deployment id for active Rally deployment + """ + cmd = ("rally deployment list | awk '/" + + getattr(config.CONF, 'rally_deployment_name') + + "/ {print $2}'") + proc = subprocess.Popen(cmd, shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + deployment_uuid = proc.stdout.readline().rstrip() + return deployment_uuid.decode("utf-8") + + @staticmethod + def create_rally_deployment(environ=None): + """Create new rally deployment""" + # set the architecture to default + pod_arch = env.get("POD_ARCH") + arch_filter = ['aarch64'] + + if pod_arch and pod_arch in arch_filter: + LOGGER.info("Apply aarch64 specific to rally config...") + with open(RallyBase.RALLY_AARCH64_PATCH_PATH, "r") as pfile: + rally_patch_conf = pfile.read() + + for line in fileinput.input(RallyBase.RALLY_CONF_PATH): + print(line, end=' ') + if "cirros|testvm" in line: + print(rally_patch_conf) + + LOGGER.info("Creating Rally environment...") + try: + cmd = ['rally', 'deployment', 'destroy', + '--deployment', + str(getattr(config.CONF, 'rally_deployment_name'))] + output = subprocess.check_output(cmd) + LOGGER.info("%s\n%s", " ".join(cmd), output.decode("utf-8")) + except subprocess.CalledProcessError: + pass + + cmd = ['rally', 'deployment', 'create', '--fromenv', + '--name', str(getattr(config.CONF, 'rally_deployment_name'))] + output = subprocess.check_output(cmd, env=environ) + LOGGER.info("%s\n%s", " ".join(cmd), output.decode("utf-8")) + + cmd = ['rally', 'deployment', 'check'] + output = subprocess.check_output(cmd) + LOGGER.info("%s\n%s", " ".join(cmd), output.decode("utf-8")) + return RallyBase.get_verifier_deployment_id() + + @staticmethod + def update_keystone_default_role(rally_conf='/etc/rally/rally.conf'): + """Set keystone_default_role in rally.conf""" + if env.get("NEW_USER_ROLE").lower() != "member": + rconfig = configparser.RawConfigParser() + rconfig.read(rally_conf) + if not rconfig.has_section('openstack'): + rconfig.add_section('openstack') + rconfig.set( + 'openstack', 'keystone_default_role', env.get("NEW_USER_ROLE")) + with open(rally_conf, 'w') as config_file: + rconfig.write(config_file) + + @staticmethod + def clean_rally_conf(rally_conf='/etc/rally/rally.conf'): + """Clean Rally config""" + if env.get("NEW_USER_ROLE").lower() != "member": + rconfig = configparser.RawConfigParser() + rconfig.read(rally_conf) + if rconfig.has_option('openstack', 'keystone_default_role'): + rconfig.remove_option('openstack', 'keystone_default_role') + with open(rally_conf, 'w') as config_file: + rconfig.write(config_file) + @staticmethod def get_task_id(cmd_raw): """ @@ -167,7 +240,7 @@ class RallyBase(testcase.TestCase): taskid_re = re.compile('^Task +(.*): started$') for line in cmd_raw.splitlines(True): line = line.strip() - match = taskid_re.match(line) + match = taskid_re.match(line.decode("utf-8")) if match: return match.group(1) return None @@ -195,16 +268,13 @@ class RallyBase(testcase.TestCase): """Determine if migration is supported.""" if self.compute_cnt > 1: return True - return False - @staticmethod - def get_cmd_output(proc): - """Get command stdout.""" - result = "" - for line in proc.stdout: - result += line - return result + def _network_trunk_supported(self): + """Determine if network trunk service is available""" + if 'trunk' in self.network_extensions: + return True + return False @staticmethod def excl_scenario(): @@ -214,16 +284,13 @@ class RallyBase(testcase.TestCase): with open(RallyBase.BLACKLIST_FILE, 'r') as black_list_file: black_list_yaml = yaml.safe_load(black_list_file) - installer_type = env.get('INSTALLER_TYPE') deploy_scenario = env.get('DEPLOY_SCENARIO') - if (bool(installer_type) and bool(deploy_scenario) and + if (bool(deploy_scenario) and 'scenario' in black_list_yaml.keys()): for item in black_list_yaml['scenario']: scenarios = item['scenarios'] - installers = item['installers'] in_it = RallyBase.in_iterable_re - if (in_it(deploy_scenario, scenarios) and - in_it(installer_type, installers)): + if in_it(deploy_scenario, scenarios): tests = item['tests'] black_tests.extend(tests) except Exception: # pylint: disable=broad-except @@ -261,8 +328,12 @@ class RallyBase(testcase.TestCase): with open(RallyBase.BLACKLIST_FILE, 'r') as black_list_file: black_list_yaml = yaml.safe_load(black_list_file) + if env.get('BLOCK_MIGRATION').lower() == 'true': + func_list.append("block_migration") if not self._migration_supported(): func_list.append("no_migration") + if not self._network_trunk_supported(): + func_list.append("no_net_trunk_service") if 'functionality' in black_list_yaml.keys(): for item in black_list_yaml['functionality']: @@ -276,7 +347,7 @@ class RallyBase(testcase.TestCase): return black_tests - def _apply_blacklist(self, case_file_name, result_file_name): + def apply_blacklist(self, case_file_name, result_file_name): """Apply blacklist.""" LOGGER.debug("Applying blacklist...") cases_file = open(case_file_name, 'r') @@ -286,7 +357,7 @@ class RallyBase(testcase.TestCase): self.excl_scenario())) if black_tests: - LOGGER.debug("Blacklisted tests: " + str(black_tests)) + LOGGER.debug("Blacklisted tests: %s", str(black_tests)) include = True for cases_line in cases_file: @@ -319,86 +390,49 @@ class RallyBase(testcase.TestCase): def _save_results(self, test_name, task_id): """ Generate and save task execution results""" # check for result directory and create it otherwise - if not os.path.exists(self.RESULTS_DIR): + if not os.path.exists(self.results_dir): LOGGER.debug('%s does not exist, we create it.', - self.RESULTS_DIR) - os.makedirs(self.RESULTS_DIR) + self.results_dir) + os.makedirs(self.results_dir) # put detailed result to log cmd = (["rally", "task", "detailed", "--uuid", task_id]) LOGGER.debug('running command: %s', cmd) - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - json_detailed = self.get_cmd_output(proc) - LOGGER.info('%s', json_detailed) + output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + LOGGER.info("%s\n%s", " ".join(cmd), output.decode("utf-8")) # save report as JSON - cmd = (["rally", "task", "report", "--json", "--uuid", task_id]) + report_json_name = '{}.json'.format(test_name) + report_json_dir = os.path.join(self.results_dir, report_json_name) + cmd = (["rally", "task", "report", "--json", "--uuid", task_id, + "--out", report_json_dir]) LOGGER.debug('running command: %s', cmd) - with open(os.devnull, 'w') as devnull: - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=devnull) - json_results = self.get_cmd_output(proc) - report_json_name = 'opnfv-{}.json'.format(test_name) - report_json_dir = os.path.join(self.RESULTS_DIR, report_json_name) - with open(report_json_dir, 'w') as r_file: - LOGGER.debug('saving json file') - r_file.write(json_results) - - # save report as HTML - report_html_name = 'opnfv-{}.html'.format(test_name) - report_html_dir = os.path.join(self.RESULTS_DIR, report_html_name) - cmd = (["rally", "task", "report", "--html", "--uuid", task_id, - "--out", report_html_dir]) - LOGGER.debug('running command: %s', cmd) - with open(os.devnull, 'w') as devnull: - subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=devnull) + output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + LOGGER.info("%s\n%s", " ".join(cmd), output.decode("utf-8")) + json_results = open(report_json_dir).read() self._append_summary(json_results, test_name) # parse JSON operation result if self.task_succeed(json_results): - LOGGER.info('Test scenario: "{}" OK.'.format(test_name) + "\n") + LOGGER.info('Test scenario: "%s" OK.', test_name) else: - LOGGER.info('Test scenario: "{}" Failed.'.format(test_name) + "\n") + LOGGER.info('Test scenario: "%s" Failed.', test_name) - def _run_task(self, test_name): + def run_task(self, test_name): """Run a task.""" LOGGER.info('Starting test scenario "%s" ...', test_name) - - task_file = os.path.join(self.RALLY_DIR, 'task.yaml') - if not os.path.exists(task_file): - LOGGER.error("Task file '%s' does not exist.", task_file) - raise Exception("Task file '%s' does not exist.", task_file) - - file_name = self._prepare_test_list(test_name) - if self.file_is_empty(file_name): - LOGGER.info('No tests for scenario "%s"', test_name) - return - - cmd = (["rally", "task", "start", "--abort-on-sla-failure", "--task", - task_file, "--task-args", - str(self._build_task_args(test_name))]) - LOGGER.debug('running command: %s', cmd) - - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + LOGGER.debug('running command: %s', self.run_cmd) + proc = subprocess.Popen(self.run_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - output = self.get_cmd_output(proc) - task_id = self.get_task_id(output) + output = proc.communicate()[0] + task_id = self.get_task_id(output) LOGGER.debug('task_id : %s', task_id) - if task_id is None: - LOGGER.error('Failed to retrieve task_id, validating task...') - cmd = (["rally", "task", "validate", "--task", task_file, - "--task-args", str(self._build_task_args(test_name))]) - LOGGER.debug('running command: %s', cmd) - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - output = self.get_cmd_output(proc) - LOGGER.error("Task validation result:" + "\n" + output) + LOGGER.error("Failed to retrieve task_id") + LOGGER.error("Result:\n%s", output) raise Exception("Failed to retrieve task id") - self._save_results(test_name, task_id) def _append_summary(self, json_raw, test_name): @@ -406,10 +440,13 @@ class RallyBase(testcase.TestCase): nb_tests = 0 nb_success = 0 overall_duration = 0.0 + success = [] + failures = [] rally_report = json.loads(json_raw) for task in rally_report.get('tasks'): for subtask in task.get('subtasks'): + has_errors = False for workload in subtask.get('workloads'): if workload.get('full_duration'): overall_duration += workload.get('full_duration') @@ -420,100 +457,81 @@ class RallyBase(testcase.TestCase): for result in workload.get('data'): if not result.get('error'): nb_success += 1 + else: + has_errors = True + + if has_errors: + failures.append(subtask['title']) + else: + success.append(subtask['title']) scenario_summary = {'test_name': test_name, 'overall_duration': overall_duration, 'nb_tests': nb_tests, 'nb_success': nb_success, + 'success': success, + 'failures': failures, 'task_status': self.task_succeed(json_raw)} self.summary.append(scenario_summary) - def _prepare_env(self): - """Create resources needed by test scenarios.""" + def prepare_run(self, **kwargs): + """Prepare resources needed by test scenarios.""" assert self.cloud - LOGGER.debug('Validating the test name...') - if self.test_name not in self.TESTS: - raise Exception("Test name '%s' is invalid" % self.test_name) - - network_name = self.RALLY_PRIVATE_NET_NAME + self.guid - subnet_name = self.RALLY_PRIVATE_SUBNET_NAME + self.guid - router_name = self.RALLY_ROUTER_NAME + self.guid - self.image_name = self.GLANCE_IMAGE_NAME + self.guid - self.flavor_name = self.FLAVOR_NAME + self.guid - self.flavor_alt_name = self.FLAVOR_ALT_NAME + self.guid + LOGGER.debug('Validating run tests...') + for test in kwargs.get('tests', self.TESTS): + if test in self.TESTS: + self.tests.append(test) + else: + raise Exception("Test name '%s' is invalid" % test) + + if not os.path.exists(self.TASK_DIR): + os.makedirs(self.TASK_DIR) + + task = os.path.join(self.RALLY_DIR, 'task.yaml') + if not os.path.exists(task): + LOGGER.error("Task file '%s' does not exist.", task) + raise Exception("Task file '{}' does not exist.". + format(task)) + self.task_file = os.path.join(self.TASK_DIR, 'task.yaml') + shutil.copyfile(task, self.task_file) + + task_macro = os.path.join(self.RALLY_DIR, 'macro') + if not os.path.exists(task_macro): + LOGGER.error("Task macro dir '%s' does not exist.", task_macro) + raise Exception("Task macro dir '{}' does not exist.". + format(task_macro)) + macro_dir = os.path.join(self.TASK_DIR, 'macro') + if os.path.exists(macro_dir): + shutil.rmtree(macro_dir) + shutil.copytree(task_macro, macro_dir) + + self.update_keystone_default_role() self.compute_cnt = len(self.cloud.list_hypervisors()) - self.ext_net = functest_utils.get_external_network(self.cloud) - if self.ext_net is None: - raise Exception("No external network found") - - LOGGER.debug("Creating image '%s'...", self.image_name) - self.image = self.cloud.create_image( - self.image_name, - filename=self.GLANCE_IMAGE_PATH, - disk_format=self.GLANCE_IMAGE_FORMAT, - meta=self.GLANCE_IMAGE_EXTRA_PROPERTIES, - is_public=True) - if self.image is None: - raise Exception("Failed to create image") - - LOGGER.debug("Creating network '%s'...", network_name) - provider = {} - if hasattr(config.CONF, 'rally_network_type'): - provider["network_type"] = getattr( - config.CONF, 'rally_network_type') - if hasattr(config.CONF, 'rally_physical_network'): - provider["physical_network"] = getattr( - config.CONF, 'rally_physical_network') - if hasattr(config.CONF, 'rally_segmentation_id'): - provider["segmentation_id"] = getattr( - config.CONF, 'rally_segmentation_id') - - self.network = self.cloud.create_network( - network_name, shared=True, provider=provider) - if self.network is None: - raise Exception("Failed to create private network") - - self.subnet = self.cloud.create_subnet( - self.network.id, - subnet_name=subnet_name, - cidr=self.RALLY_PRIVATE_SUBNET_CIDR, - enable_dhcp=True, - dns_nameservers=[env.get('NAMESERVER')]) - if self.subnet is None: - raise Exception("Failed to create private subnet") - - LOGGER.debug("Creating router '%s'...", router_name) - self.router = self.cloud.create_router( - router_name, ext_gateway_net_id=self.ext_net.id) - if self.router is None: - raise Exception("Failed to create router") - self.cloud.add_router_interface(self.router, subnet_id=self.subnet.id) - - LOGGER.debug("Creating flavor '%s'...", self.flavor_name) - self.flavor = self.cloud.create_flavor( - self.flavor_name, self.FLAVOR_RAM, 1, 1) - if self.flavor is None: - raise Exception("Failed to create flavor") - self.cloud.set_flavor_specs( - self.flavor.id, self.FLAVOR_EXTRA_SPECS) - - LOGGER.debug("Creating flavor '%s'...", self.flavor_alt_name) - self.flavor_alt = self.cloud.create_flavor( - self.flavor_alt_name, self.FLAVOR_RAM_ALT, 1, 1) - if self.flavor_alt is None: - raise Exception("Failed to create flavor") - self.cloud.set_flavor_specs( - self.flavor_alt.id, self.FLAVOR_EXTRA_SPECS) - - def _run_tests(self): + self.network_extensions = self.cloud.get_network_extensions() + self.flavor_alt = self.create_flavor_alt() + self.services = [service.name for service in + self.cloud.list_services()] + + LOGGER.debug("flavor: %s", self.flavor_alt) + + def prepare_task(self, test_name): + """Prepare resources for test run.""" + file_name = self._prepare_test_list(test_name) + if self.file_is_empty(file_name): + LOGGER.info('No tests for scenario "%s"', test_name) + return False + self.run_cmd = (["rally", "task", "start", "--abort-on-sla-failure", + "--task", self.task_file, "--task-args", + str(self.build_task_args(test_name))]) + return True + + def run_tests(self, **kwargs): """Execute tests.""" - if self.test_name == 'all': - for test in self.TESTS: - if test == 'all' or test == 'vm': - continue - self._run_task(test) - else: - self._run_task(self.test_name) + optional = kwargs.get('optional', []) + for test in self.tests: + if test in self.services or test not in optional: + if self.prepare_task(test): + self.run_task(test) def _generate_report(self): """Generate test execution summary report.""" @@ -542,14 +560,16 @@ class RallyBase(testcase.TestCase): except ZeroDivisionError: success_avg = 0 success_str = str("{:0.2f}".format(success_avg)) + '%' - duration_str = time.strftime("%M:%S", + duration_str = time.strftime("%H:%M:%S", time.gmtime(item['overall_duration'])) res_table.add_row([item['test_name'], duration_str, item['nb_tests'], success_str]) payload.append({'module': item['test_name'], 'details': {'duration': item['overall_duration'], 'nb tests': item['nb_tests'], - 'success': success_str}}) + 'success rate': success_str, + 'success': item['success'], + 'failures': item['failures']}}) total_duration_str = time.strftime("%H:%M:%S", time.gmtime(total_duration)) @@ -572,22 +592,46 @@ class RallyBase(testcase.TestCase): 'nb success': success_rate}}) self.details = payload - def _clean_up(self): + @staticmethod + def export_task(file_name, export_type="html"): + """Export all task results (e.g. html or xunit report) + + Raises: + subprocess.CalledProcessError: if Rally doesn't return 0 + + Returns: + None + """ + cmd = ["rally", "task", "export", "--type", export_type, + "--deployment", + str(getattr(config.CONF, 'rally_deployment_name')), + "--to", file_name] + LOGGER.debug('running command: %s', cmd) + output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + LOGGER.info("%s\n%s", " ".join(cmd), output.decode("utf-8")) + + @staticmethod + def verify_report(file_name, uuid, export_type="html"): + """Generate the verifier report (e.g. html or xunit report) + + Raises: + subprocess.CalledProcessError: if Rally doesn't return 0 + + Returns: + None + """ + cmd = ["rally", "verify", "report", "--type", export_type, + "--uuid", uuid, "--to", file_name] + LOGGER.debug('running command: %s', cmd) + output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + LOGGER.info("%s\n%s", " ".join(cmd), output.decode("utf-8")) + + def clean(self): """Cleanup of OpenStack resources. Should be called on completion.""" + self.clean_rally_conf() if self.flavor_alt: - self.cloud.delete_flavor(self.flavor_alt.id) - if self.flavor: - self.cloud.delete_flavor(self.flavor.id) - if self.router and self.subnet: - self.cloud.remove_router_interface(self.router, self.subnet.id) - if self.router: - self.cloud.delete_router(self.router.id) - if self.subnet: - self.cloud.delete_subnet(self.subnet.id) - if self.network: - self.cloud.delete_network(self.network.id) - if self.image: - self.cloud.delete_image(self.image.id) + self.orig_cloud.delete_flavor(self.flavor_alt.id) + super(RallyBase, self).clean() def is_successful(self): """The overall result of the test.""" @@ -597,22 +641,26 @@ class RallyBase(testcase.TestCase): return super(RallyBase, self).is_successful() - @energy.enable_recording def run(self, **kwargs): """Run testcase.""" self.start_time = time.time() try: - conf_utils.create_rally_deployment() - self._prepare_env() - self._run_tests() + assert super(RallyBase, self).run( + **kwargs) == testcase.TestCase.EX_OK + self.create_rally_deployment(environ=self.project.get_environ()) + self.prepare_run(**kwargs) + self.run_tests(**kwargs) self._generate_report() + self.export_task( + "{}/{}.html".format(self.results_dir, self.case_name)) + self.export_task( + "{}/{}.xml".format(self.results_dir, self.case_name), + export_type="junit-xml") res = testcase.TestCase.EX_OK - except Exception as exc: # pylint: disable=broad-except - LOGGER.error('Error with run: %s', exc) + except Exception: # pylint: disable=broad-except + LOGGER.exception('Error with run:') + self.result = 0 res = testcase.TestCase.EX_RUN_ERROR - finally: - self._clean_up() - self.stop_time = time.time() return res @@ -625,8 +673,6 @@ class RallySanity(RallyBase): if "case_name" not in kwargs: kwargs["case_name"] = "rally_sanity" super(RallySanity, self).__init__(**kwargs) - self.mode = 'sanity' - self.test_name = 'all' self.smoke = True self.scenario_dir = os.path.join(self.RALLY_SCENARIO_DIR, 'sanity') @@ -639,7 +685,121 @@ class RallyFull(RallyBase): if "case_name" not in kwargs: kwargs["case_name"] = "rally_full" super(RallyFull, self).__init__(**kwargs) - self.mode = 'full' - self.test_name = 'all' self.smoke = False self.scenario_dir = os.path.join(self.RALLY_SCENARIO_DIR, 'full') + + +class RallyJobs(RallyBase): + """Rally OpenStack CI testcase implementation.""" + + TESTS = ["neutron"] + + def __init__(self, **kwargs): + """Initialize RallyJobs object.""" + if "case_name" not in kwargs: + kwargs["case_name"] = "rally_jobs" + super(RallyJobs, self).__init__(**kwargs) + self.task_file = os.path.join(self.RALLY_DIR, 'rally_jobs.yaml') + self.task_yaml = None + + def prepare_run(self, **kwargs): + """Create resources needed by test scenarios.""" + super(RallyJobs, self).prepare_run(**kwargs) + with open(os.path.join(self.RALLY_DIR, + 'rally_jobs.yaml'), 'r') as task_file: + self.task_yaml = yaml.safe_load(task_file) + + for task in self.task_yaml: + if task not in self.tests: + raise Exception("Test '%s' not in '%s'" % + (task, self.tests)) + + def apply_blacklist(self, case_file_name, result_file_name): + # pylint: disable=too-many-branches + """Apply blacklist.""" + LOGGER.debug("Applying blacklist...") + black_tests = list(set(self.excl_func() + + self.excl_scenario())) + if black_tests: + LOGGER.debug("Blacklisted tests: %s", str(black_tests)) + + template = YAML(typ='jinja2') + with open(case_file_name, 'r') as fname: + cases = template.load(fname) + if cases.get("version", 1) == 1: + # scenarios in dictionary + for name in cases.keys(): + if self.in_iterable_re(name, black_tests): + cases.pop(name) + else: + # workloads in subtasks + for sind, subtask in enumerate(cases.get('subtasks', [])): + idx = [] + for wind, workload in enumerate(subtask.get('workloads', [])): + scenario = workload.get('scenario', {}) + for name in scenario.keys(): + if self.in_iterable_re(name, black_tests): + idx.append(wind) + break + for wind in reversed(idx): + cases['subtasks'][sind]['workloads'].pop(wind) + # scenarios in subtasks + idx = [] + for sind, subtask in enumerate(cases.get('subtasks', [])): + scenario = subtask.get('scenario', {}) + for name in scenario.keys(): + if self.in_iterable_re(name, black_tests): + idx.append(sind) + break + for sind in reversed(idx): + cases['subtasks'].pop(sind) + + with open(result_file_name, 'w') as fname: + template.dump(cases, fname) + + def build_task_args(self, test_name): + """Build arguments for the Rally task.""" + task_args = {} + if self.ext_net: + task_args['floating_network'] = str(self.ext_net.name) + else: + task_args['floating_network'] = '' + return task_args + + @staticmethod + def _remove_plugins_extra(): + inst_dir = getattr(config.CONF, 'dir_rally_inst') + try: + shutil.rmtree(os.path.join(inst_dir, 'plugins')) + shutil.rmtree(os.path.join(inst_dir, 'extra')) + except Exception: # pylint: disable=broad-except + pass + + def prepare_task(self, test_name): + """Prepare resources for test run.""" + self._remove_plugins_extra() + jobs_dir = os.path.join( + getattr(config.CONF, 'dir_rally_data'), test_name, 'rally-jobs') + inst_dir = getattr(config.CONF, 'dir_rally_inst') + shutil.copytree(os.path.join(jobs_dir, 'plugins'), + os.path.join(inst_dir, 'plugins')) + shutil.copytree(os.path.join(jobs_dir, 'extra'), + os.path.join(inst_dir, 'extra')) + + task_name = self.task_yaml.get(test_name).get("task") + task = os.path.join(jobs_dir, task_name) + if not os.path.exists(task): + raise Exception("The scenario '%s' does not exist." % task) + LOGGER.debug('Scenario fetched from : %s', task) + + if not os.path.exists(self.TEMP_DIR): + os.makedirs(self.TEMP_DIR) + task_file_name = os.path.join(self.TEMP_DIR, task_name) + self.apply_blacklist(task, task_file_name) + self.run_cmd = (["rally", "task", "start", "--task", task_file_name, + "--task-args", str(self.build_task_args(test_name))]) + return True + + def clean(self): + self._remove_plugins_extra() + super(RallyJobs, self).clean()