blob: 348dea60f2530f3768f37bca2613912fff5ceb4f [file] [log] [blame]
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +03001# Copyright 2018 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.
14
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030015import json
Oleksii Butenko71d76f32018-06-05 17:46:34 +030016import os
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030017
Oleksii Butenkob0ca4cc2018-10-22 16:29:54 +030018from devops.helpers import helpers
19
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030020from tcp_tests import logger
21from tcp_tests import settings
22
Oleksii Butenkob0ca4cc2018-10-22 16:29:54 +030023
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030024LOG = logger.logger
25
26TEMPEST_CFG_DIR = '/tmp/test'
27
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030028
29class RuntestManager(object):
30 """Helper manager for execution tempest via runtest-formula"""
31
32 image_name = settings.TEMPEST_IMAGE
33 image_version = settings.TEMPEST_IMAGE_VERSION
34 container_name = 'run-tempest-ci'
35 master_host = "cfg01"
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030036 class_name = "runtest"
37 run_cmd = '/bin/bash -c "run-tempest"'
38
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +000039 def __init__(self, config, underlay, salt_api, cluster_name,
Oleksii Butenko74566102018-09-11 11:55:13 +030040 domain_name, tempest_threads,
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030041 tempest_pattern=settings.TEMPEST_PATTERN,
Oleksii Butenko25a8f372019-03-22 17:25:47 +020042 tempest_extra_args=settings.TEMPEST_EXTRA_ARGS,
obutenkoc4314352018-12-26 16:14:24 +020043 run_cmd=None):
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +000044 self.__config = config
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030045 self.underlay = underlay
46 self.__salt_api = salt_api
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030047 self.cluster_name = cluster_name
48 self.domain_name = domain_name
49 self.tempest_threads = tempest_threads
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030050 self.tempest_pattern = tempest_pattern
Oleksii Butenko25a8f372019-03-22 17:25:47 +020051 self.tempest_extra_args = tempest_extra_args
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030052 self.run_cmd = run_cmd or self.run_cmd
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +000053 self.master_name = self.underlay.get_target_node_names(
54 self.master_host)[0]
Dennis Dmitriev1566e3f2019-01-11 17:35:43 +020055 self.master_minion = self.underlay.get_target_minion_ids(
56 self.master_host)[0]
obutenkoc4314352018-12-26 16:14:24 +020057 self.__target_name = None
sgudz6571a5e2019-02-26 15:49:22 +020058 self.__target_minion = None
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030059
60 @property
61 def salt_api(self):
62 return self.__salt_api
63
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +000064 @property
obutenkoc4314352018-12-26 16:14:24 +020065 def target_name(self):
66 if not self.__target_name:
obutenkoa1e3cea2019-01-12 21:11:55 +020067 target_host = self.__salt_api.get_single_pillar(
Dennis Dmitriev1566e3f2019-01-11 17:35:43 +020068 tgt=self.master_minion,
obutenkoc4314352018-12-26 16:14:24 +020069 pillar="runtest:tempest:test_target")
obutenkoc4314352018-12-26 16:14:24 +020070 if target_host[-1] == "*":
71 target_host = target_host[:-1]
72 self.__target_name = self.underlay.get_target_node_names(
73 target_host)[0]
74 return self.__target_name
obutenko3e44b602018-11-13 12:50:22 +020075
sgudz6571a5e2019-02-26 15:49:22 +020076 @property
77 def target_minion(self):
78 if not self.__target_minion:
79 target_host = self.__salt_api.get_single_pillar(
80 tgt=self.master_minion,
81 pillar="runtest:tempest:test_target")
82 if target_host[-1] == "*":
83 target_host = target_host[:-1]
84 self.__target_minion = self.underlay.get_target_minion_ids(
85 target_host)[0]
86 return self.__target_minion
87
Oleksii Butenko71d76f32018-06-05 17:46:34 +030088 def fetch_arficats(self, username=None, file_format='xml'):
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +000089 with self.underlay.remote(node_name=self.target_name,
90 username=None) as tgt:
Oleksii Butenko71d76f32018-06-05 17:46:34 +030091 result = tgt.execute('find {} -name "report_*.{}"'.format(
92 TEMPEST_CFG_DIR, file_format))
93 LOG.debug("Find result {0}".format(result))
94 assert len(result['stdout']) > 0, ('No report found, please check'
95 ' if test run was successful.')
96 report = result['stdout'][0].rstrip()
97 LOG.debug("Found files {0}".format(report))
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030098 tgt.download(
Oleksii Butenko71d76f32018-06-05 17:46:34 +030099 destination=report, # noqa
100 target=os.getcwd())
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300101
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300102 def save_runtime_logs(self, logs=None, inspect=None):
103 if logs:
104 with open("{path}/{target}_tempest_run.log".format(
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000105 path=settings.LOGS_DIR,
106 target=self.target_name), 'w') as f:
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300107 LOG.info("Save tempest console log")
108 container_log = logs
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300109 f.write(container_log.encode('ascii', 'ignore'))
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300110
111 if inspect:
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300112 with open("{path}/{target}_tempest_container_info.json.log".format(
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000113 path=settings.LOGS_DIR,
114 target=self.target_name), 'w') as f:
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300115 LOG.info("Save tempest container inspect data")
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300116
117 container_inspect = json.dumps(inspect,
118 indent=4, sort_keys=True)
119 f.write(container_inspect)
120
obutenkoc4314352018-12-26 16:14:24 +0200121 def prepare(self):
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000122 salt_call_cmd = "salt-call -l info --hard-crash --state-output=mixed "
obutenkoa1e3cea2019-01-12 21:11:55 +0200123 barbican_integration = self.__salt_api.get_single_pillar(
Tatyana Leontovich8b1fda02019-01-23 13:14:33 +0200124 tgt="ctl01*",
obutenkoa1e3cea2019-01-12 21:11:55 +0200125 pillar="_param:barbican_integration_enabled")
Oleksii Butenkod4b3dff2019-05-10 14:38:14 +0300126 if self.__salt_api.local('I@opencontrail:compute:enabled:true',
127 'match.pillar',
128 'opencontrail:compute:enabled:true'
129 ).get('return', [{}]) != [{}]:
130 contrail_integration = True
131 else:
132 contrail_integration = False
Tatyana Leontovich8b1fda02019-01-23 13:14:33 +0200133
obutenkoc48f4352019-01-24 17:03:06 +0200134 LOG.info("Barbican integration {0}".format(barbican_integration))
Oleksii Butenkod4b3dff2019-05-10 14:38:14 +0300135 LOG.info("Opencontrail integration {0}".format(contrail_integration))
136
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000137 commands = [
138 {
Tatyana Leontovicheea03e92018-11-20 12:51:50 +0200139 'description': ("Install docker-ce package and "
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000140 "enable packets forwarding"),
141 'node_name': self.target_name,
142 'cmd': ("set -ex;" +
Tatyana Leontovicheea03e92018-11-20 12:51:50 +0200143 salt_call_cmd + " pkg.install docker-ce && " +
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000144 " iptables --policy FORWARD ACCEPT")},
145 {
146 'description': "Install PyPI docker package",
147 'node_name': self.target_name,
148 'cmd': ("set -ex;" +
149 salt_call_cmd + " pip.install setuptools && " +
150 salt_call_cmd + " pip.install docker")},
151 {
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000152 'description': "Generate config for Tempest",
153 'node_name': self.master_name,
154 'cmd': ("set -ex;" +
obutenkoc4314352018-12-26 16:14:24 +0200155 "salt-run state.orchestrate " +
156 "runtest.orchestrate.tempest")},
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000157 ]
obutenkoa1e3cea2019-01-12 21:11:55 +0200158
Oleksii Butenkod4b3dff2019-05-10 14:38:14 +0300159 if contrail_integration:
160 vsrx_router = self.__salt_api.get_single_pillar(
161 tgt="I@opencontrail:control:role:primary",
Andrew Baraniuk6361cfc2019-06-04 12:58:14 +0300162 pillar="_param:opencontrail_router02_address")
Oleksii Butenkod4b3dff2019-05-10 14:38:14 +0300163 public_network = "192.168.200.0"
164 contrail_commands = [
165 {
166 'description': "Iproute to vsrx router",
167 'node_name': self.target_name,
168 'cmd': ("set -ex; ip route replace " +
169 public_network + "/24 via " + vsrx_router)},
170 {
Andrew Baraniuk6361cfc2019-06-04 12:58:14 +0300171 'description': "Iptables for public",
172 'node_name': self.target_name,
173 'cmd': ("set -ex; iptables -t nat -A POSTROUTING -s " +
174 public_network + "/24 ! -d " + public_network +
175 "/24 -j MASQUERADE")},
176 {
Oleksii Butenkod4b3dff2019-05-10 14:38:14 +0300177 'description': "Align security group: remove all rules",
178 'node_name': self.target_name,
179 'cmd': ("set -ex;" +
180 "salt 'ctl01*' cmd.run '. /root/keystonercv3; " +
181 "openstack security group rule list --column ID " +
182 "-f value | xargs " +
183 "openstack security group rule delete|true';")},
184 {
185 'description': "Align security group: remove all default",
186 'node_name': self.target_name,
187 'cmd': ("set -ex;" +
188 " salt ctl01* cmd.run '. /root/keystonercv3; " +
189 "openstack security group " +
190 "list --column ID --column Name -f value|" +
191 "grep default|cut -d \" \" -f 1|" +
192 "xargs openstack security group delete|true'")},
193 {
194 'description': "Align security group: add rules",
195 'node_name': self.target_name,
196 'cmd': ("set -ex;" +
197 "salt 'ctl01*' cmd.run '. /root/keystonercv3; " +
198 "openstack security group rule create default " +
199 "--egress --protocol tcp'; " +
200 "salt 'ctl01*' cmd.run '. /root/keystonercv3; " +
201 "openstack security group rule create default " +
202 "--ingress --protocol tcp'; " +
203 "salt 'ctl01*' cmd.run '. /root/keystonercv3; " +
204 "openstack security group rule create default " +
205 "--egress --protocol icmp'; " +
206 "salt 'ctl01*' cmd.run '. /root/keystonercv3; " +
207 "openstack security group rule create default " +
208 "--ingress --protocol icmp'; ")},
209 {
210 'description': "Create public network with target",
211 'node_name': self.target_name,
212 'cmd': ("set -ex;" +
213 "salt -C 'I@opencontrail:control:role:primary' " +
214 "contrail.virtual_network_create public " +
215 "'{\"external\":true,\"ip_prefix\":\"" +
216 public_network + "\",\"ip_prefix_len\":24," +
217 "\"asn\":64512,\"target\":10000}'")},
Andrew Baraniuk73b68ee2019-05-29 14:00:44 +0300218 {
Andrew Baraniuk6361cfc2019-06-04 12:58:14 +0300219 'description': "Run skiped in pipelines neutron.client",
Andrew Baraniuk73b68ee2019-05-29 14:00:44 +0300220 'node_name': self.target_name,
221 'cmd': ("set -ex;" +
Andrew Baraniuk6361cfc2019-06-04 12:58:14 +0300222 "salt -C 'I@neutron:client and cfg*' " +
223 "state.sls neutron.client|true")},
Oleksii Butenkod4b3dff2019-05-10 14:38:14 +0300224 ]
Andrew Baraniuk6361cfc2019-06-04 12:58:14 +0300225 post_contrail_commands = [
226 {
227 'description': "Delete admin role",
228 'node_name': self.target_name,
229 'cmd': ("set -ex;" +
230 "sed -i 's/tempest_roles = admin//g' " +
231 TEMPEST_CFG_DIR + "/tempest.conf")},
232 ]
233 commands = contrail_commands + commands + post_contrail_commands
Oleksii Butenkod4b3dff2019-05-10 14:38:14 +0300234
obutenkoc48f4352019-01-24 17:03:06 +0200235 if barbican_integration:
obutenkoa1e3cea2019-01-12 21:11:55 +0200236 commands.append({
237 'description': "Configure barbican",
238 'node_name': self.master_name,
239 'cmd': ("set -ex;" +
240 salt_call_cmd +
241 " state.sls barbican.client && " +
242 salt_call_cmd +
obutenkoc48f4352019-01-24 17:03:06 +0200243 " state.sls runtest.test_accounts" +
obutenkoa1e3cea2019-01-12 21:11:55 +0200244 salt_call_cmd +
245 " state.sls runtest.barbican_sign_image")},
246 )
247
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000248 self.__salt_api.execute_commands(commands=commands,
249 label="Prepare for Tempest")
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300250
251 def run_tempest(self, timeout=600):
sgudz6571a5e2019-02-26 15:49:22 +0200252 tgt = self.target_minion
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000253 image_nameversion = "{}:{}".format(self.image_name, self.image_version)
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300254
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000255 docker_args = (
Oleksii Butenko25a8f372019-03-22 17:25:47 +0200256 " -t"
257 " --net host"
258 " --name {container_name}"
259 " -e ARGS=\"-r {tempest_pattern}"
260 " -w {tempest_threads} {tempest_extra_args}\""
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000261 " -v {cfg_dir}/tempest.conf:/etc/tempest/tempest.conf"
262 " -v /tmp/:/tmp/"
263 " -v {cfg_dir}:/root/tempest"
264 " -v /etc/ssl/certs/:/etc/ssl/certs/"
Oleksii Butenko25a8f372019-03-22 17:25:47 +0200265 " -d"
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000266 " {image_nameversion} {run_cmd}"
267 .format(
268 container_name=self.container_name,
269 image_nameversion=image_nameversion,
270 tempest_pattern=self.tempest_pattern,
271 tempest_threads=self.tempest_threads,
Oleksii Butenko25a8f372019-03-22 17:25:47 +0200272 tempest_extra_args=self.tempest_extra_args,
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000273 cfg_dir=TEMPEST_CFG_DIR,
274 run_cmd=self.run_cmd,
275 ))
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300276
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000277 commands = [
278 {
279 'description': "Run Tempest tests {0}".format(
280 image_nameversion),
281 'node_name': self.target_name,
282 'cmd': ("set -ex;" +
283 " docker rm --force {container_name} || true;"
284 " docker run {docker_args}"
285 .format(container_name=self.container_name,
286 docker_args=docker_args)),
287 'timeout': timeout},
288 ]
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300289
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000290 self.__salt_api.execute_commands(commands=commands,
291 label="Run Tempest tests")
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300292
sgudz6571a5e2019-02-26 15:49:22 +0200293 def simplify_salt_api_return(api_return, only_first_match=True):
294 """
295 Salt API always returns a dict with one key as 'return'
296 and value as a list with dict. For example:
297 For single node:
298 api.local('cfg01*', 'test.ping', expr_form='compound')
299 {u'return':[{u'cfg01.cookied-cicd-queens-dvr-sl.local':True}]}
300 For multinode:
301 api.local('ctl*', 'test.ping', expr_form='compound')
302 {u'return': [{u'ctl01.cookied-cicd-queens-dvr-sl.local': True,
303 u'ctl02.cookied-cicd-queens-dvr-sl.local': True,
304 u'ctl03.cookied-cicd-queens-dvr-sl.local': True}]}
305 When wrong function is given:
306 api.local('ctl01*', 'wrong_func', expr_form='compound')
307 {u'return': [{u'ctl01.cookied-cicd-queens-dvr-sl.local':
308 u"'wrong_func' is not available."}]}
309 Empty return:
310 api.local('wrong_target', 'test.ping', expr_form='compound')
311 {u'return': [{}]}
312 """
313 if api_return.get('return', [{}]) != [{}]:
314 api_return = api_return['return'][0]
315 if only_first_match:
316 api_return = next(api_return.iteritems())[1]
sgudz6571a5e2019-02-26 15:49:22 +0200317 return api_return
318 else:
319 LOG.info('''Salt api returns empty result: [{}]''')
320 return False
321
Oleksii Butenkob0ca4cc2018-10-22 16:29:54 +0300322 def wait_status(s):
sgudz6571a5e2019-02-26 15:49:22 +0200323 inspect_res = self.salt_api.local(tgt, 'dockerng.inspect',
Oleksii Butenkob0ca4cc2018-10-22 16:29:54 +0300324 self.container_name)
sgudz6571a5e2019-02-26 15:49:22 +0200325 inspect = simplify_salt_api_return(inspect_res)
326 if inspect:
Oleksii Butenkob0ca4cc2018-10-22 16:29:54 +0300327 status = inspect['State']['Status']
Oleksii Butenkob0ca4cc2018-10-22 16:29:54 +0300328 return status.lower() == s.lower()
Oleksii Butenkob0ca4cc2018-10-22 16:29:54 +0300329 return False
330
sgudz6571a5e2019-02-26 15:49:22 +0200331 if wait_status('running'):
332 helpers.wait(lambda: wait_status('exited'),
333 timeout=timeout,
334 timeout_msg=('Tempest run didnt finished '
335 'in {}'.format(timeout)))
Oleksii Butenkob0ca4cc2018-10-22 16:29:54 +0300336
sgudz6571a5e2019-02-26 15:49:22 +0200337 inspect_res = self.salt_api.local(tgt, 'dockerng.inspect',
338 self.container_name)
339 inspect = simplify_salt_api_return(inspect_res)
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300340
sgudz6571a5e2019-02-26 15:49:22 +0200341 logs_res = self.salt_api.local(tgt, 'dockerng.logs',
342 self.container_name)
343 logs = simplify_salt_api_return(logs_res)
sgudz2457db82019-03-01 16:06:36 +0200344 rm_res = self.salt_api.local(tgt, 'dockerng.rm',
345 self.container_name)
346 LOG.info("Tempest container was removed: {}".format(
347 json.dumps(rm_res, indent=4)))
sgudz6571a5e2019-02-26 15:49:22 +0200348 else:
349 inspect_res = self.salt_api.local(tgt, 'dockerng.inspect',
350 self.container_name)
351 inspect = simplify_salt_api_return(inspect_res)
352 if inspect:
353 status = inspect['State']['Status']
354 LOG.info("Container is not in RUNNING state. "
355 "Current container status is {}".format(status))
356 logs_res = self.salt_api.local(tgt,
357 'dockerng.logs',
358 self.container_name)
359 logs = simplify_salt_api_return(logs_res)
360 else:
361 LOG.info("dockerng returns unexpected"
362 " result: {}".format(inspect_res))
363 logs = None
364 inspect = None
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300365
366 return {'inspect': inspect,
367 'logs': logs}
Oleksii Butenkoe82441d2018-06-12 16:01:33 +0300368
obutenkoc4314352018-12-26 16:14:24 +0200369 def prepare_and_run_tempest(self, username='root'):
Oleksii Butenkoe82441d2018-06-12 16:01:33 +0300370 """
371 Run tempest tests
372 """
373 tempest_timeout = settings.TEMPEST_TIMEOUT
obutenkoc4314352018-12-26 16:14:24 +0200374 self.prepare()
Oleksii Butenkoe82441d2018-06-12 16:01:33 +0300375 test_res = self.run_tempest(tempest_timeout)
376 self.fetch_arficats(username=username)
377 self.save_runtime_logs(**test_res)