56bed6b2a6e4a23c0f6704e3dea69fbd3137a2ff
[snaps.git] / snaps / openstack / utils / tests / heat_utils_tests.py
1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 #                    and others.  All rights reserved.
3 #
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:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15 import logging
16 import pkg_resources
17 import uuid
18
19 import time
20
21 from snaps.openstack import create_stack
22 from snaps.openstack.create_flavor import OpenStackFlavor, FlavorSettings
23
24 from snaps.openstack.create_image import OpenStackImage
25 from snaps.openstack.create_instance import OpenStackVmInstance
26 from snaps.openstack.create_stack import StackSettings
27 from snaps.openstack.tests import openstack_tests
28 from snaps.openstack.tests.os_source_file_test import OSComponentTestCase
29 from snaps.openstack.utils import (
30     heat_utils, neutron_utils, nova_utils, settings_utils, glance_utils,
31     cinder_utils)
32
33 __author__ = 'spisarski'
34
35 logger = logging.getLogger('heat_utils_tests')
36
37
38 class HeatSmokeTests(OSComponentTestCase):
39     """
40     Tests to ensure that the heat client can communicate with the cloud
41     """
42
43     def test_heat_connect_success(self):
44         """
45         Tests to ensure that the proper credentials can connect.
46         """
47         heat = heat_utils.heat_client(self.os_creds)
48
49         # This should not throw an exception
50         stacks = heat.stacks.list()
51         for stack in stacks:
52             print stack
53
54     def test_heat_connect_fail(self):
55         """
56         Tests to ensure that the improper credentials cannot connect.
57         """
58         from snaps.openstack.os_credentials import OSCreds
59
60         heat = heat_utils.heat_client(
61             OSCreds(username='user', password='pass',
62                     auth_url=self.os_creds.auth_url,
63                     project_name=self.os_creds.project_name,
64                     proxy_settings=self.os_creds.proxy_settings))
65         stacks = heat.stacks.list()
66
67         # This should throw an exception
68         with self.assertRaises(Exception):
69             for stack in stacks:
70                 print stack
71
72
73 class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
74     """
75     Test basic Heat functionality
76     """
77
78     def setUp(self):
79         """
80         Instantiates OpenStack instances that cannot be spawned by Heat
81         """
82         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
83         stack_name1 = guid + '-stack1'
84         stack_name2 = guid + '-stack2'
85         self.network_name = guid + '-net'
86         self.subnet_name = guid + '-subnet'
87         self.vm_inst_name = guid + '-inst'
88
89         self.image_creator = OpenStackImage(
90             self.os_creds, openstack_tests.cirros_image_settings(
91                 name=guid + '-image', image_metadata=self.image_metadata))
92         self.image_creator.create()
93
94         # Create Flavor
95         self.flavor_creator = OpenStackFlavor(
96             self.os_creds,
97             FlavorSettings(name=guid + '-flavor', ram=256, disk=10, vcpus=1))
98         self.flavor_creator.create()
99
100         env_values = {'image_name': self.image_creator.image_settings.name,
101                       'flavor_name': self.flavor_creator.flavor_settings.name,
102                       'net_name': self.network_name,
103                       'subnet_name': self.subnet_name,
104                       'inst_name': self.vm_inst_name}
105         heat_tmplt_path = pkg_resources.resource_filename(
106             'snaps.openstack.tests.heat', 'test_heat_template.yaml')
107         self.stack_settings1 = StackSettings(
108             name=stack_name1, template_path=heat_tmplt_path,
109             env_values=env_values)
110         self.stack_settings2 = StackSettings(
111             name=stack_name2, template_path=heat_tmplt_path,
112             env_values=env_values)
113         self.stack1 = None
114         self.stack2 = None
115         self.heat_client = heat_utils.heat_client(self.os_creds)
116
117     def tearDown(self):
118         """
119         Cleans the image and downloaded image file
120         """
121         if self.stack1:
122             try:
123                 heat_utils.delete_stack(self.heat_client, self.stack1)
124             except:
125                 pass
126
127         if self.stack2:
128             try:
129                 heat_utils.delete_stack(self.heat_client, self.stack2)
130             except:
131                 pass
132
133         if self.image_creator:
134             try:
135                 self.image_creator.clean()
136             except:
137                 pass
138
139         if self.flavor_creator:
140             try:
141                 self.flavor_creator.clean()
142             except:
143                 pass
144
145     def test_create_stack(self):
146         """
147         Tests the creation of an OpenStack Heat stack1 that does not exist.
148         """
149         self.stack1 = heat_utils.create_stack(self.heat_client,
150                                               self.stack_settings1)
151
152         stack_query_1 = heat_utils.get_stack(
153             self.heat_client, stack_settings=self.stack_settings1)
154         self.assertEqual(self.stack1, stack_query_1)
155
156         stack_query_2 = heat_utils.get_stack(
157             self.heat_client, stack_name=self.stack_settings1.name)
158         self.assertEqual(self.stack1, stack_query_2)
159
160         stack_query_3 = heat_utils.get_stack_by_id(self.heat_client,
161                                                    self.stack1.id)
162         self.assertEqual(self.stack1, stack_query_3)
163
164         resources = heat_utils.get_resources(self.heat_client, self.stack1)
165         self.assertIsNotNone(resources)
166         self.assertEqual(4, len(resources))
167
168         outputs = heat_utils.get_outputs(self.heat_client, self.stack1)
169         self.assertIsNotNone(outputs)
170         self.assertEqual(0, len(outputs))
171
172         self.assertTrue(stack_active(self.heat_client, self.stack1))
173
174         neutron = neutron_utils.neutron_client(self.os_creds)
175         networks = heat_utils.get_stack_networks(
176             self.heat_client, neutron, self.stack1)
177         self.assertIsNotNone(networks)
178         self.assertEqual(1, len(networks))
179         self.assertEqual(self.network_name, networks[0].name)
180
181         subnets = neutron_utils.get_subnets_by_network(neutron, networks[0])
182         self.assertEqual(1, len(subnets))
183         self.assertEqual(self.subnet_name, subnets[0].name)
184
185         nova = nova_utils.nova_client(self.os_creds)
186         servers = heat_utils.get_stack_servers(
187             self.heat_client, nova, self.stack1)
188         self.assertIsNotNone(servers)
189         self.assertEqual(1, len(servers))
190         self.assertEqual(self.vm_inst_name, servers[0].name)
191
192     def test_create_stack_x2(self):
193         """
194         Tests the creation of an OpenStack keypair that does not exist.
195         """
196         self.stack1 = heat_utils.create_stack(self.heat_client,
197                                               self.stack_settings1)
198
199         stack1_query_1 = heat_utils.get_stack(
200             self.heat_client, stack_settings=self.stack_settings1)
201         self.assertEqual(self.stack1, stack1_query_1)
202
203         stack1_query_2 = heat_utils.get_stack(
204             self.heat_client, stack_name=self.stack_settings1.name)
205         self.assertEqual(self.stack1, stack1_query_2)
206
207         stack1_query_3 = heat_utils.get_stack_by_id(self.heat_client,
208                                                     self.stack1.id)
209         self.assertEqual(self.stack1, stack1_query_3)
210
211         self.assertTrue(stack_active(self.heat_client, self.stack1))
212
213         self.stack2 = heat_utils.create_stack(self.heat_client,
214                                               self.stack_settings2)
215
216         stack2_query_1 = heat_utils.get_stack(
217             self.heat_client, stack_settings=self.stack_settings2)
218         self.assertEqual(self.stack2, stack2_query_1)
219
220         stack2_query_2 = heat_utils.get_stack(
221             self.heat_client, stack_name=self.stack_settings2.name)
222         self.assertEqual(self.stack2, stack2_query_2)
223
224         stack2_query_3 = heat_utils.get_stack_by_id(self.heat_client,
225                                                     self.stack2.id)
226         self.assertEqual(self.stack2, stack2_query_3)
227
228         self.assertTrue(stack_active(self.heat_client, self.stack2))
229
230
231 class HeatUtilsCreateComplexStackTests(OSComponentTestCase):
232     """
233     Test basic Heat functionality
234     """
235
236     def setUp(self):
237         """
238         Instantiates OpenStack instances that cannot be spawned by Heat
239         """
240         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
241         stack_name = guid + '-stack'
242         self.network_name = guid + '-net'
243         self.subnet_name = guid + '-subnet'
244         self.vm_inst1_name = guid + '-inst1'
245         self.vm_inst2_name = guid + '-inst2'
246         self.flavor1_name = guid + '-flavor1'
247         self.flavor2_name = guid + '-flavor2'
248         self.keypair_name = guid + '-keypair'
249
250         self.image_creator1 = OpenStackImage(
251             self.os_creds, openstack_tests.cirros_image_settings(
252                 name=guid + '-image1', image_metadata=self.image_metadata))
253         self.image_creator1.create()
254
255         self.image_creator2 = OpenStackImage(
256             self.os_creds, openstack_tests.cirros_image_settings(
257                 name=guid + '-image2', image_metadata=self.image_metadata))
258         self.image_creator2.create()
259
260         env_values = {'image1_name': self.image_creator1.image_settings.name,
261                       'image2_name': self.image_creator2.image_settings.name,
262                       'flavor1_name': self.flavor1_name,
263                       'flavor2_name': self.flavor2_name,
264                       'net_name': self.network_name,
265                       'subnet_name': self.subnet_name,
266                       'keypair_name': self.keypair_name,
267                       'inst1_name': self.vm_inst1_name,
268                       'inst2_name': self.vm_inst2_name,
269                       'external_net_name': self.ext_net_name}
270         heat_tmplt_path = pkg_resources.resource_filename(
271             'snaps.openstack.tests.heat', 'floating_ip_heat_template.yaml')
272         stack_settings = StackSettings(
273             name=stack_name, template_path=heat_tmplt_path,
274             env_values=env_values)
275         self.heat_client = heat_utils.heat_client(self.os_creds)
276         self.stack = heat_utils.create_stack(self.heat_client, stack_settings)
277
278         self.assertTrue(stack_active(self.heat_client, self.stack))
279
280     def tearDown(self):
281         """
282         Cleans the image and downloaded image file
283         """
284         if self.stack:
285             try:
286                 heat_utils.delete_stack(self.heat_client, self.stack)
287                 # Wait until stack deployment has completed
288                 end_time = time.time() + create_stack.STACK_COMPLETE_TIMEOUT
289                 is_deleted = False
290                 while time.time() < end_time:
291                     status = heat_utils.get_stack_status(self.heat_client,
292                                                          self.stack.id)
293                     if status == create_stack.STATUS_DELETE_COMPLETE:
294                         is_deleted = True
295                         break
296                     elif status == create_stack.STATUS_DELETE_FAILED:
297                         is_deleted = False
298                         break
299
300                     time.sleep(3)
301
302                 if not is_deleted:
303                     nova = nova_utils.nova_client(self.os_creds)
304                     neutron = neutron_utils.neutron_client(self.os_creds)
305                     glance = glance_utils.glance_client(self.os_creds)
306                     servers = heat_utils.get_stack_servers(
307                         self.heat_client, nova, self.stack)
308                     for server in servers:
309                         vm_settings = settings_utils.create_vm_inst_settings(
310                             nova, neutron, server)
311                         img_settings = settings_utils.determine_image_settings(
312                             glance, server,
313                             [self.image_creator1.image_settings,
314                              self.image_creator2.image_settings])
315                         vm_creator = OpenStackVmInstance(
316                             self.os_creds, vm_settings, img_settings)
317                         vm_creator.initialize()
318                         vm_creator.clean()
319                         vm_creator.vm_deleted(block=True)
320
321                     heat_utils.delete_stack(self.heat_client, self.stack)
322                     time.sleep(20)
323             except:
324                     raise
325
326         if self.image_creator1:
327             try:
328                 self.image_creator1.clean()
329             except:
330                 pass
331
332         if self.image_creator2:
333             try:
334                 self.image_creator2.clean()
335             except:
336                 pass
337
338     def test_get_settings_from_stack(self):
339         """
340         Tests that a heat template with floating IPs and can have the proper
341         settings derived from settings_utils.py.
342         """
343         resources = heat_utils.get_resources(self.heat_client, self.stack)
344         self.assertIsNotNone(resources)
345         self.assertEqual(12, len(resources))
346
347         options = heat_utils.get_outputs(self.heat_client, self.stack)
348         self.assertIsNotNone(options)
349         self.assertEqual(1, len(options))
350
351         neutron = neutron_utils.neutron_client(self.os_creds)
352         networks = heat_utils.get_stack_networks(
353             self.heat_client, neutron, self.stack)
354         self.assertIsNotNone(networks)
355         self.assertEqual(1, len(networks))
356         self.assertEqual(self.network_name, networks[0].name)
357
358         network_settings = settings_utils.create_network_settings(
359             neutron, networks[0])
360         self.assertIsNotNone(network_settings)
361         self.assertEqual(self.network_name, network_settings.name)
362
363         nova = nova_utils.nova_client(self.os_creds)
364         glance = glance_utils.glance_client(self.os_creds)
365
366         servers = heat_utils.get_stack_servers(
367             self.heat_client, nova, self.stack)
368         self.assertIsNotNone(servers)
369         self.assertEqual(2, len(servers))
370
371         image_settings = settings_utils.determine_image_settings(
372             glance, servers[0],
373             [self.image_creator1.image_settings,
374              self.image_creator2.image_settings])
375
376         self.assertIsNotNone(image_settings)
377         if image_settings.name.endswith('1'):
378             self.assertEqual(
379                 self.image_creator1.image_settings.name, image_settings.name)
380         else:
381             self.assertEqual(
382                 self.image_creator2.image_settings.name, image_settings.name)
383
384         image_settings = settings_utils.determine_image_settings(
385             glance, servers[1],
386             [self.image_creator1.image_settings,
387              self.image_creator2.image_settings])
388         if image_settings.name.endswith('1'):
389             self.assertEqual(
390                 self.image_creator1.image_settings.name, image_settings.name)
391         else:
392             self.assertEqual(
393                 self.image_creator2.image_settings.name, image_settings.name)
394
395         keypair1_settings = settings_utils.determine_keypair_settings(
396             self.heat_client, self.stack, servers[0],
397             priv_key_key='private_key')
398         self.assertIsNotNone(keypair1_settings)
399         self.assertEqual(self.keypair_name, keypair1_settings.name)
400
401         keypair2_settings = settings_utils.determine_keypair_settings(
402             self.heat_client, self.stack, servers[1],
403             priv_key_key='private_key')
404         self.assertIsNotNone(keypair2_settings)
405         self.assertEqual(self.keypair_name, keypair2_settings.name)
406
407
408 class HeatUtilsRouterTests(OSComponentTestCase):
409     """
410     Test Heat volume functionality
411     """
412
413     def setUp(self):
414         """
415         Instantiates OpenStack instances that cannot be spawned by Heat
416         """
417         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
418         stack_name = guid + '-stack'
419
420         self.net_name = guid + '-net'
421         self.subnet_name = guid + '-subnet'
422         self.router_name = guid + '-router'
423
424         env_values = {
425             'net_name': self.net_name,
426             'subnet_name': self.subnet_name,
427             'router_name': self.router_name,
428             'external_net_name': self.ext_net_name}
429
430         heat_tmplt_path = pkg_resources.resource_filename(
431             'snaps.openstack.tests.heat', 'router_heat_template.yaml')
432         self.stack_settings = StackSettings(
433             name=stack_name, template_path=heat_tmplt_path,
434             env_values=env_values)
435         self.stack = None
436         self.heat_client = heat_utils.heat_client(self.os_creds)
437         self.neutron = neutron_utils.neutron_client(self.os_creds)
438
439     def tearDown(self):
440         """
441         Cleans the image and downloaded image file
442         """
443         if self.stack:
444             try:
445                 heat_utils.delete_stack(self.heat_client, self.stack)
446             except:
447                 pass
448
449     def test_create_router_with_stack(self):
450         """
451         Tests the creation of an OpenStack router with Heat and the retrieval
452         of the Router Domain objects from heat_utils#get_stack_routers().
453         """
454         self.stack = heat_utils.create_stack(
455             self.heat_client, self.stack_settings)
456
457         # Wait until stack deployment has completed
458         end_time = time.time() + create_stack.STACK_COMPLETE_TIMEOUT
459         is_active = False
460         while time.time() < end_time:
461             status = heat_utils.get_stack_status(self.heat_client,
462                                                  self.stack.id)
463             if status == create_stack.STATUS_CREATE_COMPLETE:
464                 is_active = True
465                 break
466             elif status == create_stack.STATUS_CREATE_FAILED:
467                 is_active = False
468                 break
469
470             time.sleep(3)
471
472         self.assertTrue(is_active)
473
474         routers = heat_utils.get_stack_routers(
475             self.heat_client, self.neutron, self.stack)
476
477         self.assertEqual(1, len(routers))
478
479         router = routers[0]
480         self.assertEqual(self.router_name, router.name)
481
482         ext_net = neutron_utils.get_network(
483             self.neutron, network_name=self.ext_net_name)
484         self.assertEqual(ext_net.id, router.external_network_id)
485
486
487 class HeatUtilsVolumeTests(OSComponentTestCase):
488     """
489     Test Heat volume functionality
490     """
491
492     def setUp(self):
493         """
494         Instantiates OpenStack instances that cannot be spawned by Heat
495         """
496         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
497         stack_name = guid + '-stack'
498         self.volume_name = guid + '-vol'
499         self.volume_type_name = guid + '-vol-type'
500
501         env_values = {
502             'volume_name': self.volume_name,
503             'volume_type_name': self.volume_type_name}
504
505         heat_tmplt_path = pkg_resources.resource_filename(
506             'snaps.openstack.tests.heat', 'volume_heat_template.yaml')
507         self.stack_settings = StackSettings(
508             name=stack_name, template_path=heat_tmplt_path,
509             env_values=env_values)
510         self.stack = None
511         self.heat_client = heat_utils.heat_client(self.os_creds)
512         self.cinder = cinder_utils.cinder_client(self.os_creds)
513
514     def tearDown(self):
515         """
516         Cleans the image and downloaded image file
517         """
518         if self.stack:
519             try:
520                 heat_utils.delete_stack(self.heat_client, self.stack)
521             except:
522                 pass
523
524     def test_create_vol_with_stack(self):
525         """
526         Tests the creation of an OpenStack volume with Heat.
527         """
528         self.stack = heat_utils.create_stack(
529             self.heat_client, self.stack_settings)
530         self.assertTrue(stack_active(self.heat_client, self.stack))
531
532         volumes = heat_utils.get_stack_volumes(
533             self.heat_client, self.cinder, self.stack)
534
535         self.assertEqual(1, len(volumes))
536
537         volume = volumes[0]
538         self.assertEqual(self.volume_name, volume.name)
539         self.assertEqual(self.volume_type_name, volume.type)
540         self.assertEqual(1, volume.size)
541         self.assertEqual(False, volume.multi_attach)
542
543     def test_create_vol_types_with_stack(self):
544         """
545         Tests the creation of an OpenStack volume with Heat.
546         """
547         self.stack = heat_utils.create_stack(
548             self.heat_client, self.stack_settings)
549         self.assertTrue(stack_active(self.heat_client, self.stack))
550
551         volume_types = heat_utils.get_stack_volume_types(
552             self.heat_client, self.cinder, self.stack)
553
554         self.assertEqual(1, len(volume_types))
555
556         volume_type = volume_types[0]
557
558         self.assertEqual(self.volume_type_name, volume_type.name)
559         self.assertTrue(volume_type.public)
560         self.assertIsNone(volume_type.qos_spec)
561
562         encryption = volume_type.encryption
563         self.assertIsNotNone(encryption)
564         self.assertIsNone(encryption.cipher)
565         self.assertEqual('front-end', encryption.control_location)
566         self.assertIsNone(encryption.key_size)
567         self.assertEqual(u'nova.volume.encryptors.luks.LuksEncryptor',
568                          encryption.provider)
569         self.assertEqual(volume_type.id, encryption.volume_type_id)
570
571
572 class HeatUtilsFlavorTests(OSComponentTestCase):
573     """
574     Test Heat volume functionality
575     """
576
577     def setUp(self):
578         """
579         Instantiates OpenStack instances that cannot be spawned by Heat
580         """
581         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
582         self.name_prefix = guid
583         stack_name = guid + '-stack'
584
585         heat_tmplt_path = pkg_resources.resource_filename(
586             'snaps.openstack.tests.heat', 'flavor_heat_template.yaml')
587         self.stack_settings = StackSettings(
588             name=stack_name, template_path=heat_tmplt_path)
589         self.stack = None
590         self.heat_client = heat_utils.heat_client(self.os_creds)
591         self.nova = nova_utils.nova_client(self.os_creds)
592
593     def tearDown(self):
594         """
595         Cleans the image and downloaded image file
596         """
597         if self.stack:
598             try:
599                 heat_utils.delete_stack(self.heat_client, self.stack)
600             except:
601                 pass
602
603     def test_create_flavor_with_stack(self):
604         """
605         Tests the creation of an OpenStack volume with Heat.
606         """
607         self.stack = heat_utils.create_stack(
608             self.heat_client, self.stack_settings)
609
610         self.assertTrue(stack_active(self.heat_client, self.stack))
611
612         flavors = heat_utils.get_stack_flavors(
613             self.heat_client, self.nova, self.stack)
614
615         self.assertEqual(1, len(flavors))
616
617         flavor = flavors[0]
618         self.assertTrue(flavor.name.startswith(self.name_prefix))
619         self.assertEqual(1024, flavor.ram)
620         self.assertEqual(200, flavor.disk)
621         self.assertEqual(8, flavor.vcpus)
622         self.assertEqual(0, flavor.ephemeral)
623         self.assertIsNone(flavor.swap)
624         self.assertEqual(1.0, flavor.rxtx_factor)
625         self.assertTrue(flavor.is_public)
626
627
628 class HeatUtilsKeypairTests(OSComponentTestCase):
629     """
630     Test Heat volume functionality
631     """
632
633     def setUp(self):
634         """
635         Instantiates OpenStack instances that cannot be spawned by Heat
636         """
637         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
638         stack_name = guid + '-stack'
639         self.keypair_name = guid + '-kp'
640
641         env_values = {
642             'keypair_name': self.keypair_name}
643
644         heat_tmplt_path = pkg_resources.resource_filename(
645             'snaps.openstack.tests.heat', 'keypair_heat_template.yaml')
646         self.stack_settings = StackSettings(
647             name=stack_name, template_path=heat_tmplt_path,
648             env_values=env_values)
649         self.stack = None
650         self.heat_client = heat_utils.heat_client(self.os_creds)
651         self.nova = nova_utils.nova_client(self.os_creds)
652
653     def tearDown(self):
654         """
655         Cleans the image and downloaded image file
656         """
657         if self.stack:
658             try:
659                 heat_utils.delete_stack(self.heat_client, self.stack)
660             except:
661                 pass
662
663     def test_create_keypair_with_stack(self):
664         """
665         Tests the creation of an OpenStack keypair with Heat.
666         """
667         self.stack = heat_utils.create_stack(
668             self.heat_client, self.stack_settings)
669         self.assertTrue(stack_active(self.heat_client, self.stack))
670
671         keypairs = heat_utils.get_stack_keypairs(
672             self.heat_client, self.nova, self.stack)
673
674         self.assertEqual(1, len(keypairs))
675         keypair = keypairs[0]
676
677         self.assertEqual(self.keypair_name, keypair.name)
678
679         outputs = heat_utils.get_outputs(self.heat_client, self.stack)
680
681         for output in outputs:
682             if output.key == 'private_key':
683                 self.assertTrue(output.value.startswith(
684                     '-----BEGIN RSA PRIVATE KEY-----'))
685
686         keypair = nova_utils.get_keypair_by_id(self.nova, keypair.id)
687         self.assertIsNotNone(keypair)
688
689         self.assertEqual(self.keypair_name, keypair.name)
690
691
692 def stack_active(heat_cli, stack):
693     """
694     Blocks until stack application has successfully completed or failed
695     :param heat_cli: the Heat client
696     :param stack: the Stack domain object
697     :return: T/F
698     """
699     # Wait until stack deployment has completed
700     end_time = time.time() + create_stack.STACK_COMPLETE_TIMEOUT
701     is_active = False
702     while time.time() < end_time:
703         status = heat_utils.get_stack_status(heat_cli, stack.id)
704         if status == create_stack.STATUS_CREATE_COMPLETE:
705             is_active = True
706             break
707         elif status == create_stack.STATUS_CREATE_FAILED:
708             is_active = False
709             break
710
711         time.sleep(3)
712
713     return is_active