blob: b9bb0362c5b419a81fe185ad9096c9147c637c66 [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
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
Steve Baker450aa7f2014-08-25 10:37:27 +120029from heat_integrationtests.common import clients
30from heat_integrationtests.common import config
31from heat_integrationtests.common import exceptions
32from heat_integrationtests.common import remote_client
33
34LOG = logging.getLogger(__name__)
Angus Salkeld24043702014-11-21 08:49:26 +100035_LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s"
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
62def rand_name(name=''):
63 randbits = str(random.randint(1, 0x7fffffff))
64 if name:
65 return name + '-' + randbits
66 else:
67 return randbits
68
69
Angus Salkeld95f65a22014-11-24 12:38:30 +100070class HeatIntegrationTest(testscenarios.WithScenarios,
71 testtools.TestCase):
Steve Baker450aa7f2014-08-25 10:37:27 +120072
73 def setUp(self):
74 super(HeatIntegrationTest, self).setUp()
75
Steve Bakerae90e132016-08-13 09:53:07 +120076 self.conf = config.CONF.heat_plugin
Steve Baker450aa7f2014-08-25 10:37:27 +120077
78 self.assertIsNotNone(self.conf.auth_url,
79 'No auth_url configured')
80 self.assertIsNotNone(self.conf.username,
81 'No username configured')
82 self.assertIsNotNone(self.conf.password,
83 'No password configured')
rabifd98a472016-05-24 10:18:33 +053084 self.setup_clients(self.conf)
85 self.useFixture(fixtures.FakeLogger(format=_LOG_FORMAT))
86 self.updated_time = {}
87 if self.conf.disable_ssl_certificate_validation:
88 self.verify_cert = False
89 else:
90 self.verify_cert = self.conf.ca_file or True
Steve Baker450aa7f2014-08-25 10:37:27 +120091
Steve Bakerb752e912016-08-01 22:05:37 +000092 def setup_clients(self, conf, admin_credentials=False):
93 self.manager = clients.ClientManager(conf, admin_credentials)
Steve Baker450aa7f2014-08-25 10:37:27 +120094 self.identity_client = self.manager.identity_client
95 self.orchestration_client = self.manager.orchestration_client
96 self.compute_client = self.manager.compute_client
97 self.network_client = self.manager.network_client
98 self.volume_client = self.manager.volume_client
Angus Salkeld4408da32015-02-03 18:53:30 +100099 self.object_client = self.manager.object_client
Angus Salkeld406bbd52015-05-13 14:24:04 +1000100 self.metering_client = self.manager.metering_client
rabifd98a472016-05-24 10:18:33 +0530101
102 self.client = self.orchestration_client
103
104 def setup_clients_for_admin(self):
Steve Bakerb752e912016-08-01 22:05:37 +0000105 self.setup_clients(self.conf, True)
Steve Baker450aa7f2014-08-25 10:37:27 +1200106
Steve Baker450aa7f2014-08-25 10:37:27 +1200107 def get_remote_client(self, server_or_ip, username, private_key=None):
108 if isinstance(server_or_ip, six.string_types):
109 ip = server_or_ip
110 else:
111 network_name_for_ssh = self.conf.network_for_ssh
112 ip = server_or_ip.networks[network_name_for_ssh][0]
113 if private_key is None:
114 private_key = self.keypair.private_key
115 linux_client = remote_client.RemoteClient(ip, username,
116 pkey=private_key,
117 conf=self.conf)
118 try:
119 linux_client.validate_authentication()
120 except exceptions.SSHTimeout:
121 LOG.exception('ssh connection to %s failed' % ip)
122 raise
123
124 return linux_client
125
Anastasia Kuznetsova3e0ab4d2015-03-06 18:10:13 +0400126 def check_connectivity(self, check_ip):
127 def try_connect(ip):
128 try:
Sirushti Murugesan4920fda2015-04-22 00:35:26 +0530129 urllib.request.urlopen('http://%s/' % ip)
Anastasia Kuznetsova3e0ab4d2015-03-06 18:10:13 +0400130 return True
131 except IOError:
132 return False
133
134 timeout = self.conf.connectivity_timeout
135 elapsed_time = 0
136 while not try_connect(check_ip):
137 time.sleep(10)
138 elapsed_time += 10
139 if elapsed_time > timeout:
140 raise exceptions.TimeoutException()
141
Steve Baker450aa7f2014-08-25 10:37:27 +1200142 def _log_console_output(self, servers=None):
143 if not servers:
144 servers = self.compute_client.servers.list()
145 for server in servers:
Steve Baker24641292015-03-13 10:47:50 +1300146 LOG.info('Console output for %s', server.id)
147 LOG.info(server.get_console_output())
Steve Baker450aa7f2014-08-25 10:37:27 +1200148
Sergey Kraynevd6fa5c02015-02-13 03:03:55 -0500149 def _load_template(self, base_file, file_name, sub_dir=None):
150 sub_dir = sub_dir or ''
Steve Baker450aa7f2014-08-25 10:37:27 +1200151 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
Sergey Kraynevd6fa5c02015-02-13 03:03:55 -0500152 sub_dir, file_name)
Steve Baker450aa7f2014-08-25 10:37:27 +1200153 with open(filepath) as f:
154 return f.read()
155
156 def create_keypair(self, client=None, name=None):
157 if client is None:
158 client = self.compute_client
159 if name is None:
160 name = rand_name('heat-keypair')
161 keypair = client.keypairs.create(name)
162 self.assertEqual(keypair.name, name)
163
164 def delete_keypair():
165 keypair.delete()
166
167 self.addCleanup(delete_keypair)
168 return keypair
169
Sergey Krayneva265c132015-02-13 03:51:03 -0500170 def assign_keypair(self):
171 if self.conf.keypair_name:
172 self.keypair = None
173 self.keypair_name = self.conf.keypair_name
174 else:
175 self.keypair = self.create_keypair()
176 self.keypair_name = self.keypair.id
177
Steve Baker450aa7f2014-08-25 10:37:27 +1200178 @classmethod
179 def _stack_rand_name(cls):
180 return rand_name(cls.__name__)
181
Anastasia Kuznetsova673fc432015-03-12 16:41:36 +0400182 def _get_network(self, net_name=None):
183 if net_name is None:
184 net_name = self.conf.fixed_network_name
Steve Baker450aa7f2014-08-25 10:37:27 +1200185 networks = self.network_client.list_networks()
186 for net in networks['networks']:
Anastasia Kuznetsova673fc432015-03-12 16:41:36 +0400187 if net['name'] == net_name:
Steve Baker450aa7f2014-08-25 10:37:27 +1200188 return net
189
Mark Vanderwiel6d8e0862015-10-15 12:51:07 -0500190 def is_network_extension_supported(self, extension_alias):
191 try:
192 self.network_client.show_extension(extension_alias)
193 except network_exceptions.NeutronClientException:
194 return False
195 return True
196
Steve Baker450aa7f2014-08-25 10:37:27 +1200197 @staticmethod
Angus Salkelda89a0282015-07-24 15:47:38 +1000198 def _stack_output(stack, output_key, validate_errors=True):
Steve Baker450aa7f2014-08-25 10:37:27 +1200199 """Return a stack output value for a given key."""
Angus Salkelda89a0282015-07-24 15:47:38 +1000200 value = None
201 for o in stack.outputs:
202 if validate_errors and 'output_error' in o:
203 # scan for errors in the stack output.
204 raise ValueError(
205 'Unexpected output errors in %s : %s' % (
206 output_key, o['output_error']))
207 if o['output_key'] == output_key:
208 value = o['output_value']
209 return value
Steve Baker450aa7f2014-08-25 10:37:27 +1200210
211 def _ping_ip_address(self, ip_address, should_succeed=True):
212 cmd = ['ping', '-c1', '-w1', ip_address]
213
214 def ping():
215 proc = subprocess.Popen(cmd,
216 stdout=subprocess.PIPE,
217 stderr=subprocess.PIPE)
218 proc.wait()
219 return (proc.returncode == 0) == should_succeed
220
221 return call_until_true(
Angus Salkeld08514ad2015-02-06 10:08:31 +1000222 self.conf.build_timeout, 1, ping)
Steve Baker450aa7f2014-08-25 10:37:27 +1200223
Angus Salkelda7500d12015-04-10 15:44:07 +1000224 def _wait_for_all_resource_status(self, stack_identifier,
225 status, failure_pattern='^.*_FAILED$',
226 success_on_not_found=False):
227 for res in self.client.resources.list(stack_identifier):
228 self._wait_for_resource_status(
229 stack_identifier, res.resource_name,
230 status, failure_pattern=failure_pattern,
231 success_on_not_found=success_on_not_found)
232
Steve Baker450aa7f2014-08-25 10:37:27 +1200233 def _wait_for_resource_status(self, stack_identifier, resource_name,
234 status, failure_pattern='^.*_FAILED$',
235 success_on_not_found=False):
236 """Waits for a Resource to reach a given status."""
237 fail_regexp = re.compile(failure_pattern)
238 build_timeout = self.conf.build_timeout
239 build_interval = self.conf.build_interval
240
241 start = timeutils.utcnow()
242 while timeutils.delta_seconds(start,
243 timeutils.utcnow()) < build_timeout:
244 try:
245 res = self.client.resources.get(
246 stack_identifier, resource_name)
247 except heat_exceptions.HTTPNotFound:
248 if success_on_not_found:
249 return
250 # ignore this, as the resource may not have
251 # been created yet
252 else:
253 if res.resource_status == status:
254 return
Sirushti Murugesane5ad25b2015-04-18 23:30:59 +0530255 wait_for_action = status.split('_')[0]
256 resource_action = res.resource_status.split('_')[0]
257 if (resource_action == wait_for_action and
258 fail_regexp.search(res.resource_status)):
Steve Baker450aa7f2014-08-25 10:37:27 +1200259 raise exceptions.StackResourceBuildErrorException(
260 resource_name=res.resource_name,
261 stack_identifier=stack_identifier,
262 resource_status=res.resource_status,
263 resource_status_reason=res.resource_status_reason)
264 time.sleep(build_interval)
265
266 message = ('Resource %s failed to reach %s status within '
267 'the required time (%s s).' %
Anastasia Kuznetsova9a745572015-03-04 13:19:15 +0400268 (resource_name, status, build_timeout))
Steve Baker450aa7f2014-08-25 10:37:27 +1200269 raise exceptions.TimeoutException(message)
270
Rabi Mishra87be9b42016-02-15 14:15:50 +0530271 def verify_resource_status(self, stack_identifier, resource_name,
272 status='CREATE_COMPLETE'):
273 try:
274 res = self.client.resources.get(stack_identifier, resource_name)
275 except heat_exceptions.HTTPNotFound:
276 return False
277 return res.resource_status == status
278
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530279 def _verify_status(self, stack, stack_identifier, status, fail_regexp):
280 if stack.stack_status == status:
Sergey Kraynev89082a32015-09-04 04:42:33 -0400281 # Handle UPDATE_COMPLETE/FAILED case: Make sure we don't
282 # wait for a stale UPDATE_COMPLETE/FAILED status.
283 if status in ('UPDATE_FAILED', 'UPDATE_COMPLETE'):
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530284 if self.updated_time.get(
285 stack_identifier) != stack.updated_time:
286 self.updated_time[stack_identifier] = stack.updated_time
287 return True
288 else:
289 return True
290
Sirushti Murugesane5ad25b2015-04-18 23:30:59 +0530291 wait_for_action = status.split('_')[0]
292 if (stack.action == wait_for_action and
293 fail_regexp.search(stack.stack_status)):
Sergey Kraynev89082a32015-09-04 04:42:33 -0400294 # Handle UPDATE_COMPLETE/UPDATE_FAILED case.
295 if status in ('UPDATE_FAILED', 'UPDATE_COMPLETE'):
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530296 if self.updated_time.get(
297 stack_identifier) != stack.updated_time:
298 self.updated_time[stack_identifier] = stack.updated_time
299 raise exceptions.StackBuildErrorException(
300 stack_identifier=stack_identifier,
301 stack_status=stack.stack_status,
302 stack_status_reason=stack.stack_status_reason)
303 else:
304 raise exceptions.StackBuildErrorException(
305 stack_identifier=stack_identifier,
306 stack_status=stack.stack_status,
307 stack_status_reason=stack.stack_status_reason)
308
Steve Baker450aa7f2014-08-25 10:37:27 +1200309 def _wait_for_stack_status(self, stack_identifier, status,
Sergey Kraynev89082a32015-09-04 04:42:33 -0400310 failure_pattern=None,
Rabi Mishra81ca6bc2016-06-16 15:09:20 +0530311 success_on_not_found=False,
312 signal_required=False,
313 resources_to_signal=None):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +0300314 """Waits for a Stack to reach a given status.
Steve Baker450aa7f2014-08-25 10:37:27 +1200315
316 Note this compares the full $action_$status, e.g
317 CREATE_COMPLETE, not just COMPLETE which is exposed
318 via the status property of Stack in heatclient
319 """
Sergey Kraynev89082a32015-09-04 04:42:33 -0400320 if failure_pattern:
321 fail_regexp = re.compile(failure_pattern)
322 elif 'FAILED' in status:
323 # If we're looking for e.g CREATE_FAILED, COMPLETE is unexpected.
324 fail_regexp = re.compile('^.*_COMPLETE$')
325 else:
326 fail_regexp = re.compile('^.*_FAILED$')
Steve Baker450aa7f2014-08-25 10:37:27 +1200327 build_timeout = self.conf.build_timeout
328 build_interval = self.conf.build_interval
329
330 start = timeutils.utcnow()
331 while timeutils.delta_seconds(start,
332 timeutils.utcnow()) < build_timeout:
333 try:
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500334 stack = self.client.stacks.get(stack_identifier,
335 resolve_outputs=False)
Steve Baker450aa7f2014-08-25 10:37:27 +1200336 except heat_exceptions.HTTPNotFound:
337 if success_on_not_found:
338 return
339 # ignore this, as the resource may not have
340 # been created yet
341 else:
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530342 if self._verify_status(stack, stack_identifier, status,
343 fail_regexp):
Steve Baker450aa7f2014-08-25 10:37:27 +1200344 return
Rabi Mishra81ca6bc2016-06-16 15:09:20 +0530345 if signal_required:
346 self.signal_resources(resources_to_signal)
Steve Baker450aa7f2014-08-25 10:37:27 +1200347 time.sleep(build_interval)
348
349 message = ('Stack %s failed to reach %s status within '
350 'the required time (%s s).' %
Anastasia Kuznetsova9a745572015-03-04 13:19:15 +0400351 (stack_identifier, status, build_timeout))
Steve Baker450aa7f2014-08-25 10:37:27 +1200352 raise exceptions.TimeoutException(message)
353
354 def _stack_delete(self, stack_identifier):
355 try:
Thomas Herve3eab2942015-10-22 17:29:21 +0200356 self._handle_in_progress(self.client.stacks.delete,
357 stack_identifier)
Steve Baker450aa7f2014-08-25 10:37:27 +1200358 except heat_exceptions.HTTPNotFound:
359 pass
360 self._wait_for_stack_status(
361 stack_identifier, 'DELETE_COMPLETE',
362 success_on_not_found=True)
Steven Hardyc9efd972014-11-20 11:31:55 +0000363
Thomas Herve3eab2942015-10-22 17:29:21 +0200364 def _handle_in_progress(self, fn, *args, **kwargs):
365 build_timeout = self.conf.build_timeout
366 build_interval = self.conf.build_interval
367 start = timeutils.utcnow()
368 while timeutils.delta_seconds(start,
369 timeutils.utcnow()) < build_timeout:
370 try:
371 fn(*args, **kwargs)
372 except heat_exceptions.HTTPConflict as ex:
373 # FIXME(sirushtim): Wait a little for the stack lock to be
374 # released and hopefully, the stack should be usable again.
375 if ex.error['error']['type'] != 'ActionInProgress':
376 raise ex
377
378 time.sleep(build_interval)
379 else:
380 break
381
Steven Hardy23284b62015-10-01 19:03:42 +0100382 def update_stack(self, stack_identifier, template=None, environment=None,
Sabeen Syed277ea692015-02-04 23:30:02 +0000383 files=None, parameters=None, tags=None,
Rakesh H Sa3325d62015-04-04 19:42:29 +0530384 expected_status='UPDATE_COMPLETE',
Steven Hardy23284b62015-10-01 19:03:42 +0100385 disable_rollback=True,
386 existing=False):
Steven Hardyc9efd972014-11-20 11:31:55 +0000387 env = environment or {}
388 env_files = files or {}
Sergey Kraynevbcc78df2015-02-27 04:34:32 -0500389 parameters = parameters or {}
Sirushti Murugesan3a195a52015-05-01 09:47:09 +0530390
Sergey Kraynev89082a32015-09-04 04:42:33 -0400391 self.updated_time[stack_identifier] = self.client.stacks.get(
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500392 stack_identifier, resolve_outputs=False).updated_time
Sirushti Murugesan3a195a52015-05-01 09:47:09 +0530393
Thomas Herve3eab2942015-10-22 17:29:21 +0200394 self._handle_in_progress(
395 self.client.stacks.update,
396 stack_id=stack_identifier,
Thomas Herve3eab2942015-10-22 17:29:21 +0200397 template=template,
398 files=env_files,
399 disable_rollback=disable_rollback,
400 parameters=parameters,
401 environment=env,
402 tags=tags,
403 existing=existing)
Sirushti Murugesan3a195a52015-05-01 09:47:09 +0530404
Rakesh H Sa3325d62015-04-04 19:42:29 +0530405 kwargs = {'stack_identifier': stack_identifier,
406 'status': expected_status}
407 if expected_status in ['ROLLBACK_COMPLETE']:
Rakesh H Sa3325d62015-04-04 19:42:29 +0530408 # To trigger rollback you would intentionally fail the stack
409 # Hence check for rollback failures
410 kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
411
412 self._wait_for_stack_status(**kwargs)
Steven Hardyc9efd972014-11-20 11:31:55 +0000413
Oleksii Chuprykovfc2c58f2016-04-29 17:03:17 +0300414 def cancel_update_stack(self, stack_identifier,
415 expected_status='ROLLBACK_COMPLETE'):
416
417 stack_name = stack_identifier.split('/')[0]
418
419 self.updated_time[stack_identifier] = self.client.stacks.get(
420 stack_identifier, resolve_outputs=False).updated_time
421
422 self.client.actions.cancel_update(stack_name)
423
424 kwargs = {'stack_identifier': stack_identifier,
425 'status': expected_status}
426 if expected_status in ['ROLLBACK_COMPLETE']:
427 # To trigger rollback you would intentionally fail the stack
428 # Hence check for rollback failures
429 kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
430
431 self._wait_for_stack_status(**kwargs)
432
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500433 def preview_update_stack(self, stack_identifier, template,
434 environment=None, files=None, parameters=None,
Steven Hardye6de2d62015-12-07 15:59:09 +0000435 tags=None, disable_rollback=True,
436 show_nested=False):
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500437 env = environment or {}
438 env_files = files or {}
439 parameters = parameters or {}
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500440
441 return self.client.stacks.preview_update(
442 stack_id=stack_identifier,
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500443 template=template,
444 files=env_files,
445 disable_rollback=disable_rollback,
446 parameters=parameters,
447 environment=env,
Steven Hardye6de2d62015-12-07 15:59:09 +0000448 tags=tags,
449 show_nested=show_nested
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500450 )
451
Steven Hardy03da0742015-03-19 00:13:17 -0400452 def assert_resource_is_a_stack(self, stack_identifier, res_name,
453 wait=False):
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 time.sleep(build_interval)
460 try:
461 nested_identifier = self._get_nested_identifier(
462 stack_identifier, res_name)
463 except Exception:
464 # We may have to wait, if the create is in-progress
465 if wait:
466 time.sleep(build_interval)
467 else:
468 raise
469 else:
470 return nested_identifier
471
472 def _get_nested_identifier(self, stack_identifier, res_name):
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000473 rsrc = self.client.resources.get(stack_identifier, res_name)
474 nested_link = [l for l in rsrc.links if l['rel'] == 'nested']
475 nested_href = nested_link[0]['href']
476 nested_id = nested_href.split('/')[-1]
477 nested_identifier = '/'.join(nested_href.split('/')[-2:])
478 self.assertEqual(rsrc.physical_resource_id, nested_id)
479
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500480 nested_stack = self.client.stacks.get(nested_id, resolve_outputs=False)
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000481 nested_identifier2 = '%s/%s' % (nested_stack.stack_name,
482 nested_stack.id)
483 self.assertEqual(nested_identifier, nested_identifier2)
484 parent_id = stack_identifier.split("/")[-1]
485 self.assertEqual(parent_id, nested_stack.parent)
486 return nested_identifier
487
Rabi Mishra8bcff8a2015-09-21 18:15:04 +0530488 def group_nested_identifier(self, stack_identifier,
489 group_name):
490 # Get the nested stack identifier from a group resource
491 rsrc = self.client.resources.get(stack_identifier, group_name)
492 physical_resource_id = rsrc.physical_resource_id
493
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500494 nested_stack = self.client.stacks.get(physical_resource_id,
495 resolve_outputs=False)
Rabi Mishra8bcff8a2015-09-21 18:15:04 +0530496 nested_identifier = '%s/%s' % (nested_stack.stack_name,
497 nested_stack.id)
498 parent_id = stack_identifier.split("/")[-1]
499 self.assertEqual(parent_id, nested_stack.parent)
500 return nested_identifier
501
502 def list_group_resources(self, stack_identifier,
503 group_name, minimal=True):
504 nested_identifier = self.group_nested_identifier(stack_identifier,
505 group_name)
506 if minimal:
507 return self.list_resources(nested_identifier)
508 return self.client.resources.list(nested_identifier)
509
Steven Hardyc9efd972014-11-20 11:31:55 +0000510 def list_resources(self, stack_identifier):
511 resources = self.client.resources.list(stack_identifier)
512 return dict((r.resource_name, r.resource_type) for r in resources)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000513
Steven Hardyd448dae2016-06-14 14:57:28 +0100514 def get_resource_stack_id(self, r):
515 stack_link = [l for l in r.links if l.get('rel') == 'stack'][0]
516 return stack_link['href'].split("/")[-1]
517
Rabi Mishra81ca6bc2016-06-16 15:09:20 +0530518 def check_input_values(self, group_resources, key, value):
519 # Check inputs for deployment and derived config
520 for r in group_resources:
521 d = self.client.software_deployments.get(
522 r.physical_resource_id)
523 self.assertEqual({key: value}, d.input_values)
524 c = self.client.software_configs.get(
525 d.config_id)
526 foo_input_c = [i for i in c.inputs if i.get('name') == key][0]
527 self.assertEqual(value, foo_input_c.get('value'))
528
529 def signal_resources(self, resources):
530 # Signal all IN_PROGRESS resources
531 for r in resources:
532 if 'IN_PROGRESS' in r.resource_status:
533 stack_id = self.get_resource_stack_id(r)
534 self.client.resources.signal(stack_id, r.resource_name)
535
Steven Hardyf2c82c02014-11-20 14:02:17 +0000536 def stack_create(self, stack_name=None, template=None, files=None,
Sabeen Syed277ea692015-02-04 23:30:02 +0000537 parameters=None, environment=None, tags=None,
538 expected_status='CREATE_COMPLETE',
Jay Dobies39c4ce42015-11-04 10:49:08 -0500539 disable_rollback=True, enable_cleanup=True,
540 environment_files=None):
Steven Hardyf2c82c02014-11-20 14:02:17 +0000541 name = stack_name or self._stack_rand_name()
542 templ = template or self.template
543 templ_files = files or {}
544 params = parameters or {}
545 env = environment or {}
546 self.client.stacks.create(
547 stack_name=name,
548 template=templ,
549 files=templ_files,
Rakesh H Sa3325d62015-04-04 19:42:29 +0530550 disable_rollback=disable_rollback,
Steven Hardyf2c82c02014-11-20 14:02:17 +0000551 parameters=params,
Sabeen Syed277ea692015-02-04 23:30:02 +0000552 environment=env,
Jay Dobies39c4ce42015-11-04 10:49:08 -0500553 tags=tags,
554 environment_files=environment_files
Steven Hardyf2c82c02014-11-20 14:02:17 +0000555 )
Sergey Kraynevbf67ce32015-04-17 10:54:20 -0400556 if expected_status not in ['ROLLBACK_COMPLETE'] and enable_cleanup:
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200557 self.addCleanup(self._stack_delete, name)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000558
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500559 stack = self.client.stacks.get(name, resolve_outputs=False)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000560 stack_identifier = '%s/%s' % (name, stack.id)
Rakesh H Sa3325d62015-04-04 19:42:29 +0530561 kwargs = {'stack_identifier': stack_identifier,
562 'status': expected_status}
Steve Bakerf6c8f122015-02-10 13:54:46 +1300563 if expected_status:
Rakesh H Sa3325d62015-04-04 19:42:29 +0530564 if expected_status in ['ROLLBACK_COMPLETE']:
565 # To trigger rollback you would intentionally fail the stack
566 # Hence check for rollback failures
567 kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
568 self._wait_for_stack_status(**kwargs)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000569 return stack_identifier
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000570
571 def stack_adopt(self, stack_name=None, files=None,
572 parameters=None, environment=None, adopt_data=None,
573 wait_for_status='ADOPT_COMPLETE'):
Rabi Mishra477efc92015-07-31 13:01:45 +0530574 if (self.conf.skip_test_stack_action_list and
575 'ADOPT' in self.conf.skip_test_stack_action_list):
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530576 self.skipTest('Testing Stack adopt disabled in conf, skipping')
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000577 name = stack_name or self._stack_rand_name()
578 templ_files = files or {}
579 params = parameters or {}
580 env = environment or {}
581 self.client.stacks.create(
582 stack_name=name,
583 files=templ_files,
584 disable_rollback=True,
585 parameters=params,
586 environment=env,
587 adopt_stack_data=adopt_data,
588 )
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200589 self.addCleanup(self._stack_delete, name)
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500590 stack = self.client.stacks.get(name, resolve_outputs=False)
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000591 stack_identifier = '%s/%s' % (name, stack.id)
592 self._wait_for_stack_status(stack_identifier, wait_for_status)
593 return stack_identifier
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530594
595 def stack_abandon(self, stack_id):
Rabi Mishra477efc92015-07-31 13:01:45 +0530596 if (self.conf.skip_test_stack_action_list and
597 'ABANDON' in self.conf.skip_test_stack_action_list):
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200598 self.addCleanup(self._stack_delete, stack_id)
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530599 self.skipTest('Testing Stack abandon disabled in conf, skipping')
600 info = self.client.stacks.abandon(stack_id=stack_id)
601 return info
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500602
603 def stack_suspend(self, stack_identifier):
Rabi Mishra477efc92015-07-31 13:01:45 +0530604 if (self.conf.skip_test_stack_action_list and
605 'SUSPEND' in self.conf.skip_test_stack_action_list):
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200606 self.addCleanup(self._stack_delete, stack_identifier)
Rabi Mishra477efc92015-07-31 13:01:45 +0530607 self.skipTest('Testing Stack suspend disabled in conf, skipping')
rabif7d67082016-05-17 18:51:22 +0530608 self._handle_in_progress(self.client.actions.suspend, stack_identifier)
Angus Salkelda7500d12015-04-10 15:44:07 +1000609 # improve debugging by first checking the resource's state.
610 self._wait_for_all_resource_status(stack_identifier,
611 'SUSPEND_COMPLETE')
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500612 self._wait_for_stack_status(stack_identifier, 'SUSPEND_COMPLETE')
613
614 def stack_resume(self, stack_identifier):
Rabi Mishra477efc92015-07-31 13:01:45 +0530615 if (self.conf.skip_test_stack_action_list and
616 'RESUME' in self.conf.skip_test_stack_action_list):
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200617 self.addCleanup(self._stack_delete, stack_identifier)
Rabi Mishra477efc92015-07-31 13:01:45 +0530618 self.skipTest('Testing Stack resume disabled in conf, skipping')
rabif7d67082016-05-17 18:51:22 +0530619 self._handle_in_progress(self.client.actions.resume, stack_identifier)
Angus Salkelda7500d12015-04-10 15:44:07 +1000620 # improve debugging by first checking the resource's state.
621 self._wait_for_all_resource_status(stack_identifier,
622 'RESUME_COMPLETE')
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500623 self._wait_for_stack_status(stack_identifier, 'RESUME_COMPLETE')
Steven Hardy03da0742015-03-19 00:13:17 -0400624
625 def wait_for_event_with_reason(self, stack_identifier, reason,
626 rsrc_name=None, num_expected=1):
627 build_timeout = self.conf.build_timeout
628 build_interval = self.conf.build_interval
629 start = timeutils.utcnow()
630 while timeutils.delta_seconds(start,
631 timeutils.utcnow()) < build_timeout:
632 try:
633 rsrc_events = self.client.events.list(stack_identifier,
634 resource_name=rsrc_name)
635 except heat_exceptions.HTTPNotFound:
636 LOG.debug("No events yet found for %s" % rsrc_name)
637 else:
638 matched = [e for e in rsrc_events
639 if e.resource_status_reason == reason]
640 if len(matched) == num_expected:
641 return matched
642 time.sleep(build_interval)
Rakesh H Sc5735a82016-04-28 15:38:09 +0530643
644 def check_autoscale_complete(self, stack_id, expected_num):
645 res_list = self.client.resources.list(stack_id)
646 all_res_complete = all(res.resource_status in ('UPDATE_COMPLETE',
647 'CREATE_COMPLETE')
648 for res in res_list)
649 all_res = len(res_list) == expected_num
650 return all_res and all_res_complete