blob: fef449239b47b2295e9423d0ee17b527e712ea7c [file] [log] [blame]
Steve Baker450aa7f2014-08-25 10:37:27 +12001# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
Steve Baker450aa7f2014-08-25 10:37:27 +120013import random
14import re
Steve Baker450aa7f2014-08-25 10:37:27 +120015import subprocess
Steve Baker450aa7f2014-08-25 10:37:27 +120016import time
17
Pavlo Shchelokovskyy60e0ecd2014-12-14 22:17:21 +020018import fixtures
Steve Baker450aa7f2014-08-25 10:37:27 +120019from heatclient import exc as heat_exceptions
Thomas Hervedb36c092017-03-23 11:20:14 +010020from keystoneauth1 import exceptions as kc_exceptions
Steve Baker24641292015-03-13 10:47:50 +130021from oslo_log import log as logging
Jens Rosenboom4f069fb2015-02-18 14:19:07 +010022from oslo_utils import timeutils
Pavlo Shchelokovskyy60e0ecd2014-12-14 22:17:21 +020023import testscenarios
24import testtools
Takashi Kajinamidfb97392022-05-10 00:51:14 +090025import urllib
Steve Baker450aa7f2014-08-25 10:37:27 +120026
rabid2916d02017-09-22 18:19:24 +053027from heat_tempest_plugin.common import exceptions
28from heat_tempest_plugin.common import remote_client
rabid2916d02017-09-22 18:19:24 +053029from heat_tempest_plugin.services import clients
Zane Bitterb4acd962018-01-18 12:08:23 -050030from tempest import config
Pavlo Shchelokovskyye3907092025-12-19 17:37:12 +000031from tempest import test
Steve Baker450aa7f2014-08-25 10:37:27 +120032
33LOG = logging.getLogger(__name__)
Angus Salkeld24043702014-11-21 08:49:26 +100034_LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s"
rabi82b71282018-03-08 11:08:36 +053035_resource_types = None
Steve Baker450aa7f2014-08-25 10:37:27 +120036
37
Angus Salkeld08514ad2015-02-06 10:08:31 +100038def call_until_true(duration, sleep_for, func, *args, **kwargs):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +030039 """Call the function until it returns True or the duration elapsed.
40
Steve Baker450aa7f2014-08-25 10:37:27 +120041 Call the given function until it returns True (and return True) or
42 until the specified duration (in seconds) elapses (and return
43 False).
44
45 :param func: A zero argument callable that returns True on success.
46 :param duration: The number of seconds for which to attempt a
47 successful call of the function.
48 :param sleep_for: The number of seconds to sleep after an unsuccessful
49 invocation of the function.
50 """
51 now = time.time()
52 timeout = now + duration
53 while now < timeout:
Angus Salkeld08514ad2015-02-06 10:08:31 +100054 if func(*args, **kwargs):
Steve Baker450aa7f2014-08-25 10:37:27 +120055 return True
56 LOG.debug("Sleeping for %d seconds", sleep_for)
57 time.sleep(sleep_for)
58 now = time.time()
59 return False
60
61
rabi32201342017-11-17 22:57:58 +053062def isotime(at):
63 if at is None:
64 return None
65 return at.strftime('%Y-%m-%dT%H:%M:%SZ')
66
67
Steve Baker450aa7f2014-08-25 10:37:27 +120068def rand_name(name=''):
Takashi Kajinamidfb97392022-05-10 00:51:14 +090069 randbits = str(random.randint(1, 0x7fffffff))
Steve Baker450aa7f2014-08-25 10:37:27 +120070 if name:
71 return name + '-' + randbits
72 else:
73 return randbits
74
75
Zane Bitterf407e102017-10-05 14:19:32 -040076def requires_convergence(test_method):
77 '''Decorator for convergence-only tests.
78
79 The decorated test will be skipped when convergence is disabled.
80 '''
rabif89752b2017-11-18 22:14:30 +053081 plugin = config.CONF.heat_plugin
rabi94a520a2017-11-17 22:49:17 +053082 convergence_enabled = plugin.convergence_engine_enabled
Zane Bitterf407e102017-10-05 14:19:32 -040083 skipper = testtools.skipUnless(convergence_enabled,
84 "Convergence-only tests are disabled")
85 return skipper(test_method)
86
87
rabi82b71282018-03-08 11:08:36 +053088def requires_resource_type(resource_type):
89 '''Decorator for tests requiring a resource type.
90
91 The decorated test will be skipped when the resource type is not available.
92 '''
93 def decorator(test_method):
94 conf = getattr(config.CONF, 'heat_plugin', None)
95 if not conf or conf.auth_url is None:
96 return test_method
97
98 global _resource_types
99 if not _resource_types:
100 manager = clients.ClientManager(conf)
101 obj_rtypes = manager.orchestration_client.resource_types.list()
102 _resource_types = list(t.resource_type for t in obj_rtypes)
103 rtype_available = resource_type and resource_type in _resource_types
104 skipper = testtools.skipUnless(
105 rtype_available,
106 "%s resource type not available, skipping test." % resource_type)
107 return skipper(test_method)
108 return decorator
109
110
Takashi Kajinami1e6b3882022-08-21 11:37:20 +0900111def requires_service(service):
112 '''Decorator for tests requiring a specific service being available.
113
114 The decorated test will be skipped when a service is not available. This
115 based on the [service_available] options implemented in tempest
116 '''
117 def decorator(test_method):
118 if not getattr(config.CONF.service_available, service, True):
119 skipper = testtools.skip(
120 "%s service not available, skipping test." % service)
121 return skipper(test_method)
122 else:
123 return test_method
124 return decorator
125
126
Rabi Mishrad6b25352018-10-17 13:11:05 +0530127def requires_service_type(service_type):
128 '''Decorator for tests requiring a specific service being available.
129
130 The decorated test will be skipped when a service is not available.
131 '''
132 def decorator(test_method):
133 conf = getattr(config.CONF, 'heat_plugin', None)
134 if not conf or conf.auth_url is None:
135 return test_method
136
137 manager = clients.ClientManager(conf)
138 try:
139 manager.identity_client.get_endpoint_url(
140 service_type, conf.region, conf.endpoint_type)
141 except kc_exceptions.EndpointNotFound:
142 skipper = testtools.skip(
Takashi Kajinami1e6b3882022-08-21 11:37:20 +0900143 "%s service type not available, skipping test." % service_type)
Rabi Mishrad6b25352018-10-17 13:11:05 +0530144 return skipper(test_method)
145 else:
146 return test_method
147 return decorator
148
149
Rabi Mishra144bdc62019-01-10 17:00:57 +0530150def _check_require(group, feature, test_method):
151 features_group = getattr(config.CONF, group, None)
152 if not features_group:
153 return test_method
154 feature_enabled = features_group.get(feature, True)
155 skipper = testtools.skipUnless(feature_enabled,
156 "%s - Feature not enabled." % feature)
157 return skipper(test_method)
158
159
rabi876449a2018-03-15 21:56:49 +0530160def requires_feature(feature):
161 '''Decorator for tests requring specific feature.
162
163 The decorated test will be skipped when a specific feature is disabled.
164 '''
165 def decorator(test_method):
Rabi Mishra144bdc62019-01-10 17:00:57 +0530166 return _check_require('heat_features_enabled', feature, test_method)
167 return decorator
168
169
170def requires_service_feature(service, feature):
Takashi Kajinami70e516a2024-01-20 18:56:19 +0900171 '''Decorator for tests requring specific service feature
Rabi Mishra144bdc62019-01-10 17:00:57 +0530172
173 The decorated test will be skipped when a specific feature is disabled.
174 '''
175 def decorator(test_method):
176 group = service + '_feature_enabled'
177 return _check_require(group, feature, test_method)
rabi876449a2018-03-15 21:56:49 +0530178 return decorator
179
180
Pavlo Shchelokovskyye3907092025-12-19 17:37:12 +0000181class HeatIntegrationTest(test.BaseTestCase, testscenarios.WithScenarios):
Steve Baker450aa7f2014-08-25 10:37:27 +1200182
183 def setUp(self):
184 super(HeatIntegrationTest, self).setUp()
185
Takashi Kajinamic4b871a2022-07-04 11:42:06 +0900186 if not config.CONF.service_available.heat:
187 raise self.skipException("Heat is not available")
188
rabif89752b2017-11-18 22:14:30 +0530189 self.conf = config.CONF.heat_plugin
Steve Baker450aa7f2014-08-25 10:37:27 +1200190
191 self.assertIsNotNone(self.conf.auth_url,
192 'No auth_url configured')
193 self.assertIsNotNone(self.conf.username,
194 'No username configured')
195 self.assertIsNotNone(self.conf.password,
196 'No password configured')
Pavlo Shchelokovskyye3907092025-12-19 17:37:12 +0000197 self.setup_plugin_clients(self.conf)
rabifd98a472016-05-24 10:18:33 +0530198 self.useFixture(fixtures.FakeLogger(format=_LOG_FORMAT))
rabifd98a472016-05-24 10:18:33 +0530199 if self.conf.disable_ssl_certificate_validation:
200 self.verify_cert = False
201 else:
202 self.verify_cert = self.conf.ca_file or True
Steve Baker450aa7f2014-08-25 10:37:27 +1200203
Pavlo Shchelokovskyye3907092025-12-19 17:37:12 +0000204 def setup_plugin_clients(self, conf, admin_credentials=False):
Steve Bakerb752e912016-08-01 22:05:37 +0000205 self.manager = clients.ClientManager(conf, admin_credentials)
Steve Baker450aa7f2014-08-25 10:37:27 +1200206 self.identity_client = self.manager.identity_client
207 self.orchestration_client = self.manager.orchestration_client
208 self.compute_client = self.manager.compute_client
209 self.network_client = self.manager.network_client
210 self.volume_client = self.manager.volume_client
Angus Salkeld4408da32015-02-03 18:53:30 +1000211 self.object_client = self.manager.object_client
rabid69f0312017-10-26 14:52:52 +0530212 self.metric_client = self.manager.metric_client
rabifd98a472016-05-24 10:18:33 +0530213
214 self.client = self.orchestration_client
215
216 def setup_clients_for_admin(self):
Pavlo Shchelokovskyye3907092025-12-19 17:37:12 +0000217 self.setup_plugin_clients(self.conf, True)
Steve Baker450aa7f2014-08-25 10:37:27 +1200218
Steve Baker450aa7f2014-08-25 10:37:27 +1200219 def get_remote_client(self, server_or_ip, username, private_key=None):
Takashi Kajinamidfb97392022-05-10 00:51:14 +0900220 if isinstance(server_or_ip, str):
Steve Baker450aa7f2014-08-25 10:37:27 +1200221 ip = server_or_ip
222 else:
223 network_name_for_ssh = self.conf.network_for_ssh
224 ip = server_or_ip.networks[network_name_for_ssh][0]
225 if private_key is None:
226 private_key = self.keypair.private_key
227 linux_client = remote_client.RemoteClient(ip, username,
228 pkey=private_key,
229 conf=self.conf)
230 try:
231 linux_client.validate_authentication()
232 except exceptions.SSHTimeout:
liyi09461f72017-03-21 12:17:51 +0800233 LOG.exception('ssh connection to %s failed', ip)
Steve Baker450aa7f2014-08-25 10:37:27 +1200234 raise
235
236 return linux_client
237
Anastasia Kuznetsova3e0ab4d2015-03-06 18:10:13 +0400238 def check_connectivity(self, check_ip):
239 def try_connect(ip):
240 try:
Sirushti Murugesan4920fda2015-04-22 00:35:26 +0530241 urllib.request.urlopen('http://%s/' % ip)
Anastasia Kuznetsova3e0ab4d2015-03-06 18:10:13 +0400242 return True
243 except IOError:
244 return False
245
246 timeout = self.conf.connectivity_timeout
247 elapsed_time = 0
248 while not try_connect(check_ip):
249 time.sleep(10)
250 elapsed_time += 10
251 if elapsed_time > timeout:
252 raise exceptions.TimeoutException()
253
Steve Baker450aa7f2014-08-25 10:37:27 +1200254 def _log_console_output(self, servers=None):
255 if not servers:
256 servers = self.compute_client.servers.list()
257 for server in servers:
Steve Baker24641292015-03-13 10:47:50 +1300258 LOG.info('Console output for %s', server.id)
259 LOG.info(server.get_console_output())
Steve Baker450aa7f2014-08-25 10:37:27 +1200260
Steve Baker450aa7f2014-08-25 10:37:27 +1200261 def create_keypair(self, client=None, name=None):
262 if client is None:
263 client = self.compute_client
264 if name is None:
265 name = rand_name('heat-keypair')
266 keypair = client.keypairs.create(name)
267 self.assertEqual(keypair.name, name)
268
269 def delete_keypair():
270 keypair.delete()
271
272 self.addCleanup(delete_keypair)
273 return keypair
274
Sergey Krayneva265c132015-02-13 03:51:03 -0500275 def assign_keypair(self):
276 if self.conf.keypair_name:
277 self.keypair = None
278 self.keypair_name = self.conf.keypair_name
279 else:
280 self.keypair = self.create_keypair()
281 self.keypair_name = self.keypair.id
282
Steve Baker450aa7f2014-08-25 10:37:27 +1200283 @classmethod
284 def _stack_rand_name(cls):
285 return rand_name(cls.__name__)
286
Anastasia Kuznetsova673fc432015-03-12 16:41:36 +0400287 def _get_network(self, net_name=None):
288 if net_name is None:
289 net_name = self.conf.fixed_network_name
Steve Baker450aa7f2014-08-25 10:37:27 +1200290 networks = self.network_client.list_networks()
291 for net in networks['networks']:
Anastasia Kuznetsova673fc432015-03-12 16:41:36 +0400292 if net['name'] == net_name:
Steve Baker450aa7f2014-08-25 10:37:27 +1200293 return net
294
295 @staticmethod
Angus Salkelda89a0282015-07-24 15:47:38 +1000296 def _stack_output(stack, output_key, validate_errors=True):
Steve Baker450aa7f2014-08-25 10:37:27 +1200297 """Return a stack output value for a given key."""
Angus Salkelda89a0282015-07-24 15:47:38 +1000298 value = None
299 for o in stack.outputs:
300 if validate_errors and 'output_error' in o:
301 # scan for errors in the stack output.
302 raise ValueError(
303 'Unexpected output errors in %s : %s' % (
304 output_key, o['output_error']))
305 if o['output_key'] == output_key:
306 value = o['output_value']
307 return value
Steve Baker450aa7f2014-08-25 10:37:27 +1200308
309 def _ping_ip_address(self, ip_address, should_succeed=True):
310 cmd = ['ping', '-c1', '-w1', ip_address]
311
312 def ping():
313 proc = subprocess.Popen(cmd,
314 stdout=subprocess.PIPE,
315 stderr=subprocess.PIPE)
316 proc.wait()
317 return (proc.returncode == 0) == should_succeed
318
319 return call_until_true(
Angus Salkeld08514ad2015-02-06 10:08:31 +1000320 self.conf.build_timeout, 1, ping)
Steve Baker450aa7f2014-08-25 10:37:27 +1200321
Angus Salkelda7500d12015-04-10 15:44:07 +1000322 def _wait_for_all_resource_status(self, stack_identifier,
323 status, failure_pattern='^.*_FAILED$',
324 success_on_not_found=False):
325 for res in self.client.resources.list(stack_identifier):
326 self._wait_for_resource_status(
327 stack_identifier, res.resource_name,
328 status, failure_pattern=failure_pattern,
329 success_on_not_found=success_on_not_found)
330
Steve Baker450aa7f2014-08-25 10:37:27 +1200331 def _wait_for_resource_status(self, stack_identifier, resource_name,
332 status, failure_pattern='^.*_FAILED$',
333 success_on_not_found=False):
334 """Waits for a Resource to reach a given status."""
335 fail_regexp = re.compile(failure_pattern)
336 build_timeout = self.conf.build_timeout
337 build_interval = self.conf.build_interval
338
339 start = timeutils.utcnow()
340 while timeutils.delta_seconds(start,
341 timeutils.utcnow()) < build_timeout:
342 try:
343 res = self.client.resources.get(
344 stack_identifier, resource_name)
345 except heat_exceptions.HTTPNotFound:
346 if success_on_not_found:
347 return
348 # ignore this, as the resource may not have
349 # been created yet
350 else:
351 if res.resource_status == status:
352 return
Sirushti Murugesane5ad25b2015-04-18 23:30:59 +0530353 wait_for_action = status.split('_')[0]
354 resource_action = res.resource_status.split('_')[0]
355 if (resource_action == wait_for_action and
356 fail_regexp.search(res.resource_status)):
Steve Baker450aa7f2014-08-25 10:37:27 +1200357 raise exceptions.StackResourceBuildErrorException(
358 resource_name=res.resource_name,
359 stack_identifier=stack_identifier,
360 resource_status=res.resource_status,
361 resource_status_reason=res.resource_status_reason)
362 time.sleep(build_interval)
363
364 message = ('Resource %s failed to reach %s status within '
365 'the required time (%s s).' %
Anastasia Kuznetsova9a745572015-03-04 13:19:15 +0400366 (resource_name, status, build_timeout))
Steve Baker450aa7f2014-08-25 10:37:27 +1200367 raise exceptions.TimeoutException(message)
368
Rabi Mishra87be9b42016-02-15 14:15:50 +0530369 def verify_resource_status(self, stack_identifier, resource_name,
370 status='CREATE_COMPLETE'):
371 try:
372 res = self.client.resources.get(stack_identifier, resource_name)
373 except heat_exceptions.HTTPNotFound:
374 return False
375 return res.resource_status == status
376
rabi5eaa4962017-08-31 10:55:13 +0530377 def _verify_status(self, stack, stack_identifier, status,
378 fail_regexp, is_action_cancelled=False):
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530379 if stack.stack_status == status:
Zane Bitter8a142e32018-07-31 19:40:54 -0400380 if status == 'DELETE_COMPLETE' and stack.deletion_time is None:
Thomas Herve0e8567e2016-09-22 15:07:37 +0200381 # Wait for deleted_time to be filled, so that we have more
382 # confidence the operation is finished.
383 return False
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530384 else:
385 return True
386
Sirushti Murugesane5ad25b2015-04-18 23:30:59 +0530387 wait_for_action = status.split('_')[0]
388 if (stack.action == wait_for_action and
389 fail_regexp.search(stack.stack_status)):
Zane Bitter8a142e32018-07-31 19:40:54 -0400390 raise exceptions.StackBuildErrorException(
391 stack_identifier=stack_identifier,
392 stack_status=stack.stack_status,
393 stack_status_reason=stack.stack_status_reason)
394
395 return False
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530396
Steve Baker450aa7f2014-08-25 10:37:27 +1200397 def _wait_for_stack_status(self, stack_identifier, status,
Sergey Kraynev89082a32015-09-04 04:42:33 -0400398 failure_pattern=None,
Rabi Mishra81ca6bc2016-06-16 15:09:20 +0530399 success_on_not_found=False,
400 signal_required=False,
rabi5eaa4962017-08-31 10:55:13 +0530401 resources_to_signal=None,
402 is_action_cancelled=False):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +0300403 """Waits for a Stack to reach a given status.
Steve Baker450aa7f2014-08-25 10:37:27 +1200404
405 Note this compares the full $action_$status, e.g
406 CREATE_COMPLETE, not just COMPLETE which is exposed
407 via the status property of Stack in heatclient
408 """
Sergey Kraynev89082a32015-09-04 04:42:33 -0400409 if failure_pattern:
410 fail_regexp = re.compile(failure_pattern)
411 elif 'FAILED' in status:
412 # If we're looking for e.g CREATE_FAILED, COMPLETE is unexpected.
413 fail_regexp = re.compile('^.*_COMPLETE$')
414 else:
415 fail_regexp = re.compile('^.*_FAILED$')
Steve Baker450aa7f2014-08-25 10:37:27 +1200416 build_timeout = self.conf.build_timeout
417 build_interval = self.conf.build_interval
418
419 start = timeutils.utcnow()
420 while timeutils.delta_seconds(start,
421 timeutils.utcnow()) < build_timeout:
422 try:
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500423 stack = self.client.stacks.get(stack_identifier,
424 resolve_outputs=False)
Steve Baker450aa7f2014-08-25 10:37:27 +1200425 except heat_exceptions.HTTPNotFound:
426 if success_on_not_found:
427 return
428 # ignore this, as the resource may not have
429 # been created yet
430 else:
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530431 if self._verify_status(stack, stack_identifier, status,
rabi5eaa4962017-08-31 10:55:13 +0530432 fail_regexp, is_action_cancelled):
Steve Baker450aa7f2014-08-25 10:37:27 +1200433 return
Rabi Mishra81ca6bc2016-06-16 15:09:20 +0530434 if signal_required:
435 self.signal_resources(resources_to_signal)
Steve Baker450aa7f2014-08-25 10:37:27 +1200436 time.sleep(build_interval)
437
438 message = ('Stack %s failed to reach %s status within '
439 'the required time (%s s).' %
Anastasia Kuznetsova9a745572015-03-04 13:19:15 +0400440 (stack_identifier, status, build_timeout))
Steve Baker450aa7f2014-08-25 10:37:27 +1200441 raise exceptions.TimeoutException(message)
442
443 def _stack_delete(self, stack_identifier):
444 try:
Thomas Herve3eab2942015-10-22 17:29:21 +0200445 self._handle_in_progress(self.client.stacks.delete,
446 stack_identifier)
Steve Baker450aa7f2014-08-25 10:37:27 +1200447 except heat_exceptions.HTTPNotFound:
448 pass
449 self._wait_for_stack_status(
450 stack_identifier, 'DELETE_COMPLETE',
451 success_on_not_found=True)
Steven Hardyc9efd972014-11-20 11:31:55 +0000452
Thomas Herve3eab2942015-10-22 17:29:21 +0200453 def _handle_in_progress(self, fn, *args, **kwargs):
454 build_timeout = self.conf.build_timeout
455 build_interval = self.conf.build_interval
456 start = timeutils.utcnow()
457 while timeutils.delta_seconds(start,
458 timeutils.utcnow()) < build_timeout:
459 try:
460 fn(*args, **kwargs)
461 except heat_exceptions.HTTPConflict as ex:
462 # FIXME(sirushtim): Wait a little for the stack lock to be
463 # released and hopefully, the stack should be usable again.
464 if ex.error['error']['type'] != 'ActionInProgress':
465 raise ex
466
467 time.sleep(build_interval)
468 else:
469 break
470
Steven Hardy23284b62015-10-01 19:03:42 +0100471 def update_stack(self, stack_identifier, template=None, environment=None,
Sabeen Syed277ea692015-02-04 23:30:02 +0000472 files=None, parameters=None, tags=None,
Rakesh H Sa3325d62015-04-04 19:42:29 +0530473 expected_status='UPDATE_COMPLETE',
Steven Hardy23284b62015-10-01 19:03:42 +0100474 disable_rollback=True,
475 existing=False):
Steven Hardyc9efd972014-11-20 11:31:55 +0000476 env = environment or {}
477 env_files = files or {}
Sergey Kraynevbcc78df2015-02-27 04:34:32 -0500478 parameters = parameters or {}
Sirushti Murugesan3a195a52015-05-01 09:47:09 +0530479
Thomas Herve3eab2942015-10-22 17:29:21 +0200480 self._handle_in_progress(
481 self.client.stacks.update,
482 stack_id=stack_identifier,
Thomas Herve3eab2942015-10-22 17:29:21 +0200483 template=template,
484 files=env_files,
485 disable_rollback=disable_rollback,
486 parameters=parameters,
487 environment=env,
488 tags=tags,
489 existing=existing)
Sirushti Murugesan3a195a52015-05-01 09:47:09 +0530490
Rakesh H Sa3325d62015-04-04 19:42:29 +0530491 kwargs = {'stack_identifier': stack_identifier,
492 'status': expected_status}
493 if expected_status in ['ROLLBACK_COMPLETE']:
Rakesh H Sa3325d62015-04-04 19:42:29 +0530494 # To trigger rollback you would intentionally fail the stack
495 # Hence check for rollback failures
496 kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
497
498 self._wait_for_stack_status(**kwargs)
Steven Hardyc9efd972014-11-20 11:31:55 +0000499
rabi5eaa4962017-08-31 10:55:13 +0530500 def cancel_update_stack(self, stack_identifier, rollback=True,
Oleksii Chuprykovfc2c58f2016-04-29 17:03:17 +0300501 expected_status='ROLLBACK_COMPLETE'):
502
503 stack_name = stack_identifier.split('/')[0]
504
rabi5eaa4962017-08-31 10:55:13 +0530505 if rollback:
506 self.client.actions.cancel_update(stack_name)
507 else:
508 self.client.actions.cancel_without_rollback(stack_name)
Oleksii Chuprykovfc2c58f2016-04-29 17:03:17 +0300509
510 kwargs = {'stack_identifier': stack_identifier,
511 'status': expected_status}
rabi5eaa4962017-08-31 10:55:13 +0530512 if expected_status == 'UPDATE_FAILED':
513 kwargs['is_action_cancelled'] = True
514
Oleksii Chuprykovfc2c58f2016-04-29 17:03:17 +0300515 if expected_status in ['ROLLBACK_COMPLETE']:
516 # To trigger rollback you would intentionally fail the stack
517 # Hence check for rollback failures
518 kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
519
520 self._wait_for_stack_status(**kwargs)
521
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500522 def preview_update_stack(self, stack_identifier, template,
523 environment=None, files=None, parameters=None,
Steven Hardye6de2d62015-12-07 15:59:09 +0000524 tags=None, disable_rollback=True,
525 show_nested=False):
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500526 env = environment or {}
527 env_files = files or {}
528 parameters = parameters or {}
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500529
530 return self.client.stacks.preview_update(
531 stack_id=stack_identifier,
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500532 template=template,
533 files=env_files,
534 disable_rollback=disable_rollback,
535 parameters=parameters,
536 environment=env,
Steven Hardye6de2d62015-12-07 15:59:09 +0000537 tags=tags,
538 show_nested=show_nested
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500539 )
540
Steven Hardy03da0742015-03-19 00:13:17 -0400541 def assert_resource_is_a_stack(self, stack_identifier, res_name,
542 wait=False):
543 build_timeout = self.conf.build_timeout
544 build_interval = self.conf.build_interval
545 start = timeutils.utcnow()
546 while timeutils.delta_seconds(start,
547 timeutils.utcnow()) < build_timeout:
548 time.sleep(build_interval)
549 try:
550 nested_identifier = self._get_nested_identifier(
551 stack_identifier, res_name)
552 except Exception:
553 # We may have to wait, if the create is in-progress
554 if wait:
555 time.sleep(build_interval)
556 else:
557 raise
558 else:
559 return nested_identifier
560
561 def _get_nested_identifier(self, stack_identifier, res_name):
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000562 rsrc = self.client.resources.get(stack_identifier, res_name)
563 nested_link = [l for l in rsrc.links if l['rel'] == 'nested']
564 nested_href = nested_link[0]['href']
565 nested_id = nested_href.split('/')[-1]
566 nested_identifier = '/'.join(nested_href.split('/')[-2:])
567 self.assertEqual(rsrc.physical_resource_id, nested_id)
568
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500569 nested_stack = self.client.stacks.get(nested_id, resolve_outputs=False)
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000570 nested_identifier2 = '%s/%s' % (nested_stack.stack_name,
571 nested_stack.id)
572 self.assertEqual(nested_identifier, nested_identifier2)
573 parent_id = stack_identifier.split("/")[-1]
574 self.assertEqual(parent_id, nested_stack.parent)
575 return nested_identifier
576
Rabi Mishra8bcff8a2015-09-21 18:15:04 +0530577 def group_nested_identifier(self, stack_identifier,
578 group_name):
579 # Get the nested stack identifier from a group resource
580 rsrc = self.client.resources.get(stack_identifier, group_name)
581 physical_resource_id = rsrc.physical_resource_id
582
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500583 nested_stack = self.client.stacks.get(physical_resource_id,
584 resolve_outputs=False)
Rabi Mishra8bcff8a2015-09-21 18:15:04 +0530585 nested_identifier = '%s/%s' % (nested_stack.stack_name,
586 nested_stack.id)
587 parent_id = stack_identifier.split("/")[-1]
588 self.assertEqual(parent_id, nested_stack.parent)
589 return nested_identifier
590
591 def list_group_resources(self, stack_identifier,
592 group_name, minimal=True):
593 nested_identifier = self.group_nested_identifier(stack_identifier,
594 group_name)
595 if minimal:
596 return self.list_resources(nested_identifier)
597 return self.client.resources.list(nested_identifier)
598
Steven Hardyc9efd972014-11-20 11:31:55 +0000599 def list_resources(self, stack_identifier):
600 resources = self.client.resources.list(stack_identifier)
601 return dict((r.resource_name, r.resource_type) for r in resources)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000602
Steven Hardyd448dae2016-06-14 14:57:28 +0100603 def get_resource_stack_id(self, r):
604 stack_link = [l for l in r.links if l.get('rel') == 'stack'][0]
605 return stack_link['href'].split("/")[-1]
606
Botond Zoltáne0b7aa12017-03-28 08:42:16 +0200607 def get_physical_resource_id(self, stack_identifier, resource_name):
608 try:
609 resource = self.client.resources.get(
610 stack_identifier, resource_name)
611 return resource.physical_resource_id
612 except Exception:
613 raise Exception('Resource (%s) not found in stack (%s)!' %
614 (stack_identifier, resource_name))
615
616 def get_stack_output(self, stack_identifier, output_key,
617 validate_errors=True):
618 stack = self.client.stacks.get(stack_identifier)
619 return self._stack_output(stack, output_key, validate_errors)
620
Rabi Mishra81ca6bc2016-06-16 15:09:20 +0530621 def check_input_values(self, group_resources, key, value):
622 # Check inputs for deployment and derived config
623 for r in group_resources:
624 d = self.client.software_deployments.get(
625 r.physical_resource_id)
626 self.assertEqual({key: value}, d.input_values)
627 c = self.client.software_configs.get(
628 d.config_id)
629 foo_input_c = [i for i in c.inputs if i.get('name') == key][0]
630 self.assertEqual(value, foo_input_c.get('value'))
631
632 def signal_resources(self, resources):
633 # Signal all IN_PROGRESS resources
634 for r in resources:
635 if 'IN_PROGRESS' in r.resource_status:
636 stack_id = self.get_resource_stack_id(r)
637 self.client.resources.signal(stack_id, r.resource_name)
638
Steven Hardyf2c82c02014-11-20 14:02:17 +0000639 def stack_create(self, stack_name=None, template=None, files=None,
Sabeen Syed277ea692015-02-04 23:30:02 +0000640 parameters=None, environment=None, tags=None,
641 expected_status='CREATE_COMPLETE',
Jay Dobies39c4ce42015-11-04 10:49:08 -0500642 disable_rollback=True, enable_cleanup=True,
rabi6ce8d962017-07-10 16:40:12 +0530643 environment_files=None, timeout=None):
Steven Hardyf2c82c02014-11-20 14:02:17 +0000644 name = stack_name or self._stack_rand_name()
645 templ = template or self.template
646 templ_files = files or {}
647 params = parameters or {}
648 env = environment or {}
rabi6ce8d962017-07-10 16:40:12 +0530649 timeout_mins = timeout or self.conf.build_timeout
Steven Hardyf2c82c02014-11-20 14:02:17 +0000650 self.client.stacks.create(
651 stack_name=name,
652 template=templ,
653 files=templ_files,
Rakesh H Sa3325d62015-04-04 19:42:29 +0530654 disable_rollback=disable_rollback,
Steven Hardyf2c82c02014-11-20 14:02:17 +0000655 parameters=params,
Sabeen Syed277ea692015-02-04 23:30:02 +0000656 environment=env,
Jay Dobies39c4ce42015-11-04 10:49:08 -0500657 tags=tags,
rabi6ce8d962017-07-10 16:40:12 +0530658 environment_files=environment_files,
659 timeout_mins=timeout_mins
Steven Hardyf2c82c02014-11-20 14:02:17 +0000660 )
rabic570e0f2017-10-26 13:07:13 +0530661 if enable_cleanup:
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200662 self.addCleanup(self._stack_delete, name)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000663
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500664 stack = self.client.stacks.get(name, resolve_outputs=False)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000665 stack_identifier = '%s/%s' % (name, stack.id)
Rakesh H Sa3325d62015-04-04 19:42:29 +0530666 kwargs = {'stack_identifier': stack_identifier,
667 'status': expected_status}
Steve Bakerf6c8f122015-02-10 13:54:46 +1300668 if expected_status:
Rakesh H Sa3325d62015-04-04 19:42:29 +0530669 if expected_status in ['ROLLBACK_COMPLETE']:
670 # To trigger rollback you would intentionally fail the stack
671 # Hence check for rollback failures
672 kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
673 self._wait_for_stack_status(**kwargs)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000674 return stack_identifier
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000675
676 def stack_adopt(self, stack_name=None, files=None,
677 parameters=None, environment=None, adopt_data=None,
678 wait_for_status='ADOPT_COMPLETE'):
Rabi Mishra477efc92015-07-31 13:01:45 +0530679 if (self.conf.skip_test_stack_action_list and
680 'ADOPT' in self.conf.skip_test_stack_action_list):
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530681 self.skipTest('Testing Stack adopt disabled in conf, skipping')
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000682 name = stack_name or self._stack_rand_name()
683 templ_files = files or {}
684 params = parameters or {}
685 env = environment or {}
686 self.client.stacks.create(
687 stack_name=name,
688 files=templ_files,
689 disable_rollback=True,
690 parameters=params,
691 environment=env,
692 adopt_stack_data=adopt_data,
693 )
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200694 self.addCleanup(self._stack_delete, name)
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500695 stack = self.client.stacks.get(name, resolve_outputs=False)
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000696 stack_identifier = '%s/%s' % (name, stack.id)
697 self._wait_for_stack_status(stack_identifier, wait_for_status)
698 return stack_identifier
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530699
700 def stack_abandon(self, stack_id):
Rabi Mishra477efc92015-07-31 13:01:45 +0530701 if (self.conf.skip_test_stack_action_list and
702 'ABANDON' in self.conf.skip_test_stack_action_list):
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200703 self.addCleanup(self._stack_delete, stack_id)
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530704 self.skipTest('Testing Stack abandon disabled in conf, skipping')
705 info = self.client.stacks.abandon(stack_id=stack_id)
706 return info
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500707
rabi90b3ab42017-05-04 13:02:28 +0530708 def stack_snapshot(self, stack_id,
709 wait_for_status='SNAPSHOT_COMPLETE'):
710 snapshot = self.client.stacks.snapshot(stack_id=stack_id)
711 self._wait_for_stack_status(stack_id, wait_for_status)
712 return snapshot['id']
713
714 def stack_restore(self, stack_id, snapshot_id,
715 wait_for_status='RESTORE_COMPLETE'):
716 self.client.stacks.restore(stack_id, snapshot_id)
717 self._wait_for_stack_status(stack_id, wait_for_status)
718
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500719 def stack_suspend(self, stack_identifier):
Rabi Mishra477efc92015-07-31 13:01:45 +0530720 if (self.conf.skip_test_stack_action_list and
721 'SUSPEND' in self.conf.skip_test_stack_action_list):
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200722 self.addCleanup(self._stack_delete, stack_identifier)
Rabi Mishra477efc92015-07-31 13:01:45 +0530723 self.skipTest('Testing Stack suspend disabled in conf, skipping')
rabif7d67082016-05-17 18:51:22 +0530724 self._handle_in_progress(self.client.actions.suspend, stack_identifier)
Angus Salkelda7500d12015-04-10 15:44:07 +1000725 # improve debugging by first checking the resource's state.
726 self._wait_for_all_resource_status(stack_identifier,
727 'SUSPEND_COMPLETE')
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500728 self._wait_for_stack_status(stack_identifier, 'SUSPEND_COMPLETE')
729
730 def stack_resume(self, stack_identifier):
Rabi Mishra477efc92015-07-31 13:01:45 +0530731 if (self.conf.skip_test_stack_action_list and
732 'RESUME' in self.conf.skip_test_stack_action_list):
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200733 self.addCleanup(self._stack_delete, stack_identifier)
Rabi Mishra477efc92015-07-31 13:01:45 +0530734 self.skipTest('Testing Stack resume disabled in conf, skipping')
rabif7d67082016-05-17 18:51:22 +0530735 self._handle_in_progress(self.client.actions.resume, stack_identifier)
Angus Salkelda7500d12015-04-10 15:44:07 +1000736 # improve debugging by first checking the resource's state.
737 self._wait_for_all_resource_status(stack_identifier,
738 'RESUME_COMPLETE')
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500739 self._wait_for_stack_status(stack_identifier, 'RESUME_COMPLETE')
Steven Hardy03da0742015-03-19 00:13:17 -0400740
741 def wait_for_event_with_reason(self, stack_identifier, reason,
742 rsrc_name=None, num_expected=1):
743 build_timeout = self.conf.build_timeout
744 build_interval = self.conf.build_interval
745 start = timeutils.utcnow()
746 while timeutils.delta_seconds(start,
747 timeutils.utcnow()) < build_timeout:
748 try:
749 rsrc_events = self.client.events.list(stack_identifier,
750 resource_name=rsrc_name)
751 except heat_exceptions.HTTPNotFound:
liyi09461f72017-03-21 12:17:51 +0800752 LOG.debug("No events yet found for %s", rsrc_name)
Steven Hardy03da0742015-03-19 00:13:17 -0400753 else:
754 matched = [e for e in rsrc_events
755 if e.resource_status_reason == reason]
756 if len(matched) == num_expected:
757 return matched
758 time.sleep(build_interval)
Rakesh H Sc5735a82016-04-28 15:38:09 +0530759
Thomas Hervea6afca82017-04-10 23:44:26 +0200760 def check_autoscale_complete(self, stack_id, expected_num, parent_stack,
rabi55c0f752018-02-07 09:21:28 +0530761 group_name):
Rakesh H Sc5735a82016-04-28 15:38:09 +0530762 res_list = self.client.resources.list(stack_id)
763 all_res_complete = all(res.resource_status in ('UPDATE_COMPLETE',
764 'CREATE_COMPLETE')
765 for res in res_list)
766 all_res = len(res_list) == expected_num
Thomas Hervea6afca82017-04-10 23:44:26 +0200767 if all_res and all_res_complete:
rabi55c0f752018-02-07 09:21:28 +0530768 metadata = self.client.resources.metadata(parent_stack, group_name)
Thomas Hervea6afca82017-04-10 23:44:26 +0200769 return not metadata.get('scaling_in_progress')
770 return False