Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 1 | # Copyright 2016 Mirantis, Inc. |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 4 | # not use this file except in compliance with the License. You may obtain |
| 5 | # a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 12 | # License for the specific language governing permissions and limitations |
| 13 | # under the License. |
Dennis Dmitriev | 9cc4ca3 | 2016-11-03 13:50:45 +0200 | [diff] [blame] | 14 | import datetime |
| 15 | import json |
| 16 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 17 | from devops import error |
| 18 | from functools32 import lru_cache |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 19 | |
| 20 | from tcp_tests import logger |
| 21 | from tcp_tests import settings |
| 22 | |
| 23 | |
| 24 | LOG = logger.logger |
| 25 | |
| 26 | |
| 27 | class RallyManager(object): |
| 28 | """docstring for RallyManager""" |
| 29 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 30 | image_name = ( |
| 31 | 'docker-prod-virtual.docker.mirantis.net/' |
| 32 | 'mirantis/oscore/rally-tempest') |
| 33 | image_version = 'latest' |
| 34 | tempest_tag = "16.0.0" |
| 35 | designate_tag = "0.2.0" |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 36 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 37 | def __init__(self, underlay, rally_node='gtw01.'): |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 38 | super(RallyManager, self).__init__() |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 39 | self._underlay = underlay |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 40 | self._node_name = self.get_target_node(target=rally_node) |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 41 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 42 | @property |
| 43 | @lru_cache(maxsize=None) |
| 44 | def image_id(self): |
| 45 | LOG.info("Getting image id") |
| 46 | cmd = ("docker images | grep {0}| grep {1}| awk '{{print $3}}'" |
| 47 | .format(self.image_name, self.image_version)) |
| 48 | res = self._underlay.check_call(cmd, node_name=self._node_name) |
| 49 | image_id = res['stdout'][0].strip() |
| 50 | LOG.info("Image ID is {}".format(image_id)) |
| 51 | return image_id |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 52 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 53 | @property |
| 54 | @lru_cache(maxsize=None) |
| 55 | def docker_id(self): |
| 56 | cmd = ("docker ps | grep {image_id} | " |
| 57 | "awk '{{print $1}}'| head -1").format( |
| 58 | image_id=self.image_id) |
| 59 | LOG.info("Getting container id") |
| 60 | res = self._underlay.check_call(cmd, node_name=self._node_name) |
| 61 | docker_id = res['stdout'][0].strip() |
| 62 | LOG.info("Container ID is {}".format(docker_id)) |
| 63 | return docker_id |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 64 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 65 | # Move method to underlay |
| 66 | def get_target_node(self, target='gtw01.'): |
| 67 | return [node_name for node_name |
| 68 | in self._underlay.node_names() |
| 69 | if node_name.startswith(target)][0] |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 70 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 71 | def _docker_exec(self, cmd, timeout=None, verbose=False): |
| 72 | docker_cmd = ('docker exec -i {docker_id} bash -c "{cmd}"' |
| 73 | .format(cmd=cmd, docker_id=self.docker_id)) |
| 74 | LOG.info("Executing: {docker_cmd}".format(docker_cmd=docker_cmd)) |
| 75 | self._underlay.check_call(docker_cmd, node_name=self._node_name, |
| 76 | verbose=verbose, timeout=timeout) |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 77 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 78 | def _run(self): |
| 79 | """Start the rally container in the background""" |
| 80 | with self._underlay.remote(node_name=self._node_name) as remote: |
| 81 | cmd = ("docker run --net host -v /root/rally:/home/rally/.rally " |
| 82 | "-v /etc/ssl/certs/:/etc/ssl/certs/ " |
| 83 | "-tid -u root --entrypoint /bin/bash {image_id}" |
| 84 | .format(image_id=self.image_id)) |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 85 | LOG.info("Run Rally container") |
| 86 | remote.check_call(cmd) |
| 87 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 88 | def run_container(self, version=None): |
| 89 | """Install docker, configure and run rally container""" |
| 90 | version = version or self.image_version |
| 91 | image = self.image_name |
| 92 | LOG.info("Pull {image}:{version}".format(image=image, |
| 93 | version=version)) |
| 94 | cmd = ("apt-get -y install docker.io &&" |
| 95 | " docker pull {image}:{version}".format(image=image, |
| 96 | version=version)) |
| 97 | self._underlay.check_call(cmd, node_name=self._node_name) |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 98 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 99 | LOG.info("Create rally workdir") |
| 100 | cmd = 'mkdir -p /root/rally; chown 65500 /root/rally' |
| 101 | self._underlay.check_call(cmd, node_name=self._node_name) |
| 102 | |
| 103 | LOG.info("Copy keystonercv3") |
| 104 | cmd = "cp /root/keystonercv3 /root/rally/keystonercv3" |
| 105 | self._underlay.check_call(cmd, node_name=self._node_name) |
| 106 | self._run() |
| 107 | |
| 108 | LOG.info("Create rally deployment") |
| 109 | self._docker_exec("rally-manage db recreate") |
| 110 | self._docker_exec("source /home/rally/.rally/keystonercv3;" |
| 111 | "rally deployment create --fromenv --name=Abathur") |
| 112 | self._docker_exec("rally deployment list") |
| 113 | |
| 114 | def prepare_rally_task(self, target_node='ctl01.'): |
| 115 | """Prepare cirros image and private network for rally task""" |
| 116 | ctl_node_name = self._underlay.get_target_node_names( |
| 117 | target=target_node)[0] |
| 118 | cmds = [ |
| 119 | ". keystonercv3 ; openstack flavor create --public m1.tiny", |
| 120 | ("wget http://download.cirros-cloud.net/0.3.4/" |
| 121 | "cirros-0.3.4-i386-disk.img"), |
| 122 | (". /root/keystonercv3; glance --timeout 120 image-create " |
| 123 | "--name cirros-disk --visibility public --disk-format qcow2 " |
| 124 | "--container-format bare --progress " |
| 125 | "< /root/cirros-0.3.4-i386-disk.img"), |
| 126 | ". /root/keystonercv3; neutron net-create net04", |
Dennis Dmitriev | 6f59add | 2016-10-18 13:45:27 +0300 | [diff] [blame] | 127 | ] |
Dennis Dmitriev | 9cc4ca3 | 2016-11-03 13:50:45 +0200 | [diff] [blame] | 128 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 129 | for cmd in cmds: |
| 130 | self._underlay.check_call(cmd, node_name=ctl_node_name) |
Dennis Dmitriev | 9cc4ca3 | 2016-11-03 13:50:45 +0200 | [diff] [blame] | 131 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 132 | def prepare_tempest_task(self): |
| 133 | """Configure rally.conf for tempest tests""" |
| 134 | pass |
| 135 | # LOG.info("Modify rally.conf") |
| 136 | # cmd = ("sed -i 's|#swift_operator_role = Member|" |
| 137 | # "swift_operator_role=SwiftOperator|g' " |
| 138 | # "/etc/rally/rally.conf") |
| 139 | # self._docker_exec(cmd) |
Dennis Dmitriev | 9cc4ca3 | 2016-11-03 13:50:45 +0200 | [diff] [blame] | 140 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 141 | def create_rally_task(self, task_path, task_content): |
| 142 | """Create a file with rally task definition |
Dennis Dmitriev | 9cc4ca3 | 2016-11-03 13:50:45 +0200 | [diff] [blame] | 143 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 144 | :param task_path: path to JSON or YAML file on target node |
| 145 | :task_content: string with json or yaml content to store in file |
| 146 | """ |
| 147 | cmd = "cat > {task_path} << EOF\n{task_content}\nEOF".format( |
| 148 | task_path=task_path, task_content=task_content) |
| 149 | self._underlay.check_call(cmd, node_name=self._node_name) |
Dennis Dmitriev | 9cc4ca3 | 2016-11-03 13:50:45 +0200 | [diff] [blame] | 150 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 151 | def run_task(self, task='', timeout=None, raise_on_timeout=True): |
| 152 | """Run rally task |
Dennis Dmitriev | 9cc4ca3 | 2016-11-03 13:50:45 +0200 | [diff] [blame] | 153 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 154 | :param taks: path to json or yaml file with the task definition |
| 155 | :param raise_on_timeout: bool, ignore TimeoutError if False |
| 156 | """ |
| 157 | try: |
| 158 | self._docker_exec("rally task start {task}".format(task=task), |
| 159 | timeout=timeout, verbose=True) |
| 160 | except error.TimeoutError: |
| 161 | if raise_on_timeout: |
| 162 | raise |
| 163 | else: |
| 164 | pass |
Dennis Dmitriev | 9cc4ca3 | 2016-11-03 13:50:45 +0200 | [diff] [blame] | 165 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 166 | # Updated to replace the OpenStackManager method run_tempest |
| 167 | def run_tempest(self, conf_name='/var/lib/lvm_mcp.conf', |
| 168 | pattern='set=smoke', concurrency=0, timeout=None, |
| 169 | report_prefix='', report_types=None): |
| 170 | """Run tempest tests |
Dennis Dmitriev | 9cc4ca3 | 2016-11-03 13:50:45 +0200 | [diff] [blame] | 171 | |
Dennis Dmitriev | 2d643bc | 2017-12-04 12:23:47 +0200 | [diff] [blame^] | 172 | :param conf_name: tempest config placed in the rally container |
| 173 | :param pattern: tempest testcase name or one of existing 'set=...' |
| 174 | :param concurrency: how many threads to use in parallel. 0 means |
| 175 | to take the amount of the cores on the node |
| 176 | <self._node_name>. |
| 177 | :param timeout: stop tempest tests after specified timeout. |
| 178 | :param report_prefix: str, prefix for report filenames. Usually the |
| 179 | output of the fixture 'func_name' |
| 180 | :param report_types: list of the report types that need to download |
| 181 | from the environment: ['html', 'xml', 'json']. |
| 182 | None by default. |
| 183 | """ |
| 184 | report_types = report_types or [] |
| 185 | |
| 186 | cmd = ( |
| 187 | "cat > /root/rally/install_tempest.sh << EOF\n" |
| 188 | "rally verify create-verifier" |
| 189 | " --type tempest " |
| 190 | " --name tempest-verifier" |
| 191 | " --source /var/lib/tempest" |
| 192 | " --version {tempest_tag}" |
| 193 | " --system-wide\n" |
| 194 | "rally verify add-verifier-ext" |
| 195 | " --source /var/lib/designate-tempest-plugin" |
| 196 | " --version {designate_tag}\n" |
| 197 | "rally verify configure-verifier --extend {tempest_conf}\n" |
| 198 | "rally verify configure-verifier --show\n" |
| 199 | "EOF".format(tempest_tag=self.tempest_tag, |
| 200 | designate_tag=self.designate_tag, |
| 201 | tempest_conf=conf_name)) |
| 202 | with self._underlay.remote(node_name=self._node_name) as remote: |
| 203 | LOG.info("Create install_tempest.sh") |
| 204 | remote.check_call(cmd) |
| 205 | remote.check_call("chmod +x /root/rally/install_tempest.sh") |
| 206 | |
| 207 | LOG.info("Run tempest inside Rally container") |
| 208 | self._docker_exec("/home/rally/.rally/install_tempest.sh") |
| 209 | self._docker_exec( |
| 210 | ("source /home/rally/.rally/keystonercv3 && " |
| 211 | "rally verify start --skip-list /var/lib/mcp_skip.list " |
| 212 | " --concurrency {concurrency} --pattern {pattern}" |
| 213 | .format(concurrency=concurrency, pattern=pattern)), |
| 214 | timeout=timeout, verbose=True) |
| 215 | if report_prefix: |
| 216 | report_filename = '{0}_report_{1}'.format( |
| 217 | report_prefix, |
| 218 | datetime.datetime.now().strftime('%Y%m%d_%H%M%S')) |
| 219 | else: |
| 220 | report_filename = 'report_{1}'.format( |
| 221 | datetime.datetime.now().strftime('%Y%m%d_%H%M%S')) |
| 222 | docker_file_prefix = '/home/rally/.rally/' + report_filename |
| 223 | |
| 224 | # Create reports |
| 225 | if 'xml' in report_types: |
| 226 | self._docker_exec( |
| 227 | "rally verify report --type junit-xml --to {0}.xml" |
| 228 | .format(docker_file_prefix)) |
| 229 | if 'html' in report_types: |
| 230 | self._docker_exec( |
| 231 | "rally verify report --type html --to {0}.html" |
| 232 | .format(docker_file_prefix)) |
| 233 | # Always create report in JSON to return results into test case |
| 234 | # However, it won't be downloaded until ('json' in report_prefix) |
| 235 | self._docker_exec("rally verify report --type json --to {0}.json" |
| 236 | .format(docker_file_prefix)) |
| 237 | |
| 238 | # Download reports to the settings.LOGS_DIR |
| 239 | file_src_prefix = '/root/rally/{0}'.format(report_filename) |
| 240 | file_dst_prefix = '{0}/{1}'.format(settings.LOGS_DIR, report_filename) |
| 241 | with self._underlay.remote(node_name=self._node_name) as remote: |
| 242 | for suffix in report_types: |
| 243 | remote.download(file_src_prefix + '.' + suffix, |
| 244 | file_dst_prefix + '.' + suffix) |
| 245 | res = json.load(remote.open(file_src_prefix + '.json')) |
| 246 | |
| 247 | # Get latest verification ID to find the lates testcases in the report |
| 248 | vtime = {vdata['finished_at']: vid |
| 249 | for vid, vdata in res['verifications'].items()} |
| 250 | vlatest_id = vtime[max(vtime.keys())] |
| 251 | |
| 252 | # Each status has the dict with pairs: |
| 253 | # <status>: { |
| 254 | # <case_name>: <case_details>, |
| 255 | # } |
| 256 | formatted_tc = { |
| 257 | 'success': {}, |
| 258 | 'fail': {}, |
| 259 | 'xfail': {}, |
| 260 | 'skip': {} |
| 261 | } |
| 262 | |
| 263 | for tname, tdata in res['tests'].items(): |
| 264 | status = tdata['by_verification'][vlatest_id]['status'] |
| 265 | details = tdata['by_verification'][vlatest_id].get('details', '') |
| 266 | if status not in formatted_tc: |
| 267 | # Fail if tempest return a new status that may be |
| 268 | # necessary to take into account in test cases |
| 269 | raise Exception("Unknown testcase {0} status: {1} " |
| 270 | .format(tname, status)) |
| 271 | formatted_tc[status][tdata['name']] = details |
| 272 | LOG.debug("Formatted testcases: {0}".format(formatted_tc)) |
| 273 | return formatted_tc |