blob: b5772c52ca26655eb8d55ac4ca2bc72af84de916 [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
18from devops.helpers import helpers
19
20from tcp_tests import logger
21from tcp_tests import settings
22
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030023LOG = logger.logger
24
25TEMPEST_CFG_DIR = '/tmp/test'
26
27CONFIG = {
28 'classes': ['service.runtest.tempest'],
29 'parameters': {
30 '_param': {
31 'runtest_tempest_cfg_dir': TEMPEST_CFG_DIR,
32 'runtest_tempest_cfg_name': 'tempest.conf',
33 'runtest_tempest_public_net': 'net04_ext',
34 'tempest_test_target': 'gtw01*'
35 },
36 'neutron': {
37 'client': {
38 'enabled': True
39 }
40 },
41 'runtest': {
42 'enabled': True,
43 'keystonerc_node': 'ctl01*',
44 'tempest': {
45 'enabled': True,
46 'cfg_dir': '${_param:runtest_tempest_cfg_dir}',
47 'cfg_name': '${_param:runtest_tempest_cfg_name}',
48 'DEFAULT': {
49 'log_file': 'tempest.log'
50 },
51 'compute': {
52 'build_timeout': 600,
53 'max_microversion': 2.53,
54 'min_compute_nodes': 2,
55 'min_microversion': 2.1,
56 'volume_device_name': 'vdc'
57 },
58 'convert_to_uuid': {
59 'network': {
60 'public_network_id':
61 '${_param:runtest_tempest_public_net}'
62 }
63 },
64 'dns_feature_enabled': {
65 'api_admin': False,
66 'api_v1': False,
67 'api_v2': True,
68 'api_v2_quotas': True,
69 'api_v2_root_recordsets': True,
70 'bug_1573141_fixed': True
71 },
72 'heat_plugin': {
73 'floating_network_name':
74 '${_param:runtest_tempest_public_net}'
75 },
76 'network': {
77 'floating_network_name':
78 '${_param:runtest_tempest_public_net}'
79 },
80 'share': {
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030081 'capability_snapshot_support': True,
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030082 'run_driver_assisted_migration_tests': False,
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030083 'run_manage_unmanage_snapshot_tests': False,
84 'run_manage_unmanage_tests': False,
85 'run_migration_with_preserve_snapshots_tests': False,
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030086 'run_quota_tests': True,
87 'run_replication_tests': False,
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030088 'run_snapshot_tests': True,
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030089 }}}}}
90
91
92class RuntestManager(object):
93 """Helper manager for execution tempest via runtest-formula"""
94
95 image_name = settings.TEMPEST_IMAGE
96 image_version = settings.TEMPEST_IMAGE_VERSION
97 container_name = 'run-tempest-ci'
98 master_host = "cfg01"
99 master_tgt = "{}*".format(master_host)
100 class_name = "runtest"
101 run_cmd = '/bin/bash -c "run-tempest"'
102
103 def __init__(self, underlay, salt_api, cluster_name, domain_name,
104 tempest_threads, tempest_exclude_test_args,
105 tempest_pattern=settings.TEMPEST_PATTERN,
106 run_cmd=None, target='gtw01'):
107 self.underlay = underlay
108 self.__salt_api = salt_api
109 self.target = target
110 self.cluster_name = cluster_name
111 self.domain_name = domain_name
112 self.tempest_threads = tempest_threads
113 self.tempest_exclude_test_args = tempest_exclude_test_args
114 self.tempest_pattern = tempest_pattern
115 self.run_cmd = run_cmd or self.run_cmd
116
117 @property
118 def salt_api(self):
119 return self.__salt_api
120
121 def install_python_lib(self):
122 return self.salt_api.local(
123 "{}*".format(self.target),
124 'pip.install', 'docker'), None
125
Oleksii Butenko5cd0a162018-06-14 18:18:10 +0300126 def run_salt_minion_state(self):
127 return self.salt_api.enforce_state(self.master_tgt, 'salt.minion')
128
129 def check_ping_salt_master(self):
130 return self.salt_api.local('cfg01*', 'test.ping')
131
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300132 def install_formula(self):
133 return self.salt_api.local(
134 self.master_tgt,
135 'pkg.install', 'salt-formula-runtest'), None
136
137 def create_networks(self):
138 return self.salt_api.enforce_state(self.master_tgt, 'neutron.client')
139
140 def create_flavors(self):
141 return self.salt_api.enforce_state(self.master_tgt, 'nova.client')
142
143 def create_cirros(self):
144 return self.salt_api.enforce_state(self.master_tgt, 'glance.client')
145
146 def generate_config(self):
147 return self.salt_api.enforce_state(self.master_tgt, 'runtest')
148
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300149 def fetch_arficats(self, username=None, file_format='xml'):
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300150 target_name = next(node_name for node_name
151 in self.underlay.node_names() if
152 self.target in node_name)
153 with self.underlay.remote(node_name=target_name, username=None) as tgt:
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300154 result = tgt.execute('find {} -name "report_*.{}"'.format(
155 TEMPEST_CFG_DIR, file_format))
156 LOG.debug("Find result {0}".format(result))
157 assert len(result['stdout']) > 0, ('No report found, please check'
158 ' if test run was successful.')
159 report = result['stdout'][0].rstrip()
160 LOG.debug("Found files {0}".format(report))
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300161 tgt.download(
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300162 destination=report, # noqa
163 target=os.getcwd())
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300164
165 def store_runtest_model(self, config=CONFIG):
166 master_name = next(node_name for node_name
167 in self.underlay.node_names() if
168 self.master_host in node_name)
169 with self.underlay.yaml_editor(
170 file_path="/srv/salt/reclass/classes/cluster/"
171 "{cluster_name}/infra/"
172 "{class_name}.yml".format(
173 cluster_name=self.cluster_name,
174 class_name=self.class_name),
175 node_name=master_name) as editor:
176 editor.content = config
177
178 with self.underlay.yaml_editor(
179 file_path="/srv/salt/reclass/nodes/_generated/"
180 "cfg01.{domain_name}.yml".format(
181 domain_name=self.domain_name),
182 node_name=master_name) as editor:
183 editor.content['classes'].append(
184 'cluster.{cluster_name}.infra.{class_name}'.format(
185 cluster_name=self.cluster_name,
186 class_name=self.class_name))
187
188 self.salt_api.local('*', 'saltutil.refresh_pillar')
189 self.salt_api.local('*', 'saltutil.sync_all')
190
191 def save_runtime_logs(self, logs=None, inspect=None):
192 if logs:
193 with open("{path}/{target}_tempest_run.log".format(
194 path=settings.LOGS_DIR, target=self.target), 'w') as f:
195 LOG.info("Save tempest console log")
196 container_log = logs
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300197 f.write(container_log.encode('ascii', 'ignore'))
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300198
199 if inspect:
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300200 with open("{path}/{target}_tempest_container_info.json.log".format(
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300201 path=settings.LOGS_DIR, target=self.target), 'w') as f:
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300202 LOG.info("Save tempest container inspect data")
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300203
204 container_inspect = json.dumps(inspect,
205 indent=4, sort_keys=True)
206 f.write(container_inspect)
207
208 def prepare(self):
209 self.store_runtest_model()
210 res = self.install_formula()
211 LOG.info(json.dumps(res, indent=4))
Oleksii Butenko5cd0a162018-06-14 18:18:10 +0300212
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300213 res = self.install_python_lib()
214 LOG.info(json.dumps(res, indent=4))
Oleksii Butenko5cd0a162018-06-14 18:18:10 +0300215
216 res = self.run_salt_minion_state()
217 LOG.info(json.dumps(res, indent=4))
218
219 res = self.check_ping_salt_master()
220 LOG.info(json.dumps(res, indent=4))
221
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300222 res = self.create_networks()
223 LOG.info(json.dumps(res, indent=4))
Oleksii Butenko5cd0a162018-06-14 18:18:10 +0300224
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300225 res = self.create_flavors()
226 LOG.info(json.dumps(res, indent=4))
Oleksii Butenko5cd0a162018-06-14 18:18:10 +0300227
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300228 res = self.create_cirros()
229 LOG.info(json.dumps(res, indent=4))
Oleksii Butenko5cd0a162018-06-14 18:18:10 +0300230
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300231 res = self.generate_config()
232 LOG.info(json.dumps(res, indent=4))
233
234 def run_tempest(self, timeout=600):
235 tgt = "{}*".format(self.target)
236 params = {
237 "name": self.container_name,
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300238 "image": "{}:{}".format(self.image_name, self.image_version),
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300239 "environment": {
240 "ARGS": "-r {tempest_pattern} -w "
241 "{tempest_threads} "
242 "{tempest_exclude_test_args}".format(
243 tempest_pattern=self.tempest_pattern,
244 tempest_threads=self.tempest_threads,
245 tempest_exclude_test_args=self.tempest_exclude_test_args) # noqa
246 },
247 "binds": [
248 "{cfg_dir}/tempest.conf:/etc/tempest/tempest.conf".format(cfg_dir=TEMPEST_CFG_DIR), # noqa
249 "/tmp/:/tmp/",
250 "{cfg_dir}:/root/tempest".format(cfg_dir=TEMPEST_CFG_DIR),
251 "/etc/ssl/certs/:/etc/ssl/certs/"
252 ],
253 "auto_remove": False,
254 "cmd": self.run_cmd
255 }
256
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300257 res = self.salt_api.local(tgt, 'dockerng.pull', "{}:{}".format(
258 self.image_name, self.image_version))
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300259 LOG.info("Tempest image has beed pulled- \n{}".format(
260 json.dumps(res, indent=4)))
261
262 res = self.salt_api.local(tgt, 'dockerng.create', kwargs=params)
263 LOG.info("Tempest container has been created - \n{}".format(
264 json.dumps(res, indent=4)))
265
266 res = self.salt_api.local(tgt, 'dockerng.start', self.container_name)
267 LOG.info("Tempest container has been started - \n{}".format(
268 json.dumps(res, indent=4)))
269
270 def wait_status(s):
271 inspect_res = self.salt_api.local(tgt,
272 'dockerng.inspect',
273 self.container_name)
274 if 'return' in inspect_res:
275 inspect = inspect_res['return']
276 inspect = inspect[0]
277 inspect = next(inspect.iteritems())[1]
278 status = inspect['State']['Status']
279
280 return status.lower() == s.lower()
281
282 return False
283
284 helpers.wait(lambda: wait_status('exited'),
285 timeout=timeout,
286 timeout_msg=('Tempest run didnt finished '
287 'in {}'.format(timeout)))
288
289 inspect_res = self.salt_api.local(tgt,
290 'dockerng.inspect',
291 self.container_name)
292 inspect = inspect_res['return'][0]
293 inspect = next(inspect.iteritems())[1]
294 if inspect['State']['ExitCode'] != 0:
295 LOG.error("Tempest running failed")
296 LOG.info("Tempest tests have been finished - \n{}".format(
297 json.dumps(res, indent=4)))
298
299 logs_res = self.salt_api.local(tgt,
300 'dockerng.logs',
301 self.container_name)
302 logs = logs_res['return'][0]
303 logs = next(logs.iteritems())[1]
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300304 LOG.info("Tempest result - \n{}".format(
305 logs.encode('ascii', 'ignore')))
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300306
307 res = self.salt_api.local(tgt, 'dockerng.rm', self.container_name)
308 LOG.info("Tempest container was removed".format(
309 json.dumps(res, indent=4)))
310
311 return {'inspect': inspect,
312 'logs': logs}
Oleksii Butenkoe82441d2018-06-12 16:01:33 +0300313
314 def prepare_and_run_tempest(self, username='root'):
315 """
316 Run tempest tests
317 """
318 tempest_timeout = settings.TEMPEST_TIMEOUT
319 self.prepare()
320 test_res = self.run_tempest(tempest_timeout)
321 self.fetch_arficats(username=username)
322 self.save_runtime_logs(**test_res)