blob: 34ab24e4c67c7e6f339b3e1dd48b3f1e9a43b3bf [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 os
14import random
15import re
Steve Baker450aa7f2014-08-25 10:37:27 +120016import subprocess
Steve Baker450aa7f2014-08-25 10:37:27 +120017import time
18
Pavlo Shchelokovskyy60e0ecd2014-12-14 22:17:21 +020019import fixtures
Steve Baker450aa7f2014-08-25 10:37:27 +120020from heatclient import exc as heat_exceptions
Thomas Hervedb36c092017-03-23 11:20:14 +010021from keystoneauth1 import exceptions as kc_exceptions
Mark Vanderwiel6d8e0862015-10-15 12:51:07 -050022from neutronclient.common import exceptions as network_exceptions
Steve Baker24641292015-03-13 10:47:50 +130023from oslo_log import log as logging
Jens Rosenboom4f069fb2015-02-18 14:19:07 +010024from oslo_utils import timeutils
Pavlo Shchelokovskyy60e0ecd2014-12-14 22:17:21 +020025import six
Sirushti Murugesan4920fda2015-04-22 00:35:26 +053026from six.moves import urllib
Pavlo Shchelokovskyy60e0ecd2014-12-14 22:17:21 +020027import testscenarios
28import testtools
Steve Baker450aa7f2014-08-25 10:37:27 +120029
rabid2916d02017-09-22 18:19:24 +053030from heat_tempest_plugin.common import exceptions
31from heat_tempest_plugin.common import remote_client
rabid2916d02017-09-22 18:19:24 +053032from heat_tempest_plugin.services import clients
Zane Bitterb4acd962018-01-18 12:08:23 -050033from tempest import config
Steve Baker450aa7f2014-08-25 10:37:27 +120034
35LOG = logging.getLogger(__name__)
Angus Salkeld24043702014-11-21 08:49:26 +100036_LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s"
rabi82b71282018-03-08 11:08:36 +053037_resource_types = None
Steve Baker450aa7f2014-08-25 10:37:27 +120038
39
Angus Salkeld08514ad2015-02-06 10:08:31 +100040def call_until_true(duration, sleep_for, func, *args, **kwargs):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +030041 """Call the function until it returns True or the duration elapsed.
42
Steve Baker450aa7f2014-08-25 10:37:27 +120043 Call the given function until it returns True (and return True) or
44 until the specified duration (in seconds) elapses (and return
45 False).
46
47 :param func: A zero argument callable that returns True on success.
48 :param duration: The number of seconds for which to attempt a
49 successful call of the function.
50 :param sleep_for: The number of seconds to sleep after an unsuccessful
51 invocation of the function.
52 """
53 now = time.time()
54 timeout = now + duration
55 while now < timeout:
Angus Salkeld08514ad2015-02-06 10:08:31 +100056 if func(*args, **kwargs):
Steve Baker450aa7f2014-08-25 10:37:27 +120057 return True
58 LOG.debug("Sleeping for %d seconds", sleep_for)
59 time.sleep(sleep_for)
60 now = time.time()
61 return False
62
63
rabi32201342017-11-17 22:57:58 +053064def isotime(at):
65 if at is None:
66 return None
67 return at.strftime('%Y-%m-%dT%H:%M:%SZ')
68
69
Steve Baker450aa7f2014-08-25 10:37:27 +120070def rand_name(name=''):
ricolina4eb53d2017-04-24 23:51:09 +080071 randbits = six.text_type(random.randint(1, 0x7fffffff))
Steve Baker450aa7f2014-08-25 10:37:27 +120072 if name:
73 return name + '-' + randbits
74 else:
75 return randbits
76
77
Zane Bitterf407e102017-10-05 14:19:32 -040078def requires_convergence(test_method):
79 '''Decorator for convergence-only tests.
80
81 The decorated test will be skipped when convergence is disabled.
82 '''
rabif89752b2017-11-18 22:14:30 +053083 plugin = config.CONF.heat_plugin
rabi94a520a2017-11-17 22:49:17 +053084 convergence_enabled = plugin.convergence_engine_enabled
Zane Bitterf407e102017-10-05 14:19:32 -040085 skipper = testtools.skipUnless(convergence_enabled,
86 "Convergence-only tests are disabled")
87 return skipper(test_method)
88
89
rabi82b71282018-03-08 11:08:36 +053090def requires_resource_type(resource_type):
91 '''Decorator for tests requiring a resource type.
92
93 The decorated test will be skipped when the resource type is not available.
94 '''
95 def decorator(test_method):
96 conf = getattr(config.CONF, 'heat_plugin', None)
97 if not conf or conf.auth_url is None:
98 return test_method
99
100 global _resource_types
101 if not _resource_types:
102 manager = clients.ClientManager(conf)
103 obj_rtypes = manager.orchestration_client.resource_types.list()
104 _resource_types = list(t.resource_type for t in obj_rtypes)
105 rtype_available = resource_type and resource_type in _resource_types
106 skipper = testtools.skipUnless(
107 rtype_available,
108 "%s resource type not available, skipping test." % resource_type)
109 return skipper(test_method)
110 return decorator
111
112
rabi876449a2018-03-15 21:56:49 +0530113def requires_feature(feature):
114 '''Decorator for tests requring specific feature.
115
116 The decorated test will be skipped when a specific feature is disabled.
117 '''
118 def decorator(test_method):
119 features_group = getattr(config.CONF, 'heat_features_enabled', None)
120 if not features_group:
121 return test_method
122 feature_enabled = config.CONF.heat_features_enabled.get(feature, False)
123 skipper = testtools.skipUnless(feature_enabled,
124 "%s - Feature not enabled." % feature)
125 return skipper(test_method)
126 return decorator
127
128
Andrea Frittolid908bef2018-02-22 11:29:53 +0000129class HeatIntegrationTest(testtools.testcase.WithAttributes,
130 testscenarios.WithScenarios,
Angus Salkeld95f65a22014-11-24 12:38:30 +1000131 testtools.TestCase):
Steve Baker450aa7f2014-08-25 10:37:27 +1200132
133 def setUp(self):
134 super(HeatIntegrationTest, self).setUp()
135
rabif89752b2017-11-18 22:14:30 +0530136 self.conf = config.CONF.heat_plugin
Steve Baker450aa7f2014-08-25 10:37:27 +1200137
138 self.assertIsNotNone(self.conf.auth_url,
139 'No auth_url configured')
140 self.assertIsNotNone(self.conf.username,
141 'No username configured')
142 self.assertIsNotNone(self.conf.password,
143 'No password configured')
rabifd98a472016-05-24 10:18:33 +0530144 self.setup_clients(self.conf)
145 self.useFixture(fixtures.FakeLogger(format=_LOG_FORMAT))
146 self.updated_time = {}
147 if self.conf.disable_ssl_certificate_validation:
148 self.verify_cert = False
149 else:
150 self.verify_cert = self.conf.ca_file or True
Steve Baker450aa7f2014-08-25 10:37:27 +1200151
Steve Bakerb752e912016-08-01 22:05:37 +0000152 def setup_clients(self, conf, admin_credentials=False):
153 self.manager = clients.ClientManager(conf, admin_credentials)
Steve Baker450aa7f2014-08-25 10:37:27 +1200154 self.identity_client = self.manager.identity_client
155 self.orchestration_client = self.manager.orchestration_client
156 self.compute_client = self.manager.compute_client
157 self.network_client = self.manager.network_client
158 self.volume_client = self.manager.volume_client
Angus Salkeld4408da32015-02-03 18:53:30 +1000159 self.object_client = self.manager.object_client
rabid69f0312017-10-26 14:52:52 +0530160 self.metric_client = self.manager.metric_client
rabifd98a472016-05-24 10:18:33 +0530161
162 self.client = self.orchestration_client
163
164 def setup_clients_for_admin(self):
Steve Bakerb752e912016-08-01 22:05:37 +0000165 self.setup_clients(self.conf, True)
Steve Baker450aa7f2014-08-25 10:37:27 +1200166
Steve Baker450aa7f2014-08-25 10:37:27 +1200167 def get_remote_client(self, server_or_ip, username, private_key=None):
168 if isinstance(server_or_ip, six.string_types):
169 ip = server_or_ip
170 else:
171 network_name_for_ssh = self.conf.network_for_ssh
172 ip = server_or_ip.networks[network_name_for_ssh][0]
173 if private_key is None:
174 private_key = self.keypair.private_key
175 linux_client = remote_client.RemoteClient(ip, username,
176 pkey=private_key,
177 conf=self.conf)
178 try:
179 linux_client.validate_authentication()
180 except exceptions.SSHTimeout:
liyi09461f72017-03-21 12:17:51 +0800181 LOG.exception('ssh connection to %s failed', ip)
Steve Baker450aa7f2014-08-25 10:37:27 +1200182 raise
183
184 return linux_client
185
Anastasia Kuznetsova3e0ab4d2015-03-06 18:10:13 +0400186 def check_connectivity(self, check_ip):
187 def try_connect(ip):
188 try:
Sirushti Murugesan4920fda2015-04-22 00:35:26 +0530189 urllib.request.urlopen('http://%s/' % ip)
Anastasia Kuznetsova3e0ab4d2015-03-06 18:10:13 +0400190 return True
191 except IOError:
192 return False
193
194 timeout = self.conf.connectivity_timeout
195 elapsed_time = 0
196 while not try_connect(check_ip):
197 time.sleep(10)
198 elapsed_time += 10
199 if elapsed_time > timeout:
200 raise exceptions.TimeoutException()
201
Steve Baker450aa7f2014-08-25 10:37:27 +1200202 def _log_console_output(self, servers=None):
203 if not servers:
204 servers = self.compute_client.servers.list()
205 for server in servers:
Steve Baker24641292015-03-13 10:47:50 +1300206 LOG.info('Console output for %s', server.id)
207 LOG.info(server.get_console_output())
Steve Baker450aa7f2014-08-25 10:37:27 +1200208
Sergey Kraynevd6fa5c02015-02-13 03:03:55 -0500209 def _load_template(self, base_file, file_name, sub_dir=None):
210 sub_dir = sub_dir or ''
Steve Baker450aa7f2014-08-25 10:37:27 +1200211 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
Sergey Kraynevd6fa5c02015-02-13 03:03:55 -0500212 sub_dir, file_name)
Steve Baker450aa7f2014-08-25 10:37:27 +1200213 with open(filepath) as f:
214 return f.read()
215
216 def create_keypair(self, client=None, name=None):
217 if client is None:
218 client = self.compute_client
219 if name is None:
220 name = rand_name('heat-keypair')
221 keypair = client.keypairs.create(name)
222 self.assertEqual(keypair.name, name)
223
224 def delete_keypair():
225 keypair.delete()
226
227 self.addCleanup(delete_keypair)
228 return keypair
229
Sergey Krayneva265c132015-02-13 03:51:03 -0500230 def assign_keypair(self):
231 if self.conf.keypair_name:
232 self.keypair = None
233 self.keypair_name = self.conf.keypair_name
234 else:
235 self.keypair = self.create_keypair()
236 self.keypair_name = self.keypair.id
237
Steve Baker450aa7f2014-08-25 10:37:27 +1200238 @classmethod
239 def _stack_rand_name(cls):
240 return rand_name(cls.__name__)
241
Anastasia Kuznetsova673fc432015-03-12 16:41:36 +0400242 def _get_network(self, net_name=None):
243 if net_name is None:
244 net_name = self.conf.fixed_network_name
Steve Baker450aa7f2014-08-25 10:37:27 +1200245 networks = self.network_client.list_networks()
246 for net in networks['networks']:
Anastasia Kuznetsova673fc432015-03-12 16:41:36 +0400247 if net['name'] == net_name:
Steve Baker450aa7f2014-08-25 10:37:27 +1200248 return net
249
Mark Vanderwiel6d8e0862015-10-15 12:51:07 -0500250 def is_network_extension_supported(self, extension_alias):
251 try:
252 self.network_client.show_extension(extension_alias)
253 except network_exceptions.NeutronClientException:
254 return False
255 return True
256
Thomas Hervedb36c092017-03-23 11:20:14 +0100257 def is_service_available(self, service_type):
258 try:
259 self.identity_client.get_endpoint_url(
Matthias Bastian092d8bd2018-07-12 12:16:30 +0200260 service_type, self.conf.region, self.conf.endpoint_type)
Thomas Hervedb36c092017-03-23 11:20:14 +0100261 except kc_exceptions.EndpointNotFound:
262 return False
263 else:
264 return True
265
Steve Baker450aa7f2014-08-25 10:37:27 +1200266 @staticmethod
Angus Salkelda89a0282015-07-24 15:47:38 +1000267 def _stack_output(stack, output_key, validate_errors=True):
Steve Baker450aa7f2014-08-25 10:37:27 +1200268 """Return a stack output value for a given key."""
Angus Salkelda89a0282015-07-24 15:47:38 +1000269 value = None
270 for o in stack.outputs:
271 if validate_errors and 'output_error' in o:
272 # scan for errors in the stack output.
273 raise ValueError(
274 'Unexpected output errors in %s : %s' % (
275 output_key, o['output_error']))
276 if o['output_key'] == output_key:
277 value = o['output_value']
278 return value
Steve Baker450aa7f2014-08-25 10:37:27 +1200279
280 def _ping_ip_address(self, ip_address, should_succeed=True):
281 cmd = ['ping', '-c1', '-w1', ip_address]
282
283 def ping():
284 proc = subprocess.Popen(cmd,
285 stdout=subprocess.PIPE,
286 stderr=subprocess.PIPE)
287 proc.wait()
288 return (proc.returncode == 0) == should_succeed
289
290 return call_until_true(
Angus Salkeld08514ad2015-02-06 10:08:31 +1000291 self.conf.build_timeout, 1, ping)
Steve Baker450aa7f2014-08-25 10:37:27 +1200292
Angus Salkelda7500d12015-04-10 15:44:07 +1000293 def _wait_for_all_resource_status(self, stack_identifier,
294 status, failure_pattern='^.*_FAILED$',
295 success_on_not_found=False):
296 for res in self.client.resources.list(stack_identifier):
297 self._wait_for_resource_status(
298 stack_identifier, res.resource_name,
299 status, failure_pattern=failure_pattern,
300 success_on_not_found=success_on_not_found)
301
Steve Baker450aa7f2014-08-25 10:37:27 +1200302 def _wait_for_resource_status(self, stack_identifier, resource_name,
303 status, failure_pattern='^.*_FAILED$',
304 success_on_not_found=False):
305 """Waits for a Resource to reach a given status."""
306 fail_regexp = re.compile(failure_pattern)
307 build_timeout = self.conf.build_timeout
308 build_interval = self.conf.build_interval
309
310 start = timeutils.utcnow()
311 while timeutils.delta_seconds(start,
312 timeutils.utcnow()) < build_timeout:
313 try:
314 res = self.client.resources.get(
315 stack_identifier, resource_name)
316 except heat_exceptions.HTTPNotFound:
317 if success_on_not_found:
318 return
319 # ignore this, as the resource may not have
320 # been created yet
321 else:
322 if res.resource_status == status:
323 return
Sirushti Murugesane5ad25b2015-04-18 23:30:59 +0530324 wait_for_action = status.split('_')[0]
325 resource_action = res.resource_status.split('_')[0]
326 if (resource_action == wait_for_action and
327 fail_regexp.search(res.resource_status)):
Steve Baker450aa7f2014-08-25 10:37:27 +1200328 raise exceptions.StackResourceBuildErrorException(
329 resource_name=res.resource_name,
330 stack_identifier=stack_identifier,
331 resource_status=res.resource_status,
332 resource_status_reason=res.resource_status_reason)
333 time.sleep(build_interval)
334
335 message = ('Resource %s failed to reach %s status within '
336 'the required time (%s s).' %
Anastasia Kuznetsova9a745572015-03-04 13:19:15 +0400337 (resource_name, status, build_timeout))
Steve Baker450aa7f2014-08-25 10:37:27 +1200338 raise exceptions.TimeoutException(message)
339
Rabi Mishra87be9b42016-02-15 14:15:50 +0530340 def verify_resource_status(self, stack_identifier, resource_name,
341 status='CREATE_COMPLETE'):
342 try:
343 res = self.client.resources.get(stack_identifier, resource_name)
344 except heat_exceptions.HTTPNotFound:
345 return False
346 return res.resource_status == status
347
rabi5eaa4962017-08-31 10:55:13 +0530348 def _verify_status(self, stack, stack_identifier, status,
349 fail_regexp, is_action_cancelled=False):
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530350 if stack.stack_status == status:
Sergey Kraynev89082a32015-09-04 04:42:33 -0400351 # Handle UPDATE_COMPLETE/FAILED case: Make sure we don't
352 # wait for a stale UPDATE_COMPLETE/FAILED status.
353 if status in ('UPDATE_FAILED', 'UPDATE_COMPLETE'):
rabi5eaa4962017-08-31 10:55:13 +0530354 if is_action_cancelled:
355 return True
356
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530357 if self.updated_time.get(
358 stack_identifier) != stack.updated_time:
359 self.updated_time[stack_identifier] = stack.updated_time
360 return True
Thomas Herve0e8567e2016-09-22 15:07:37 +0200361 elif status == 'DELETE_COMPLETE' and stack.deletion_time is None:
362 # Wait for deleted_time to be filled, so that we have more
363 # confidence the operation is finished.
364 return False
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530365 else:
366 return True
367
Sirushti Murugesane5ad25b2015-04-18 23:30:59 +0530368 wait_for_action = status.split('_')[0]
369 if (stack.action == wait_for_action and
370 fail_regexp.search(stack.stack_status)):
Sergey Kraynev89082a32015-09-04 04:42:33 -0400371 # Handle UPDATE_COMPLETE/UPDATE_FAILED case.
372 if status in ('UPDATE_FAILED', 'UPDATE_COMPLETE'):
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530373 if self.updated_time.get(
374 stack_identifier) != stack.updated_time:
375 self.updated_time[stack_identifier] = stack.updated_time
376 raise exceptions.StackBuildErrorException(
377 stack_identifier=stack_identifier,
378 stack_status=stack.stack_status,
379 stack_status_reason=stack.stack_status_reason)
380 else:
381 raise exceptions.StackBuildErrorException(
382 stack_identifier=stack_identifier,
383 stack_status=stack.stack_status,
384 stack_status_reason=stack.stack_status_reason)
385
Steve Baker450aa7f2014-08-25 10:37:27 +1200386 def _wait_for_stack_status(self, stack_identifier, status,
Sergey Kraynev89082a32015-09-04 04:42:33 -0400387 failure_pattern=None,
Rabi Mishra81ca6bc2016-06-16 15:09:20 +0530388 success_on_not_found=False,
389 signal_required=False,
rabi5eaa4962017-08-31 10:55:13 +0530390 resources_to_signal=None,
391 is_action_cancelled=False):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +0300392 """Waits for a Stack to reach a given status.
Steve Baker450aa7f2014-08-25 10:37:27 +1200393
394 Note this compares the full $action_$status, e.g
395 CREATE_COMPLETE, not just COMPLETE which is exposed
396 via the status property of Stack in heatclient
397 """
Sergey Kraynev89082a32015-09-04 04:42:33 -0400398 if failure_pattern:
399 fail_regexp = re.compile(failure_pattern)
400 elif 'FAILED' in status:
401 # If we're looking for e.g CREATE_FAILED, COMPLETE is unexpected.
402 fail_regexp = re.compile('^.*_COMPLETE$')
403 else:
404 fail_regexp = re.compile('^.*_FAILED$')
Steve Baker450aa7f2014-08-25 10:37:27 +1200405 build_timeout = self.conf.build_timeout
406 build_interval = self.conf.build_interval
407
408 start = timeutils.utcnow()
409 while timeutils.delta_seconds(start,
410 timeutils.utcnow()) < build_timeout:
411 try:
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500412 stack = self.client.stacks.get(stack_identifier,
413 resolve_outputs=False)
Steve Baker450aa7f2014-08-25 10:37:27 +1200414 except heat_exceptions.HTTPNotFound:
415 if success_on_not_found:
416 return
417 # ignore this, as the resource may not have
418 # been created yet
419 else:
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530420 if self._verify_status(stack, stack_identifier, status,
rabi5eaa4962017-08-31 10:55:13 +0530421 fail_regexp, is_action_cancelled):
Steve Baker450aa7f2014-08-25 10:37:27 +1200422 return
Rabi Mishra81ca6bc2016-06-16 15:09:20 +0530423 if signal_required:
424 self.signal_resources(resources_to_signal)
Steve Baker450aa7f2014-08-25 10:37:27 +1200425 time.sleep(build_interval)
426
427 message = ('Stack %s failed to reach %s status within '
428 'the required time (%s s).' %
Anastasia Kuznetsova9a745572015-03-04 13:19:15 +0400429 (stack_identifier, status, build_timeout))
Steve Baker450aa7f2014-08-25 10:37:27 +1200430 raise exceptions.TimeoutException(message)
431
432 def _stack_delete(self, stack_identifier):
433 try:
Thomas Herve3eab2942015-10-22 17:29:21 +0200434 self._handle_in_progress(self.client.stacks.delete,
435 stack_identifier)
Steve Baker450aa7f2014-08-25 10:37:27 +1200436 except heat_exceptions.HTTPNotFound:
437 pass
438 self._wait_for_stack_status(
439 stack_identifier, 'DELETE_COMPLETE',
440 success_on_not_found=True)
Steven Hardyc9efd972014-11-20 11:31:55 +0000441
Thomas Herve3eab2942015-10-22 17:29:21 +0200442 def _handle_in_progress(self, fn, *args, **kwargs):
443 build_timeout = self.conf.build_timeout
444 build_interval = self.conf.build_interval
445 start = timeutils.utcnow()
446 while timeutils.delta_seconds(start,
447 timeutils.utcnow()) < build_timeout:
448 try:
449 fn(*args, **kwargs)
450 except heat_exceptions.HTTPConflict as ex:
451 # FIXME(sirushtim): Wait a little for the stack lock to be
452 # released and hopefully, the stack should be usable again.
453 if ex.error['error']['type'] != 'ActionInProgress':
454 raise ex
455
456 time.sleep(build_interval)
457 else:
458 break
459
Steven Hardy23284b62015-10-01 19:03:42 +0100460 def update_stack(self, stack_identifier, template=None, environment=None,
Sabeen Syed277ea692015-02-04 23:30:02 +0000461 files=None, parameters=None, tags=None,
Rakesh H Sa3325d62015-04-04 19:42:29 +0530462 expected_status='UPDATE_COMPLETE',
Steven Hardy23284b62015-10-01 19:03:42 +0100463 disable_rollback=True,
464 existing=False):
Steven Hardyc9efd972014-11-20 11:31:55 +0000465 env = environment or {}
466 env_files = files or {}
Sergey Kraynevbcc78df2015-02-27 04:34:32 -0500467 parameters = parameters or {}
Sirushti Murugesan3a195a52015-05-01 09:47:09 +0530468
Sergey Kraynev89082a32015-09-04 04:42:33 -0400469 self.updated_time[stack_identifier] = self.client.stacks.get(
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500470 stack_identifier, resolve_outputs=False).updated_time
Sirushti Murugesan3a195a52015-05-01 09:47:09 +0530471
Thomas Herve3eab2942015-10-22 17:29:21 +0200472 self._handle_in_progress(
473 self.client.stacks.update,
474 stack_id=stack_identifier,
Thomas Herve3eab2942015-10-22 17:29:21 +0200475 template=template,
476 files=env_files,
477 disable_rollback=disable_rollback,
478 parameters=parameters,
479 environment=env,
480 tags=tags,
481 existing=existing)
Sirushti Murugesan3a195a52015-05-01 09:47:09 +0530482
Rakesh H Sa3325d62015-04-04 19:42:29 +0530483 kwargs = {'stack_identifier': stack_identifier,
484 'status': expected_status}
485 if expected_status in ['ROLLBACK_COMPLETE']:
Rakesh H Sa3325d62015-04-04 19:42:29 +0530486 # To trigger rollback you would intentionally fail the stack
487 # Hence check for rollback failures
488 kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
489
490 self._wait_for_stack_status(**kwargs)
Steven Hardyc9efd972014-11-20 11:31:55 +0000491
rabi5eaa4962017-08-31 10:55:13 +0530492 def cancel_update_stack(self, stack_identifier, rollback=True,
Oleksii Chuprykovfc2c58f2016-04-29 17:03:17 +0300493 expected_status='ROLLBACK_COMPLETE'):
494
495 stack_name = stack_identifier.split('/')[0]
496
497 self.updated_time[stack_identifier] = self.client.stacks.get(
498 stack_identifier, resolve_outputs=False).updated_time
499
rabi5eaa4962017-08-31 10:55:13 +0530500 if rollback:
501 self.client.actions.cancel_update(stack_name)
502 else:
503 self.client.actions.cancel_without_rollback(stack_name)
Oleksii Chuprykovfc2c58f2016-04-29 17:03:17 +0300504
505 kwargs = {'stack_identifier': stack_identifier,
506 'status': expected_status}
rabi5eaa4962017-08-31 10:55:13 +0530507 if expected_status == 'UPDATE_FAILED':
508 kwargs['is_action_cancelled'] = True
509
Oleksii Chuprykovfc2c58f2016-04-29 17:03:17 +0300510 if expected_status in ['ROLLBACK_COMPLETE']:
511 # To trigger rollback you would intentionally fail the stack
512 # Hence check for rollback failures
513 kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
514
515 self._wait_for_stack_status(**kwargs)
516
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500517 def preview_update_stack(self, stack_identifier, template,
518 environment=None, files=None, parameters=None,
Steven Hardye6de2d62015-12-07 15:59:09 +0000519 tags=None, disable_rollback=True,
520 show_nested=False):
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500521 env = environment or {}
522 env_files = files or {}
523 parameters = parameters or {}
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500524
525 return self.client.stacks.preview_update(
526 stack_id=stack_identifier,
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500527 template=template,
528 files=env_files,
529 disable_rollback=disable_rollback,
530 parameters=parameters,
531 environment=env,
Steven Hardye6de2d62015-12-07 15:59:09 +0000532 tags=tags,
533 show_nested=show_nested
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500534 )
535
Steven Hardy03da0742015-03-19 00:13:17 -0400536 def assert_resource_is_a_stack(self, stack_identifier, res_name,
537 wait=False):
538 build_timeout = self.conf.build_timeout
539 build_interval = self.conf.build_interval
540 start = timeutils.utcnow()
541 while timeutils.delta_seconds(start,
542 timeutils.utcnow()) < build_timeout:
543 time.sleep(build_interval)
544 try:
545 nested_identifier = self._get_nested_identifier(
546 stack_identifier, res_name)
547 except Exception:
548 # We may have to wait, if the create is in-progress
549 if wait:
550 time.sleep(build_interval)
551 else:
552 raise
553 else:
554 return nested_identifier
555
556 def _get_nested_identifier(self, stack_identifier, res_name):
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000557 rsrc = self.client.resources.get(stack_identifier, res_name)
558 nested_link = [l for l in rsrc.links if l['rel'] == 'nested']
559 nested_href = nested_link[0]['href']
560 nested_id = nested_href.split('/')[-1]
561 nested_identifier = '/'.join(nested_href.split('/')[-2:])
562 self.assertEqual(rsrc.physical_resource_id, nested_id)
563
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500564 nested_stack = self.client.stacks.get(nested_id, resolve_outputs=False)
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000565 nested_identifier2 = '%s/%s' % (nested_stack.stack_name,
566 nested_stack.id)
567 self.assertEqual(nested_identifier, nested_identifier2)
568 parent_id = stack_identifier.split("/")[-1]
569 self.assertEqual(parent_id, nested_stack.parent)
570 return nested_identifier
571
Rabi Mishra8bcff8a2015-09-21 18:15:04 +0530572 def group_nested_identifier(self, stack_identifier,
573 group_name):
574 # Get the nested stack identifier from a group resource
575 rsrc = self.client.resources.get(stack_identifier, group_name)
576 physical_resource_id = rsrc.physical_resource_id
577
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500578 nested_stack = self.client.stacks.get(physical_resource_id,
579 resolve_outputs=False)
Rabi Mishra8bcff8a2015-09-21 18:15:04 +0530580 nested_identifier = '%s/%s' % (nested_stack.stack_name,
581 nested_stack.id)
582 parent_id = stack_identifier.split("/")[-1]
583 self.assertEqual(parent_id, nested_stack.parent)
584 return nested_identifier
585
586 def list_group_resources(self, stack_identifier,
587 group_name, minimal=True):
588 nested_identifier = self.group_nested_identifier(stack_identifier,
589 group_name)
590 if minimal:
591 return self.list_resources(nested_identifier)
592 return self.client.resources.list(nested_identifier)
593
Steven Hardyc9efd972014-11-20 11:31:55 +0000594 def list_resources(self, stack_identifier):
595 resources = self.client.resources.list(stack_identifier)
596 return dict((r.resource_name, r.resource_type) for r in resources)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000597
Steven Hardyd448dae2016-06-14 14:57:28 +0100598 def get_resource_stack_id(self, r):
599 stack_link = [l for l in r.links if l.get('rel') == 'stack'][0]
600 return stack_link['href'].split("/")[-1]
601
Botond Zoltáne0b7aa12017-03-28 08:42:16 +0200602 def get_physical_resource_id(self, stack_identifier, resource_name):
603 try:
604 resource = self.client.resources.get(
605 stack_identifier, resource_name)
606 return resource.physical_resource_id
607 except Exception:
608 raise Exception('Resource (%s) not found in stack (%s)!' %
609 (stack_identifier, resource_name))
610
611 def get_stack_output(self, stack_identifier, output_key,
612 validate_errors=True):
613 stack = self.client.stacks.get(stack_identifier)
614 return self._stack_output(stack, output_key, validate_errors)
615
Rabi Mishra81ca6bc2016-06-16 15:09:20 +0530616 def check_input_values(self, group_resources, key, value):
617 # Check inputs for deployment and derived config
618 for r in group_resources:
619 d = self.client.software_deployments.get(
620 r.physical_resource_id)
621 self.assertEqual({key: value}, d.input_values)
622 c = self.client.software_configs.get(
623 d.config_id)
624 foo_input_c = [i for i in c.inputs if i.get('name') == key][0]
625 self.assertEqual(value, foo_input_c.get('value'))
626
627 def signal_resources(self, resources):
628 # Signal all IN_PROGRESS resources
629 for r in resources:
630 if 'IN_PROGRESS' in r.resource_status:
631 stack_id = self.get_resource_stack_id(r)
632 self.client.resources.signal(stack_id, r.resource_name)
633
Steven Hardyf2c82c02014-11-20 14:02:17 +0000634 def stack_create(self, stack_name=None, template=None, files=None,
Sabeen Syed277ea692015-02-04 23:30:02 +0000635 parameters=None, environment=None, tags=None,
636 expected_status='CREATE_COMPLETE',
Jay Dobies39c4ce42015-11-04 10:49:08 -0500637 disable_rollback=True, enable_cleanup=True,
rabi6ce8d962017-07-10 16:40:12 +0530638 environment_files=None, timeout=None):
Steven Hardyf2c82c02014-11-20 14:02:17 +0000639 name = stack_name or self._stack_rand_name()
640 templ = template or self.template
641 templ_files = files or {}
642 params = parameters or {}
643 env = environment or {}
rabi6ce8d962017-07-10 16:40:12 +0530644 timeout_mins = timeout or self.conf.build_timeout
Steven Hardyf2c82c02014-11-20 14:02:17 +0000645 self.client.stacks.create(
646 stack_name=name,
647 template=templ,
648 files=templ_files,
Rakesh H Sa3325d62015-04-04 19:42:29 +0530649 disable_rollback=disable_rollback,
Steven Hardyf2c82c02014-11-20 14:02:17 +0000650 parameters=params,
Sabeen Syed277ea692015-02-04 23:30:02 +0000651 environment=env,
Jay Dobies39c4ce42015-11-04 10:49:08 -0500652 tags=tags,
rabi6ce8d962017-07-10 16:40:12 +0530653 environment_files=environment_files,
654 timeout_mins=timeout_mins
Steven Hardyf2c82c02014-11-20 14:02:17 +0000655 )
rabic570e0f2017-10-26 13:07:13 +0530656 if enable_cleanup:
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200657 self.addCleanup(self._stack_delete, name)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000658
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500659 stack = self.client.stacks.get(name, resolve_outputs=False)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000660 stack_identifier = '%s/%s' % (name, stack.id)
Rakesh H Sa3325d62015-04-04 19:42:29 +0530661 kwargs = {'stack_identifier': stack_identifier,
662 'status': expected_status}
Steve Bakerf6c8f122015-02-10 13:54:46 +1300663 if expected_status:
Rakesh H Sa3325d62015-04-04 19:42:29 +0530664 if expected_status in ['ROLLBACK_COMPLETE']:
665 # To trigger rollback you would intentionally fail the stack
666 # Hence check for rollback failures
667 kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
668 self._wait_for_stack_status(**kwargs)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000669 return stack_identifier
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000670
671 def stack_adopt(self, stack_name=None, files=None,
672 parameters=None, environment=None, adopt_data=None,
673 wait_for_status='ADOPT_COMPLETE'):
Rabi Mishra477efc92015-07-31 13:01:45 +0530674 if (self.conf.skip_test_stack_action_list and
675 'ADOPT' in self.conf.skip_test_stack_action_list):
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530676 self.skipTest('Testing Stack adopt disabled in conf, skipping')
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000677 name = stack_name or self._stack_rand_name()
678 templ_files = files or {}
679 params = parameters or {}
680 env = environment or {}
681 self.client.stacks.create(
682 stack_name=name,
683 files=templ_files,
684 disable_rollback=True,
685 parameters=params,
686 environment=env,
687 adopt_stack_data=adopt_data,
688 )
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200689 self.addCleanup(self._stack_delete, name)
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500690 stack = self.client.stacks.get(name, resolve_outputs=False)
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000691 stack_identifier = '%s/%s' % (name, stack.id)
692 self._wait_for_stack_status(stack_identifier, wait_for_status)
693 return stack_identifier
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530694
695 def stack_abandon(self, stack_id):
Rabi Mishra477efc92015-07-31 13:01:45 +0530696 if (self.conf.skip_test_stack_action_list and
697 'ABANDON' in self.conf.skip_test_stack_action_list):
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200698 self.addCleanup(self._stack_delete, stack_id)
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530699 self.skipTest('Testing Stack abandon disabled in conf, skipping')
700 info = self.client.stacks.abandon(stack_id=stack_id)
701 return info
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500702
rabi90b3ab42017-05-04 13:02:28 +0530703 def stack_snapshot(self, stack_id,
704 wait_for_status='SNAPSHOT_COMPLETE'):
705 snapshot = self.client.stacks.snapshot(stack_id=stack_id)
706 self._wait_for_stack_status(stack_id, wait_for_status)
707 return snapshot['id']
708
709 def stack_restore(self, stack_id, snapshot_id,
710 wait_for_status='RESTORE_COMPLETE'):
711 self.client.stacks.restore(stack_id, snapshot_id)
712 self._wait_for_stack_status(stack_id, wait_for_status)
713
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500714 def stack_suspend(self, stack_identifier):
Rabi Mishra477efc92015-07-31 13:01:45 +0530715 if (self.conf.skip_test_stack_action_list and
716 'SUSPEND' in self.conf.skip_test_stack_action_list):
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200717 self.addCleanup(self._stack_delete, stack_identifier)
Rabi Mishra477efc92015-07-31 13:01:45 +0530718 self.skipTest('Testing Stack suspend disabled in conf, skipping')
rabif7d67082016-05-17 18:51:22 +0530719 self._handle_in_progress(self.client.actions.suspend, stack_identifier)
Angus Salkelda7500d12015-04-10 15:44:07 +1000720 # improve debugging by first checking the resource's state.
721 self._wait_for_all_resource_status(stack_identifier,
722 'SUSPEND_COMPLETE')
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500723 self._wait_for_stack_status(stack_identifier, 'SUSPEND_COMPLETE')
724
725 def stack_resume(self, stack_identifier):
Rabi Mishra477efc92015-07-31 13:01:45 +0530726 if (self.conf.skip_test_stack_action_list and
727 'RESUME' in self.conf.skip_test_stack_action_list):
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200728 self.addCleanup(self._stack_delete, stack_identifier)
Rabi Mishra477efc92015-07-31 13:01:45 +0530729 self.skipTest('Testing Stack resume disabled in conf, skipping')
rabif7d67082016-05-17 18:51:22 +0530730 self._handle_in_progress(self.client.actions.resume, stack_identifier)
Angus Salkelda7500d12015-04-10 15:44:07 +1000731 # improve debugging by first checking the resource's state.
732 self._wait_for_all_resource_status(stack_identifier,
733 'RESUME_COMPLETE')
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500734 self._wait_for_stack_status(stack_identifier, 'RESUME_COMPLETE')
Steven Hardy03da0742015-03-19 00:13:17 -0400735
736 def wait_for_event_with_reason(self, stack_identifier, reason,
737 rsrc_name=None, num_expected=1):
738 build_timeout = self.conf.build_timeout
739 build_interval = self.conf.build_interval
740 start = timeutils.utcnow()
741 while timeutils.delta_seconds(start,
742 timeutils.utcnow()) < build_timeout:
743 try:
744 rsrc_events = self.client.events.list(stack_identifier,
745 resource_name=rsrc_name)
746 except heat_exceptions.HTTPNotFound:
liyi09461f72017-03-21 12:17:51 +0800747 LOG.debug("No events yet found for %s", rsrc_name)
Steven Hardy03da0742015-03-19 00:13:17 -0400748 else:
749 matched = [e for e in rsrc_events
750 if e.resource_status_reason == reason]
751 if len(matched) == num_expected:
752 return matched
753 time.sleep(build_interval)
Rakesh H Sc5735a82016-04-28 15:38:09 +0530754
Thomas Hervea6afca82017-04-10 23:44:26 +0200755 def check_autoscale_complete(self, stack_id, expected_num, parent_stack,
rabi55c0f752018-02-07 09:21:28 +0530756 group_name):
Rakesh H Sc5735a82016-04-28 15:38:09 +0530757 res_list = self.client.resources.list(stack_id)
758 all_res_complete = all(res.resource_status in ('UPDATE_COMPLETE',
759 'CREATE_COMPLETE')
760 for res in res_list)
761 all_res = len(res_list) == expected_num
Thomas Hervea6afca82017-04-10 23:44:26 +0200762 if all_res and all_res_complete:
rabi55c0f752018-02-07 09:21:28 +0530763 metadata = self.client.resources.metadata(parent_stack, group_name)
Thomas Hervea6afca82017-04-10 23:44:26 +0200764 return not metadata.get('scaling_in_progress')
765 return False