blob: 9944bd16d0c9a25d0327a88aad01ef3bf13e009f [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
Steve Baker450aa7f2014-08-25 10:37:27 +120030from heat_integrationtests.common import clients
31from heat_integrationtests.common import config
32from heat_integrationtests.common import exceptions
33from heat_integrationtests.common import remote_client
34
35LOG = logging.getLogger(__name__)
Angus Salkeld24043702014-11-21 08:49:26 +100036_LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s"
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
63def rand_name(name=''):
ricolina4eb53d2017-04-24 23:51:09 +080064 randbits = six.text_type(random.randint(1, 0x7fffffff))
Steve Baker450aa7f2014-08-25 10:37:27 +120065 if name:
66 return name + '-' + randbits
67 else:
68 return randbits
69
70
Angus Salkeld95f65a22014-11-24 12:38:30 +100071class HeatIntegrationTest(testscenarios.WithScenarios,
72 testtools.TestCase):
Steve Baker450aa7f2014-08-25 10:37:27 +120073
74 def setUp(self):
75 super(HeatIntegrationTest, self).setUp()
76
Steve Bakerae90e132016-08-13 09:53:07 +120077 self.conf = config.CONF.heat_plugin
Steve Baker450aa7f2014-08-25 10:37:27 +120078
79 self.assertIsNotNone(self.conf.auth_url,
80 'No auth_url configured')
81 self.assertIsNotNone(self.conf.username,
82 'No username configured')
83 self.assertIsNotNone(self.conf.password,
84 'No password configured')
rabifd98a472016-05-24 10:18:33 +053085 self.setup_clients(self.conf)
86 self.useFixture(fixtures.FakeLogger(format=_LOG_FORMAT))
87 self.updated_time = {}
88 if self.conf.disable_ssl_certificate_validation:
89 self.verify_cert = False
90 else:
91 self.verify_cert = self.conf.ca_file or True
Steve Baker450aa7f2014-08-25 10:37:27 +120092
Steve Bakerb752e912016-08-01 22:05:37 +000093 def setup_clients(self, conf, admin_credentials=False):
94 self.manager = clients.ClientManager(conf, admin_credentials)
Steve Baker450aa7f2014-08-25 10:37:27 +120095 self.identity_client = self.manager.identity_client
96 self.orchestration_client = self.manager.orchestration_client
97 self.compute_client = self.manager.compute_client
98 self.network_client = self.manager.network_client
99 self.volume_client = self.manager.volume_client
Angus Salkeld4408da32015-02-03 18:53:30 +1000100 self.object_client = self.manager.object_client
Angus Salkeld406bbd52015-05-13 14:24:04 +1000101 self.metering_client = self.manager.metering_client
rabifd98a472016-05-24 10:18:33 +0530102
103 self.client = self.orchestration_client
104
105 def setup_clients_for_admin(self):
Steve Bakerb752e912016-08-01 22:05:37 +0000106 self.setup_clients(self.conf, True)
Steve Baker450aa7f2014-08-25 10:37:27 +1200107
Steve Baker450aa7f2014-08-25 10:37:27 +1200108 def get_remote_client(self, server_or_ip, username, private_key=None):
109 if isinstance(server_or_ip, six.string_types):
110 ip = server_or_ip
111 else:
112 network_name_for_ssh = self.conf.network_for_ssh
113 ip = server_or_ip.networks[network_name_for_ssh][0]
114 if private_key is None:
115 private_key = self.keypair.private_key
116 linux_client = remote_client.RemoteClient(ip, username,
117 pkey=private_key,
118 conf=self.conf)
119 try:
120 linux_client.validate_authentication()
121 except exceptions.SSHTimeout:
liyi09461f72017-03-21 12:17:51 +0800122 LOG.exception('ssh connection to %s failed', ip)
Steve Baker450aa7f2014-08-25 10:37:27 +1200123 raise
124
125 return linux_client
126
Anastasia Kuznetsova3e0ab4d2015-03-06 18:10:13 +0400127 def check_connectivity(self, check_ip):
128 def try_connect(ip):
129 try:
Sirushti Murugesan4920fda2015-04-22 00:35:26 +0530130 urllib.request.urlopen('http://%s/' % ip)
Anastasia Kuznetsova3e0ab4d2015-03-06 18:10:13 +0400131 return True
132 except IOError:
133 return False
134
135 timeout = self.conf.connectivity_timeout
136 elapsed_time = 0
137 while not try_connect(check_ip):
138 time.sleep(10)
139 elapsed_time += 10
140 if elapsed_time > timeout:
141 raise exceptions.TimeoutException()
142
Steve Baker450aa7f2014-08-25 10:37:27 +1200143 def _log_console_output(self, servers=None):
144 if not servers:
145 servers = self.compute_client.servers.list()
146 for server in servers:
Steve Baker24641292015-03-13 10:47:50 +1300147 LOG.info('Console output for %s', server.id)
148 LOG.info(server.get_console_output())
Steve Baker450aa7f2014-08-25 10:37:27 +1200149
Sergey Kraynevd6fa5c02015-02-13 03:03:55 -0500150 def _load_template(self, base_file, file_name, sub_dir=None):
151 sub_dir = sub_dir or ''
Steve Baker450aa7f2014-08-25 10:37:27 +1200152 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
Sergey Kraynevd6fa5c02015-02-13 03:03:55 -0500153 sub_dir, file_name)
Steve Baker450aa7f2014-08-25 10:37:27 +1200154 with open(filepath) as f:
155 return f.read()
156
157 def create_keypair(self, client=None, name=None):
158 if client is None:
159 client = self.compute_client
160 if name is None:
161 name = rand_name('heat-keypair')
162 keypair = client.keypairs.create(name)
163 self.assertEqual(keypair.name, name)
164
165 def delete_keypair():
166 keypair.delete()
167
168 self.addCleanup(delete_keypair)
169 return keypair
170
Sergey Krayneva265c132015-02-13 03:51:03 -0500171 def assign_keypair(self):
172 if self.conf.keypair_name:
173 self.keypair = None
174 self.keypair_name = self.conf.keypair_name
175 else:
176 self.keypair = self.create_keypair()
177 self.keypair_name = self.keypair.id
178
Steve Baker450aa7f2014-08-25 10:37:27 +1200179 @classmethod
180 def _stack_rand_name(cls):
181 return rand_name(cls.__name__)
182
Anastasia Kuznetsova673fc432015-03-12 16:41:36 +0400183 def _get_network(self, net_name=None):
184 if net_name is None:
185 net_name = self.conf.fixed_network_name
Steve Baker450aa7f2014-08-25 10:37:27 +1200186 networks = self.network_client.list_networks()
187 for net in networks['networks']:
Anastasia Kuznetsova673fc432015-03-12 16:41:36 +0400188 if net['name'] == net_name:
Steve Baker450aa7f2014-08-25 10:37:27 +1200189 return net
190
Mark Vanderwiel6d8e0862015-10-15 12:51:07 -0500191 def is_network_extension_supported(self, extension_alias):
192 try:
193 self.network_client.show_extension(extension_alias)
194 except network_exceptions.NeutronClientException:
195 return False
196 return True
197
Thomas Hervedb36c092017-03-23 11:20:14 +0100198 def is_service_available(self, service_type):
199 try:
200 self.identity_client.get_endpoint_url(
201 service_type, self.conf.region)
202 except kc_exceptions.EndpointNotFound:
203 return False
204 else:
205 return True
206
Steve Baker450aa7f2014-08-25 10:37:27 +1200207 @staticmethod
Angus Salkelda89a0282015-07-24 15:47:38 +1000208 def _stack_output(stack, output_key, validate_errors=True):
Steve Baker450aa7f2014-08-25 10:37:27 +1200209 """Return a stack output value for a given key."""
Angus Salkelda89a0282015-07-24 15:47:38 +1000210 value = None
211 for o in stack.outputs:
212 if validate_errors and 'output_error' in o:
213 # scan for errors in the stack output.
214 raise ValueError(
215 'Unexpected output errors in %s : %s' % (
216 output_key, o['output_error']))
217 if o['output_key'] == output_key:
218 value = o['output_value']
219 return value
Steve Baker450aa7f2014-08-25 10:37:27 +1200220
221 def _ping_ip_address(self, ip_address, should_succeed=True):
222 cmd = ['ping', '-c1', '-w1', ip_address]
223
224 def ping():
225 proc = subprocess.Popen(cmd,
226 stdout=subprocess.PIPE,
227 stderr=subprocess.PIPE)
228 proc.wait()
229 return (proc.returncode == 0) == should_succeed
230
231 return call_until_true(
Angus Salkeld08514ad2015-02-06 10:08:31 +1000232 self.conf.build_timeout, 1, ping)
Steve Baker450aa7f2014-08-25 10:37:27 +1200233
Angus Salkelda7500d12015-04-10 15:44:07 +1000234 def _wait_for_all_resource_status(self, stack_identifier,
235 status, failure_pattern='^.*_FAILED$',
236 success_on_not_found=False):
237 for res in self.client.resources.list(stack_identifier):
238 self._wait_for_resource_status(
239 stack_identifier, res.resource_name,
240 status, failure_pattern=failure_pattern,
241 success_on_not_found=success_on_not_found)
242
Steve Baker450aa7f2014-08-25 10:37:27 +1200243 def _wait_for_resource_status(self, stack_identifier, resource_name,
244 status, failure_pattern='^.*_FAILED$',
245 success_on_not_found=False):
246 """Waits for a Resource to reach a given status."""
247 fail_regexp = re.compile(failure_pattern)
248 build_timeout = self.conf.build_timeout
249 build_interval = self.conf.build_interval
250
251 start = timeutils.utcnow()
252 while timeutils.delta_seconds(start,
253 timeutils.utcnow()) < build_timeout:
254 try:
255 res = self.client.resources.get(
256 stack_identifier, resource_name)
257 except heat_exceptions.HTTPNotFound:
258 if success_on_not_found:
259 return
260 # ignore this, as the resource may not have
261 # been created yet
262 else:
263 if res.resource_status == status:
264 return
Sirushti Murugesane5ad25b2015-04-18 23:30:59 +0530265 wait_for_action = status.split('_')[0]
266 resource_action = res.resource_status.split('_')[0]
267 if (resource_action == wait_for_action and
268 fail_regexp.search(res.resource_status)):
Steve Baker450aa7f2014-08-25 10:37:27 +1200269 raise exceptions.StackResourceBuildErrorException(
270 resource_name=res.resource_name,
271 stack_identifier=stack_identifier,
272 resource_status=res.resource_status,
273 resource_status_reason=res.resource_status_reason)
274 time.sleep(build_interval)
275
276 message = ('Resource %s failed to reach %s status within '
277 'the required time (%s s).' %
Anastasia Kuznetsova9a745572015-03-04 13:19:15 +0400278 (resource_name, status, build_timeout))
Steve Baker450aa7f2014-08-25 10:37:27 +1200279 raise exceptions.TimeoutException(message)
280
Rabi Mishra87be9b42016-02-15 14:15:50 +0530281 def verify_resource_status(self, stack_identifier, resource_name,
282 status='CREATE_COMPLETE'):
283 try:
284 res = self.client.resources.get(stack_identifier, resource_name)
285 except heat_exceptions.HTTPNotFound:
286 return False
287 return res.resource_status == status
288
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530289 def _verify_status(self, stack, stack_identifier, status, fail_regexp):
290 if stack.stack_status == status:
Sergey Kraynev89082a32015-09-04 04:42:33 -0400291 # Handle UPDATE_COMPLETE/FAILED case: Make sure we don't
292 # wait for a stale UPDATE_COMPLETE/FAILED status.
293 if status in ('UPDATE_FAILED', 'UPDATE_COMPLETE'):
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530294 if self.updated_time.get(
295 stack_identifier) != stack.updated_time:
296 self.updated_time[stack_identifier] = stack.updated_time
297 return True
Thomas Herve0e8567e2016-09-22 15:07:37 +0200298 elif status == 'DELETE_COMPLETE' and stack.deletion_time is None:
299 # Wait for deleted_time to be filled, so that we have more
300 # confidence the operation is finished.
301 return False
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530302 else:
303 return True
304
Sirushti Murugesane5ad25b2015-04-18 23:30:59 +0530305 wait_for_action = status.split('_')[0]
306 if (stack.action == wait_for_action and
307 fail_regexp.search(stack.stack_status)):
Sergey Kraynev89082a32015-09-04 04:42:33 -0400308 # Handle UPDATE_COMPLETE/UPDATE_FAILED case.
309 if status in ('UPDATE_FAILED', 'UPDATE_COMPLETE'):
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530310 if self.updated_time.get(
311 stack_identifier) != stack.updated_time:
312 self.updated_time[stack_identifier] = stack.updated_time
313 raise exceptions.StackBuildErrorException(
314 stack_identifier=stack_identifier,
315 stack_status=stack.stack_status,
316 stack_status_reason=stack.stack_status_reason)
317 else:
318 raise exceptions.StackBuildErrorException(
319 stack_identifier=stack_identifier,
320 stack_status=stack.stack_status,
321 stack_status_reason=stack.stack_status_reason)
322
Steve Baker450aa7f2014-08-25 10:37:27 +1200323 def _wait_for_stack_status(self, stack_identifier, status,
Sergey Kraynev89082a32015-09-04 04:42:33 -0400324 failure_pattern=None,
Rabi Mishra81ca6bc2016-06-16 15:09:20 +0530325 success_on_not_found=False,
326 signal_required=False,
327 resources_to_signal=None):
Peter Razumovskyf0ac9582015-09-24 16:49:03 +0300328 """Waits for a Stack to reach a given status.
Steve Baker450aa7f2014-08-25 10:37:27 +1200329
330 Note this compares the full $action_$status, e.g
331 CREATE_COMPLETE, not just COMPLETE which is exposed
332 via the status property of Stack in heatclient
333 """
Sergey Kraynev89082a32015-09-04 04:42:33 -0400334 if failure_pattern:
335 fail_regexp = re.compile(failure_pattern)
336 elif 'FAILED' in status:
337 # If we're looking for e.g CREATE_FAILED, COMPLETE is unexpected.
338 fail_regexp = re.compile('^.*_COMPLETE$')
339 else:
340 fail_regexp = re.compile('^.*_FAILED$')
Steve Baker450aa7f2014-08-25 10:37:27 +1200341 build_timeout = self.conf.build_timeout
342 build_interval = self.conf.build_interval
343
344 start = timeutils.utcnow()
345 while timeutils.delta_seconds(start,
346 timeutils.utcnow()) < build_timeout:
347 try:
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500348 stack = self.client.stacks.get(stack_identifier,
349 resolve_outputs=False)
Steve Baker450aa7f2014-08-25 10:37:27 +1200350 except heat_exceptions.HTTPNotFound:
351 if success_on_not_found:
352 return
353 # ignore this, as the resource may not have
354 # been created yet
355 else:
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530356 if self._verify_status(stack, stack_identifier, status,
357 fail_regexp):
Steve Baker450aa7f2014-08-25 10:37:27 +1200358 return
Rabi Mishra81ca6bc2016-06-16 15:09:20 +0530359 if signal_required:
360 self.signal_resources(resources_to_signal)
Steve Baker450aa7f2014-08-25 10:37:27 +1200361 time.sleep(build_interval)
362
363 message = ('Stack %s failed to reach %s status within '
364 'the required time (%s s).' %
Anastasia Kuznetsova9a745572015-03-04 13:19:15 +0400365 (stack_identifier, status, build_timeout))
Steve Baker450aa7f2014-08-25 10:37:27 +1200366 raise exceptions.TimeoutException(message)
367
368 def _stack_delete(self, stack_identifier):
369 try:
Thomas Herve3eab2942015-10-22 17:29:21 +0200370 self._handle_in_progress(self.client.stacks.delete,
371 stack_identifier)
Steve Baker450aa7f2014-08-25 10:37:27 +1200372 except heat_exceptions.HTTPNotFound:
373 pass
374 self._wait_for_stack_status(
375 stack_identifier, 'DELETE_COMPLETE',
376 success_on_not_found=True)
Steven Hardyc9efd972014-11-20 11:31:55 +0000377
Thomas Herve3eab2942015-10-22 17:29:21 +0200378 def _handle_in_progress(self, fn, *args, **kwargs):
379 build_timeout = self.conf.build_timeout
380 build_interval = self.conf.build_interval
381 start = timeutils.utcnow()
382 while timeutils.delta_seconds(start,
383 timeutils.utcnow()) < build_timeout:
384 try:
385 fn(*args, **kwargs)
386 except heat_exceptions.HTTPConflict as ex:
387 # FIXME(sirushtim): Wait a little for the stack lock to be
388 # released and hopefully, the stack should be usable again.
389 if ex.error['error']['type'] != 'ActionInProgress':
390 raise ex
391
392 time.sleep(build_interval)
393 else:
394 break
395
Steven Hardy23284b62015-10-01 19:03:42 +0100396 def update_stack(self, stack_identifier, template=None, environment=None,
Sabeen Syed277ea692015-02-04 23:30:02 +0000397 files=None, parameters=None, tags=None,
Rakesh H Sa3325d62015-04-04 19:42:29 +0530398 expected_status='UPDATE_COMPLETE',
Steven Hardy23284b62015-10-01 19:03:42 +0100399 disable_rollback=True,
400 existing=False):
Steven Hardyc9efd972014-11-20 11:31:55 +0000401 env = environment or {}
402 env_files = files or {}
Sergey Kraynevbcc78df2015-02-27 04:34:32 -0500403 parameters = parameters or {}
Sirushti Murugesan3a195a52015-05-01 09:47:09 +0530404
Sergey Kraynev89082a32015-09-04 04:42:33 -0400405 self.updated_time[stack_identifier] = self.client.stacks.get(
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500406 stack_identifier, resolve_outputs=False).updated_time
Sirushti Murugesan3a195a52015-05-01 09:47:09 +0530407
Thomas Herve3eab2942015-10-22 17:29:21 +0200408 self._handle_in_progress(
409 self.client.stacks.update,
410 stack_id=stack_identifier,
Thomas Herve3eab2942015-10-22 17:29:21 +0200411 template=template,
412 files=env_files,
413 disable_rollback=disable_rollback,
414 parameters=parameters,
415 environment=env,
416 tags=tags,
417 existing=existing)
Sirushti Murugesan3a195a52015-05-01 09:47:09 +0530418
Rakesh H Sa3325d62015-04-04 19:42:29 +0530419 kwargs = {'stack_identifier': stack_identifier,
420 'status': expected_status}
421 if expected_status in ['ROLLBACK_COMPLETE']:
Rakesh H Sa3325d62015-04-04 19:42:29 +0530422 # To trigger rollback you would intentionally fail the stack
423 # Hence check for rollback failures
424 kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
425
426 self._wait_for_stack_status(**kwargs)
Steven Hardyc9efd972014-11-20 11:31:55 +0000427
Oleksii Chuprykovfc2c58f2016-04-29 17:03:17 +0300428 def cancel_update_stack(self, stack_identifier,
429 expected_status='ROLLBACK_COMPLETE'):
430
431 stack_name = stack_identifier.split('/')[0]
432
433 self.updated_time[stack_identifier] = self.client.stacks.get(
434 stack_identifier, resolve_outputs=False).updated_time
435
436 self.client.actions.cancel_update(stack_name)
437
438 kwargs = {'stack_identifier': stack_identifier,
439 'status': expected_status}
440 if expected_status in ['ROLLBACK_COMPLETE']:
441 # To trigger rollback you would intentionally fail the stack
442 # Hence check for rollback failures
443 kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
444
445 self._wait_for_stack_status(**kwargs)
446
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500447 def preview_update_stack(self, stack_identifier, template,
448 environment=None, files=None, parameters=None,
Steven Hardye6de2d62015-12-07 15:59:09 +0000449 tags=None, disable_rollback=True,
450 show_nested=False):
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500451 env = environment or {}
452 env_files = files or {}
453 parameters = parameters or {}
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500454
455 return self.client.stacks.preview_update(
456 stack_id=stack_identifier,
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500457 template=template,
458 files=env_files,
459 disable_rollback=disable_rollback,
460 parameters=parameters,
461 environment=env,
Steven Hardye6de2d62015-12-07 15:59:09 +0000462 tags=tags,
463 show_nested=show_nested
Jason Dunsmoreb5aa9022015-09-09 16:57:04 -0500464 )
465
Steven Hardy03da0742015-03-19 00:13:17 -0400466 def assert_resource_is_a_stack(self, stack_identifier, res_name,
467 wait=False):
468 build_timeout = self.conf.build_timeout
469 build_interval = self.conf.build_interval
470 start = timeutils.utcnow()
471 while timeutils.delta_seconds(start,
472 timeutils.utcnow()) < build_timeout:
473 time.sleep(build_interval)
474 try:
475 nested_identifier = self._get_nested_identifier(
476 stack_identifier, res_name)
477 except Exception:
478 # We may have to wait, if the create is in-progress
479 if wait:
480 time.sleep(build_interval)
481 else:
482 raise
483 else:
484 return nested_identifier
485
486 def _get_nested_identifier(self, stack_identifier, res_name):
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000487 rsrc = self.client.resources.get(stack_identifier, res_name)
488 nested_link = [l for l in rsrc.links if l['rel'] == 'nested']
489 nested_href = nested_link[0]['href']
490 nested_id = nested_href.split('/')[-1]
491 nested_identifier = '/'.join(nested_href.split('/')[-2:])
492 self.assertEqual(rsrc.physical_resource_id, nested_id)
493
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500494 nested_stack = self.client.stacks.get(nested_id, resolve_outputs=False)
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000495 nested_identifier2 = '%s/%s' % (nested_stack.stack_name,
496 nested_stack.id)
497 self.assertEqual(nested_identifier, nested_identifier2)
498 parent_id = stack_identifier.split("/")[-1]
499 self.assertEqual(parent_id, nested_stack.parent)
500 return nested_identifier
501
Rabi Mishra8bcff8a2015-09-21 18:15:04 +0530502 def group_nested_identifier(self, stack_identifier,
503 group_name):
504 # Get the nested stack identifier from a group resource
505 rsrc = self.client.resources.get(stack_identifier, group_name)
506 physical_resource_id = rsrc.physical_resource_id
507
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500508 nested_stack = self.client.stacks.get(physical_resource_id,
509 resolve_outputs=False)
Rabi Mishra8bcff8a2015-09-21 18:15:04 +0530510 nested_identifier = '%s/%s' % (nested_stack.stack_name,
511 nested_stack.id)
512 parent_id = stack_identifier.split("/")[-1]
513 self.assertEqual(parent_id, nested_stack.parent)
514 return nested_identifier
515
516 def list_group_resources(self, stack_identifier,
517 group_name, minimal=True):
518 nested_identifier = self.group_nested_identifier(stack_identifier,
519 group_name)
520 if minimal:
521 return self.list_resources(nested_identifier)
522 return self.client.resources.list(nested_identifier)
523
Steven Hardyc9efd972014-11-20 11:31:55 +0000524 def list_resources(self, stack_identifier):
525 resources = self.client.resources.list(stack_identifier)
526 return dict((r.resource_name, r.resource_type) for r in resources)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000527
Steven Hardyd448dae2016-06-14 14:57:28 +0100528 def get_resource_stack_id(self, r):
529 stack_link = [l for l in r.links if l.get('rel') == 'stack'][0]
530 return stack_link['href'].split("/")[-1]
531
Rabi Mishra81ca6bc2016-06-16 15:09:20 +0530532 def check_input_values(self, group_resources, key, value):
533 # Check inputs for deployment and derived config
534 for r in group_resources:
535 d = self.client.software_deployments.get(
536 r.physical_resource_id)
537 self.assertEqual({key: value}, d.input_values)
538 c = self.client.software_configs.get(
539 d.config_id)
540 foo_input_c = [i for i in c.inputs if i.get('name') == key][0]
541 self.assertEqual(value, foo_input_c.get('value'))
542
543 def signal_resources(self, resources):
544 # Signal all IN_PROGRESS resources
545 for r in resources:
546 if 'IN_PROGRESS' in r.resource_status:
547 stack_id = self.get_resource_stack_id(r)
548 self.client.resources.signal(stack_id, r.resource_name)
549
Steven Hardyf2c82c02014-11-20 14:02:17 +0000550 def stack_create(self, stack_name=None, template=None, files=None,
Sabeen Syed277ea692015-02-04 23:30:02 +0000551 parameters=None, environment=None, tags=None,
552 expected_status='CREATE_COMPLETE',
Jay Dobies39c4ce42015-11-04 10:49:08 -0500553 disable_rollback=True, enable_cleanup=True,
554 environment_files=None):
Steven Hardyf2c82c02014-11-20 14:02:17 +0000555 name = stack_name or self._stack_rand_name()
556 templ = template or self.template
557 templ_files = files or {}
558 params = parameters or {}
559 env = environment or {}
560 self.client.stacks.create(
561 stack_name=name,
562 template=templ,
563 files=templ_files,
Rakesh H Sa3325d62015-04-04 19:42:29 +0530564 disable_rollback=disable_rollback,
Steven Hardyf2c82c02014-11-20 14:02:17 +0000565 parameters=params,
Sabeen Syed277ea692015-02-04 23:30:02 +0000566 environment=env,
Jay Dobies39c4ce42015-11-04 10:49:08 -0500567 tags=tags,
568 environment_files=environment_files
Steven Hardyf2c82c02014-11-20 14:02:17 +0000569 )
Sergey Kraynevbf67ce32015-04-17 10:54:20 -0400570 if expected_status not in ['ROLLBACK_COMPLETE'] and enable_cleanup:
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200571 self.addCleanup(self._stack_delete, name)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000572
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500573 stack = self.client.stacks.get(name, resolve_outputs=False)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000574 stack_identifier = '%s/%s' % (name, stack.id)
Rakesh H Sa3325d62015-04-04 19:42:29 +0530575 kwargs = {'stack_identifier': stack_identifier,
576 'status': expected_status}
Steve Bakerf6c8f122015-02-10 13:54:46 +1300577 if expected_status:
Rakesh H Sa3325d62015-04-04 19:42:29 +0530578 if expected_status in ['ROLLBACK_COMPLETE']:
579 # To trigger rollback you would intentionally fail the stack
580 # Hence check for rollback failures
581 kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
582 self._wait_for_stack_status(**kwargs)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000583 return stack_identifier
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000584
585 def stack_adopt(self, stack_name=None, files=None,
586 parameters=None, environment=None, adopt_data=None,
587 wait_for_status='ADOPT_COMPLETE'):
Rabi Mishra477efc92015-07-31 13:01:45 +0530588 if (self.conf.skip_test_stack_action_list and
589 'ADOPT' in self.conf.skip_test_stack_action_list):
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530590 self.skipTest('Testing Stack adopt disabled in conf, skipping')
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000591 name = stack_name or self._stack_rand_name()
592 templ_files = files or {}
593 params = parameters or {}
594 env = environment or {}
595 self.client.stacks.create(
596 stack_name=name,
597 files=templ_files,
598 disable_rollback=True,
599 parameters=params,
600 environment=env,
601 adopt_stack_data=adopt_data,
602 )
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200603 self.addCleanup(self._stack_delete, name)
Sergey Kraynevf07f4712016-02-15 05:24:17 -0500604 stack = self.client.stacks.get(name, resolve_outputs=False)
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000605 stack_identifier = '%s/%s' % (name, stack.id)
606 self._wait_for_stack_status(stack_identifier, wait_for_status)
607 return stack_identifier
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530608
609 def stack_abandon(self, stack_id):
Rabi Mishra477efc92015-07-31 13:01:45 +0530610 if (self.conf.skip_test_stack_action_list and
611 'ABANDON' in self.conf.skip_test_stack_action_list):
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200612 self.addCleanup(self._stack_delete, stack_id)
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530613 self.skipTest('Testing Stack abandon disabled in conf, skipping')
614 info = self.client.stacks.abandon(stack_id=stack_id)
615 return info
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500616
rabi90b3ab42017-05-04 13:02:28 +0530617 def stack_snapshot(self, stack_id,
618 wait_for_status='SNAPSHOT_COMPLETE'):
619 snapshot = self.client.stacks.snapshot(stack_id=stack_id)
620 self._wait_for_stack_status(stack_id, wait_for_status)
621 return snapshot['id']
622
623 def stack_restore(self, stack_id, snapshot_id,
624 wait_for_status='RESTORE_COMPLETE'):
625 self.client.stacks.restore(stack_id, snapshot_id)
626 self._wait_for_stack_status(stack_id, wait_for_status)
627
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500628 def stack_suspend(self, stack_identifier):
Rabi Mishra477efc92015-07-31 13:01:45 +0530629 if (self.conf.skip_test_stack_action_list and
630 'SUSPEND' in self.conf.skip_test_stack_action_list):
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200631 self.addCleanup(self._stack_delete, stack_identifier)
Rabi Mishra477efc92015-07-31 13:01:45 +0530632 self.skipTest('Testing Stack suspend disabled in conf, skipping')
rabif7d67082016-05-17 18:51:22 +0530633 self._handle_in_progress(self.client.actions.suspend, stack_identifier)
Angus Salkelda7500d12015-04-10 15:44:07 +1000634 # improve debugging by first checking the resource's state.
635 self._wait_for_all_resource_status(stack_identifier,
636 'SUSPEND_COMPLETE')
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500637 self._wait_for_stack_status(stack_identifier, 'SUSPEND_COMPLETE')
638
639 def stack_resume(self, stack_identifier):
Rabi Mishra477efc92015-07-31 13:01:45 +0530640 if (self.conf.skip_test_stack_action_list and
641 'RESUME' in self.conf.skip_test_stack_action_list):
Steve Bakerdbea6ab2015-08-19 13:37:08 +1200642 self.addCleanup(self._stack_delete, stack_identifier)
Rabi Mishra477efc92015-07-31 13:01:45 +0530643 self.skipTest('Testing Stack resume disabled in conf, skipping')
rabif7d67082016-05-17 18:51:22 +0530644 self._handle_in_progress(self.client.actions.resume, stack_identifier)
Angus Salkelda7500d12015-04-10 15:44:07 +1000645 # improve debugging by first checking the resource's state.
646 self._wait_for_all_resource_status(stack_identifier,
647 'RESUME_COMPLETE')
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500648 self._wait_for_stack_status(stack_identifier, 'RESUME_COMPLETE')
Steven Hardy03da0742015-03-19 00:13:17 -0400649
650 def wait_for_event_with_reason(self, stack_identifier, reason,
651 rsrc_name=None, num_expected=1):
652 build_timeout = self.conf.build_timeout
653 build_interval = self.conf.build_interval
654 start = timeutils.utcnow()
655 while timeutils.delta_seconds(start,
656 timeutils.utcnow()) < build_timeout:
657 try:
658 rsrc_events = self.client.events.list(stack_identifier,
659 resource_name=rsrc_name)
660 except heat_exceptions.HTTPNotFound:
liyi09461f72017-03-21 12:17:51 +0800661 LOG.debug("No events yet found for %s", rsrc_name)
Steven Hardy03da0742015-03-19 00:13:17 -0400662 else:
663 matched = [e for e in rsrc_events
664 if e.resource_status_reason == reason]
665 if len(matched) == num_expected:
666 return matched
667 time.sleep(build_interval)
Rakesh H Sc5735a82016-04-28 15:38:09 +0530668
Thomas Hervea6afca82017-04-10 23:44:26 +0200669 def check_autoscale_complete(self, stack_id, expected_num, parent_stack,
670 policy):
Rakesh H Sc5735a82016-04-28 15:38:09 +0530671 res_list = self.client.resources.list(stack_id)
672 all_res_complete = all(res.resource_status in ('UPDATE_COMPLETE',
673 'CREATE_COMPLETE')
674 for res in res_list)
675 all_res = len(res_list) == expected_num
Thomas Hervea6afca82017-04-10 23:44:26 +0200676 if all_res and all_res_complete:
677 metadata = self.client.resources.metadata(parent_stack, policy)
678 return not metadata.get('scaling_in_progress')
679 return False