blob: 70d573b63c2e917ee577e4990d869e71bf89eaa7 [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
Oleksii Butenkoe41d39f2018-06-22 17:12:41 +030017import time
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030018
19from devops.helpers import helpers
20
21from tcp_tests import logger
22from tcp_tests import settings
23
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030024LOG = logger.logger
25
26TEMPEST_CFG_DIR = '/tmp/test'
27
28CONFIG = {
29 'classes': ['service.runtest.tempest'],
30 'parameters': {
31 '_param': {
32 'runtest_tempest_cfg_dir': TEMPEST_CFG_DIR,
33 'runtest_tempest_cfg_name': 'tempest.conf',
34 'runtest_tempest_public_net': 'net04_ext',
35 'tempest_test_target': 'gtw01*'
36 },
37 'neutron': {
38 'client': {
39 'enabled': True
40 }
41 },
42 'runtest': {
43 'enabled': True,
44 'keystonerc_node': 'ctl01*',
45 'tempest': {
46 'enabled': True,
47 'cfg_dir': '${_param:runtest_tempest_cfg_dir}',
48 'cfg_name': '${_param:runtest_tempest_cfg_name}',
49 'DEFAULT': {
50 'log_file': 'tempest.log'
51 },
52 'compute': {
53 'build_timeout': 600,
54 'max_microversion': 2.53,
55 'min_compute_nodes': 2,
56 'min_microversion': 2.1,
57 'volume_device_name': 'vdc'
58 },
59 'convert_to_uuid': {
60 'network': {
61 'public_network_id':
62 '${_param:runtest_tempest_public_net}'
63 }
64 },
65 'dns_feature_enabled': {
66 'api_admin': False,
67 'api_v1': False,
68 'api_v2': True,
69 'api_v2_quotas': True,
70 'api_v2_root_recordsets': True,
71 'bug_1573141_fixed': True
72 },
73 'heat_plugin': {
74 'floating_network_name':
75 '${_param:runtest_tempest_public_net}'
76 },
77 'network': {
78 'floating_network_name':
79 '${_param:runtest_tempest_public_net}'
80 },
81 'share': {
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030082 'capability_snapshot_support': True,
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030083 'run_driver_assisted_migration_tests': False,
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030084 'run_manage_unmanage_snapshot_tests': False,
85 'run_manage_unmanage_tests': False,
86 'run_migration_with_preserve_snapshots_tests': False,
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030087 'run_quota_tests': True,
88 'run_replication_tests': False,
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030089 'run_snapshot_tests': True,
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +030090 }}}}}
91
92
93class RuntestManager(object):
94 """Helper manager for execution tempest via runtest-formula"""
95
96 image_name = settings.TEMPEST_IMAGE
97 image_version = settings.TEMPEST_IMAGE_VERSION
98 container_name = 'run-tempest-ci'
99 master_host = "cfg01"
100 master_tgt = "{}*".format(master_host)
101 class_name = "runtest"
102 run_cmd = '/bin/bash -c "run-tempest"'
103
104 def __init__(self, underlay, salt_api, cluster_name, domain_name,
105 tempest_threads, tempest_exclude_test_args,
106 tempest_pattern=settings.TEMPEST_PATTERN,
107 run_cmd=None, target='gtw01'):
108 self.underlay = underlay
109 self.__salt_api = salt_api
110 self.target = target
111 self.cluster_name = cluster_name
112 self.domain_name = domain_name
113 self.tempest_threads = tempest_threads
114 self.tempest_exclude_test_args = tempest_exclude_test_args
115 self.tempest_pattern = tempest_pattern
116 self.run_cmd = run_cmd or self.run_cmd
117
118 @property
119 def salt_api(self):
120 return self.__salt_api
121
122 def install_python_lib(self):
123 return self.salt_api.local(
124 "{}*".format(self.target),
125 'pip.install', 'docker'), None
126
Oleksii Butenko5cd0a162018-06-14 18:18:10 +0300127 def run_salt_minion_state(self):
Oleksii Butenkoe41d39f2018-06-22 17:12:41 +0300128 return self.salt_api.local('cfg01*', 'state.sls', 'salt.minion')
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300129
130 def create_networks(self):
131 return self.salt_api.enforce_state(self.master_tgt, 'neutron.client')
132
133 def create_flavors(self):
134 return self.salt_api.enforce_state(self.master_tgt, 'nova.client')
135
136 def create_cirros(self):
137 return self.salt_api.enforce_state(self.master_tgt, 'glance.client')
138
139 def generate_config(self):
140 return self.salt_api.enforce_state(self.master_tgt, 'runtest')
141
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300142 def fetch_arficats(self, username=None, file_format='xml'):
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300143 target_name = next(node_name for node_name
144 in self.underlay.node_names() if
145 self.target in node_name)
146 with self.underlay.remote(node_name=target_name, username=None) as tgt:
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300147 result = tgt.execute('find {} -name "report_*.{}"'.format(
148 TEMPEST_CFG_DIR, file_format))
149 LOG.debug("Find result {0}".format(result))
150 assert len(result['stdout']) > 0, ('No report found, please check'
151 ' if test run was successful.')
152 report = result['stdout'][0].rstrip()
153 LOG.debug("Found files {0}".format(report))
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300154 tgt.download(
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300155 destination=report, # noqa
156 target=os.getcwd())
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300157
158 def store_runtest_model(self, config=CONFIG):
159 master_name = next(node_name for node_name
160 in self.underlay.node_names() if
161 self.master_host in node_name)
162 with self.underlay.yaml_editor(
163 file_path="/srv/salt/reclass/classes/cluster/"
164 "{cluster_name}/infra/"
165 "{class_name}.yml".format(
166 cluster_name=self.cluster_name,
167 class_name=self.class_name),
168 node_name=master_name) as editor:
169 editor.content = config
170
171 with self.underlay.yaml_editor(
172 file_path="/srv/salt/reclass/nodes/_generated/"
173 "cfg01.{domain_name}.yml".format(
174 domain_name=self.domain_name),
175 node_name=master_name) as editor:
176 editor.content['classes'].append(
177 'cluster.{cluster_name}.infra.{class_name}'.format(
178 cluster_name=self.cluster_name,
179 class_name=self.class_name))
180
181 self.salt_api.local('*', 'saltutil.refresh_pillar')
182 self.salt_api.local('*', 'saltutil.sync_all')
183
184 def save_runtime_logs(self, logs=None, inspect=None):
185 if logs:
186 with open("{path}/{target}_tempest_run.log".format(
187 path=settings.LOGS_DIR, target=self.target), 'w') as f:
188 LOG.info("Save tempest console log")
189 container_log = logs
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300190 f.write(container_log.encode('ascii', 'ignore'))
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300191
192 if inspect:
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300193 with open("{path}/{target}_tempest_container_info.json.log".format(
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300194 path=settings.LOGS_DIR, target=self.target), 'w') as f:
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300195 LOG.info("Save tempest container inspect data")
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300196
197 container_inspect = json.dumps(inspect,
198 indent=4, sort_keys=True)
199 f.write(container_inspect)
200
201 def prepare(self):
202 self.store_runtest_model()
Oleksii Butenko5cd0a162018-06-14 18:18:10 +0300203
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300204 res = self.install_python_lib()
205 LOG.info(json.dumps(res, indent=4))
Oleksii Butenko5cd0a162018-06-14 18:18:10 +0300206
207 res = self.run_salt_minion_state()
208 LOG.info(json.dumps(res, indent=4))
Oleksii Butenkoe41d39f2018-06-22 17:12:41 +0300209 time.sleep(10)
Oleksii Butenko5cd0a162018-06-14 18:18:10 +0300210
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300211 res = self.create_networks()
212 LOG.info(json.dumps(res, indent=4))
Oleksii Butenko5cd0a162018-06-14 18:18:10 +0300213
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300214 res = self.create_flavors()
215 LOG.info(json.dumps(res, indent=4))
Oleksii Butenko5cd0a162018-06-14 18:18:10 +0300216
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300217 res = self.create_cirros()
218 LOG.info(json.dumps(res, indent=4))
Oleksii Butenko5cd0a162018-06-14 18:18:10 +0300219
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300220 res = self.generate_config()
221 LOG.info(json.dumps(res, indent=4))
222
223 def run_tempest(self, timeout=600):
224 tgt = "{}*".format(self.target)
225 params = {
226 "name": self.container_name,
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300227 "image": "{}:{}".format(self.image_name, self.image_version),
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300228 "environment": {
229 "ARGS": "-r {tempest_pattern} -w "
230 "{tempest_threads} "
231 "{tempest_exclude_test_args}".format(
232 tempest_pattern=self.tempest_pattern,
233 tempest_threads=self.tempest_threads,
234 tempest_exclude_test_args=self.tempest_exclude_test_args) # noqa
235 },
236 "binds": [
237 "{cfg_dir}/tempest.conf:/etc/tempest/tempest.conf".format(cfg_dir=TEMPEST_CFG_DIR), # noqa
238 "/tmp/:/tmp/",
239 "{cfg_dir}:/root/tempest".format(cfg_dir=TEMPEST_CFG_DIR),
240 "/etc/ssl/certs/:/etc/ssl/certs/"
241 ],
242 "auto_remove": False,
243 "cmd": self.run_cmd
244 }
245
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300246 res = self.salt_api.local(tgt, 'dockerng.pull', "{}:{}".format(
247 self.image_name, self.image_version))
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300248 LOG.info("Tempest image has beed pulled- \n{}".format(
249 json.dumps(res, indent=4)))
250
251 res = self.salt_api.local(tgt, 'dockerng.create', kwargs=params)
252 LOG.info("Tempest container has been created - \n{}".format(
253 json.dumps(res, indent=4)))
254
255 res = self.salt_api.local(tgt, 'dockerng.start', self.container_name)
256 LOG.info("Tempest container has been started - \n{}".format(
257 json.dumps(res, indent=4)))
258
259 def wait_status(s):
260 inspect_res = self.salt_api.local(tgt,
261 'dockerng.inspect',
262 self.container_name)
263 if 'return' in inspect_res:
264 inspect = inspect_res['return']
265 inspect = inspect[0]
266 inspect = next(inspect.iteritems())[1]
267 status = inspect['State']['Status']
268
269 return status.lower() == s.lower()
270
271 return False
272
273 helpers.wait(lambda: wait_status('exited'),
274 timeout=timeout,
275 timeout_msg=('Tempest run didnt finished '
276 'in {}'.format(timeout)))
277
278 inspect_res = self.salt_api.local(tgt,
279 'dockerng.inspect',
280 self.container_name)
281 inspect = inspect_res['return'][0]
282 inspect = next(inspect.iteritems())[1]
283 if inspect['State']['ExitCode'] != 0:
284 LOG.error("Tempest running failed")
285 LOG.info("Tempest tests have been finished - \n{}".format(
286 json.dumps(res, indent=4)))
287
288 logs_res = self.salt_api.local(tgt,
289 'dockerng.logs',
290 self.container_name)
291 logs = logs_res['return'][0]
292 logs = next(logs.iteritems())[1]
Oleksii Butenko71d76f32018-06-05 17:46:34 +0300293 LOG.info("Tempest result - \n{}".format(
294 logs.encode('ascii', 'ignore')))
Dmitry Tyzhnenkoc56b77e2018-05-21 11:01:43 +0300295
296 res = self.salt_api.local(tgt, 'dockerng.rm', self.container_name)
297 LOG.info("Tempest container was removed".format(
298 json.dumps(res, indent=4)))
299
300 return {'inspect': inspect,
301 'logs': logs}
Oleksii Butenkoe82441d2018-06-12 16:01:33 +0300302
303 def prepare_and_run_tempest(self, username='root'):
304 """
305 Run tempest tests
306 """
307 tempest_timeout = settings.TEMPEST_TIMEOUT
308 self.prepare()
309 test_res = self.run_tempest(tempest_timeout)
310 self.fetch_arficats(username=username)
311 self.save_runtime_logs(**test_res)