1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 # and others. All rights reserved.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
23 import snaps.config.stack as stack_config
24 from snaps.config.flavor import FlavorConfig
25 from snaps.openstack.create_flavor import OpenStackFlavor
27 from snaps.openstack.create_image import OpenStackImage
28 from snaps.openstack.create_instance import OpenStackVmInstance
29 from snaps.openstack.create_stack import StackConfig
30 from snaps.openstack.tests import openstack_tests
31 from snaps.openstack.tests.os_source_file_test import OSComponentTestCase
32 from snaps.openstack.utils import (
33 heat_utils, neutron_utils, nova_utils, settings_utils, glance_utils,
34 cinder_utils, keystone_utils)
36 __author__ = 'spisarski'
38 logger = logging.getLogger('heat_utils_tests')
41 class HeatSmokeTests(OSComponentTestCase):
43 Tests to ensure that the heat client can communicate with the cloud
46 def test_heat_connect_success(self):
48 Tests to ensure that the proper credentials can connect.
50 heat = heat_utils.heat_client(self.os_creds)
52 # This should not throw an exception
53 stacks = heat.stacks.list()
57 def test_heat_connect_fail(self):
59 Tests to ensure that the improper credentials cannot connect.
61 from snaps.openstack.os_credentials import OSCreds
63 heat = heat_utils.heat_client(
64 OSCreds(username='user', password='pass',
65 auth_url=self.os_creds.auth_url,
66 project_name=self.os_creds.project_name,
67 proxy_settings=self.os_creds.proxy_settings))
68 stacks = heat.stacks.list()
70 # This should throw an exception
71 with self.assertRaises(Exception):
76 class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
78 Test basic Heat functionality
83 Instantiates OpenStack instances that cannot be spawned by Heat
85 guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
86 stack_name1 = guid + '-stack1'
87 stack_name2 = guid + '-stack2'
88 self.network_name = guid + '-net'
89 self.subnet_name = guid + '-subnet'
90 self.vm_inst_name = guid + '-inst'
92 self.image_creator = OpenStackImage(
93 self.os_creds, openstack_tests.cirros_image_settings(
94 name=guid + '-image', image_metadata=self.image_metadata))
95 self.image_creator.create()
98 self.flavor_creator = OpenStackFlavor(
100 FlavorConfig(name=guid + '-flavor', ram=256, disk=10, vcpus=1))
101 self.flavor_creator.create()
103 env_values = {'image_name': self.image_creator.image_settings.name,
104 'flavor_name': self.flavor_creator.flavor_settings.name,
105 'net_name': self.network_name,
106 'subnet_name': self.subnet_name,
107 'inst_name': self.vm_inst_name}
108 heat_tmplt_path = pkg_resources.resource_filename(
109 'snaps.openstack.tests.heat', 'test_heat_template.yaml')
110 self.stack_settings1 = StackConfig(
111 name=stack_name1, template_path=heat_tmplt_path,
112 env_values=env_values)
113 self.stack_settings2 = StackConfig(
114 name=stack_name2, template_path=heat_tmplt_path,
115 env_values=env_values)
118 self.heat_client = heat_utils.heat_client(self.os_creds)
122 Cleans the stack and image
126 heat_utils.delete_stack(self.heat_client, self.stack1)
132 heat_utils.delete_stack(self.heat_client, self.stack2)
136 if self.image_creator:
138 self.image_creator.clean()
142 if self.flavor_creator:
144 self.flavor_creator.clean()
148 def test_create_stack(self):
150 Tests the creation of an OpenStack Heat stack1 that does not exist.
152 self.stack1 = heat_utils.create_stack(self.heat_client,
153 self.stack_settings1)
155 stack_query_1 = heat_utils.get_stack(
156 self.heat_client, stack_settings=self.stack_settings1)
157 self.assertEqual(self.stack1, stack_query_1)
159 stack_query_2 = heat_utils.get_stack(
160 self.heat_client, stack_name=self.stack_settings1.name)
161 self.assertEqual(self.stack1, stack_query_2)
163 stack_query_3 = heat_utils.get_stack_by_id(self.heat_client,
165 self.assertEqual(self.stack1, stack_query_3)
167 resources = heat_utils.get_resources(self.heat_client, self.stack1.id)
168 self.assertIsNotNone(resources)
169 self.assertEqual(4, len(resources))
171 outputs = heat_utils.get_outputs(self.heat_client, self.stack1)
172 self.assertIsNotNone(outputs)
173 self.assertEqual(0, len(outputs))
175 self.assertTrue(stack_active(self.heat_client, self.stack1))
177 neutron = neutron_utils.neutron_client(self.os_creds)
178 networks = heat_utils.get_stack_networks(
179 self.heat_client, neutron, self.stack1)
180 self.assertIsNotNone(networks)
181 self.assertEqual(1, len(networks))
182 self.assertEqual(self.network_name, networks[0].name)
184 subnets = neutron_utils.get_subnets_by_network(neutron, networks[0])
185 self.assertEqual(1, len(subnets))
186 self.assertEqual(self.subnet_name, subnets[0].name)
188 nova = nova_utils.nova_client(self.os_creds)
189 keystone = keystone_utils.keystone_client(self.os_creds)
190 servers = heat_utils.get_stack_servers(
191 self.heat_client, nova, neutron, keystone, self.stack1,
192 self.os_creds.project_name)
193 self.assertIsNotNone(servers)
194 self.assertEqual(1, len(servers))
195 self.assertEqual(self.vm_inst_name, servers[0].name)
197 def test_create_stack_x2(self):
199 Tests the creation of an OpenStack keypair that does not exist.
201 self.stack1 = heat_utils.create_stack(self.heat_client,
202 self.stack_settings1)
204 stack1_query_1 = heat_utils.get_stack(
205 self.heat_client, stack_settings=self.stack_settings1)
206 self.assertEqual(self.stack1, stack1_query_1)
208 stack1_query_2 = heat_utils.get_stack(
209 self.heat_client, stack_name=self.stack_settings1.name)
210 self.assertEqual(self.stack1, stack1_query_2)
212 stack1_query_3 = heat_utils.get_stack_by_id(self.heat_client,
214 self.assertEqual(self.stack1, stack1_query_3)
216 self.assertTrue(stack_active(self.heat_client, self.stack1))
218 self.stack2 = heat_utils.create_stack(self.heat_client,
219 self.stack_settings2)
221 stack2_query_1 = heat_utils.get_stack(
222 self.heat_client, stack_settings=self.stack_settings2)
223 self.assertEqual(self.stack2, stack2_query_1)
225 stack2_query_2 = heat_utils.get_stack(
226 self.heat_client, stack_name=self.stack_settings2.name)
227 self.assertEqual(self.stack2, stack2_query_2)
229 stack2_query_3 = heat_utils.get_stack_by_id(self.heat_client,
231 self.assertEqual(self.stack2, stack2_query_3)
233 self.assertTrue(stack_active(self.heat_client, self.stack2))
236 class HeatUtilsCreateComplexStackTests(OSComponentTestCase):
238 Test basic Heat functionality
243 Instantiates OpenStack instances that cannot be spawned by Heat
245 guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
246 stack_name = guid + '-stack'
247 self.network_name = guid + '-net'
248 self.subnet_name = guid + '-subnet'
249 self.vm_inst1_name = guid + '-inst1'
250 self.vm_inst2_name = guid + '-inst2'
251 self.flavor1_name = guid + '-flavor1'
252 self.flavor2_name = guid + '-flavor2'
253 self.keypair_name = guid + '-keypair'
255 self.image_creator1 = OpenStackImage(
256 self.os_creds, openstack_tests.cirros_image_settings(
257 name=guid + '-image1', image_metadata=self.image_metadata))
258 self.image_creator1.create()
260 self.image_creator2 = OpenStackImage(
261 self.os_creds, openstack_tests.cirros_image_settings(
262 name=guid + '-image2', image_metadata=self.image_metadata))
263 self.image_creator2.create()
265 env_values = {'image1_name': self.image_creator1.image_settings.name,
266 'image2_name': self.image_creator2.image_settings.name,
267 'flavor1_name': self.flavor1_name,
268 'flavor2_name': self.flavor2_name,
269 'net_name': self.network_name,
270 'subnet_name': self.subnet_name,
271 'keypair_name': self.keypair_name,
272 'inst1_name': self.vm_inst1_name,
273 'inst2_name': self.vm_inst2_name,
274 'external_net_name': self.ext_net_name}
275 heat_tmplt_path = pkg_resources.resource_filename(
276 'snaps.openstack.tests.heat', 'floating_ip_heat_template.yaml')
277 stack_settings = StackConfig(
278 name=stack_name, template_path=heat_tmplt_path,
279 env_values=env_values)
280 self.heat_client = heat_utils.heat_client(self.os_creds)
281 self.stack = heat_utils.create_stack(self.heat_client, stack_settings)
283 self.assertTrue(stack_active(self.heat_client, self.stack))
285 self.keypair1_settings = None
286 self.keypair2_settings = None
290 Cleans the stack and image
294 heat_utils.delete_stack(self.heat_client, self.stack)
295 # Wait until stack deployment has completed
296 end_time = (time.time() +
297 stack_config.STACK_COMPLETE_TIMEOUT)
299 while time.time() < end_time:
300 status = heat_utils.get_stack_status(self.heat_client,
302 if status == stack_config.STATUS_DELETE_COMPLETE:
305 elif status == stack_config.STATUS_DELETE_FAILED:
312 nova = nova_utils.nova_client(self.os_creds)
313 keystone = keystone_utils.keystone_client(self.os_creds)
314 neutron = neutron_utils.neutron_client(self.os_creds)
315 glance = glance_utils.glance_client(self.os_creds)
316 servers = heat_utils.get_stack_servers(
317 self.heat_client, nova, neutron, keystone, self.stack,
318 self.os_creds.project_name)
319 for server in servers:
320 vm_settings = settings_utils.create_vm_inst_config(
321 nova, keystone, neutron, server,
322 self.os_creds.project_name)
323 img_settings = settings_utils.determine_image_config(
325 [self.image_creator1.image_settings,
326 self.image_creator2.image_settings])
327 vm_creator = OpenStackVmInstance(
328 self.os_creds, vm_settings, img_settings)
329 vm_creator.initialize()
331 vm_creator.vm_deleted(block=True)
333 heat_utils.delete_stack(self.heat_client, self.stack)
338 if self.image_creator1:
340 self.image_creator1.clean()
344 if self.image_creator2:
346 self.image_creator2.clean()
350 if self.keypair1_settings:
351 expanded_path = os.path.expanduser(
352 self.keypair1_settings.private_filepath)
353 os.chmod(expanded_path, 0o755)
354 os.remove(expanded_path)
356 if self.keypair2_settings:
357 expanded_path = os.path.expanduser(
358 self.keypair2_settings.private_filepath)
359 os.chmod(expanded_path, 0o755)
360 os.remove(expanded_path)
362 def test_get_settings_from_stack(self):
364 Tests that a heat template with floating IPs and can have the proper
365 settings derived from settings_utils.py.
367 resources = heat_utils.get_resources(self.heat_client, self.stack.id)
368 self.assertIsNotNone(resources)
369 self.assertEqual(12, len(resources))
371 options = heat_utils.get_outputs(self.heat_client, self.stack)
372 self.assertIsNotNone(options)
373 self.assertEqual(1, len(options))
375 neutron = neutron_utils.neutron_client(self.os_creds)
376 networks = heat_utils.get_stack_networks(
377 self.heat_client, neutron, self.stack)
378 self.assertIsNotNone(networks)
379 self.assertEqual(1, len(networks))
380 self.assertEqual(self.network_name, networks[0].name)
382 network_settings = settings_utils.create_network_config(
383 neutron, networks[0])
384 self.assertIsNotNone(network_settings)
385 self.assertEqual(self.network_name, network_settings.name)
387 nova = nova_utils.nova_client(self.os_creds)
388 glance = glance_utils.glance_client(self.os_creds)
389 keystone = keystone_utils.keystone_client(self.os_creds)
390 servers = heat_utils.get_stack_servers(
391 self.heat_client, nova, neutron, keystone, self.stack,
392 self.os_creds.project_name)
393 self.assertIsNotNone(servers)
394 self.assertEqual(2, len(servers))
396 image_settings = settings_utils.determine_image_config(
398 [self.image_creator1.image_settings,
399 self.image_creator2.image_settings])
401 self.assertIsNotNone(image_settings)
402 if image_settings.name.endswith('1'):
404 self.image_creator1.image_settings.name, image_settings.name)
407 self.image_creator2.image_settings.name, image_settings.name)
409 image_settings = settings_utils.determine_image_config(
411 [self.image_creator1.image_settings,
412 self.image_creator2.image_settings])
413 if image_settings.name.endswith('1'):
415 self.image_creator1.image_settings.name, image_settings.name)
418 self.image_creator2.image_settings.name, image_settings.name)
420 self.keypair1_settings = settings_utils.determine_keypair_config(
421 self.heat_client, self.stack, servers[0],
422 priv_key_key='private_key')
423 self.assertIsNotNone(self.keypair1_settings)
424 self.assertEqual(self.keypair_name, self.keypair1_settings.name)
426 self.keypair2_settings = settings_utils.determine_keypair_config(
427 self.heat_client, self.stack, servers[1],
428 priv_key_key='private_key')
429 self.assertIsNotNone(self.keypair2_settings)
430 self.assertEqual(self.keypair_name, self.keypair2_settings.name)
433 class HeatUtilsRouterTests(OSComponentTestCase):
435 Test Heat volume functionality
440 Instantiates OpenStack instances that cannot be spawned by Heat
442 guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
443 stack_name = guid + '-stack'
445 self.net_name = guid + '-net'
446 self.subnet_name = guid + '-subnet'
447 self.router_name = guid + '-router'
450 'net_name': self.net_name,
451 'subnet_name': self.subnet_name,
452 'router_name': self.router_name,
453 'external_net_name': self.ext_net_name}
455 heat_tmplt_path = pkg_resources.resource_filename(
456 'snaps.openstack.tests.heat', 'router_heat_template.yaml')
457 self.stack_settings = StackConfig(
458 name=stack_name, template_path=heat_tmplt_path,
459 env_values=env_values)
461 self.heat_client = heat_utils.heat_client(self.os_creds)
462 self.neutron = neutron_utils.neutron_client(self.os_creds)
466 Cleans the image and downloaded image file
470 heat_utils.delete_stack(self.heat_client, self.stack)
474 def test_create_router_with_stack(self):
476 Tests the creation of an OpenStack router with Heat and the retrieval
477 of the Router Domain objects from heat_utils#get_stack_routers().
479 self.stack = heat_utils.create_stack(
480 self.heat_client, self.stack_settings)
482 # Wait until stack deployment has completed
483 end_time = time.time() + stack_config.STACK_COMPLETE_TIMEOUT
485 while time.time() < end_time:
486 status = heat_utils.get_stack_status(self.heat_client,
488 if status == stack_config.STATUS_CREATE_COMPLETE:
491 elif status == stack_config.STATUS_CREATE_FAILED:
497 self.assertTrue(is_active)
499 routers = heat_utils.get_stack_routers(
500 self.heat_client, self.neutron, self.stack)
502 self.assertEqual(1, len(routers))
505 self.assertEqual(self.router_name, router.name)
507 keystone = keystone_utils.keystone_client(self.os_creds)
508 ext_net = neutron_utils.get_network(
509 self.neutron, keystone, network_name=self.ext_net_name)
510 self.assertEqual(ext_net.id, router.external_network_id)
513 class HeatUtilsVolumeTests(OSComponentTestCase):
515 Test Heat volume functionality
520 Instantiates OpenStack instances that cannot be spawned by Heat
522 guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
523 stack_name = guid + '-stack'
524 self.volume_name = guid + '-vol'
525 self.volume_type_name = guid + '-vol-type'
528 'volume_name': self.volume_name,
529 'volume_type_name': self.volume_type_name}
531 heat_tmplt_path = pkg_resources.resource_filename(
532 'snaps.openstack.tests.heat', 'volume_heat_template.yaml')
533 self.stack_settings = StackConfig(
534 name=stack_name, template_path=heat_tmplt_path,
535 env_values=env_values)
537 self.heat_client = heat_utils.heat_client(self.os_creds)
538 self.cinder = cinder_utils.cinder_client(self.os_creds)
546 heat_utils.delete_stack(self.heat_client, self.stack)
550 def test_create_vol_with_stack(self):
552 Tests the creation of an OpenStack volume with Heat.
554 self.stack = heat_utils.create_stack(
555 self.heat_client, self.stack_settings)
556 self.assertTrue(stack_active(self.heat_client, self.stack))
558 volumes = heat_utils.get_stack_volumes(
559 self.heat_client, self.cinder, self.stack)
561 self.assertEqual(1, len(volumes))
564 self.assertEqual(self.volume_name, volume.name)
565 self.assertEqual(self.volume_type_name, volume.type)
566 self.assertEqual(1, volume.size)
567 self.assertEqual(False, volume.multi_attach)
569 def test_create_vol_types_with_stack(self):
571 Tests the creation of an OpenStack volume with Heat.
573 self.stack = heat_utils.create_stack(
574 self.heat_client, self.stack_settings)
575 self.assertTrue(stack_active(self.heat_client, self.stack))
577 volume_types = heat_utils.get_stack_volume_types(
578 self.heat_client, self.cinder, self.stack)
580 self.assertEqual(1, len(volume_types))
582 volume_type = volume_types[0]
584 self.assertEqual(self.volume_type_name, volume_type.name)
585 self.assertTrue(volume_type.public)
586 self.assertIsNone(volume_type.qos_spec)
588 # TODO - Add encryption back and find out why it broke in Pike
589 # encryption = volume_type.encryption
590 # self.assertIsNotNone(encryption)
591 # self.assertIsNone(encryption.cipher)
592 # self.assertEqual('front-end', encryption.control_location)
593 # self.assertIsNone(encryption.key_size)
594 # self.assertEqual(u'nova.volume.encryptors.luks.LuksEncryptor',
595 # encryption.provider)
596 # self.assertEqual(volume_type.id, encryption.volume_type_id)
599 class HeatUtilsFlavorTests(OSComponentTestCase):
601 Test Heat volume functionality
606 Instantiates OpenStack instances that cannot be spawned by Heat
608 guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
609 self.name_prefix = guid
610 stack_name = guid + '-stack'
612 heat_tmplt_path = pkg_resources.resource_filename(
613 'snaps.openstack.tests.heat', 'flavor_heat_template.yaml')
614 self.stack_settings = StackConfig(
615 name=stack_name, template_path=heat_tmplt_path)
617 self.heat_client = heat_utils.heat_client(self.os_creds)
618 self.nova = nova_utils.nova_client(self.os_creds)
626 heat_utils.delete_stack(self.heat_client, self.stack)
630 def test_create_flavor_with_stack(self):
632 Tests the creation of an OpenStack volume with Heat.
634 self.stack = heat_utils.create_stack(
635 self.heat_client, self.stack_settings)
637 self.assertTrue(stack_active(self.heat_client, self.stack))
639 flavors = heat_utils.get_stack_flavors(
640 self.heat_client, self.nova, self.stack)
642 self.assertEqual(1, len(flavors))
645 self.assertTrue(flavor.name.startswith(self.name_prefix))
646 self.assertEqual(1024, flavor.ram)
647 self.assertEqual(200, flavor.disk)
648 self.assertEqual(8, flavor.vcpus)
649 self.assertEqual(0, flavor.ephemeral)
650 self.assertIsNone(flavor.swap)
651 self.assertEqual(1.0, flavor.rxtx_factor)
652 self.assertTrue(flavor.is_public)
655 class HeatUtilsKeypairTests(OSComponentTestCase):
657 Test Heat volume functionality
662 Instantiates OpenStack instances that cannot be spawned by Heat
664 guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
665 stack_name = guid + '-stack'
666 self.keypair_name = guid + '-kp'
668 env_values = {'keypair_name': self.keypair_name}
670 heat_tmplt_path = pkg_resources.resource_filename(
671 'snaps.openstack.tests.heat', 'keypair_heat_template.yaml')
672 self.stack_settings = StackConfig(
673 name=stack_name, template_path=heat_tmplt_path,
674 env_values=env_values)
676 self.heat_client = heat_utils.heat_client(self.os_creds)
677 self.nova = nova_utils.nova_client(self.os_creds)
685 heat_utils.delete_stack(self.heat_client, self.stack)
689 def test_create_keypair_with_stack(self):
691 Tests the creation of an OpenStack keypair with Heat.
693 self.stack = heat_utils.create_stack(
694 self.heat_client, self.stack_settings)
695 self.assertTrue(stack_active(self.heat_client, self.stack))
697 keypairs = heat_utils.get_stack_keypairs(
698 self.heat_client, self.nova, self.stack)
700 self.assertEqual(1, len(keypairs))
701 keypair = keypairs[0]
703 self.assertEqual(self.keypair_name, keypair.name)
705 outputs = heat_utils.get_outputs(self.heat_client, self.stack)
707 for output in outputs:
708 if output.key == 'private_key':
709 self.assertTrue(output.value.startswith(
710 '-----BEGIN RSA PRIVATE KEY-----'))
712 keypair = nova_utils.get_keypair_by_id(self.nova, keypair.id)
713 self.assertIsNotNone(keypair)
715 self.assertEqual(self.keypair_name, keypair.name)
718 class HeatUtilsSecurityGroupTests(OSComponentTestCase):
720 Test Heat volume functionality
725 Instantiates OpenStack instances that cannot be spawned by Heat
727 guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
728 stack_name = guid + '-stack'
729 self.sec_grp_name = guid + '-sec-grp'
731 env_values = {'security_group_name': self.sec_grp_name}
733 heat_tmplt_path = pkg_resources.resource_filename(
734 'snaps.openstack.tests.heat', 'security_group_heat_template.yaml')
735 self.stack_settings = StackConfig(
736 name=stack_name, template_path=heat_tmplt_path,
737 env_values=env_values)
739 self.heat_client = heat_utils.heat_client(self.os_creds)
740 self.neutron = neutron_utils.neutron_client(self.os_creds)
748 heat_utils.delete_stack(self.heat_client, self.stack)
752 def test_create_security_group_with_stack(self):
754 Tests the creation of an OpenStack SecurityGroup with Heat.
756 self.stack = heat_utils.create_stack(
757 self.heat_client, self.stack_settings)
758 self.assertTrue(stack_active(self.heat_client, self.stack))
760 sec_grp = heat_utils.get_stack_security_groups(
761 self.heat_client, self.neutron, self.stack)[0]
763 self.assertEqual(self.sec_grp_name, sec_grp.name)
764 self.assertEqual('Test description', sec_grp.description)
765 self.assertEqual(2, len(sec_grp.rules))
768 has_icmp_rule = False
770 for rule in sec_grp.rules:
771 if (rule.security_group_id == sec_grp.id
772 and rule.direction == 'egress'
773 and rule.ethertype == 'IPv4'
774 and rule.port_range_min == 22
775 and rule.port_range_max == 22
776 and rule.protocol == 'tcp'
777 and rule.remote_group_id is None
778 and rule.remote_ip_prefix == '0.0.0.0/0'):
780 if (rule.security_group_id == sec_grp.id
781 and rule.direction == 'ingress'
782 and rule.ethertype == 'IPv4'
783 and rule.port_range_min is None
784 and rule.port_range_max is None
785 and rule.protocol == 'icmp'
786 and rule.remote_group_id is None
787 and rule.remote_ip_prefix == '0.0.0.0/0'):
790 self.assertTrue(has_ssh_rule)
791 self.assertTrue(has_icmp_rule)
794 def stack_active(heat_cli, stack):
796 Blocks until stack application has successfully completed or failed
797 :param heat_cli: the Heat client
798 :param stack: the Stack domain object
801 # Wait until stack deployment has completed
802 end_time = time.time() + stack_config.STACK_COMPLETE_TIMEOUT
804 while time.time() < end_time:
805 status = heat_utils.get_stack_status(heat_cli, stack.id)
806 if status == stack_config.STATUS_CREATE_COMPLETE:
809 elif status == stack_config.STATUS_CREATE_FAILED: