Initial movement to new repo with cleanup
diff --git a/heat_tempest_plugin/common/test.py b/heat_tempest_plugin/common/test.py
new file mode 100644
index 0000000..64fd1a0
--- /dev/null
+++ b/heat_tempest_plugin/common/test.py
@@ -0,0 +1,706 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import os
+import random
+import re
+import subprocess
+import time
+
+import fixtures
+from heatclient import exc as heat_exceptions
+from keystoneauth1 import exceptions as kc_exceptions
+from neutronclient.common import exceptions as network_exceptions
+from oslo_log import log as logging
+from oslo_utils import timeutils
+import six
+from six.moves import urllib
+import testscenarios
+import testtools
+
+from heat_tempest_plugin.common import exceptions
+from heat_tempest_plugin.common import remote_client
+from heat_tempest_plugin import config
+from heat_tempest_plugin.services import clients
+
+LOG = logging.getLogger(__name__)
+_LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s"
+
+
+def call_until_true(duration, sleep_for, func, *args, **kwargs):
+ """Call the function until it returns True or the duration elapsed.
+
+ Call the given function until it returns True (and return True) or
+ until the specified duration (in seconds) elapses (and return
+ False).
+
+ :param func: A zero argument callable that returns True on success.
+ :param duration: The number of seconds for which to attempt a
+ successful call of the function.
+ :param sleep_for: The number of seconds to sleep after an unsuccessful
+ invocation of the function.
+ """
+ now = time.time()
+ timeout = now + duration
+ while now < timeout:
+ if func(*args, **kwargs):
+ return True
+ LOG.debug("Sleeping for %d seconds", sleep_for)
+ time.sleep(sleep_for)
+ now = time.time()
+ return False
+
+
+def rand_name(name=''):
+ randbits = six.text_type(random.randint(1, 0x7fffffff))
+ if name:
+ return name + '-' + randbits
+ else:
+ return randbits
+
+
+def requires_convergence(test_method):
+ '''Decorator for convergence-only tests.
+
+ The decorated test will be skipped when convergence is disabled.
+ '''
+ convergence_enabled = config.CONF.heat_plugin.convergence_engine_enabled
+ skipper = testtools.skipUnless(convergence_enabled,
+ "Convergence-only tests are disabled")
+ return skipper(test_method)
+
+
+class HeatIntegrationTest(testscenarios.WithScenarios,
+ testtools.TestCase):
+
+ def setUp(self):
+ super(HeatIntegrationTest, self).setUp()
+
+ self.conf = config.CONF.orchestration_plugin
+
+ self.assertIsNotNone(self.conf.auth_url,
+ 'No auth_url configured')
+ self.assertIsNotNone(self.conf.username,
+ 'No username configured')
+ self.assertIsNotNone(self.conf.password,
+ 'No password configured')
+ self.setup_clients(self.conf)
+ self.useFixture(fixtures.FakeLogger(format=_LOG_FORMAT))
+ self.updated_time = {}
+ if self.conf.disable_ssl_certificate_validation:
+ self.verify_cert = False
+ else:
+ self.verify_cert = self.conf.ca_file or True
+
+ def setup_clients(self, conf, admin_credentials=False):
+ self.manager = clients.ClientManager(conf, admin_credentials)
+ self.identity_client = self.manager.identity_client
+ self.orchestration_client = self.manager.orchestration_client
+ self.compute_client = self.manager.compute_client
+ self.network_client = self.manager.network_client
+ self.volume_client = self.manager.volume_client
+ self.object_client = self.manager.object_client
+ self.metering_client = self.manager.metering_client
+
+ self.client = self.orchestration_client
+
+ def setup_clients_for_admin(self):
+ self.setup_clients(self.conf, True)
+
+ def get_remote_client(self, server_or_ip, username, private_key=None):
+ if isinstance(server_or_ip, six.string_types):
+ ip = server_or_ip
+ else:
+ network_name_for_ssh = self.conf.network_for_ssh
+ ip = server_or_ip.networks[network_name_for_ssh][0]
+ if private_key is None:
+ private_key = self.keypair.private_key
+ linux_client = remote_client.RemoteClient(ip, username,
+ pkey=private_key,
+ conf=self.conf)
+ try:
+ linux_client.validate_authentication()
+ except exceptions.SSHTimeout:
+ LOG.exception('ssh connection to %s failed', ip)
+ raise
+
+ return linux_client
+
+ def check_connectivity(self, check_ip):
+ def try_connect(ip):
+ try:
+ urllib.request.urlopen('http://%s/' % ip)
+ return True
+ except IOError:
+ return False
+
+ timeout = self.conf.connectivity_timeout
+ elapsed_time = 0
+ while not try_connect(check_ip):
+ time.sleep(10)
+ elapsed_time += 10
+ if elapsed_time > timeout:
+ raise exceptions.TimeoutException()
+
+ def _log_console_output(self, servers=None):
+ if not servers:
+ servers = self.compute_client.servers.list()
+ for server in servers:
+ LOG.info('Console output for %s', server.id)
+ LOG.info(server.get_console_output())
+
+ def _load_template(self, base_file, file_name, sub_dir=None):
+ sub_dir = sub_dir or ''
+ filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
+ sub_dir, file_name)
+ with open(filepath) as f:
+ return f.read()
+
+ def create_keypair(self, client=None, name=None):
+ if client is None:
+ client = self.compute_client
+ if name is None:
+ name = rand_name('heat-keypair')
+ keypair = client.keypairs.create(name)
+ self.assertEqual(keypair.name, name)
+
+ def delete_keypair():
+ keypair.delete()
+
+ self.addCleanup(delete_keypair)
+ return keypair
+
+ def assign_keypair(self):
+ if self.conf.keypair_name:
+ self.keypair = None
+ self.keypair_name = self.conf.keypair_name
+ else:
+ self.keypair = self.create_keypair()
+ self.keypair_name = self.keypair.id
+
+ @classmethod
+ def _stack_rand_name(cls):
+ return rand_name(cls.__name__)
+
+ def _get_network(self, net_name=None):
+ if net_name is None:
+ net_name = self.conf.fixed_network_name
+ networks = self.network_client.list_networks()
+ for net in networks['networks']:
+ if net['name'] == net_name:
+ return net
+
+ def is_network_extension_supported(self, extension_alias):
+ try:
+ self.network_client.show_extension(extension_alias)
+ except network_exceptions.NeutronClientException:
+ return False
+ return True
+
+ def is_service_available(self, service_type):
+ try:
+ self.identity_client.get_endpoint_url(
+ service_type, self.conf.region)
+ except kc_exceptions.EndpointNotFound:
+ return False
+ else:
+ return True
+
+ @staticmethod
+ def _stack_output(stack, output_key, validate_errors=True):
+ """Return a stack output value for a given key."""
+ value = None
+ for o in stack.outputs:
+ if validate_errors and 'output_error' in o:
+ # scan for errors in the stack output.
+ raise ValueError(
+ 'Unexpected output errors in %s : %s' % (
+ output_key, o['output_error']))
+ if o['output_key'] == output_key:
+ value = o['output_value']
+ return value
+
+ def _ping_ip_address(self, ip_address, should_succeed=True):
+ cmd = ['ping', '-c1', '-w1', ip_address]
+
+ def ping():
+ proc = subprocess.Popen(cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ proc.wait()
+ return (proc.returncode == 0) == should_succeed
+
+ return call_until_true(
+ self.conf.build_timeout, 1, ping)
+
+ def _wait_for_all_resource_status(self, stack_identifier,
+ status, failure_pattern='^.*_FAILED$',
+ success_on_not_found=False):
+ for res in self.client.resources.list(stack_identifier):
+ self._wait_for_resource_status(
+ stack_identifier, res.resource_name,
+ status, failure_pattern=failure_pattern,
+ success_on_not_found=success_on_not_found)
+
+ def _wait_for_resource_status(self, stack_identifier, resource_name,
+ status, failure_pattern='^.*_FAILED$',
+ success_on_not_found=False):
+ """Waits for a Resource to reach a given status."""
+ fail_regexp = re.compile(failure_pattern)
+ build_timeout = self.conf.build_timeout
+ build_interval = self.conf.build_interval
+
+ start = timeutils.utcnow()
+ while timeutils.delta_seconds(start,
+ timeutils.utcnow()) < build_timeout:
+ try:
+ res = self.client.resources.get(
+ stack_identifier, resource_name)
+ except heat_exceptions.HTTPNotFound:
+ if success_on_not_found:
+ return
+ # ignore this, as the resource may not have
+ # been created yet
+ else:
+ if res.resource_status == status:
+ return
+ wait_for_action = status.split('_')[0]
+ resource_action = res.resource_status.split('_')[0]
+ if (resource_action == wait_for_action and
+ fail_regexp.search(res.resource_status)):
+ raise exceptions.StackResourceBuildErrorException(
+ resource_name=res.resource_name,
+ stack_identifier=stack_identifier,
+ resource_status=res.resource_status,
+ resource_status_reason=res.resource_status_reason)
+ time.sleep(build_interval)
+
+ message = ('Resource %s failed to reach %s status within '
+ 'the required time (%s s).' %
+ (resource_name, status, build_timeout))
+ raise exceptions.TimeoutException(message)
+
+ def verify_resource_status(self, stack_identifier, resource_name,
+ status='CREATE_COMPLETE'):
+ try:
+ res = self.client.resources.get(stack_identifier, resource_name)
+ except heat_exceptions.HTTPNotFound:
+ return False
+ return res.resource_status == status
+
+ def _verify_status(self, stack, stack_identifier, status, fail_regexp):
+ if stack.stack_status == status:
+ # Handle UPDATE_COMPLETE/FAILED case: Make sure we don't
+ # wait for a stale UPDATE_COMPLETE/FAILED status.
+ if status in ('UPDATE_FAILED', 'UPDATE_COMPLETE'):
+ if self.updated_time.get(
+ stack_identifier) != stack.updated_time:
+ self.updated_time[stack_identifier] = stack.updated_time
+ return True
+ elif status == 'DELETE_COMPLETE' and stack.deletion_time is None:
+ # Wait for deleted_time to be filled, so that we have more
+ # confidence the operation is finished.
+ return False
+ else:
+ return True
+
+ wait_for_action = status.split('_')[0]
+ if (stack.action == wait_for_action and
+ fail_regexp.search(stack.stack_status)):
+ # Handle UPDATE_COMPLETE/UPDATE_FAILED case.
+ if status in ('UPDATE_FAILED', 'UPDATE_COMPLETE'):
+ if self.updated_time.get(
+ stack_identifier) != stack.updated_time:
+ self.updated_time[stack_identifier] = stack.updated_time
+ raise exceptions.StackBuildErrorException(
+ stack_identifier=stack_identifier,
+ stack_status=stack.stack_status,
+ stack_status_reason=stack.stack_status_reason)
+ else:
+ raise exceptions.StackBuildErrorException(
+ stack_identifier=stack_identifier,
+ stack_status=stack.stack_status,
+ stack_status_reason=stack.stack_status_reason)
+
+ def _wait_for_stack_status(self, stack_identifier, status,
+ failure_pattern=None,
+ success_on_not_found=False,
+ signal_required=False,
+ resources_to_signal=None):
+ """Waits for a Stack to reach a given status.
+
+ Note this compares the full $action_$status, e.g
+ CREATE_COMPLETE, not just COMPLETE which is exposed
+ via the status property of Stack in heatclient
+ """
+ if failure_pattern:
+ fail_regexp = re.compile(failure_pattern)
+ elif 'FAILED' in status:
+ # If we're looking for e.g CREATE_FAILED, COMPLETE is unexpected.
+ fail_regexp = re.compile('^.*_COMPLETE$')
+ else:
+ fail_regexp = re.compile('^.*_FAILED$')
+ build_timeout = self.conf.build_timeout
+ build_interval = self.conf.build_interval
+
+ start = timeutils.utcnow()
+ while timeutils.delta_seconds(start,
+ timeutils.utcnow()) < build_timeout:
+ try:
+ stack = self.client.stacks.get(stack_identifier,
+ resolve_outputs=False)
+ except heat_exceptions.HTTPNotFound:
+ if success_on_not_found:
+ return
+ # ignore this, as the resource may not have
+ # been created yet
+ else:
+ if self._verify_status(stack, stack_identifier, status,
+ fail_regexp):
+ return
+ if signal_required:
+ self.signal_resources(resources_to_signal)
+ time.sleep(build_interval)
+
+ message = ('Stack %s failed to reach %s status within '
+ 'the required time (%s s).' %
+ (stack_identifier, status, build_timeout))
+ raise exceptions.TimeoutException(message)
+
+ def _stack_delete(self, stack_identifier):
+ try:
+ self._handle_in_progress(self.client.stacks.delete,
+ stack_identifier)
+ except heat_exceptions.HTTPNotFound:
+ pass
+ self._wait_for_stack_status(
+ stack_identifier, 'DELETE_COMPLETE',
+ success_on_not_found=True)
+
+ def _handle_in_progress(self, fn, *args, **kwargs):
+ build_timeout = self.conf.build_timeout
+ build_interval = self.conf.build_interval
+ start = timeutils.utcnow()
+ while timeutils.delta_seconds(start,
+ timeutils.utcnow()) < build_timeout:
+ try:
+ fn(*args, **kwargs)
+ except heat_exceptions.HTTPConflict as ex:
+ # FIXME(sirushtim): Wait a little for the stack lock to be
+ # released and hopefully, the stack should be usable again.
+ if ex.error['error']['type'] != 'ActionInProgress':
+ raise ex
+
+ time.sleep(build_interval)
+ else:
+ break
+
+ def update_stack(self, stack_identifier, template=None, environment=None,
+ files=None, parameters=None, tags=None,
+ expected_status='UPDATE_COMPLETE',
+ disable_rollback=True,
+ existing=False):
+ env = environment or {}
+ env_files = files or {}
+ parameters = parameters or {}
+
+ self.updated_time[stack_identifier] = self.client.stacks.get(
+ stack_identifier, resolve_outputs=False).updated_time
+
+ self._handle_in_progress(
+ self.client.stacks.update,
+ stack_id=stack_identifier,
+ template=template,
+ files=env_files,
+ disable_rollback=disable_rollback,
+ parameters=parameters,
+ environment=env,
+ tags=tags,
+ existing=existing)
+
+ kwargs = {'stack_identifier': stack_identifier,
+ 'status': expected_status}
+ if expected_status in ['ROLLBACK_COMPLETE']:
+ # To trigger rollback you would intentionally fail the stack
+ # Hence check for rollback failures
+ kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
+
+ self._wait_for_stack_status(**kwargs)
+
+ def cancel_update_stack(self, stack_identifier,
+ expected_status='ROLLBACK_COMPLETE'):
+
+ stack_name = stack_identifier.split('/')[0]
+
+ self.updated_time[stack_identifier] = self.client.stacks.get(
+ stack_identifier, resolve_outputs=False).updated_time
+
+ self.client.actions.cancel_update(stack_name)
+
+ kwargs = {'stack_identifier': stack_identifier,
+ 'status': expected_status}
+ if expected_status in ['ROLLBACK_COMPLETE']:
+ # To trigger rollback you would intentionally fail the stack
+ # Hence check for rollback failures
+ kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
+
+ self._wait_for_stack_status(**kwargs)
+
+ def preview_update_stack(self, stack_identifier, template,
+ environment=None, files=None, parameters=None,
+ tags=None, disable_rollback=True,
+ show_nested=False):
+ env = environment or {}
+ env_files = files or {}
+ parameters = parameters or {}
+
+ return self.client.stacks.preview_update(
+ stack_id=stack_identifier,
+ template=template,
+ files=env_files,
+ disable_rollback=disable_rollback,
+ parameters=parameters,
+ environment=env,
+ tags=tags,
+ show_nested=show_nested
+ )
+
+ def assert_resource_is_a_stack(self, stack_identifier, res_name,
+ wait=False):
+ build_timeout = self.conf.build_timeout
+ build_interval = self.conf.build_interval
+ start = timeutils.utcnow()
+ while timeutils.delta_seconds(start,
+ timeutils.utcnow()) < build_timeout:
+ time.sleep(build_interval)
+ try:
+ nested_identifier = self._get_nested_identifier(
+ stack_identifier, res_name)
+ except Exception:
+ # We may have to wait, if the create is in-progress
+ if wait:
+ time.sleep(build_interval)
+ else:
+ raise
+ else:
+ return nested_identifier
+
+ def _get_nested_identifier(self, stack_identifier, res_name):
+ rsrc = self.client.resources.get(stack_identifier, res_name)
+ nested_link = [l for l in rsrc.links if l['rel'] == 'nested']
+ nested_href = nested_link[0]['href']
+ nested_id = nested_href.split('/')[-1]
+ nested_identifier = '/'.join(nested_href.split('/')[-2:])
+ self.assertEqual(rsrc.physical_resource_id, nested_id)
+
+ nested_stack = self.client.stacks.get(nested_id, resolve_outputs=False)
+ nested_identifier2 = '%s/%s' % (nested_stack.stack_name,
+ nested_stack.id)
+ self.assertEqual(nested_identifier, nested_identifier2)
+ parent_id = stack_identifier.split("/")[-1]
+ self.assertEqual(parent_id, nested_stack.parent)
+ return nested_identifier
+
+ def group_nested_identifier(self, stack_identifier,
+ group_name):
+ # Get the nested stack identifier from a group resource
+ rsrc = self.client.resources.get(stack_identifier, group_name)
+ physical_resource_id = rsrc.physical_resource_id
+
+ nested_stack = self.client.stacks.get(physical_resource_id,
+ resolve_outputs=False)
+ nested_identifier = '%s/%s' % (nested_stack.stack_name,
+ nested_stack.id)
+ parent_id = stack_identifier.split("/")[-1]
+ self.assertEqual(parent_id, nested_stack.parent)
+ return nested_identifier
+
+ def list_group_resources(self, stack_identifier,
+ group_name, minimal=True):
+ nested_identifier = self.group_nested_identifier(stack_identifier,
+ group_name)
+ if minimal:
+ return self.list_resources(nested_identifier)
+ return self.client.resources.list(nested_identifier)
+
+ def list_resources(self, stack_identifier):
+ resources = self.client.resources.list(stack_identifier)
+ return dict((r.resource_name, r.resource_type) for r in resources)
+
+ def get_resource_stack_id(self, r):
+ stack_link = [l for l in r.links if l.get('rel') == 'stack'][0]
+ return stack_link['href'].split("/")[-1]
+
+ def get_physical_resource_id(self, stack_identifier, resource_name):
+ try:
+ resource = self.client.resources.get(
+ stack_identifier, resource_name)
+ return resource.physical_resource_id
+ except Exception:
+ raise Exception('Resource (%s) not found in stack (%s)!' %
+ (stack_identifier, resource_name))
+
+ def get_stack_output(self, stack_identifier, output_key,
+ validate_errors=True):
+ stack = self.client.stacks.get(stack_identifier)
+ return self._stack_output(stack, output_key, validate_errors)
+
+ def check_input_values(self, group_resources, key, value):
+ # Check inputs for deployment and derived config
+ for r in group_resources:
+ d = self.client.software_deployments.get(
+ r.physical_resource_id)
+ self.assertEqual({key: value}, d.input_values)
+ c = self.client.software_configs.get(
+ d.config_id)
+ foo_input_c = [i for i in c.inputs if i.get('name') == key][0]
+ self.assertEqual(value, foo_input_c.get('value'))
+
+ def signal_resources(self, resources):
+ # Signal all IN_PROGRESS resources
+ for r in resources:
+ if 'IN_PROGRESS' in r.resource_status:
+ stack_id = self.get_resource_stack_id(r)
+ self.client.resources.signal(stack_id, r.resource_name)
+
+ def stack_create(self, stack_name=None, template=None, files=None,
+ parameters=None, environment=None, tags=None,
+ expected_status='CREATE_COMPLETE',
+ disable_rollback=True, enable_cleanup=True,
+ environment_files=None, timeout=None):
+ name = stack_name or self._stack_rand_name()
+ templ = template or self.template
+ templ_files = files or {}
+ params = parameters or {}
+ env = environment or {}
+ timeout_mins = timeout or self.conf.build_timeout
+ self.client.stacks.create(
+ stack_name=name,
+ template=templ,
+ files=templ_files,
+ disable_rollback=disable_rollback,
+ parameters=params,
+ environment=env,
+ tags=tags,
+ environment_files=environment_files,
+ timeout_mins=timeout_mins
+ )
+ if expected_status not in ['ROLLBACK_COMPLETE'] and enable_cleanup:
+ self.addCleanup(self._stack_delete, name)
+
+ stack = self.client.stacks.get(name, resolve_outputs=False)
+ stack_identifier = '%s/%s' % (name, stack.id)
+ kwargs = {'stack_identifier': stack_identifier,
+ 'status': expected_status}
+ if expected_status:
+ if expected_status in ['ROLLBACK_COMPLETE']:
+ # To trigger rollback you would intentionally fail the stack
+ # Hence check for rollback failures
+ kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
+ self._wait_for_stack_status(**kwargs)
+ return stack_identifier
+
+ def stack_adopt(self, stack_name=None, files=None,
+ parameters=None, environment=None, adopt_data=None,
+ wait_for_status='ADOPT_COMPLETE'):
+ if (self.conf.skip_test_stack_action_list and
+ 'ADOPT' in self.conf.skip_test_stack_action_list):
+ self.skipTest('Testing Stack adopt disabled in conf, skipping')
+ name = stack_name or self._stack_rand_name()
+ templ_files = files or {}
+ params = parameters or {}
+ env = environment or {}
+ self.client.stacks.create(
+ stack_name=name,
+ files=templ_files,
+ disable_rollback=True,
+ parameters=params,
+ environment=env,
+ adopt_stack_data=adopt_data,
+ )
+ self.addCleanup(self._stack_delete, name)
+ stack = self.client.stacks.get(name, resolve_outputs=False)
+ stack_identifier = '%s/%s' % (name, stack.id)
+ self._wait_for_stack_status(stack_identifier, wait_for_status)
+ return stack_identifier
+
+ def stack_abandon(self, stack_id):
+ if (self.conf.skip_test_stack_action_list and
+ 'ABANDON' in self.conf.skip_test_stack_action_list):
+ self.addCleanup(self._stack_delete, stack_id)
+ self.skipTest('Testing Stack abandon disabled in conf, skipping')
+ info = self.client.stacks.abandon(stack_id=stack_id)
+ return info
+
+ def stack_snapshot(self, stack_id,
+ wait_for_status='SNAPSHOT_COMPLETE'):
+ snapshot = self.client.stacks.snapshot(stack_id=stack_id)
+ self._wait_for_stack_status(stack_id, wait_for_status)
+ return snapshot['id']
+
+ def stack_restore(self, stack_id, snapshot_id,
+ wait_for_status='RESTORE_COMPLETE'):
+ self.client.stacks.restore(stack_id, snapshot_id)
+ self._wait_for_stack_status(stack_id, wait_for_status)
+
+ def stack_suspend(self, stack_identifier):
+ if (self.conf.skip_test_stack_action_list and
+ 'SUSPEND' in self.conf.skip_test_stack_action_list):
+ self.addCleanup(self._stack_delete, stack_identifier)
+ self.skipTest('Testing Stack suspend disabled in conf, skipping')
+ self._handle_in_progress(self.client.actions.suspend, stack_identifier)
+ # improve debugging by first checking the resource's state.
+ self._wait_for_all_resource_status(stack_identifier,
+ 'SUSPEND_COMPLETE')
+ self._wait_for_stack_status(stack_identifier, 'SUSPEND_COMPLETE')
+
+ def stack_resume(self, stack_identifier):
+ if (self.conf.skip_test_stack_action_list and
+ 'RESUME' in self.conf.skip_test_stack_action_list):
+ self.addCleanup(self._stack_delete, stack_identifier)
+ self.skipTest('Testing Stack resume disabled in conf, skipping')
+ self._handle_in_progress(self.client.actions.resume, stack_identifier)
+ # improve debugging by first checking the resource's state.
+ self._wait_for_all_resource_status(stack_identifier,
+ 'RESUME_COMPLETE')
+ self._wait_for_stack_status(stack_identifier, 'RESUME_COMPLETE')
+
+ def wait_for_event_with_reason(self, stack_identifier, reason,
+ rsrc_name=None, num_expected=1):
+ build_timeout = self.conf.build_timeout
+ build_interval = self.conf.build_interval
+ start = timeutils.utcnow()
+ while timeutils.delta_seconds(start,
+ timeutils.utcnow()) < build_timeout:
+ try:
+ rsrc_events = self.client.events.list(stack_identifier,
+ resource_name=rsrc_name)
+ except heat_exceptions.HTTPNotFound:
+ LOG.debug("No events yet found for %s", rsrc_name)
+ else:
+ matched = [e for e in rsrc_events
+ if e.resource_status_reason == reason]
+ if len(matched) == num_expected:
+ return matched
+ time.sleep(build_interval)
+
+ def check_autoscale_complete(self, stack_id, expected_num, parent_stack,
+ policy):
+ res_list = self.client.resources.list(stack_id)
+ all_res_complete = all(res.resource_status in ('UPDATE_COMPLETE',
+ 'CREATE_COMPLETE')
+ for res in res_list)
+ all_res = len(res_list) == expected_num
+ if all_res and all_res_complete:
+ metadata = self.client.resources.metadata(parent_stack, policy)
+ return not metadata.get('scaling_in_progress')
+ return False