blob: 7c8300d60a8715db89b971f12418ea727e54a842 [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
Steve Baker24641292015-03-13 10:47:50 +130021from oslo_log import log as logging
Jens Rosenboom4f069fb2015-02-18 14:19:07 +010022from oslo_utils import timeutils
Pavlo Shchelokovskyy60e0ecd2014-12-14 22:17:21 +020023import six
Sirushti Murugesan4920fda2015-04-22 00:35:26 +053024from six.moves import urllib
Pavlo Shchelokovskyy60e0ecd2014-12-14 22:17:21 +020025import testscenarios
26import testtools
Steve Baker450aa7f2014-08-25 10:37:27 +120027
Steve Baker450aa7f2014-08-25 10:37:27 +120028from heat_integrationtests.common import clients
29from heat_integrationtests.common import config
30from heat_integrationtests.common import exceptions
31from heat_integrationtests.common import remote_client
32
33LOG = logging.getLogger(__name__)
Angus Salkeld24043702014-11-21 08:49:26 +100034_LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s"
Steve Baker450aa7f2014-08-25 10:37:27 +120035
36
Angus Salkeld08514ad2015-02-06 10:08:31 +100037def call_until_true(duration, sleep_for, func, *args, **kwargs):
Steve Baker450aa7f2014-08-25 10:37:27 +120038 """
39 Call the given function until it returns True (and return True) or
40 until the specified duration (in seconds) elapses (and return
41 False).
42
43 :param func: A zero argument callable that returns True on success.
44 :param duration: The number of seconds for which to attempt a
45 successful call of the function.
46 :param sleep_for: The number of seconds to sleep after an unsuccessful
47 invocation of the function.
48 """
49 now = time.time()
50 timeout = now + duration
51 while now < timeout:
Angus Salkeld08514ad2015-02-06 10:08:31 +100052 if func(*args, **kwargs):
Steve Baker450aa7f2014-08-25 10:37:27 +120053 return True
54 LOG.debug("Sleeping for %d seconds", sleep_for)
55 time.sleep(sleep_for)
56 now = time.time()
57 return False
58
59
60def rand_name(name=''):
61 randbits = str(random.randint(1, 0x7fffffff))
62 if name:
63 return name + '-' + randbits
64 else:
65 return randbits
66
67
Angus Salkeld95f65a22014-11-24 12:38:30 +100068class HeatIntegrationTest(testscenarios.WithScenarios,
69 testtools.TestCase):
Steve Baker450aa7f2014-08-25 10:37:27 +120070
71 def setUp(self):
72 super(HeatIntegrationTest, self).setUp()
73
74 self.conf = config.init_conf()
75
76 self.assertIsNotNone(self.conf.auth_url,
77 'No auth_url configured')
78 self.assertIsNotNone(self.conf.username,
79 'No username configured')
80 self.assertIsNotNone(self.conf.password,
81 'No password configured')
82
83 self.manager = clients.ClientManager(self.conf)
84 self.identity_client = self.manager.identity_client
85 self.orchestration_client = self.manager.orchestration_client
86 self.compute_client = self.manager.compute_client
87 self.network_client = self.manager.network_client
88 self.volume_client = self.manager.volume_client
Angus Salkeld4408da32015-02-03 18:53:30 +100089 self.object_client = self.manager.object_client
Angus Salkeld24043702014-11-21 08:49:26 +100090 self.useFixture(fixtures.FakeLogger(format=_LOG_FORMAT))
Sirushti Murugesan13a8a172015-04-14 00:30:05 +053091 self.updated_time = {}
Steve Baker450aa7f2014-08-25 10:37:27 +120092
Steve Baker450aa7f2014-08-25 10:37:27 +120093 def get_remote_client(self, server_or_ip, username, private_key=None):
94 if isinstance(server_or_ip, six.string_types):
95 ip = server_or_ip
96 else:
97 network_name_for_ssh = self.conf.network_for_ssh
98 ip = server_or_ip.networks[network_name_for_ssh][0]
99 if private_key is None:
100 private_key = self.keypair.private_key
101 linux_client = remote_client.RemoteClient(ip, username,
102 pkey=private_key,
103 conf=self.conf)
104 try:
105 linux_client.validate_authentication()
106 except exceptions.SSHTimeout:
107 LOG.exception('ssh connection to %s failed' % ip)
108 raise
109
110 return linux_client
111
Anastasia Kuznetsova3e0ab4d2015-03-06 18:10:13 +0400112 def check_connectivity(self, check_ip):
113 def try_connect(ip):
114 try:
Sirushti Murugesan4920fda2015-04-22 00:35:26 +0530115 urllib.request.urlopen('http://%s/' % ip)
Anastasia Kuznetsova3e0ab4d2015-03-06 18:10:13 +0400116 return True
117 except IOError:
118 return False
119
120 timeout = self.conf.connectivity_timeout
121 elapsed_time = 0
122 while not try_connect(check_ip):
123 time.sleep(10)
124 elapsed_time += 10
125 if elapsed_time > timeout:
126 raise exceptions.TimeoutException()
127
Steve Baker450aa7f2014-08-25 10:37:27 +1200128 def _log_console_output(self, servers=None):
129 if not servers:
130 servers = self.compute_client.servers.list()
131 for server in servers:
Steve Baker24641292015-03-13 10:47:50 +1300132 LOG.info('Console output for %s', server.id)
133 LOG.info(server.get_console_output())
Steve Baker450aa7f2014-08-25 10:37:27 +1200134
Sergey Kraynevd6fa5c02015-02-13 03:03:55 -0500135 def _load_template(self, base_file, file_name, sub_dir=None):
136 sub_dir = sub_dir or ''
Steve Baker450aa7f2014-08-25 10:37:27 +1200137 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
Sergey Kraynevd6fa5c02015-02-13 03:03:55 -0500138 sub_dir, file_name)
Steve Baker450aa7f2014-08-25 10:37:27 +1200139 with open(filepath) as f:
140 return f.read()
141
142 def create_keypair(self, client=None, name=None):
143 if client is None:
144 client = self.compute_client
145 if name is None:
146 name = rand_name('heat-keypair')
147 keypair = client.keypairs.create(name)
148 self.assertEqual(keypair.name, name)
149
150 def delete_keypair():
151 keypair.delete()
152
153 self.addCleanup(delete_keypair)
154 return keypair
155
Sergey Krayneva265c132015-02-13 03:51:03 -0500156 def assign_keypair(self):
157 if self.conf.keypair_name:
158 self.keypair = None
159 self.keypair_name = self.conf.keypair_name
160 else:
161 self.keypair = self.create_keypair()
162 self.keypair_name = self.keypair.id
163
Steve Baker450aa7f2014-08-25 10:37:27 +1200164 @classmethod
165 def _stack_rand_name(cls):
166 return rand_name(cls.__name__)
167
Anastasia Kuznetsova673fc432015-03-12 16:41:36 +0400168 def _get_network(self, net_name=None):
169 if net_name is None:
170 net_name = self.conf.fixed_network_name
Steve Baker450aa7f2014-08-25 10:37:27 +1200171 networks = self.network_client.list_networks()
172 for net in networks['networks']:
Anastasia Kuznetsova673fc432015-03-12 16:41:36 +0400173 if net['name'] == net_name:
Steve Baker450aa7f2014-08-25 10:37:27 +1200174 return net
175
Sergey Kraynev83ef84d2015-04-29 05:31:54 -0400176 def _get_subnet_by_version(self, network, ip_version=4):
177 for subnet_id in self.net['subnets']:
178 subnet_info = self.network_client.show_subnet(subnet_id)
179 if subnet_info['subnet']['ip_version'] == ip_version:
180 return subnet_id
181
Rabi Mishra95ac9aa2015-04-30 10:40:43 +0530182 def _get_server_ip_by_version(self, addresses, ip_version=4):
183 for address in addresses:
184 if address['version'] == ip_version:
185 return address['addr']
186
Steve Baker450aa7f2014-08-25 10:37:27 +1200187 @staticmethod
188 def _stack_output(stack, output_key):
189 """Return a stack output value for a given key."""
190 return next((o['output_value'] for o in stack.outputs
191 if o['output_key'] == output_key), None)
192
193 def _ping_ip_address(self, ip_address, should_succeed=True):
194 cmd = ['ping', '-c1', '-w1', ip_address]
195
196 def ping():
197 proc = subprocess.Popen(cmd,
198 stdout=subprocess.PIPE,
199 stderr=subprocess.PIPE)
200 proc.wait()
201 return (proc.returncode == 0) == should_succeed
202
203 return call_until_true(
Angus Salkeld08514ad2015-02-06 10:08:31 +1000204 self.conf.build_timeout, 1, ping)
Steve Baker450aa7f2014-08-25 10:37:27 +1200205
Angus Salkelda7500d12015-04-10 15:44:07 +1000206 def _wait_for_all_resource_status(self, stack_identifier,
207 status, failure_pattern='^.*_FAILED$',
208 success_on_not_found=False):
209 for res in self.client.resources.list(stack_identifier):
210 self._wait_for_resource_status(
211 stack_identifier, res.resource_name,
212 status, failure_pattern=failure_pattern,
213 success_on_not_found=success_on_not_found)
214
Steve Baker450aa7f2014-08-25 10:37:27 +1200215 def _wait_for_resource_status(self, stack_identifier, resource_name,
216 status, failure_pattern='^.*_FAILED$',
217 success_on_not_found=False):
218 """Waits for a Resource to reach a given status."""
219 fail_regexp = re.compile(failure_pattern)
220 build_timeout = self.conf.build_timeout
221 build_interval = self.conf.build_interval
222
223 start = timeutils.utcnow()
224 while timeutils.delta_seconds(start,
225 timeutils.utcnow()) < build_timeout:
226 try:
227 res = self.client.resources.get(
228 stack_identifier, resource_name)
229 except heat_exceptions.HTTPNotFound:
230 if success_on_not_found:
231 return
232 # ignore this, as the resource may not have
233 # been created yet
234 else:
235 if res.resource_status == status:
236 return
Sirushti Murugesane5ad25b2015-04-18 23:30:59 +0530237 wait_for_action = status.split('_')[0]
238 resource_action = res.resource_status.split('_')[0]
239 if (resource_action == wait_for_action and
240 fail_regexp.search(res.resource_status)):
Steve Baker450aa7f2014-08-25 10:37:27 +1200241 raise exceptions.StackResourceBuildErrorException(
242 resource_name=res.resource_name,
243 stack_identifier=stack_identifier,
244 resource_status=res.resource_status,
245 resource_status_reason=res.resource_status_reason)
246 time.sleep(build_interval)
247
248 message = ('Resource %s failed to reach %s status within '
249 'the required time (%s s).' %
Anastasia Kuznetsova9a745572015-03-04 13:19:15 +0400250 (resource_name, status, build_timeout))
Steve Baker450aa7f2014-08-25 10:37:27 +1200251 raise exceptions.TimeoutException(message)
252
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530253 def _verify_status(self, stack, stack_identifier, status, fail_regexp):
254 if stack.stack_status == status:
255 # Handle UPDATE_COMPLETE case: Make sure we don't
256 # wait for a stale UPDATE_COMPLETE status.
257 if status == 'UPDATE_COMPLETE':
258 if self.updated_time.get(
259 stack_identifier) != stack.updated_time:
260 self.updated_time[stack_identifier] = stack.updated_time
261 return True
262 else:
263 return True
264
Sirushti Murugesane5ad25b2015-04-18 23:30:59 +0530265 wait_for_action = status.split('_')[0]
266 if (stack.action == wait_for_action and
267 fail_regexp.search(stack.stack_status)):
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530268 # Handle UPDATE_FAILED case.
269 if status == 'UPDATE_FAILED':
270 if self.updated_time.get(
271 stack_identifier) != stack.updated_time:
272 self.updated_time[stack_identifier] = stack.updated_time
273 raise exceptions.StackBuildErrorException(
274 stack_identifier=stack_identifier,
275 stack_status=stack.stack_status,
276 stack_status_reason=stack.stack_status_reason)
277 else:
278 raise exceptions.StackBuildErrorException(
279 stack_identifier=stack_identifier,
280 stack_status=stack.stack_status,
281 stack_status_reason=stack.stack_status_reason)
282
Steve Baker450aa7f2014-08-25 10:37:27 +1200283 def _wait_for_stack_status(self, stack_identifier, status,
284 failure_pattern='^.*_FAILED$',
285 success_on_not_found=False):
286 """
287 Waits for a Stack to reach a given status.
288
289 Note this compares the full $action_$status, e.g
290 CREATE_COMPLETE, not just COMPLETE which is exposed
291 via the status property of Stack in heatclient
292 """
293 fail_regexp = re.compile(failure_pattern)
294 build_timeout = self.conf.build_timeout
295 build_interval = self.conf.build_interval
296
297 start = timeutils.utcnow()
298 while timeutils.delta_seconds(start,
299 timeutils.utcnow()) < build_timeout:
300 try:
301 stack = self.client.stacks.get(stack_identifier)
302 except heat_exceptions.HTTPNotFound:
303 if success_on_not_found:
304 return
305 # ignore this, as the resource may not have
306 # been created yet
307 else:
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530308 if self._verify_status(stack, stack_identifier, status,
309 fail_regexp):
Steve Baker450aa7f2014-08-25 10:37:27 +1200310 return
Sirushti Murugesan13a8a172015-04-14 00:30:05 +0530311
Steve Baker450aa7f2014-08-25 10:37:27 +1200312 time.sleep(build_interval)
313
314 message = ('Stack %s failed to reach %s status within '
315 'the required time (%s s).' %
Anastasia Kuznetsova9a745572015-03-04 13:19:15 +0400316 (stack_identifier, status, build_timeout))
Steve Baker450aa7f2014-08-25 10:37:27 +1200317 raise exceptions.TimeoutException(message)
318
319 def _stack_delete(self, stack_identifier):
320 try:
321 self.client.stacks.delete(stack_identifier)
322 except heat_exceptions.HTTPNotFound:
323 pass
324 self._wait_for_stack_status(
325 stack_identifier, 'DELETE_COMPLETE',
326 success_on_not_found=True)
Steven Hardyc9efd972014-11-20 11:31:55 +0000327
328 def update_stack(self, stack_identifier, template, environment=None,
Sabeen Syed277ea692015-02-04 23:30:02 +0000329 files=None, parameters=None, tags=None,
Rakesh H Sa3325d62015-04-04 19:42:29 +0530330 expected_status='UPDATE_COMPLETE',
331 disable_rollback=True):
Steven Hardyc9efd972014-11-20 11:31:55 +0000332 env = environment or {}
333 env_files = files or {}
Sergey Kraynevbcc78df2015-02-27 04:34:32 -0500334 parameters = parameters or {}
Steven Hardyc9efd972014-11-20 11:31:55 +0000335 stack_name = stack_identifier.split('/')[0]
Sirushti Murugesan3a195a52015-05-01 09:47:09 +0530336
337 build_timeout = self.conf.build_timeout
338 build_interval = self.conf.build_interval
339 start = timeutils.utcnow()
340 while timeutils.delta_seconds(start,
341 timeutils.utcnow()) < build_timeout:
342 try:
343 self.client.stacks.update(
344 stack_id=stack_identifier,
345 stack_name=stack_name,
346 template=template,
347 files=env_files,
348 disable_rollback=disable_rollback,
349 parameters=parameters,
Sabeen Syed277ea692015-02-04 23:30:02 +0000350 environment=env,
351 tags=tags
Sirushti Murugesan3a195a52015-05-01 09:47:09 +0530352 )
353 except heat_exceptions.HTTPConflict as ex:
354 # FIXME(sirushtim): Wait a little for the stack lock to be
355 # released and hopefully, the stack should be updatable again.
356 if ex.error['error']['type'] != 'ActionInProgress':
357 raise ex
358
359 time.sleep(build_interval)
360 else:
361 break
362
Rakesh H Sa3325d62015-04-04 19:42:29 +0530363 kwargs = {'stack_identifier': stack_identifier,
364 'status': expected_status}
365 if expected_status in ['ROLLBACK_COMPLETE']:
Rakesh H Sa3325d62015-04-04 19:42:29 +0530366 # To trigger rollback you would intentionally fail the stack
367 # Hence check for rollback failures
368 kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
369
370 self._wait_for_stack_status(**kwargs)
Steven Hardyc9efd972014-11-20 11:31:55 +0000371
Steven Hardy03da0742015-03-19 00:13:17 -0400372 def assert_resource_is_a_stack(self, stack_identifier, res_name,
373 wait=False):
374 build_timeout = self.conf.build_timeout
375 build_interval = self.conf.build_interval
376 start = timeutils.utcnow()
377 while timeutils.delta_seconds(start,
378 timeutils.utcnow()) < build_timeout:
379 time.sleep(build_interval)
380 try:
381 nested_identifier = self._get_nested_identifier(
382 stack_identifier, res_name)
383 except Exception:
384 # We may have to wait, if the create is in-progress
385 if wait:
386 time.sleep(build_interval)
387 else:
388 raise
389 else:
390 return nested_identifier
391
392 def _get_nested_identifier(self, stack_identifier, res_name):
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000393 rsrc = self.client.resources.get(stack_identifier, res_name)
394 nested_link = [l for l in rsrc.links if l['rel'] == 'nested']
395 nested_href = nested_link[0]['href']
396 nested_id = nested_href.split('/')[-1]
397 nested_identifier = '/'.join(nested_href.split('/')[-2:])
398 self.assertEqual(rsrc.physical_resource_id, nested_id)
399
400 nested_stack = self.client.stacks.get(nested_id)
401 nested_identifier2 = '%s/%s' % (nested_stack.stack_name,
402 nested_stack.id)
403 self.assertEqual(nested_identifier, nested_identifier2)
404 parent_id = stack_identifier.split("/")[-1]
405 self.assertEqual(parent_id, nested_stack.parent)
406 return nested_identifier
407
Steven Hardyc9efd972014-11-20 11:31:55 +0000408 def list_resources(self, stack_identifier):
409 resources = self.client.resources.list(stack_identifier)
410 return dict((r.resource_name, r.resource_type) for r in resources)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000411
412 def stack_create(self, stack_name=None, template=None, files=None,
Sabeen Syed277ea692015-02-04 23:30:02 +0000413 parameters=None, environment=None, tags=None,
414 expected_status='CREATE_COMPLETE',
415 disable_rollback=True, enable_cleanup=True):
Steven Hardyf2c82c02014-11-20 14:02:17 +0000416 name = stack_name or self._stack_rand_name()
417 templ = template or self.template
418 templ_files = files or {}
419 params = parameters or {}
420 env = environment or {}
421 self.client.stacks.create(
422 stack_name=name,
423 template=templ,
424 files=templ_files,
Rakesh H Sa3325d62015-04-04 19:42:29 +0530425 disable_rollback=disable_rollback,
Steven Hardyf2c82c02014-11-20 14:02:17 +0000426 parameters=params,
Sabeen Syed277ea692015-02-04 23:30:02 +0000427 environment=env,
428 tags=tags
Steven Hardyf2c82c02014-11-20 14:02:17 +0000429 )
Sergey Kraynevbf67ce32015-04-17 10:54:20 -0400430 if expected_status not in ['ROLLBACK_COMPLETE'] and enable_cleanup:
Rakesh H Sa3325d62015-04-04 19:42:29 +0530431 self.addCleanup(self.client.stacks.delete, name)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000432
433 stack = self.client.stacks.get(name)
434 stack_identifier = '%s/%s' % (name, stack.id)
Rakesh H Sa3325d62015-04-04 19:42:29 +0530435 kwargs = {'stack_identifier': stack_identifier,
436 'status': expected_status}
Steve Bakerf6c8f122015-02-10 13:54:46 +1300437 if expected_status:
Rakesh H Sa3325d62015-04-04 19:42:29 +0530438 if expected_status in ['ROLLBACK_COMPLETE']:
439 # To trigger rollback you would intentionally fail the stack
440 # Hence check for rollback failures
441 kwargs['failure_pattern'] = '^ROLLBACK_FAILED$'
442 self._wait_for_stack_status(**kwargs)
Steven Hardyf2c82c02014-11-20 14:02:17 +0000443 return stack_identifier
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000444
445 def stack_adopt(self, stack_name=None, files=None,
446 parameters=None, environment=None, adopt_data=None,
447 wait_for_status='ADOPT_COMPLETE'):
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530448 if self.conf.skip_stack_adopt_tests:
449 self.skipTest('Testing Stack adopt disabled in conf, skipping')
Angus Salkeld2bd63a42015-01-07 11:11:29 +1000450 name = stack_name or self._stack_rand_name()
451 templ_files = files or {}
452 params = parameters or {}
453 env = environment or {}
454 self.client.stacks.create(
455 stack_name=name,
456 files=templ_files,
457 disable_rollback=True,
458 parameters=params,
459 environment=env,
460 adopt_stack_data=adopt_data,
461 )
462 self.addCleanup(self.client.stacks.delete, name)
463
464 stack = self.client.stacks.get(name)
465 stack_identifier = '%s/%s' % (name, stack.id)
466 self._wait_for_stack_status(stack_identifier, wait_for_status)
467 return stack_identifier
Sirushti Murugesan04ee8022015-02-02 23:00:23 +0530468
469 def stack_abandon(self, stack_id):
470 if self.conf.skip_stack_abandon_tests:
471 self.addCleanup(self.client.stacks.delete, stack_id)
472 self.skipTest('Testing Stack abandon disabled in conf, skipping')
473 info = self.client.stacks.abandon(stack_id=stack_id)
474 return info
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500475
476 def stack_suspend(self, stack_identifier):
477 stack_name = stack_identifier.split('/')[0]
478 self.client.actions.suspend(stack_name)
Angus Salkelda7500d12015-04-10 15:44:07 +1000479
480 # improve debugging by first checking the resource's state.
481 self._wait_for_all_resource_status(stack_identifier,
482 'SUSPEND_COMPLETE')
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500483 self._wait_for_stack_status(stack_identifier, 'SUSPEND_COMPLETE')
484
485 def stack_resume(self, stack_identifier):
486 stack_name = stack_identifier.split('/')[0]
487 self.client.actions.resume(stack_name)
Angus Salkelda7500d12015-04-10 15:44:07 +1000488
489 # improve debugging by first checking the resource's state.
490 self._wait_for_all_resource_status(stack_identifier,
491 'RESUME_COMPLETE')
Oleksii Chuprykovd9cd9dc2015-02-03 10:34:55 -0500492 self._wait_for_stack_status(stack_identifier, 'RESUME_COMPLETE')
Steven Hardy03da0742015-03-19 00:13:17 -0400493
494 def wait_for_event_with_reason(self, stack_identifier, reason,
495 rsrc_name=None, num_expected=1):
496 build_timeout = self.conf.build_timeout
497 build_interval = self.conf.build_interval
498 start = timeutils.utcnow()
499 while timeutils.delta_seconds(start,
500 timeutils.utcnow()) < build_timeout:
501 try:
502 rsrc_events = self.client.events.list(stack_identifier,
503 resource_name=rsrc_name)
504 except heat_exceptions.HTTPNotFound:
505 LOG.debug("No events yet found for %s" % rsrc_name)
506 else:
507 matched = [e for e in rsrc_events
508 if e.resource_status_reason == reason]
509 if len(matched) == num_expected:
510 return matched
511 time.sleep(build_interval)