Fixed vm instance instantiation from Heat when using nested resources
[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 os
17
18 import pkg_resources
19 import uuid
20
21 import time
22
23 import snaps.config.stack as stack_config
24 from snaps.config.flavor import FlavorConfig
25 from snaps.openstack.create_flavor import OpenStackFlavor
26
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)
35
36 __author__ = 'spisarski'
37
38 logger = logging.getLogger('heat_utils_tests')
39
40
41 class HeatSmokeTests(OSComponentTestCase):
42     """
43     Tests to ensure that the heat client can communicate with the cloud
44     """
45
46     def test_heat_connect_success(self):
47         """
48         Tests to ensure that the proper credentials can connect.
49         """
50         heat = heat_utils.heat_client(self.os_creds)
51
52         # This should not throw an exception
53         stacks = heat.stacks.list()
54         for stack in stacks:
55             print stack
56
57     def test_heat_connect_fail(self):
58         """
59         Tests to ensure that the improper credentials cannot connect.
60         """
61         from snaps.openstack.os_credentials import OSCreds
62
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()
69
70         # This should throw an exception
71         with self.assertRaises(Exception):
72             for stack in stacks:
73                 print stack
74
75
76 class HeatUtilsCreateSimpleStackTests(OSComponentTestCase):
77     """
78     Test basic Heat functionality
79     """
80
81     def setUp(self):
82         """
83         Instantiates OpenStack instances that cannot be spawned by Heat
84         """
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'
91
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()
96
97         # Create Flavor
98         self.flavor_creator = OpenStackFlavor(
99             self.os_creds,
100             FlavorConfig(name=guid + '-flavor', ram=256, disk=10, vcpus=1))
101         self.flavor_creator.create()
102
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)
116         self.stack1 = None
117         self.stack2 = None
118         self.heat_client = heat_utils.heat_client(self.os_creds)
119
120     def tearDown(self):
121         """
122         Cleans the stack and image
123         """
124         if self.stack1:
125             try:
126                 heat_utils.delete_stack(self.heat_client, self.stack1)
127             except:
128                 pass
129
130         if self.stack2:
131             try:
132                 heat_utils.delete_stack(self.heat_client, self.stack2)
133             except:
134                 pass
135
136         if self.image_creator:
137             try:
138                 self.image_creator.clean()
139             except:
140                 pass
141
142         if self.flavor_creator:
143             try:
144                 self.flavor_creator.clean()
145             except:
146                 pass
147
148     def test_create_stack(self):
149         """
150         Tests the creation of an OpenStack Heat stack1 that does not exist.
151         """
152         self.stack1 = heat_utils.create_stack(self.heat_client,
153                                               self.stack_settings1)
154
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)
158
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)
162
163         stack_query_3 = heat_utils.get_stack_by_id(self.heat_client,
164                                                    self.stack1.id)
165         self.assertEqual(self.stack1, stack_query_3)
166
167         resources = heat_utils.get_resources(self.heat_client, self.stack1.id)
168         self.assertIsNotNone(resources)
169         self.assertEqual(4, len(resources))
170
171         outputs = heat_utils.get_outputs(self.heat_client, self.stack1)
172         self.assertIsNotNone(outputs)
173         self.assertEqual(0, len(outputs))
174
175         self.assertTrue(stack_active(self.heat_client, self.stack1))
176
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)
183
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)
187
188         nova = nova_utils.nova_client(self.os_creds)
189         servers = heat_utils.get_stack_servers(
190             self.heat_client, nova, neutron, self.stack1)
191         self.assertIsNotNone(servers)
192         self.assertEqual(1, len(servers))
193         self.assertEqual(self.vm_inst_name, servers[0].name)
194
195     def test_create_stack_x2(self):
196         """
197         Tests the creation of an OpenStack keypair that does not exist.
198         """
199         self.stack1 = heat_utils.create_stack(self.heat_client,
200                                               self.stack_settings1)
201
202         stack1_query_1 = heat_utils.get_stack(
203             self.heat_client, stack_settings=self.stack_settings1)
204         self.assertEqual(self.stack1, stack1_query_1)
205
206         stack1_query_2 = heat_utils.get_stack(
207             self.heat_client, stack_name=self.stack_settings1.name)
208         self.assertEqual(self.stack1, stack1_query_2)
209
210         stack1_query_3 = heat_utils.get_stack_by_id(self.heat_client,
211                                                     self.stack1.id)
212         self.assertEqual(self.stack1, stack1_query_3)
213
214         self.assertTrue(stack_active(self.heat_client, self.stack1))
215
216         self.stack2 = heat_utils.create_stack(self.heat_client,
217                                               self.stack_settings2)
218
219         stack2_query_1 = heat_utils.get_stack(
220             self.heat_client, stack_settings=self.stack_settings2)
221         self.assertEqual(self.stack2, stack2_query_1)
222
223         stack2_query_2 = heat_utils.get_stack(
224             self.heat_client, stack_name=self.stack_settings2.name)
225         self.assertEqual(self.stack2, stack2_query_2)
226
227         stack2_query_3 = heat_utils.get_stack_by_id(self.heat_client,
228                                                     self.stack2.id)
229         self.assertEqual(self.stack2, stack2_query_3)
230
231         self.assertTrue(stack_active(self.heat_client, self.stack2))
232
233
234 class HeatUtilsCreateComplexStackTests(OSComponentTestCase):
235     """
236     Test basic Heat functionality
237     """
238
239     def setUp(self):
240         """
241         Instantiates OpenStack instances that cannot be spawned by Heat
242         """
243         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
244         stack_name = guid + '-stack'
245         self.network_name = guid + '-net'
246         self.subnet_name = guid + '-subnet'
247         self.vm_inst1_name = guid + '-inst1'
248         self.vm_inst2_name = guid + '-inst2'
249         self.flavor1_name = guid + '-flavor1'
250         self.flavor2_name = guid + '-flavor2'
251         self.keypair_name = guid + '-keypair'
252
253         self.image_creator1 = OpenStackImage(
254             self.os_creds, openstack_tests.cirros_image_settings(
255                 name=guid + '-image1', image_metadata=self.image_metadata))
256         self.image_creator1.create()
257
258         self.image_creator2 = OpenStackImage(
259             self.os_creds, openstack_tests.cirros_image_settings(
260                 name=guid + '-image2', image_metadata=self.image_metadata))
261         self.image_creator2.create()
262
263         env_values = {'image1_name': self.image_creator1.image_settings.name,
264                       'image2_name': self.image_creator2.image_settings.name,
265                       'flavor1_name': self.flavor1_name,
266                       'flavor2_name': self.flavor2_name,
267                       'net_name': self.network_name,
268                       'subnet_name': self.subnet_name,
269                       'keypair_name': self.keypair_name,
270                       'inst1_name': self.vm_inst1_name,
271                       'inst2_name': self.vm_inst2_name,
272                       'external_net_name': self.ext_net_name}
273         heat_tmplt_path = pkg_resources.resource_filename(
274             'snaps.openstack.tests.heat', 'floating_ip_heat_template.yaml')
275         stack_settings = StackConfig(
276             name=stack_name, template_path=heat_tmplt_path,
277             env_values=env_values)
278         self.heat_client = heat_utils.heat_client(self.os_creds)
279         self.stack = heat_utils.create_stack(self.heat_client, stack_settings)
280
281         self.assertTrue(stack_active(self.heat_client, self.stack))
282
283         self.keypair1_settings = None
284         self.keypair2_settings = None
285
286     def tearDown(self):
287         """
288         Cleans the stack and image
289         """
290         if self.stack:
291             try:
292                 heat_utils.delete_stack(self.heat_client, self.stack)
293                 # Wait until stack deployment has completed
294                 end_time = (time.time() +
295                             stack_config.STACK_COMPLETE_TIMEOUT)
296                 is_deleted = False
297                 while time.time() < end_time:
298                     status = heat_utils.get_stack_status(self.heat_client,
299                                                          self.stack.id)
300                     if status == stack_config.STATUS_DELETE_COMPLETE:
301                         is_deleted = True
302                         break
303                     elif status == stack_config.STATUS_DELETE_FAILED:
304                         is_deleted = False
305                         break
306
307                     time.sleep(3)
308
309                 if not is_deleted:
310                     nova = nova_utils.nova_client(self.os_creds)
311                     neutron = neutron_utils.neutron_client(self.os_creds)
312                     glance = glance_utils.glance_client(self.os_creds)
313                     servers = heat_utils.get_stack_servers(
314                         self.heat_client, nova, neutron, self.stack)
315                     for server in servers:
316                         vm_settings = settings_utils.create_vm_inst_config(
317                             nova, neutron, server)
318                         img_settings = settings_utils.determine_image_config(
319                             glance, server,
320                             [self.image_creator1.image_settings,
321                              self.image_creator2.image_settings])
322                         vm_creator = OpenStackVmInstance(
323                             self.os_creds, vm_settings, img_settings)
324                         vm_creator.initialize()
325                         vm_creator.clean()
326                         vm_creator.vm_deleted(block=True)
327
328                     heat_utils.delete_stack(self.heat_client, self.stack)
329                     time.sleep(20)
330             except:
331                     raise
332
333         if self.image_creator1:
334             try:
335                 self.image_creator1.clean()
336             except:
337                 pass
338
339         if self.image_creator2:
340             try:
341                 self.image_creator2.clean()
342             except:
343                 pass
344
345         if self.keypair1_settings:
346             expanded_path = os.path.expanduser(
347                 self.keypair1_settings.private_filepath)
348             os.chmod(expanded_path, 0o755)
349             os.remove(expanded_path)
350
351         if self.keypair2_settings:
352             expanded_path = os.path.expanduser(
353                 self.keypair2_settings.private_filepath)
354             os.chmod(expanded_path, 0o755)
355             os.remove(expanded_path)
356
357     def test_get_settings_from_stack(self):
358         """
359         Tests that a heat template with floating IPs and can have the proper
360         settings derived from settings_utils.py.
361         """
362         resources = heat_utils.get_resources(self.heat_client, self.stack.id)
363         self.assertIsNotNone(resources)
364         self.assertEqual(12, len(resources))
365
366         options = heat_utils.get_outputs(self.heat_client, self.stack)
367         self.assertIsNotNone(options)
368         self.assertEqual(1, len(options))
369
370         neutron = neutron_utils.neutron_client(self.os_creds)
371         networks = heat_utils.get_stack_networks(
372             self.heat_client, neutron, self.stack)
373         self.assertIsNotNone(networks)
374         self.assertEqual(1, len(networks))
375         self.assertEqual(self.network_name, networks[0].name)
376
377         network_settings = settings_utils.create_network_config(
378             neutron, networks[0])
379         self.assertIsNotNone(network_settings)
380         self.assertEqual(self.network_name, network_settings.name)
381
382         nova = nova_utils.nova_client(self.os_creds)
383         glance = glance_utils.glance_client(self.os_creds)
384
385         servers = heat_utils.get_stack_servers(
386             self.heat_client, nova, neutron, self.stack)
387         self.assertIsNotNone(servers)
388         self.assertEqual(2, len(servers))
389
390         image_settings = settings_utils.determine_image_config(
391             glance, servers[0],
392             [self.image_creator1.image_settings,
393              self.image_creator2.image_settings])
394
395         self.assertIsNotNone(image_settings)
396         if image_settings.name.endswith('1'):
397             self.assertEqual(
398                 self.image_creator1.image_settings.name, image_settings.name)
399         else:
400             self.assertEqual(
401                 self.image_creator2.image_settings.name, image_settings.name)
402
403         image_settings = settings_utils.determine_image_config(
404             glance, servers[1],
405             [self.image_creator1.image_settings,
406              self.image_creator2.image_settings])
407         if image_settings.name.endswith('1'):
408             self.assertEqual(
409                 self.image_creator1.image_settings.name, image_settings.name)
410         else:
411             self.assertEqual(
412                 self.image_creator2.image_settings.name, image_settings.name)
413
414         self.keypair1_settings = settings_utils.determine_keypair_config(
415             self.heat_client, self.stack, servers[0],
416             priv_key_key='private_key')
417         self.assertIsNotNone(self.keypair1_settings)
418         self.assertEqual(self.keypair_name, self.keypair1_settings.name)
419
420         self.keypair2_settings = settings_utils.determine_keypair_config(
421             self.heat_client, self.stack, servers[1],
422             priv_key_key='private_key')
423         self.assertIsNotNone(self.keypair2_settings)
424         self.assertEqual(self.keypair_name, self.keypair2_settings.name)
425
426
427 class HeatUtilsRouterTests(OSComponentTestCase):
428     """
429     Test Heat volume functionality
430     """
431
432     def setUp(self):
433         """
434         Instantiates OpenStack instances that cannot be spawned by Heat
435         """
436         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
437         stack_name = guid + '-stack'
438
439         self.net_name = guid + '-net'
440         self.subnet_name = guid + '-subnet'
441         self.router_name = guid + '-router'
442
443         env_values = {
444             'net_name': self.net_name,
445             'subnet_name': self.subnet_name,
446             'router_name': self.router_name,
447             'external_net_name': self.ext_net_name}
448
449         heat_tmplt_path = pkg_resources.resource_filename(
450             'snaps.openstack.tests.heat', 'router_heat_template.yaml')
451         self.stack_settings = StackConfig(
452             name=stack_name, template_path=heat_tmplt_path,
453             env_values=env_values)
454         self.stack = None
455         self.heat_client = heat_utils.heat_client(self.os_creds)
456         self.neutron = neutron_utils.neutron_client(self.os_creds)
457
458     def tearDown(self):
459         """
460         Cleans the image and downloaded image file
461         """
462         if self.stack:
463             try:
464                 heat_utils.delete_stack(self.heat_client, self.stack)
465             except:
466                 pass
467
468     def test_create_router_with_stack(self):
469         """
470         Tests the creation of an OpenStack router with Heat and the retrieval
471         of the Router Domain objects from heat_utils#get_stack_routers().
472         """
473         self.stack = heat_utils.create_stack(
474             self.heat_client, self.stack_settings)
475
476         # Wait until stack deployment has completed
477         end_time = time.time() + stack_config.STACK_COMPLETE_TIMEOUT
478         is_active = False
479         while time.time() < end_time:
480             status = heat_utils.get_stack_status(self.heat_client,
481                                                  self.stack.id)
482             if status == stack_config.STATUS_CREATE_COMPLETE:
483                 is_active = True
484                 break
485             elif status == stack_config.STATUS_CREATE_FAILED:
486                 is_active = False
487                 break
488
489             time.sleep(3)
490
491         self.assertTrue(is_active)
492
493         routers = heat_utils.get_stack_routers(
494             self.heat_client, self.neutron, self.stack)
495
496         self.assertEqual(1, len(routers))
497
498         router = routers[0]
499         self.assertEqual(self.router_name, router.name)
500
501         ext_net = neutron_utils.get_network(
502             self.neutron, network_name=self.ext_net_name)
503         self.assertEqual(ext_net.id, router.external_network_id)
504
505
506 class HeatUtilsVolumeTests(OSComponentTestCase):
507     """
508     Test Heat volume functionality
509     """
510
511     def setUp(self):
512         """
513         Instantiates OpenStack instances that cannot be spawned by Heat
514         """
515         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
516         stack_name = guid + '-stack'
517         self.volume_name = guid + '-vol'
518         self.volume_type_name = guid + '-vol-type'
519
520         env_values = {
521             'volume_name': self.volume_name,
522             'volume_type_name': self.volume_type_name}
523
524         heat_tmplt_path = pkg_resources.resource_filename(
525             'snaps.openstack.tests.heat', 'volume_heat_template.yaml')
526         self.stack_settings = StackConfig(
527             name=stack_name, template_path=heat_tmplt_path,
528             env_values=env_values)
529         self.stack = None
530         self.heat_client = heat_utils.heat_client(self.os_creds)
531         self.cinder = cinder_utils.cinder_client(self.os_creds)
532
533     def tearDown(self):
534         """
535         Cleans the stack
536         """
537         if self.stack:
538             try:
539                 heat_utils.delete_stack(self.heat_client, self.stack)
540             except:
541                 pass
542
543     def test_create_vol_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         volumes = heat_utils.get_stack_volumes(
552             self.heat_client, self.cinder, self.stack)
553
554         self.assertEqual(1, len(volumes))
555
556         volume = volumes[0]
557         self.assertEqual(self.volume_name, volume.name)
558         self.assertEqual(self.volume_type_name, volume.type)
559         self.assertEqual(1, volume.size)
560         self.assertEqual(False, volume.multi_attach)
561
562     def test_create_vol_types_with_stack(self):
563         """
564         Tests the creation of an OpenStack volume with Heat.
565         """
566         self.stack = heat_utils.create_stack(
567             self.heat_client, self.stack_settings)
568         self.assertTrue(stack_active(self.heat_client, self.stack))
569
570         volume_types = heat_utils.get_stack_volume_types(
571             self.heat_client, self.cinder, self.stack)
572
573         self.assertEqual(1, len(volume_types))
574
575         volume_type = volume_types[0]
576
577         self.assertEqual(self.volume_type_name, volume_type.name)
578         self.assertTrue(volume_type.public)
579         self.assertIsNone(volume_type.qos_spec)
580
581         # TODO - Add encryption back and find out why it broke in Pike
582         # encryption = volume_type.encryption
583         # self.assertIsNotNone(encryption)
584         # self.assertIsNone(encryption.cipher)
585         # self.assertEqual('front-end', encryption.control_location)
586         # self.assertIsNone(encryption.key_size)
587         # self.assertEqual(u'nova.volume.encryptors.luks.LuksEncryptor',
588         #                  encryption.provider)
589         # self.assertEqual(volume_type.id, encryption.volume_type_id)
590
591
592 class HeatUtilsFlavorTests(OSComponentTestCase):
593     """
594     Test Heat volume functionality
595     """
596
597     def setUp(self):
598         """
599         Instantiates OpenStack instances that cannot be spawned by Heat
600         """
601         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
602         self.name_prefix = guid
603         stack_name = guid + '-stack'
604
605         heat_tmplt_path = pkg_resources.resource_filename(
606             'snaps.openstack.tests.heat', 'flavor_heat_template.yaml')
607         self.stack_settings = StackConfig(
608             name=stack_name, template_path=heat_tmplt_path)
609         self.stack = None
610         self.heat_client = heat_utils.heat_client(self.os_creds)
611         self.nova = nova_utils.nova_client(self.os_creds)
612
613     def tearDown(self):
614         """
615         Cleans the stack
616         """
617         if self.stack:
618             try:
619                 heat_utils.delete_stack(self.heat_client, self.stack)
620             except:
621                 pass
622
623     def test_create_flavor_with_stack(self):
624         """
625         Tests the creation of an OpenStack volume with Heat.
626         """
627         self.stack = heat_utils.create_stack(
628             self.heat_client, self.stack_settings)
629
630         self.assertTrue(stack_active(self.heat_client, self.stack))
631
632         flavors = heat_utils.get_stack_flavors(
633             self.heat_client, self.nova, self.stack)
634
635         self.assertEqual(1, len(flavors))
636
637         flavor = flavors[0]
638         self.assertTrue(flavor.name.startswith(self.name_prefix))
639         self.assertEqual(1024, flavor.ram)
640         self.assertEqual(200, flavor.disk)
641         self.assertEqual(8, flavor.vcpus)
642         self.assertEqual(0, flavor.ephemeral)
643         self.assertIsNone(flavor.swap)
644         self.assertEqual(1.0, flavor.rxtx_factor)
645         self.assertTrue(flavor.is_public)
646
647
648 class HeatUtilsKeypairTests(OSComponentTestCase):
649     """
650     Test Heat volume functionality
651     """
652
653     def setUp(self):
654         """
655         Instantiates OpenStack instances that cannot be spawned by Heat
656         """
657         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
658         stack_name = guid + '-stack'
659         self.keypair_name = guid + '-kp'
660
661         env_values = {'keypair_name': self.keypair_name}
662
663         heat_tmplt_path = pkg_resources.resource_filename(
664             'snaps.openstack.tests.heat', 'keypair_heat_template.yaml')
665         self.stack_settings = StackConfig(
666             name=stack_name, template_path=heat_tmplt_path,
667             env_values=env_values)
668         self.stack = None
669         self.heat_client = heat_utils.heat_client(self.os_creds)
670         self.nova = nova_utils.nova_client(self.os_creds)
671
672     def tearDown(self):
673         """
674         Cleans the stack
675         """
676         if self.stack:
677             try:
678                 heat_utils.delete_stack(self.heat_client, self.stack)
679             except:
680                 pass
681
682     def test_create_keypair_with_stack(self):
683         """
684         Tests the creation of an OpenStack keypair with Heat.
685         """
686         self.stack = heat_utils.create_stack(
687             self.heat_client, self.stack_settings)
688         self.assertTrue(stack_active(self.heat_client, self.stack))
689
690         keypairs = heat_utils.get_stack_keypairs(
691             self.heat_client, self.nova, self.stack)
692
693         self.assertEqual(1, len(keypairs))
694         keypair = keypairs[0]
695
696         self.assertEqual(self.keypair_name, keypair.name)
697
698         outputs = heat_utils.get_outputs(self.heat_client, self.stack)
699
700         for output in outputs:
701             if output.key == 'private_key':
702                 self.assertTrue(output.value.startswith(
703                     '-----BEGIN RSA PRIVATE KEY-----'))
704
705         keypair = nova_utils.get_keypair_by_id(self.nova, keypair.id)
706         self.assertIsNotNone(keypair)
707
708         self.assertEqual(self.keypair_name, keypair.name)
709
710
711 class HeatUtilsSecurityGroupTests(OSComponentTestCase):
712     """
713     Test Heat volume functionality
714     """
715
716     def setUp(self):
717         """
718         Instantiates OpenStack instances that cannot be spawned by Heat
719         """
720         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
721         stack_name = guid + '-stack'
722         self.sec_grp_name = guid + '-sec-grp'
723
724         env_values = {'security_group_name': self.sec_grp_name}
725
726         heat_tmplt_path = pkg_resources.resource_filename(
727             'snaps.openstack.tests.heat', 'security_group_heat_template.yaml')
728         self.stack_settings = StackConfig(
729             name=stack_name, template_path=heat_tmplt_path,
730             env_values=env_values)
731         self.stack = None
732         self.heat_client = heat_utils.heat_client(self.os_creds)
733         self.neutron = neutron_utils.neutron_client(self.os_creds)
734
735     def tearDown(self):
736         """
737         Cleans the stack
738         """
739         if self.stack:
740             try:
741                 heat_utils.delete_stack(self.heat_client, self.stack)
742             except:
743                 pass
744
745     def test_create_security_group_with_stack(self):
746         """
747         Tests the creation of an OpenStack SecurityGroup with Heat.
748         """
749         self.stack = heat_utils.create_stack(
750             self.heat_client, self.stack_settings)
751         self.assertTrue(stack_active(self.heat_client, self.stack))
752
753         sec_grp = heat_utils.get_stack_security_groups(
754             self.heat_client, self.neutron, self.stack)[0]
755
756         self.assertEqual(self.sec_grp_name, sec_grp.name)
757         self.assertEqual('Test description', sec_grp.description)
758         self.assertEqual(2, len(sec_grp.rules))
759
760         has_ssh_rule = False
761         has_icmp_rule = False
762
763         for rule in sec_grp.rules:
764             if (rule.security_group_id == sec_grp.id
765                     and rule.direction == 'egress'
766                     and rule.ethertype == 'IPv4'
767                     and rule.port_range_min == 22
768                     and rule.port_range_max == 22
769                     and rule.protocol == 'tcp'
770                     and rule.remote_group_id is None
771                     and rule.remote_ip_prefix == '0.0.0.0/0'):
772                 has_ssh_rule = True
773             if (rule.security_group_id == sec_grp.id
774                     and rule.direction == 'ingress'
775                     and rule.ethertype == 'IPv4'
776                     and rule.port_range_min is None
777                     and rule.port_range_max is None
778                     and rule.protocol == 'icmp'
779                     and rule.remote_group_id is None
780                     and rule.remote_ip_prefix == '0.0.0.0/0'):
781                 has_icmp_rule = True
782
783         self.assertTrue(has_ssh_rule)
784         self.assertTrue(has_icmp_rule)
785
786
787 def stack_active(heat_cli, stack):
788     """
789     Blocks until stack application has successfully completed or failed
790     :param heat_cli: the Heat client
791     :param stack: the Stack domain object
792     :return: T/F
793     """
794     # Wait until stack deployment has completed
795     end_time = time.time() + stack_config.STACK_COMPLETE_TIMEOUT
796     is_active = False
797     while time.time() < end_time:
798         status = heat_utils.get_stack_status(heat_cli, stack.id)
799         if status == stack_config.STATUS_CREATE_COMPLETE:
800             is_active = True
801             break
802         elif status == stack_config.STATUS_CREATE_FAILED:
803             is_active = False
804             break
805
806         time.sleep(3)
807
808     return is_active