blob: bca5e32a79b8fb498136c7b9c4b3e0b5a40dbd31 [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
Mark Vanderwiel6d8e0862015-10-15 12:51:07 -050021from neutronclient.common import exceptions as network_exceptions
Steve Baker24641292015-03-13 10:47:50 +130022from oslo_log import log as logging
Jens Rosenboom4f069fb2015-02-18 14:19:07 +010023from oslo_utils import timeutils
Pavlo Shchelokovskyy60e0ecd2014-12-14 22:17:21 +020024import six
Sirushti Murugesan4920fda2015-04-22 00:35:26 +053025from six.moves import urllib
Pavlo Shchelokovskyy60e0ecd2014-12-14 22:17:21 +020026import testscenarios
27import testtools
Steve Baker450aa7f2014-08-25 10:37:27 +120028
rabid2916d02017-09-22 18:19:24 +053029from heat_tempest_plugin.common import exceptions
30from heat_tempest_plugin.common import remote_client
rabid2916d02017-09-22 18:19:24 +053031from heat_tempest_plugin.services import clients
Zane Bitterb4acd962018-01-18 12:08:23 -050032from tempest import config
Steve Baker450aa7f2014-08-25 10:37:27 +120033
34LOG = logging.getLogger(__name__)
Angus Salkeld24043702014-11-21 08:49:26 +100035_LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s"
rabi82b71282018-03-08 11:08:36 +053036_resource_types = None
Steve Baker450aa7f2014-08-25 10:37:27 +120037
38
Angus Salkeld08514ad2015-02-06 10:08:31 +100039def call_until_true(duration, sleep_for, func, *args, **kwargs):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +030040 """Call the function until it returns True or the duration elapsed.
41
Steve Baker450aa7f2014-08-25 10:37:27 +120042 Call the given function until it returns True (and return True) or
43 until the specified duration (in seconds) elapses (and return
44 False).
45
46 :param func: A zero argument callable that returns True on success.
47 :param duration: The number of seconds for which to attempt a
48 successful call of the function.
49 :param sleep_for: The number of seconds to sleep after an unsuccessful
50 invocation of the function.
51 """
52 now = time.time()
53 timeout = now + duration
54 while now < timeout:
Angus Salkeld08514ad2015-02-06 10:08:31 +100055 if func(*args, **kwargs):
Steve Baker450aa7f2014-08-25 10:37:27 +120056 return True
57 LOG.debug("Sleeping for %d seconds", sleep_for)
58 time.sleep(sleep_for)
59 now = time.time()
60 return False
61
62
rabi32201342017-11-17 22:57:58 +053063def isotime(at):
64 if at is None:
65 return None
66 return at.strftime('%Y-%m-%dT%H:%M:%SZ')
67
68
Steve Baker450aa7f2014-08-25 10:37:27 +120069def rand_name(name=''):
ricolina4eb53d2017-04-24 23:51:09 +080070 randbits = six.text_type(random.randint(1, 0x7fffffff))
Steve Baker450aa7f2014-08-25 10:37:27 +120071 if name:
72 return name + '-' + randbits
73 else:
74 return randbits
75
76
Zane Bitterf407e102017-10-05 14:19:32 -040077def requires_convergence(test_method):
78 '''Decorator for convergence-only tests.
79
80 The decorated test will be skipped when convergence is disabled.
81 '''
rabif89752b2017-11-18 22:14:30 +053082 plugin = config.CONF.heat_plugin
rabi94a520a2017-11-17 22:49:17 +053083 convergence_enabled = plugin.convergence_engine_enabled
Zane Bitterf407e102017-10-05 14:19:32 -040084 skipper = testtools.skipUnless(convergence_enabled,
85 "Convergence-only tests are disabled")
86 return skipper(test_method)
87
88
rabi82b71282018-03-08 11:08:36 +053089def requires_resource_type(resource_type):
90 '''Decorator for tests requiring a resource type.
91
92 The decorated test will be skipped when the resource type is not available.
93 '''
94 def decorator(test_method):
95 conf = getattr(config.CONF, 'heat_plugin', None)
96 if not conf or conf.auth_url is None:
97 return test_method
98
99 global _resource_types
100 if not _resource_types:
101 manager = clients.ClientManager(conf)
102 obj_rtypes = manager.orchestration_client.resource_types.list()
103 _resource_types = list(t.resource_type for t in obj_rtypes)
104 rtype_available = resource_type and resource_type in _resource_types
105 skipper = testtools.skipUnless(
106 rtype_available,
107 "%s resource type not available, skipping test." % resource_type)
108 return skipper(test_method)
109 return decorator
110
111
Rabi Mishrad6b25352018-10-17 13:11:05 +0530112def requires_service_type(service_type):
113 '''Decorator for tests requiring a specific service being available.
114
115 The decorated test will be skipped when a service is not available.
116 '''
117 def decorator(test_method):
118 conf = getattr(config.CONF, 'heat_plugin', None)
119 if not conf or conf.auth_url is None:
120 return test_method
121
122 manager = clients.ClientManager(conf)
123 try:
124 manager.identity_client.get_endpoint_url(
125 service_type, conf.region, conf.endpoint_type)
126 except kc_exceptions.EndpointNotFound:
127 skipper = testtools.skip(
128 "%s service not available, skipping test." % service_type)
129 return skipper(test_method)
130 else:
131 return test_method
132 return decorator
133
134
Rabi Mishra144bdc62019-01-10 17:00:57 +0530135def _check_require(group, feature, test_method):
136 features_group = getattr(config.CONF, group, None)
137 if not features_group:
138 return test_method
139 feature_enabled = features_group.get(feature, True)
140 skipper = testtools.skipUnless(feature_enabled,
141 "%s - Feature not enabled." % feature)
142 return skipper(test_method)
143
144
rabi876449a2018-03-15 21:56:49 +0530145def requires_feature(feature):
146 '''Decorator for tests requring specific feature.
147
148 The decorated test will be skipped when a specific feature is disabled.
149 '''
150 def decorator(test_method):
Rabi Mishra144bdc62019-01-10 17:00:57 +0530151 return _check_require('heat_features_enabled', feature, test_method)
152 return decorator
153
154
155def requires_service_feature(service, feature):
156 '''Decorator for tests requring specific service feature enabled in tempest.
157
158 The decorated test will be skipped when a specific feature is disabled.
159 '''
160 def decorator(test_method):
161 group = service + '_feature_enabled'
162 return _check_require(group, feature, test_method)
rabi876449a2018-03-15 21:56:49 +0530163 return decorator
164
165
Andrea Frittolid908bef2018-02-22 11:29:53 +0000166class HeatIntegrationTest(testtools.testcase.WithAttributes,
167 testscenarios.WithScenarios,
Angus Salkeld95f65a22014-11-24 12:38:30 +1000168 testtools.TestCase):
Steve Baker450aa7f2014-08-25 10:37:27 +1200169
170 def setUp(self):
171 super(HeatIntegrationTest, self).setUp()
172
rabif89752b2017-11-18 22:14:30 +0530173 self.conf = config.CONF.heat_plugin
Steve Baker450aa7f2014-08-25 10:37:27 +1200174
175 self.assertIsNotNone(self.conf.auth_url,
176 'No auth_url configured')
177 self.assertIsNotNone(self.conf.username,
178 'No username configured')
179 self.assertIsNotNone(self.conf.password,
180 'No password configured')
rabifd98a472016-05-24 10:18:33 +0530181 self.setup_clients(self.conf)
182 self.useFixture(fixtures.FakeLogger(format=_LOG_FORMAT))
rabifd98a472016-05-24 10:18:33 +0530183 if self.conf.disable_ssl_certificate_validation:
184 self.verify_cert = False
185 else:
186 self.verify_cert = self.conf.ca_file or True
Steve Baker450aa7f2014-08-25 10:37:27 +1200187
Steve Bakerb752e912016-08-01 22:05:37 +0000188 def setup_clients(self, conf, admin_credentials=False):
189 self.manager = clients.ClientManager(conf, admin_credentials)
Steve Baker450aa7f2014-08-25 10:37:27 +1200190 self.identity_client = self.manager.identity_client
191 self.orchestration_client = self.manager.orchestration_client
192 self.compute_client = self.manager.compute_client
193 self.network_client = self.manager.network_client
194 self.volume_client = self.manager.volume_client
Angus Salkeld4408da32015-02-03 18:53:30 +1000195 self.object_client = self.manager.object_client
rabid69f0312017-10-26 14:52:52 +0530196 self.metric_client = self.manager.metric_client
rabifd98a472016-05-24 10:18:33 +0530197
198 self.client = self.orchestration_client
199
200 def setup_clients_for_admin(self):
Steve Bakerb752e912016-08-01 22:05:37 +0000201 self.setup_clients(self.conf, True)
Steve Baker450aa7f2014-08-25 10:37:27 +1200202
Steve Baker450aa7f2014-08-25 10:37:27 +1200203 def get_remote_client(self, server_or_ip, username, private_key=None):
204 if isinstance(server_or_ip, six.string_types):
205 ip = server_or_ip
206 else:
207 network_name_for_ssh = self.conf.network_for_ssh
208 ip = server_or_ip.networks[network_name_for_ssh][0]
209 if private_key is None:
210 private_key = self.keypair.private_key
211 linux_client = remote_client.RemoteClient(ip, username,
212 pkey=private_key,
213 conf=self.conf)
214 try:
215 linux_client.validate_authentication()
216 except exceptions.SSHTimeout:
liyi09461f72017-03-21 12:17:51 +0800217 LOG.exception('ssh connection to %s failed', ip)
Steve Baker450aa7f2014-08-25 10:37:27 +1200218 raise
219
220 return linux_client
221
Anastasia Kuznetsova3e0ab4d2015-03-06 18:10:13 +0400222 def check_connectivity(self, check_ip):
223 def try_connect(ip):
224 try:
Sirushti Murugesan4920fda2015-04-22 00:35:26 +0530225 urllib.request.urlopen('http://%s/' % ip)
Anastasia Kuznetsova3e0ab4d2015-03-06 18:10:13 +0400226 return True
227 except IOError:
228 return False
229
230 timeout = self.conf.connectivity_timeout
231 elapsed_time = 0
232 while not try_connect(check_ip):
233 time.sleep(10)
234 elapsed_time += 10
235 if elapsed_time > timeout:
236 raise exceptions.TimeoutException()
237
Steve Baker450aa7f2014-08-25 10:37:27 +1200238 def _log_console_output(self, servers=None):
239 if not servers:
240 servers = self.compute_client.servers.list()
241 for server in servers:
Pavlo Shchelokovskyy6646c952021-03-09 14:43:05 +0200242 LOG.info('Server %s', server)
Steve Baker24641292015-03-13 10:47:50 +1300243 LOG.info('Console output for %s', server.id)
244 LOG.info(server.get_console_output())
Steve Baker450aa7f2014-08-25 10:37:27 +1200245
Steve Baker450aa7f2014-08-25 10:37:27 +1200246 def create_keypair(self, client=None, name=None):
247 if client is None:
248 client = self.compute_client
249 if name is None:
250 name = rand_name('heat-keypair')
251 keypair = client.keypairs.create(name)
252 self.assertEqual(keypair.name, name)
253
254 def delete_keypair():
255 keypair.delete()
256
257 self.addCleanup(delete_keypair)
258 return keypair
259
Sergey Krayneva265c132015-02-13 03:51:03 -0500260 def assign_keypair(self):
261 if self.conf.keypair_name:
262 self.keypair = None
263 self.keypair_name = self.conf.keypair_name
264 else:
265 self.keypair = self.create_keypair()
266 self.keypair_name = self.keypair.id
267
Steve Baker450aa7f2014-08-25 10:37:27 +1200268 @classmethod
269 def _stack_rand_name(cls):
270 return rand_name(cls.__name__)
271
Anastasia Kuznetsova673fc432015-03-12 16:41:36 +0400272 def _get_network(self, net_name=None):
273 if net_name is None:
274 net_name = self.conf.fixed_network_name
Steve Baker450aa7f2014-08-25 10:37:27 +1200275 networks = self.network_client.list_networks()
276 for net in networks['networks']:
Anastasia Kuznetsova673fc432015-03-12 16:41:36 +0400277 if net['name'] == net_name:
Steve Baker450aa7f2014-08-25 10:37:27 +1200278 return net
279
Mark Vanderwiel6d8e0862015-10-15 12:51:07 -0500280 def is_network_extension_supported(self, extension_alias):
281 try:
282 self.network_client.show_extension(extension_alias)
283 except network_exceptions.NeutronClientException:
284 return False
285 return True
286
Steve Baker450aa7f2014-08-25 10:37:27 +1200287 @staticmethod
Angus Salkelda89a0282015-07-24 15:47:38 +1000288 def _stack_output(stack, output_key, validate_errors=True):
Steve Baker450aa7f2014-08-25 10:37:27 +1200289 """Return a stack output value for a given key."""
Angus Salkelda89a0282015-07-24 15:47:38 +1000290 value = None
291 for o in stack.outputs:
292 if validate_errors and 'output_error' in o:
293 # scan for errors in the stack output.
294 raise ValueError(
295 'Unexpected output errors in %s : %s' % (
296 output_key, o['output_error']))
297 if o['output_key'] == output_key:
298 value = o['output_value']
299 return value
Steve Baker450aa7f2014-08-25 10:37:27 +1200300
301 def _ping_ip_address(self, ip_address, should_succeed=True):
302 cmd = ['ping', '-c1', '-w1', ip_address]
303
304 def ping():
305 proc = subprocess.Popen(cmd,
306 stdout=subprocess.PIPE,
307 stderr=subprocess.PIPE)
308 proc.wait()
309 return (proc.returncode == 0) == should_succeed
310
311 return call_until_true(
Angus Salkeld08514ad2015-02-06 10:08:31 +1000312 self.conf.build_timeout, 1, ping)
Steve Baker450aa7f2014-08-25 10:37:27 +1200313
Angus Salkelda7500d12015-04-10 15:44:07 +1000314 def _wait_for_all_resource_status(self, stack_identifier,
315 status, failure_pattern='^.*_FAILED$',
316 success_on_not_found=False):
317 for res in self.client.resources.list(stack_identifier):
318 self._wait_for_resource_status(
319 stack_identifier, res.resource_name,
320 status, failure_pattern=failure_pattern,
321 success_on_not_found=success_on_not_found)
322
Steve Baker450aa7f2014-08-25 10:37:27 +1200323 def _wait_for_resource_status(self, stack_identifier, resource_name,
324 status, failure_pattern='^.*_FAILED$',
325 success_on_not_found=False):
326 """Waits for a Resource to reach a given status."""
327 fail_regexp = re.compile(failure_pattern)
328 build_timeout = self.conf.build_timeout
329 build_interval = self.conf.build_interval
330
331 start = timeutils.utcnow()
332 while timeutils.delta_seconds(start,
333 timeutils.utcnow()) < build_timeout:
334 try:
335 res = self.client.resources.get(
336 stack_identifier, resource_name)
337 except heat_exceptions.HTTPNotFound:
338 if success_on_not_found:
339 return
340 # ignore this, as the resource may not have
341 # been created yet
342 else:
343 if res.resource_status == status:
344 return
Sirushti Murugesane5ad25b2015-04-18 23:30:59 +0530345 wait_for_action = status.split('_')[0]
346 resource_action = res.resource_status.split('_')[0]
347 if (resource_action == wait_for_action and
348 fail_regexp.search(res.resource_status)):
Steve Baker450aa7f2014-08-25 10:37:27 +1200349 raise exceptions.StackResourceBuildErrorException(
350 resource_name=res.resource_name,
351 stack_identifier=stack_identifier,
352 resource_status=res.resource_status,
353 resource_status_reason=res.resource_status_reason)
354 time.sleep(build_interval)
355
356 message = ('Resource %s failed to reach %s status within '
357 'the required time (%s s).' %
Anastasia Kuznetsova9a745572015-03-04 13:19:15 +0400358 (resource_name, status, build_timeout))
Steve Baker450aa7f2014-08-25 10:37:27 +1200359 raise exceptions.TimeoutException(message)
360
Rabi Mishra87be9b42016-02-15 14:15:50 +0530361 def verify_resource_status(self, stack_identifier, resource_name,
362 status='CREATE_COMPLETE'):
363 try:
364 res = self.client.resources.get(stack_identifier, resource_name)
365 except heat_exceptions.HTTPNotFound:
366 return False
367 return res.resource_status == status
368
rabi5eaa4962017-08-31 10:55:13 +0530369 def _verify_status(self, stack, stack_identifier, status,
370 fail_regexp, is_action_cancelled=False):
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530371 if stack.stack_status == status:
Zane Bitter8a142e32018-07-31 19:40:54 -0400372 if status == 'DELETE_COMPLETE' and stack.deletion_time is None:
Thomas Herve0e8567e2016-09-22 15:07:37 +0200373 # Wait for deleted_time to be filled, so that we have more
374 # confidence the operation is finished.
375 return False
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530376 else:
377 return True
378
Sirushti Murugesane5ad25b2015-04-18 23:30:59 +0530379 wait_for_action = status.split('_')[0]
380 if (stack.action == wait_for_action and
381 fail_regexp.search(stack.stack_status)):
Zane Bitter8a142e32018-07-31 19:40:54 -0400382 raise exceptions.StackBuildErrorException(
383 stack_identifier=stack_identifier,
384 stack_status=stack.stack_status,
385 stack_status_reason=stack.stack_status_reason)
386
387 return False
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530388
Steve Baker450aa7f2014-08-25 10:37:27 +1200389 def _wait_for_stack_status(self, stack_identifier, status,
Sergey Kraynev89082a32015-09-04 04:42:33 -0400390 failure_pattern=None,
Rabi Mishra81ca6bc2016-06-16 15:09:20 +0530391 success_on_not_found=False,
392 signal_required=False,
rabi5eaa4962017-08-31 10:55:13 +0530393 resources_to_signal=None,
Pavlo Shchelokovskyy6646c952021-03-09 14:43:05 +0200394 is_action_cancelled=False,
395 log_nova_servers=False):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +0300396 """Waits for a Stack to reach a given status.
Steve Baker450aa7f2014-08-25 10:37:27 +1200397
398 Note this compares the full $action_$status, e.g
399 CREATE_COMPLETE, not just COMPLETE which is exposed
400 via the status property of Stack in heatclient
401 """
Sergey Kraynev89082a32015-09-04 04:42:33 -0400402 if failure_pattern:
403 fail_regexp = re.compile(failure_pattern)
404 elif 'FAILED' in status:
405 # If we're looking for e.g CREATE_FAILED, COMPLETE is unexpected.
406 fail_regexp = re.compile('^.*_COMPLETE$')
407 else:
408 fail_regexp = re.compile('^.*_FAILED$')
Steve Baker450aa7f2014-08-25 10:37:27 +1200409 build_timeout = self.conf.build_timeout
410 build_interval = self.conf.build_interval
411
412 start = timeutils.utcnow()
413 while timeutils.delta_seconds(start,
414 timeutils.utcnow()) < build_timeout:
415 try:
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500416 stack = self.client.stacks.get(stack_identifier,
417 resolve_outputs=False)
Steve Baker450aa7f2014-08-25 10:37:27 +1200418 except heat_exceptions.HTTPNotFound:
419 if success_on_not_found:
420 return
421 # ignore this, as the resource may not have
422 # been created yet
423 else:
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530424 if self._verify_status(stack, stack_identifier, status,
rabi5eaa4962017-08-31 10:55:13 +0530425 fail_regexp, is_action_cancelled):
Steve Baker450aa7f2014-08-25 10:37:27 +1200426 return
Rabi Mishra81ca6bc2016-06-16 15:09:20 +0530427 if signal_required:
428 self.signal_resources(resources_to_signal)
Steve Baker450aa7f2014-08-25 10:37:27 +1200429 time.sleep(build_interval)
430
431 message = ('Stack %s failed to reach %s status within '
432 'the required time (%s s).' %
Anastasia Kuznetsova9a745572015-03-04 13:19:15 +0400433 (stack_identifier, status, build_timeout))
oklymenko9f139632022-11-22 14:26:54 +0200434 LOG.info(
435 f"{message} "
436 f"Event list:"
437 f" {self.client.events.list(stack_identifier, nested_depth=999)}"
438 )
Pavlo Shchelokovskyy6646c952021-03-09 14:43:05 +0200439 if log_nova_servers:
440 self._log_nova_servers(stack_identifier)
Steve Baker450aa7f2014-08-25 10:37:27 +1200441 raise exceptions.TimeoutException(message)
442
Pavlo Shchelokovskyy6646c952021-03-09 14:43:05 +0200443 def _log_nova_servers(self, stack_identifier):
444 server_resources = self.client.resources.list(
445 stack_identifier,
446 type="OS::Nova::Server",
447 nested_depth=999)
448 servers = list(self.compute_client.servers.get(s.physical_resource_id)
449 for s in server_resources)
450 if not servers:
451 LOG.info("No OS::Nova::Server resources found in stack %s",
452 stack_identifier)
453 return
454 self._log_console_output(servers=servers)
455
Steve Baker450aa7f2014-08-25 10:37:27 +1200456 def _stack_delete(self, stack_identifier):
457 try:
Thomas Herve3eab2942015-10-22 17:29:21 +0200458 self._handle_in_progress(self.client.stacks.delete,
459 stack_identifier)
Steve Baker450aa7f2014-08-25 10:37:27 +1200460 except heat_exceptions.HTTPNotFound:
461 pass
462 self._wait_for_stack_status(
463 stack_identifier, 'DELETE_COMPLETE',
464 success_on_not_found=True)
Steven Hardyc9efd972014-11-20 11:31:55 +0000465
Thomas Herve3eab2942015-10-22 17:29:21 +0200466 def _handle_in_progress(self, fn, *args, **kwargs):
467 build_timeout = self.conf.build_timeout
468 build_interval = self.conf.build_interval
469 start = timeutils.utcnow()
470 while timeutils.delta_seconds(start,
471 timeutils.utcnow()) < build_timeout:
472 try:
473 fn(*args, **kwargs)
474 except heat_exceptions.HTTPConflict as ex:
475 # FIXME(sirushtim): Wait a little for the stack lock to be
476 # released and hopefully, the stack should be usable again.
477 if ex.error['error']['type'] != 'ActionInProgress':
478 raise ex
479
480 time.sleep(build_interval)
481 else:
482 break
483
Steven Hardy23284b62015-10-01 19:03:42 +0100484 def update_stack(self, stack_identifier, template=None, environment=None,
Sabeen Syed277ea692015-02-04 23:30:02 +0000485 files=None, parameters=None, tags=None,
Rakesh H Sa3325d62015-04-04 19:42:29 +0530486 expected_status='UPDATE_COMPLETE',
Steven Hardy23284b62015-10-01 19:03:42 +0100487 disable_rollback=True,
Pavlo Shchelokovskyy6646c952021-03-09 14:43:05 +0200488 existing=False,
489 log_nova_servers=False):
Steven Hardyc9efd972014-11-20 11:31:55 +0000490 env = environment or {}
491 env_files = files or {}
Sergey Kraynevbcc78df2015-02-27 04:34:32 -0500492 parameters = parameters or {}
Sirushti Murugesan3a195a52015-05-01 09:47:09 +0530493
Thomas Herve3eab2942015-10-22 17:29:21 +0200494 self._handle_in_progress(
495 self.client.stacks.update,
496 stack_id=stack_identifier,
Thomas Herve3eab2942015-10-22 17:29:21 +0200497 template=template,
498 files=env_files,
499 disable_rollback=disable_rollback,
500 parameters=parameters,
501 environment=env,
502 tags=tags,
503 existing=existing)
Sirushti Murugesan3a195a52015-05-01 09:47:09 +0530504
Rakesh H Sa3325d62015-04-04 19:42:29 +0530505 kwargs = {'stack_identifier': stack_identifier,
Pavlo Shchelokovskyy6646c952021-03-09 14:43:05 +0200506 'status': expected_status,
507 'log_nova_servers': log_nova_servers}
Rakesh H Sa3325d62015-04-04 19:42:29 +0530508 if expected_status in ['ROLLBACK_COMPLETE']:
Rakesh H Sa3325d62015-04-04 19:42:29 +0530509 # To trigger rollback you would intentionally fail the stack
510 # Hence check for rollback failures
511 kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
512
513 self._wait_for_stack_status(**kwargs)
Steven Hardyc9efd972014-11-20 11:31:55 +0000514
rabi5eaa4962017-08-31 10:55:13 +0530515 def cancel_update_stack(self, stack_identifier, rollback=True,
Oleksii Chuprykovfc2c58f2016-04-29 17:03:17 +0300516 expected_status='ROLLBACK_COMPLETE'):
517
518 stack_name = stack_identifier.split('/')[0]
519
rabi5eaa4962017-08-31 10:55:13 +0530520 if rollback:
521 self.client.actions.cancel_update(stack_name)
522 else:
523 self.client.actions.cancel_without_rollback(stack_name)
Oleksii Chuprykovfc2c58f2016-04-29 17:03:17 +0300524
525 kwargs = {'stack_identifier': stack_identifier,
526 'status': expected_status}
rabi5eaa4962017-08-31 10:55:13 +0530527 if expected_status == 'UPDATE_FAILED':
528 kwargs['is_action_cancelled'] = True
529
Oleksii Chuprykovfc2c58f2016-04-29 17:03:17 +0300530 if expected_status in ['ROLLBACK_COMPLETE']:
531 # To trigger rollback you would intentionally fail the stack
532 # Hence check for rollback failures
533 kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
534
535 self._wait_for_stack_status(**kwargs)
536
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500537 def preview_update_stack(self, stack_identifier, template,
538 environment=None, files=None, parameters=None,
Steven Hardye6de2d62015-12-07 15:59:09 +0000539 tags=None, disable_rollback=True,
540 show_nested=False):
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500541 env = environment or {}
542 env_files = files or {}
543 parameters = parameters or {}
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500544
545 return self.client.stacks.preview_update(
546 stack_id=stack_identifier,
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500547 template=template,
548 files=env_files,
549 disable_rollback=disable_rollback,
550 parameters=parameters,
551 environment=env,
Steven Hardye6de2d62015-12-07 15:59:09 +0000552 tags=tags,
553 show_nested=show_nested
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500554 )
555
Steven Hardy03da0742015-03-19 00:13:17 -0400556 def assert_resource_is_a_stack(self, stack_identifier, res_name,
557 wait=False):
558 build_timeout = self.conf.build_timeout
559 build_interval = self.conf.build_interval
560 start = timeutils.utcnow()
561 while timeutils.delta_seconds(start,
562 timeutils.utcnow()) < build_timeout:
563 time.sleep(build_interval)
564 try:
565 nested_identifier = self._get_nested_identifier(
566 stack_identifier, res_name)
567 except Exception:
568 # We may have to wait, if the create is in-progress
569 if wait:
570 time.sleep(build_interval)
571 else:
572 raise
573 else:
574 return nested_identifier
575
576 def _get_nested_identifier(self, stack_identifier, res_name):
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000577 rsrc = self.client.resources.get(stack_identifier, res_name)
578 nested_link = [l for l in rsrc.links if l['rel'] == 'nested']
579 nested_href = nested_link[0]['href']
580 nested_id = nested_href.split('/')[-1]
581 nested_identifier = '/'.join(nested_href.split('/')[-2:])
582 self.assertEqual(rsrc.physical_resource_id, nested_id)
583
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500584 nested_stack = self.client.stacks.get(nested_id, resolve_outputs=False)
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000585 nested_identifier2 = '%s/%s' % (nested_stack.stack_name,
586 nested_stack.id)
587 self.assertEqual(nested_identifier, nested_identifier2)
588 parent_id = stack_identifier.split("/")[-1]
589 self.assertEqual(parent_id, nested_stack.parent)
590 return nested_identifier
591
Rabi Mishra8bcff8a2015-09-21 18:15:04 +0530592 def group_nested_identifier(self, stack_identifier,
593 group_name):
594 # Get the nested stack identifier from a group resource
595 rsrc = self.client.resources.get(stack_identifier, group_name)
596 physical_resource_id = rsrc.physical_resource_id
597
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500598 nested_stack = self.client.stacks.get(physical_resource_id,
599 resolve_outputs=False)
Rabi Mishra8bcff8a2015-09-21 18:15:04 +0530600 nested_identifier = '%s/%s' % (nested_stack.stack_name,
601 nested_stack.id)
602 parent_id = stack_identifier.split("/")[-1]
603 self.assertEqual(parent_id, nested_stack.parent)
604 return nested_identifier
605
606 def list_group_resources(self, stack_identifier,
607 group_name, minimal=True):
608 nested_identifier = self.group_nested_identifier(stack_identifier,
609 group_name)
610 if minimal:
611 return self.list_resources(nested_identifier)
612 return self.client.resources.list(nested_identifier)
613
Steven Hardyc9efd972014-11-20 11:31:55 +0000614 def list_resources(self, stack_identifier):
615 resources = self.client.resources.list(stack_identifier)
616 return dict((r.resource_name, r.resource_type) for r in resources)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000617
Steven Hardyd448dae2016-06-14 14:57:28 +0100618 def get_resource_stack_id(self, r):
619 stack_link = [l for l in r.links if l.get('rel') == 'stack'][0]
620 return stack_link['href'].split("/")[-1]
621
Botond Zoltáne0b7aa12017-03-28 08:42:16 +0200622 def get_physical_resource_id(self, stack_identifier, resource_name):
623 try:
624 resource = self.client.resources.get(
625 stack_identifier, resource_name)
626 return resource.physical_resource_id
627 except Exception:
628 raise Exception('Resource (%s) not found in stack (%s)!' %
629 (stack_identifier, resource_name))
630
631 def get_stack_output(self, stack_identifier, output_key,
632 validate_errors=True):
633 stack = self.client.stacks.get(stack_identifier)
634 return self._stack_output(stack, output_key, validate_errors)
635
Rabi Mishra81ca6bc2016-06-16 15:09:20 +0530636 def check_input_values(self, group_resources, key, value):
637 # Check inputs for deployment and derived config
638 for r in group_resources:
639 d = self.client.software_deployments.get(
640 r.physical_resource_id)
641 self.assertEqual({key: value}, d.input_values)
642 c = self.client.software_configs.get(
643 d.config_id)
644 foo_input_c = [i for i in c.inputs if i.get('name') == key][0]
645 self.assertEqual(value, foo_input_c.get('value'))
646
647 def signal_resources(self, resources):
648 # Signal all IN_PROGRESS resources
649 for r in resources:
650 if 'IN_PROGRESS' in r.resource_status:
651 stack_id = self.get_resource_stack_id(r)
652 self.client.resources.signal(stack_id, r.resource_name)
653
Steven Hardyf2c82c02014-11-20 14:02:17 +0000654 def stack_create(self, stack_name=None, template=None, files=None,
Sabeen Syed277ea692015-02-04 23:30:02 +0000655 parameters=None, environment=None, tags=None,
656 expected_status='CREATE_COMPLETE',
Jay Dobies39c4ce42015-11-04 10:49:08 -0500657 disable_rollback=True, enable_cleanup=True,
Pavlo Shchelokovskyy6646c952021-03-09 14:43:05 +0200658 environment_files=None, timeout=None,
659 log_nova_servers=False):
Steven Hardyf2c82c02014-11-20 14:02:17 +0000660 name = stack_name or self._stack_rand_name()
661 templ = template or self.template
662 templ_files = files or {}
663 params = parameters or {}
664 env = environment or {}
rabi6ce8d962017-07-10 16:40:12 +0530665 timeout_mins = timeout or self.conf.build_timeout
Steven Hardyf2c82c02014-11-20 14:02:17 +0000666 self.client.stacks.create(
667 stack_name=name,
668 template=templ,
669 files=templ_files,
Rakesh H Sa3325d62015-04-04 19:42:29 +0530670 disable_rollback=disable_rollback,
Steven Hardyf2c82c02014-11-20 14:02:17 +0000671 parameters=params,
Sabeen Syed277ea692015-02-04 23:30:02 +0000672 environment=env,
Jay Dobies39c4ce42015-11-04 10:49:08 -0500673 tags=tags,
rabi6ce8d962017-07-10 16:40:12 +0530674 environment_files=environment_files,
675 timeout_mins=timeout_mins
Steven Hardyf2c82c02014-11-20 14:02:17 +0000676 )
rabic570e0f2017-10-26 13:07:13 +0530677 if enable_cleanup:
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200678 self.addCleanup(self._stack_delete, name)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000679
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500680 stack = self.client.stacks.get(name, resolve_outputs=False)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000681 stack_identifier = '%s/%s' % (name, stack.id)
Rakesh H Sa3325d62015-04-04 19:42:29 +0530682 kwargs = {'stack_identifier': stack_identifier,
Pavlo Shchelokovskyy6646c952021-03-09 14:43:05 +0200683 'status': expected_status,
684 'log_nova_servers': log_nova_servers}
Steve Bakerf6c8f122015-02-10 13:54:46 +1300685 if expected_status:
Rakesh H Sa3325d62015-04-04 19:42:29 +0530686 if expected_status in ['ROLLBACK_COMPLETE']:
687 # To trigger rollback you would intentionally fail the stack
688 # Hence check for rollback failures
689 kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
690 self._wait_for_stack_status(**kwargs)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000691 return stack_identifier
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000692
693 def stack_adopt(self, stack_name=None, files=None,
694 parameters=None, environment=None, adopt_data=None,
695 wait_for_status='ADOPT_COMPLETE'):
Rabi Mishra477efc92015-07-31 13:01:45 +0530696 if (self.conf.skip_test_stack_action_list and
697 'ADOPT' in self.conf.skip_test_stack_action_list):
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530698 self.skipTest('Testing Stack adopt disabled in conf, skipping')
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000699 name = stack_name or self._stack_rand_name()
700 templ_files = files or {}
701 params = parameters or {}
702 env = environment or {}
703 self.client.stacks.create(
704 stack_name=name,
705 files=templ_files,
706 disable_rollback=True,
707 parameters=params,
708 environment=env,
709 adopt_stack_data=adopt_data,
710 )
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200711 self.addCleanup(self._stack_delete, name)
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500712 stack = self.client.stacks.get(name, resolve_outputs=False)
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000713 stack_identifier = '%s/%s' % (name, stack.id)
714 self._wait_for_stack_status(stack_identifier, wait_for_status)
715 return stack_identifier
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530716
717 def stack_abandon(self, stack_id):
Rabi Mishra477efc92015-07-31 13:01:45 +0530718 if (self.conf.skip_test_stack_action_list and
719 'ABANDON' in self.conf.skip_test_stack_action_list):
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200720 self.addCleanup(self._stack_delete, stack_id)
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530721 self.skipTest('Testing Stack abandon disabled in conf, skipping')
722 info = self.client.stacks.abandon(stack_id=stack_id)
723 return info
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500724
rabi90b3ab42017-05-04 13:02:28 +0530725 def stack_snapshot(self, stack_id,
726 wait_for_status='SNAPSHOT_COMPLETE'):
727 snapshot = self.client.stacks.snapshot(stack_id=stack_id)
728 self._wait_for_stack_status(stack_id, wait_for_status)
729 return snapshot['id']
730
731 def stack_restore(self, stack_id, snapshot_id,
732 wait_for_status='RESTORE_COMPLETE'):
733 self.client.stacks.restore(stack_id, snapshot_id)
734 self._wait_for_stack_status(stack_id, wait_for_status)
735
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500736 def stack_suspend(self, stack_identifier):
Rabi Mishra477efc92015-07-31 13:01:45 +0530737 if (self.conf.skip_test_stack_action_list and
738 'SUSPEND' in self.conf.skip_test_stack_action_list):
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200739 self.addCleanup(self._stack_delete, stack_identifier)
Rabi Mishra477efc92015-07-31 13:01:45 +0530740 self.skipTest('Testing Stack suspend disabled in conf, skipping')
rabif7d67082016-05-17 18:51:22 +0530741 self._handle_in_progress(self.client.actions.suspend, stack_identifier)
Angus Salkelda7500d12015-04-10 15:44:07 +1000742 # improve debugging by first checking the resource's state.
743 self._wait_for_all_resource_status(stack_identifier,
744 'SUSPEND_COMPLETE')
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500745 self._wait_for_stack_status(stack_identifier, 'SUSPEND_COMPLETE')
746
747 def stack_resume(self, stack_identifier):
Rabi Mishra477efc92015-07-31 13:01:45 +0530748 if (self.conf.skip_test_stack_action_list and
749 'RESUME' in self.conf.skip_test_stack_action_list):
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200750 self.addCleanup(self._stack_delete, stack_identifier)
Rabi Mishra477efc92015-07-31 13:01:45 +0530751 self.skipTest('Testing Stack resume disabled in conf, skipping')
rabif7d67082016-05-17 18:51:22 +0530752 self._handle_in_progress(self.client.actions.resume, stack_identifier)
Angus Salkelda7500d12015-04-10 15:44:07 +1000753 # improve debugging by first checking the resource's state.
754 self._wait_for_all_resource_status(stack_identifier,
755 'RESUME_COMPLETE')
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500756 self._wait_for_stack_status(stack_identifier, 'RESUME_COMPLETE')
Steven Hardy03da0742015-03-19 00:13:17 -0400757
758 def wait_for_event_with_reason(self, stack_identifier, reason,
759 rsrc_name=None, num_expected=1):
760 build_timeout = self.conf.build_timeout
761 build_interval = self.conf.build_interval
762 start = timeutils.utcnow()
763 while timeutils.delta_seconds(start,
764 timeutils.utcnow()) < build_timeout:
765 try:
766 rsrc_events = self.client.events.list(stack_identifier,
767 resource_name=rsrc_name)
768 except heat_exceptions.HTTPNotFound:
liyi09461f72017-03-21 12:17:51 +0800769 LOG.debug("No events yet found for %s", rsrc_name)
Steven Hardy03da0742015-03-19 00:13:17 -0400770 else:
771 matched = [e for e in rsrc_events
772 if e.resource_status_reason == reason]
773 if len(matched) == num_expected:
774 return matched
775 time.sleep(build_interval)
Rakesh H Sc5735a82016-04-28 15:38:09 +0530776
Thomas Hervea6afca82017-04-10 23:44:26 +0200777 def check_autoscale_complete(self, stack_id, expected_num, parent_stack,
rabi55c0f752018-02-07 09:21:28 +0530778 group_name):
Rakesh H Sc5735a82016-04-28 15:38:09 +0530779 res_list = self.client.resources.list(stack_id)
780 all_res_complete = all(res.resource_status in ('UPDATE_COMPLETE',
781 'CREATE_COMPLETE')
782 for res in res_list)
783 all_res = len(res_list) == expected_num
Thomas Hervea6afca82017-04-10 23:44:26 +0200784 if all_res and all_res_complete:
rabi55c0f752018-02-07 09:21:28 +0530785 metadata = self.client.resources.metadata(parent_stack, group_name)
Thomas Hervea6afca82017-04-10 23:44:26 +0200786 return not metadata.get('scaling_in_progress')
787 return False