| # Copyright 2016 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 datetime |
| import json |
| |
| from devops import error |
| from functools32 import lru_cache |
| |
| from tcp_tests import logger |
| from tcp_tests import settings |
| |
| |
| LOG = logger.logger |
| |
| |
| class RallyManager(object): |
| """docstring for RallyManager""" |
| |
| image_name = ( |
| 'docker-prod-virtual.docker.mirantis.net/' |
| 'mirantis/oscore/rally-tempest') |
| image_version = 'latest' |
| tempest_tag = "16.0.0" |
| designate_tag = "0.2.0" |
| |
| def __init__(self, underlay, rally_node='gtw01.'): |
| super(RallyManager, self).__init__() |
| self._underlay = underlay |
| self._node_name = self.get_target_node(target=rally_node) |
| |
| @property |
| @lru_cache(maxsize=None) |
| def image_id(self): |
| LOG.info("Getting image id") |
| cmd = ("docker images | grep {0}| grep {1}| awk '{{print $3}}'" |
| .format(self.image_name, self.image_version)) |
| res = self._underlay.check_call(cmd, node_name=self._node_name) |
| LOG.debug(res['stdout']) |
| image_id = res['stdout'][0].strip() |
| LOG.info("Image ID is {}".format(image_id)) |
| return image_id |
| |
| @property |
| @lru_cache(maxsize=None) |
| def docker_id(self): |
| cmd = ("docker ps | grep {image_id} | " |
| "awk '{{print $1}}'| head -1").format( |
| image_id=self.image_id) |
| LOG.info("Getting container id") |
| res = self._underlay.check_call(cmd, node_name=self._node_name) |
| docker_id = res['stdout'][0].strip() |
| LOG.info("Container ID is {}".format(docker_id)) |
| return docker_id |
| |
| # Move method to underlay |
| def get_target_node(self, target='gtw01.'): |
| return [node_name for node_name |
| in self._underlay.node_names() |
| if node_name.startswith(target)][0] |
| |
| def _docker_exec(self, cmd, timeout=None, verbose=False): |
| docker_cmd = ('docker exec -i {docker_id} bash -c "{cmd}"' |
| .format(cmd=cmd, docker_id=self.docker_id)) |
| LOG.info("Executing: {docker_cmd}".format(docker_cmd=docker_cmd)) |
| return self._underlay.check_call(docker_cmd, node_name=self._node_name, |
| verbose=verbose, timeout=timeout) |
| |
| def _run(self): |
| """Start the rally container in the background""" |
| with self._underlay.remote(node_name=self._node_name) as remote: |
| cmd = ("docker run --net host -v /root/rally:/home/rally/.rally " |
| "-v /etc/ssl/certs/:/etc/ssl/certs/ " |
| "-tid -u root --entrypoint /bin/bash {image_id}" |
| .format(image_id=self.image_id)) |
| LOG.info("Run Rally container") |
| remote.check_call(cmd) |
| |
| def run_container(self, version=None): |
| """Install docker, configure and run rally container""" |
| version = version or self.image_version |
| image = self.image_name |
| LOG.info("Pull {image}:{version}".format(image=image, |
| version=version)) |
| try: |
| cmd = ("apt-get -y install docker-ce &&" |
| " docker pull {image}:{version}".format(image=image, |
| version=version)) |
| self._underlay.check_call(cmd, node_name=self._node_name) |
| except Exception as e: |
| LOG.debug('Cannot install docker-ce') |
| cmd = ("apt-get -y install docker.io &&" |
| " docker pull {image}:{version}".format(image=image, |
| version=version)) |
| self._underlay.check_call(cmd, node_name=self._node_name) |
| |
| cmd_iptables = "iptables --policy FORWARD ACCEPT" |
| self._underlay.check_call(cmd_iptables, node_name=self._node_name) |
| |
| LOG.info("Create rally workdir") |
| cmd = 'mkdir -p /root/rally; chown 65500 /root/rally' |
| self._underlay.check_call(cmd, node_name=self._node_name) |
| |
| LOG.info("Copy keystonercv3") |
| tgt = self._node_name.split('.')[0] |
| cmd = "scp -3 ctl01:/root/keystonercv3 " \ |
| "{tgt}:/root/rally/keystonercv3".format( |
| tgt=tgt) |
| domain = '.'.join(self._node_name.split('.')[1:]) |
| self._underlay.check_call(cmd, node_name="cfg01.{domain}".format( |
| domain=domain)) |
| self._run() |
| |
| LOG.info("Create rally deployment") |
| self._docker_exec("rally-manage db recreate") |
| self._docker_exec("source /home/rally/.rally/keystonercv3;" |
| "rally deployment create --fromenv --name=Abathur") |
| self._docker_exec("rally deployment list") |
| |
| def prepare_rally_task(self, target_node='ctl01.'): |
| """Prepare cirros image and private network for rally task""" |
| ctl_node_name = self._underlay.get_target_node_names( |
| target=target_node)[0] |
| cmds = [ |
| ". keystonercv3 ; openstack flavor create --public m1.tiny", |
| ("wget http://download.cirros-cloud.net/0.3.4/" |
| "cirros-0.3.4-i386-disk.img"), |
| (". /root/keystonercv3; glance --timeout 120 image-create " |
| "--name cirros-disk --visibility public --disk-format qcow2 " |
| "--container-format bare --progress " |
| "< /root/cirros-0.3.4-i386-disk.img"), |
| ". /root/keystonercv3; neutron net-create net04", |
| ] |
| |
| for cmd in cmds: |
| self._underlay.check_call(cmd, node_name=ctl_node_name) |
| |
| def prepare_tempest_task(self): |
| """Configure rally.conf for tempest tests""" |
| pass |
| # LOG.info("Modify rally.conf") |
| # cmd = ("sed -i 's|#swift_operator_role = Member|" |
| # "swift_operator_role=SwiftOperator|g' " |
| # "/etc/rally/rally.conf") |
| # self._docker_exec(cmd) |
| |
| def create_rally_task(self, task_path, task_content): |
| """Create a file with rally task definition |
| |
| :param task_path: path to JSON or YAML file on target node |
| :task_content: string with json or yaml content to store in file |
| """ |
| cmd = ("mkdir -p $(dirname {task_path}) && " |
| "cat > {task_path} << EOF\n{task_content}\nEOF").format( |
| task_path=task_path, task_content=task_content) |
| self._underlay.check_call(cmd, node_name=self._node_name) |
| |
| def run_task(self, task='', timeout=None, raise_on_timeout=True, |
| verbose=False): |
| """Run rally task |
| |
| :param taks: path to json or yaml file with the task definition |
| :param raise_on_timeout: bool, ignore TimeoutError if False |
| :param verbose: show rally output to console if True |
| """ |
| try: |
| res = self._docker_exec( |
| "rally task start {task}".format(task=task), |
| timeout=timeout, |
| verbose=verbose) |
| except error.TimeoutError: |
| if raise_on_timeout: |
| raise |
| else: |
| res = None |
| pass |
| return res |
| |
| # Updated to replace the OpenStackManager method run_tempest |
| def run_tempest(self, conf_name='/var/lib/lvm_mcp.conf', |
| pattern='set=smoke', concurrency=0, timeout=None, |
| report_prefix='', report_types=None, |
| designate_plugin=True): |
| """Run tempest tests |
| |
| :param conf_name: tempest config placed in the rally container |
| :param pattern: tempest testcase name or one of existing 'set=...' |
| :param concurrency: how many threads to use in parallel. 0 means |
| to take the amount of the cores on the node |
| <self._node_name>. |
| :param timeout: stop tempest tests after specified timeout. |
| :param designate_plugin: enabled by default plugin for designate |
| :param report_prefix: str, prefix for report filenames. Usually the |
| output of the fixture 'func_name' |
| :param report_types: list of the report types that need to download |
| from the environment: ['html', 'xml', 'json']. |
| None by default. |
| """ |
| report_types = report_types or [] |
| if not designate_plugin: |
| cmd = ( |
| "cat > /root/rally/install_tempest.sh << EOF\n" |
| "rally verify create-verifier" |
| " --type tempest " |
| " --name tempest-verifier" |
| " --source /var/lib/tempest" |
| " --version {tempest_tag}" |
| " --system-wide\n" |
| "rally verify configure-verifier --extend {tempest_conf}\n" |
| "rally verify configure-verifier --show\n" |
| "EOF".format(tempest_tag=self.tempest_tag, |
| tempest_conf=conf_name)) |
| else: |
| cmd = ( |
| "cat > /root/rally/install_tempest.sh << EOF\n" |
| "rally verify create-verifier" |
| " --type tempest " |
| " --name tempest-verifier" |
| " --source /var/lib/tempest" |
| " --version {tempest_tag}" |
| " --system-wide\n" |
| "rally verify add-verifier-ext" |
| " --source /var/lib/designate-tempest-plugin" |
| " --version {designate_tag}\n" |
| "rally verify configure-verifier --extend {tempest_conf}\n" |
| "rally verify configure-verifier --show\n" |
| "EOF".format(tempest_tag=self.tempest_tag, |
| designate_tag=self.designate_tag, |
| tempest_conf=conf_name)) |
| with self._underlay.remote(node_name=self._node_name) as remote: |
| LOG.info("Create install_tempest.sh") |
| remote.check_call(cmd) |
| remote.check_call("chmod +x /root/rally/install_tempest.sh") |
| |
| LOG.info("Run tempest inside Rally container") |
| self._docker_exec("/home/rally/.rally/install_tempest.sh") |
| self._docker_exec( |
| ("source /home/rally/.rally/keystonercv3 && " |
| "rally verify start --skip-list /var/lib/mcp_skip.list " |
| " --concurrency {concurrency} --pattern {pattern}" |
| .format(concurrency=concurrency, pattern=pattern)), |
| timeout=timeout, verbose=True) |
| if report_prefix: |
| report_filename = '{0}_report_{1}'.format( |
| report_prefix, |
| datetime.datetime.now().strftime('%Y%m%d_%H%M%S')) |
| else: |
| report_filename = 'report_{1}'.format( |
| datetime.datetime.now().strftime('%Y%m%d_%H%M%S')) |
| docker_file_prefix = '/home/rally/.rally/' + report_filename |
| |
| # Create reports |
| if 'xml' in report_types: |
| self._docker_exec( |
| "rally verify report --type junit-xml --to {0}.xml" |
| .format(docker_file_prefix)) |
| if 'html' in report_types: |
| self._docker_exec( |
| "rally verify report --type html --to {0}.html" |
| .format(docker_file_prefix)) |
| # Always create report in JSON to return results into test case |
| # However, it won't be downloaded until ('json' in report_prefix) |
| self._docker_exec("rally verify report --type json --to {0}.json" |
| .format(docker_file_prefix)) |
| |
| # Download reports to the settings.LOGS_DIR |
| file_src_prefix = '/root/rally/{0}'.format(report_filename) |
| file_dst_prefix = '{0}/{1}'.format(settings.LOGS_DIR, report_filename) |
| with self._underlay.remote(node_name=self._node_name) as remote: |
| for suffix in report_types: |
| remote.download(file_src_prefix + '.' + suffix, |
| file_dst_prefix + '.' + suffix) |
| res = json.load(remote.open(file_src_prefix + '.json')) |
| |
| # Get latest verification ID to find the lates testcases in the report |
| vtime = {vdata['finished_at']: vid |
| for vid, vdata in res['verifications'].items()} |
| vlatest_id = vtime[max(vtime.keys())] |
| |
| # Each status has the dict with pairs: |
| # <status>: { |
| # <case_name>: <case_details>, |
| # } |
| formatted_tc = { |
| 'success': {}, |
| 'fail': {}, |
| 'xfail': {}, |
| 'skip': {} |
| } |
| |
| for tname, tdata in res['tests'].items(): |
| status = tdata['by_verification'][vlatest_id]['status'] |
| details = tdata['by_verification'][vlatest_id].get('details', '') |
| if status not in formatted_tc: |
| # Fail if tempest return a new status that may be |
| # necessary to take into account in test cases |
| raise Exception("Unknown testcase {0} status: {1} " |
| .format(tname, status)) |
| formatted_tc[status][tdata['name']] = details |
| LOG.debug("Formatted testcases: {0}".format(formatted_tc)) |
| return formatted_tc |