blob: 66095dcdfcd9b0b011e0436978c0124670e352ef [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,
obutenkoc4314352018-12-26 16:14:24 +020042 run_cmd=None):
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +000043 self.__config = config
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030044 self.underlay = underlay
45 self.__salt_api = salt_api
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030046 self.cluster_name = cluster_name
47 self.domain_name = domain_name
48 self.tempest_threads = tempest_threads
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030049 self.tempest_pattern = tempest_pattern
50 self.run_cmd = run_cmd or self.run_cmd
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +000051 self.master_name = self.underlay.get_target_node_names(
52 self.master_host)[0]
Dennis Dmitriev1566e3f2019-01-11 17:35:43 +020053 self.master_minion = self.underlay.get_target_minion_ids(
54 self.master_host)[0]
obutenkoc4314352018-12-26 16:14:24 +020055 self.__target_name = None
sgudz6571a5e2019-02-26 15:49:22 +020056 self.__target_minion = None
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030057
58 @property
59 def salt_api(self):
60 return self.__salt_api
61
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +000062 @property
obutenkoc4314352018-12-26 16:14:24 +020063 def target_name(self):
64 if not self.__target_name:
obutenkoa1e3cea2019-01-12 21:11:55 +020065 target_host = self.__salt_api.get_single_pillar(
Dennis Dmitriev1566e3f2019-01-11 17:35:43 +020066 tgt=self.master_minion,
obutenkoc4314352018-12-26 16:14:24 +020067 pillar="runtest:tempest:test_target")
obutenkoc4314352018-12-26 16:14:24 +020068 if target_host[-1] == "*":
69 target_host = target_host[:-1]
70 self.__target_name = self.underlay.get_target_node_names(
71 target_host)[0]
72 return self.__target_name
obutenko3e44b602018-11-13 12:50:22 +020073
sgudz6571a5e2019-02-26 15:49:22 +020074 @property
75 def target_minion(self):
76 if not self.__target_minion:
77 target_host = self.__salt_api.get_single_pillar(
78 tgt=self.master_minion,
79 pillar="runtest:tempest:test_target")
80 if target_host[-1] == "*":
81 target_host = target_host[:-1]
82 self.__target_minion = self.underlay.get_target_minion_ids(
83 target_host)[0]
84 return self.__target_minion
85
Oleksii Butenko71d76f32018-06-05 17:46:34 +030086 def fetch_arficats(self, username=None, file_format='xml'):
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +000087 with self.underlay.remote(node_name=self.target_name,
88 username=None) as tgt:
Oleksii Butenko71d76f32018-06-05 17:46:34 +030089 result = tgt.execute('find {} -name "report_*.{}"'.format(
90 TEMPEST_CFG_DIR, file_format))
91 LOG.debug("Find result {0}".format(result))
92 assert len(result['stdout']) > 0, ('No report found, please check'
93 ' if test run was successful.')
94 report = result['stdout'][0].rstrip()
95 LOG.debug("Found files {0}".format(report))
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030096 tgt.download(
Oleksii Butenko71d76f32018-06-05 17:46:34 +030097 destination=report, # noqa
98 target=os.getcwd())
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030099
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300100 def save_runtime_logs(self, logs=None, inspect=None):
101 if logs:
102 with open("{path}/{target}_tempest_run.log".format(
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000103 path=settings.LOGS_DIR,
104 target=self.target_name), 'w') as f:
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300105 LOG.info("Save tempest console log")
106 container_log = logs
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300107 f.write(container_log.encode('ascii', 'ignore'))
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300108
109 if inspect:
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300110 with open("{path}/{target}_tempest_container_info.json.log".format(
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000111 path=settings.LOGS_DIR,
112 target=self.target_name), 'w') as f:
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300113 LOG.info("Save tempest container inspect data")
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300114
115 container_inspect = json.dumps(inspect,
116 indent=4, sort_keys=True)
117 f.write(container_inspect)
118
obutenkoc4314352018-12-26 16:14:24 +0200119 def prepare(self):
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000120 salt_call_cmd = "salt-call -l info --hard-crash --state-output=mixed "
obutenkoa1e3cea2019-01-12 21:11:55 +0200121 barbican_integration = self.__salt_api.get_single_pillar(
Tatyana Leontovich8b1fda02019-01-23 13:14:33 +0200122 tgt="ctl01*",
obutenkoa1e3cea2019-01-12 21:11:55 +0200123 pillar="_param:barbican_integration_enabled")
Tatyana Leontovich8b1fda02019-01-23 13:14:33 +0200124
obutenkoc48f4352019-01-24 17:03:06 +0200125 LOG.info("Barbican integration {0}".format(barbican_integration))
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000126 commands = [
127 {
Tatyana Leontovicheea03e92018-11-20 12:51:50 +0200128 'description': ("Install docker-ce package and "
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000129 "enable packets forwarding"),
130 'node_name': self.target_name,
131 'cmd': ("set -ex;" +
Tatyana Leontovicheea03e92018-11-20 12:51:50 +0200132 salt_call_cmd + " pkg.install docker-ce && " +
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000133 " iptables --policy FORWARD ACCEPT")},
134 {
135 'description': "Install PyPI docker package",
136 'node_name': self.target_name,
137 'cmd': ("set -ex;" +
138 salt_call_cmd + " pip.install setuptools && " +
139 salt_call_cmd + " pip.install docker")},
140 {
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000141 'description': "Generate config for Tempest",
142 'node_name': self.master_name,
143 'cmd': ("set -ex;" +
obutenkoc4314352018-12-26 16:14:24 +0200144 "salt-run state.orchestrate " +
145 "runtest.orchestrate.tempest")},
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000146 ]
obutenkoa1e3cea2019-01-12 21:11:55 +0200147
obutenkoc48f4352019-01-24 17:03:06 +0200148 if barbican_integration:
obutenkoa1e3cea2019-01-12 21:11:55 +0200149 commands.append({
150 'description': "Configure barbican",
151 'node_name': self.master_name,
152 'cmd': ("set -ex;" +
153 salt_call_cmd +
154 " state.sls barbican.client && " +
155 salt_call_cmd +
obutenkoc48f4352019-01-24 17:03:06 +0200156 " state.sls runtest.test_accounts" +
obutenkoa1e3cea2019-01-12 21:11:55 +0200157 salt_call_cmd +
158 " state.sls runtest.barbican_sign_image")},
159 )
160
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000161 self.__salt_api.execute_commands(commands=commands,
162 label="Prepare for Tempest")
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300163
164 def run_tempest(self, timeout=600):
sgudz6571a5e2019-02-26 15:49:22 +0200165 tgt = self.target_minion
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000166 image_nameversion = "{}:{}".format(self.image_name, self.image_version)
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300167
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000168 docker_args = (
Oleksii Butenkob0ca4cc2018-10-22 16:29:54 +0300169 " -t "
obutenkoa1e3cea2019-01-12 21:11:55 +0200170 " --net host "
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000171 " --name {container_name} "
172 " -e ARGS=\"-r {tempest_pattern} -w {tempest_threads}\""
173 " -v {cfg_dir}/tempest.conf:/etc/tempest/tempest.conf"
174 " -v /tmp/:/tmp/"
175 " -v {cfg_dir}:/root/tempest"
176 " -v /etc/ssl/certs/:/etc/ssl/certs/"
Oleksii Butenkob0ca4cc2018-10-22 16:29:54 +0300177 " -d "
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000178 " {image_nameversion} {run_cmd}"
179 .format(
180 container_name=self.container_name,
181 image_nameversion=image_nameversion,
182 tempest_pattern=self.tempest_pattern,
183 tempest_threads=self.tempest_threads,
184 cfg_dir=TEMPEST_CFG_DIR,
185 run_cmd=self.run_cmd,
186 ))
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300187
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000188 commands = [
189 {
190 'description': "Run Tempest tests {0}".format(
191 image_nameversion),
192 'node_name': self.target_name,
193 'cmd': ("set -ex;" +
194 " docker rm --force {container_name} || true;"
195 " docker run {docker_args}"
196 .format(container_name=self.container_name,
197 docker_args=docker_args)),
198 'timeout': timeout},
199 ]
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300200
Dennis Dmitrievb6bcc5c2018-09-26 11:07:53 +0000201 self.__salt_api.execute_commands(commands=commands,
202 label="Run Tempest tests")
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300203
sgudz6571a5e2019-02-26 15:49:22 +0200204 def simplify_salt_api_return(api_return, only_first_match=True):
205 """
206 Salt API always returns a dict with one key as 'return'
207 and value as a list with dict. For example:
208 For single node:
209 api.local('cfg01*', 'test.ping', expr_form='compound')
210 {u'return':[{u'cfg01.cookied-cicd-queens-dvr-sl.local':True}]}
211 For multinode:
212 api.local('ctl*', 'test.ping', expr_form='compound')
213 {u'return': [{u'ctl01.cookied-cicd-queens-dvr-sl.local': True,
214 u'ctl02.cookied-cicd-queens-dvr-sl.local': True,
215 u'ctl03.cookied-cicd-queens-dvr-sl.local': True}]}
216 When wrong function is given:
217 api.local('ctl01*', 'wrong_func', expr_form='compound')
218 {u'return': [{u'ctl01.cookied-cicd-queens-dvr-sl.local':
219 u"'wrong_func' is not available."}]}
220 Empty return:
221 api.local('wrong_target', 'test.ping', expr_form='compound')
222 {u'return': [{}]}
223 """
224 if api_return.get('return', [{}]) != [{}]:
225 api_return = api_return['return'][0]
226 if only_first_match:
227 api_return = next(api_return.iteritems())[1]
sgudz6571a5e2019-02-26 15:49:22 +0200228 return api_return
229 else:
230 LOG.info('''Salt api returns empty result: [{}]''')
231 return False
232
Oleksii Butenkob0ca4cc2018-10-22 16:29:54 +0300233 def wait_status(s):
sgudz6571a5e2019-02-26 15:49:22 +0200234 inspect_res = self.salt_api.local(tgt, 'dockerng.inspect',
Oleksii Butenkob0ca4cc2018-10-22 16:29:54 +0300235 self.container_name)
sgudz6571a5e2019-02-26 15:49:22 +0200236 inspect = simplify_salt_api_return(inspect_res)
237 if inspect:
Oleksii Butenkob0ca4cc2018-10-22 16:29:54 +0300238 status = inspect['State']['Status']
Oleksii Butenkob0ca4cc2018-10-22 16:29:54 +0300239 return status.lower() == s.lower()
Oleksii Butenkob0ca4cc2018-10-22 16:29:54 +0300240 return False
241
sgudz6571a5e2019-02-26 15:49:22 +0200242 if wait_status('running'):
243 helpers.wait(lambda: wait_status('exited'),
244 timeout=timeout,
245 timeout_msg=('Tempest run didnt finished '
246 'in {}'.format(timeout)))
Oleksii Butenkob0ca4cc2018-10-22 16:29:54 +0300247
sgudz6571a5e2019-02-26 15:49:22 +0200248 inspect_res = self.salt_api.local(tgt, 'dockerng.inspect',
249 self.container_name)
250 inspect = simplify_salt_api_return(inspect_res)
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300251
sgudz6571a5e2019-02-26 15:49:22 +0200252 logs_res = self.salt_api.local(tgt, 'dockerng.logs',
253 self.container_name)
254 logs = simplify_salt_api_return(logs_res)
sgudz2457db82019-03-01 16:06:36 +0200255 rm_res = self.salt_api.local(tgt, 'dockerng.rm',
256 self.container_name)
257 LOG.info("Tempest container was removed: {}".format(
258 json.dumps(rm_res, indent=4)))
sgudz6571a5e2019-02-26 15:49:22 +0200259 else:
260 inspect_res = self.salt_api.local(tgt, 'dockerng.inspect',
261 self.container_name)
262 inspect = simplify_salt_api_return(inspect_res)
263 if inspect:
264 status = inspect['State']['Status']
265 LOG.info("Container is not in RUNNING state. "
266 "Current container status is {}".format(status))
267 logs_res = self.salt_api.local(tgt,
268 'dockerng.logs',
269 self.container_name)
270 logs = simplify_salt_api_return(logs_res)
271 else:
272 LOG.info("dockerng returns unexpected"
273 " result: {}".format(inspect_res))
274 logs = None
275 inspect = None
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300276
277 return {'inspect': inspect,
278 'logs': logs}
Oleksii Butenkoe82441d2018-06-12 16:01:33 +0300279
obutenkoc4314352018-12-26 16:14:24 +0200280 def prepare_and_run_tempest(self, username='root'):
Oleksii Butenkoe82441d2018-06-12 16:01:33 +0300281 """
282 Run tempest tests
283 """
284 tempest_timeout = settings.TEMPEST_TIMEOUT
obutenkoc4314352018-12-26 16:14:24 +0200285 self.prepare()
Oleksii Butenkoe82441d2018-06-12 16:01:33 +0300286 test_res = self.run_tempest(tempest_timeout)
287 self.fetch_arficats(username=username)
288 self.save_runtime_logs(**test_res)