ee95a63688842d7eb6c9708ec1daa03b59d0e137
[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, self.project_id)
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                         self.project_id)
316                     for server in servers:
317                         vm_settings = settings_utils.create_vm_inst_config(
318                             nova, neutron, server, self.project_id)
319                         img_settings = settings_utils.determine_image_config(
320                             glance, server,
321                             [self.image_creator1.image_settings,
322                              self.image_creator2.image_settings])
323                         vm_creator = OpenStackVmInstance(
324                             self.os_creds, vm_settings, img_settings)
325                         vm_creator.initialize()
326                         vm_creator.clean()
327                         vm_creator.vm_deleted(block=True)
328
329                     heat_utils.delete_stack(self.heat_client, self.stack)
330                     time.sleep(20)
331             except:
332                     raise
333
334         if self.image_creator1:
335             try:
336                 self.image_creator1.clean()
337             except:
338                 pass
339
340         if self.image_creator2:
341             try:
342                 self.image_creator2.clean()
343             except:
344                 pass
345
346         if self.keypair1_settings:
347             expanded_path = os.path.expanduser(
348                 self.keypair1_settings.private_filepath)
349             os.chmod(expanded_path, 0o755)
350             os.remove(expanded_path)
351
352         if self.keypair2_settings:
353             expanded_path = os.path.expanduser(
354                 self.keypair2_settings.private_filepath)
355             os.chmod(expanded_path, 0o755)
356             os.remove(expanded_path)
357
358     def test_get_settings_from_stack(self):
359         """
360         Tests that a heat template with floating IPs and can have the proper
361         settings derived from settings_utils.py.
362         """
363         resources = heat_utils.get_resources(self.heat_client, self.stack.id)
364         self.assertIsNotNone(resources)
365         self.assertEqual(12, len(resources))
366
367         options = heat_utils.get_outputs(self.heat_client, self.stack)
368         self.assertIsNotNone(options)
369         self.assertEqual(1, len(options))
370
371         neutron = neutron_utils.neutron_client(self.os_creds)
372         networks = heat_utils.get_stack_networks(
373             self.heat_client, neutron, self.stack)
374         self.assertIsNotNone(networks)
375         self.assertEqual(1, len(networks))
376         self.assertEqual(self.network_name, networks[0].name)
377
378         network_settings = settings_utils.create_network_config(
379             neutron, networks[0])
380         self.assertIsNotNone(network_settings)
381         self.assertEqual(self.network_name, network_settings.name)
382
383         nova = nova_utils.nova_client(self.os_creds)
384         glance = glance_utils.glance_client(self.os_creds)
385
386         servers = heat_utils.get_stack_servers(
387             self.heat_client, nova, neutron, self.stack, self.project_id)
388         self.assertIsNotNone(servers)
389         self.assertEqual(2, len(servers))
390
391         image_settings = settings_utils.determine_image_config(
392             glance, servers[0],
393             [self.image_creator1.image_settings,
394              self.image_creator2.image_settings])
395
396         self.assertIsNotNone(image_settings)
397         if image_settings.name.endswith('1'):
398             self.assertEqual(
399                 self.image_creator1.image_settings.name, image_settings.name)
400         else:
401             self.assertEqual(
402                 self.image_creator2.image_settings.name, image_settings.name)
403
404         image_settings = settings_utils.determine_image_config(
405             glance, servers[1],
406             [self.image_creator1.image_settings,
407              self.image_creator2.image_settings])
408         if image_settings.name.endswith('1'):
409             self.assertEqual(
410                 self.image_creator1.image_settings.name, image_settings.name)
411         else:
412             self.assertEqual(
413                 self.image_creator2.image_settings.name, image_settings.name)
414
415         self.keypair1_settings = settings_utils.determine_keypair_config(
416             self.heat_client, self.stack, servers[0],
417             priv_key_key='private_key')
418         self.assertIsNotNone(self.keypair1_settings)
419         self.assertEqual(self.keypair_name, self.keypair1_settings.name)
420
421         self.keypair2_settings = settings_utils.determine_keypair_config(
422             self.heat_client, self.stack, servers[1],
423             priv_key_key='private_key')
424         self.assertIsNotNone(self.keypair2_settings)
425         self.assertEqual(self.keypair_name, self.keypair2_settings.name)
426
427
428 class HeatUtilsRouterTests(OSComponentTestCase):
429     """
430     Test Heat volume functionality
431     """
432
433     def setUp(self):
434         """
435         Instantiates OpenStack instances that cannot be spawned by Heat
436         """
437         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
438         stack_name = guid + '-stack'
439
440         self.net_name = guid + '-net'
441         self.subnet_name = guid + '-subnet'
442         self.router_name = guid + '-router'
443
444         env_values = {
445             'net_name': self.net_name,
446             'subnet_name': self.subnet_name,
447             'router_name': self.router_name,
448             'external_net_name': self.ext_net_name}
449
450         heat_tmplt_path = pkg_resources.resource_filename(
451             'snaps.openstack.tests.heat', 'router_heat_template.yaml')
452         self.stack_settings = StackConfig(
453             name=stack_name, template_path=heat_tmplt_path,
454             env_values=env_values)
455         self.stack = None
456         self.heat_client = heat_utils.heat_client(self.os_creds)
457         self.neutron = neutron_utils.neutron_client(self.os_creds)
458
459     def tearDown(self):
460         """
461         Cleans the image and downloaded image file
462         """
463         if self.stack:
464             try:
465                 heat_utils.delete_stack(self.heat_client, self.stack)
466             except:
467                 pass
468
469     def test_create_router_with_stack(self):
470         """
471         Tests the creation of an OpenStack router with Heat and the retrieval
472         of the Router Domain objects from heat_utils#get_stack_routers().
473         """
474         self.stack = heat_utils.create_stack(
475             self.heat_client, self.stack_settings)
476
477         # Wait until stack deployment has completed
478         end_time = time.time() + stack_config.STACK_COMPLETE_TIMEOUT
479         is_active = False
480         while time.time() < end_time:
481             status = heat_utils.get_stack_status(self.heat_client,
482                                                  self.stack.id)
483             if status == stack_config.STATUS_CREATE_COMPLETE:
484                 is_active = True
485                 break
486             elif status == stack_config.STATUS_CREATE_FAILED:
487                 is_active = False
488                 break
489
490             time.sleep(3)
491
492         self.assertTrue(is_active)
493
494         routers = heat_utils.get_stack_routers(
495             self.heat_client, self.neutron, self.stack)
496
497         self.assertEqual(1, len(routers))
498
499         router = routers[0]
500         self.assertEqual(self.router_name, router.name)
501
502         ext_net = neutron_utils.get_network(
503             self.neutron, network_name=self.ext_net_name)
504         self.assertEqual(ext_net.id, router.external_network_id)
505
506
507 class HeatUtilsVolumeTests(OSComponentTestCase):
508     """
509     Test Heat volume functionality
510     """
511
512     def setUp(self):
513         """
514         Instantiates OpenStack instances that cannot be spawned by Heat
515         """
516         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
517         stack_name = guid + '-stack'
518         self.volume_name = guid + '-vol'
519         self.volume_type_name = guid + '-vol-type'
520
521         env_values = {
522             'volume_name': self.volume_name,
523             'volume_type_name': self.volume_type_name}
524
525         heat_tmplt_path = pkg_resources.resource_filename(
526             'snaps.openstack.tests.heat', 'volume_heat_template.yaml')
527         self.stack_settings = StackConfig(
528             name=stack_name, template_path=heat_tmplt_path,
529             env_values=env_values)
530         self.stack = None
531         self.heat_client = heat_utils.heat_client(self.os_creds)
532         self.cinder = cinder_utils.cinder_client(self.os_creds)
533
534     def tearDown(self):
535         """
536         Cleans the stack
537         """
538         if self.stack:
539             try:
540                 heat_utils.delete_stack(self.heat_client, self.stack)
541             except:
542                 pass
543
544     def test_create_vol_with_stack(self):
545         """
546         Tests the creation of an OpenStack volume with Heat.
547         """
548         self.stack = heat_utils.create_stack(
549             self.heat_client, self.stack_settings)
550         self.assertTrue(stack_active(self.heat_client, self.stack))
551
552         volumes = heat_utils.get_stack_volumes(
553             self.heat_client, self.cinder, self.stack)
554
555         self.assertEqual(1, len(volumes))
556
557         volume = volumes[0]
558         self.assertEqual(self.volume_name, volume.name)
559         self.assertEqual(self.volume_type_name, volume.type)
560         self.assertEqual(1, volume.size)
561         self.assertEqual(False, volume.multi_attach)
562
563     def test_create_vol_types_with_stack(self):
564         """
565         Tests the creation of an OpenStack volume with Heat.
566         """
567         self.stack = heat_utils.create_stack(
568             self.heat_client, self.stack_settings)
569         self.assertTrue(stack_active(self.heat_client, self.stack))
570
571         volume_types = heat_utils.get_stack_volume_types(
572             self.heat_client, self.cinder, self.stack)
573
574         self.assertEqual(1, len(volume_types))
575
576         volume_type = volume_types[0]
577
578         self.assertEqual(self.volume_type_name, volume_type.name)
579         self.assertTrue(volume_type.public)
580         self.assertIsNone(volume_type.qos_spec)
581
582         # TODO - Add encryption back and find out why it broke in Pike
583         # encryption = volume_type.encryption
584         # self.assertIsNotNone(encryption)
585         # self.assertIsNone(encryption.cipher)
586         # self.assertEqual('front-end', encryption.control_location)
587         # self.assertIsNone(encryption.key_size)
588         # self.assertEqual(u'nova.volume.encryptors.luks.LuksEncryptor',
589         #                  encryption.provider)
590         # self.assertEqual(volume_type.id, encryption.volume_type_id)
591
592
593 class HeatUtilsFlavorTests(OSComponentTestCase):
594     """
595     Test Heat volume functionality
596     """
597
598     def setUp(self):
599         """
600         Instantiates OpenStack instances that cannot be spawned by Heat
601         """
602         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
603         self.name_prefix = guid
604         stack_name = guid + '-stack'
605
606         heat_tmplt_path = pkg_resources.resource_filename(
607             'snaps.openstack.tests.heat', 'flavor_heat_template.yaml')
608         self.stack_settings = StackConfig(
609             name=stack_name, template_path=heat_tmplt_path)
610         self.stack = None
611         self.heat_client = heat_utils.heat_client(self.os_creds)
612         self.nova = nova_utils.nova_client(self.os_creds)
613
614     def tearDown(self):
615         """
616         Cleans the stack
617         """
618         if self.stack:
619             try:
620                 heat_utils.delete_stack(self.heat_client, self.stack)
621             except:
622                 pass
623
624     def test_create_flavor_with_stack(self):
625         """
626         Tests the creation of an OpenStack volume with Heat.
627         """
628         self.stack = heat_utils.create_stack(
629             self.heat_client, self.stack_settings)
630
631         self.assertTrue(stack_active(self.heat_client, self.stack))
632
633         flavors = heat_utils.get_stack_flavors(
634             self.heat_client, self.nova, self.stack)
635
636         self.assertEqual(1, len(flavors))
637
638         flavor = flavors[0]
639         self.assertTrue(flavor.name.startswith(self.name_prefix))
640         self.assertEqual(1024, flavor.ram)
641         self.assertEqual(200, flavor.disk)
642         self.assertEqual(8, flavor.vcpus)
643         self.assertEqual(0, flavor.ephemeral)
644         self.assertIsNone(flavor.swap)
645         self.assertEqual(1.0, flavor.rxtx_factor)
646         self.assertTrue(flavor.is_public)
647
648
649 class HeatUtilsKeypairTests(OSComponentTestCase):
650     """
651     Test Heat volume functionality
652     """
653
654     def setUp(self):
655         """
656         Instantiates OpenStack instances that cannot be spawned by Heat
657         """
658         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
659         stack_name = guid + '-stack'
660         self.keypair_name = guid + '-kp'
661
662         env_values = {'keypair_name': self.keypair_name}
663
664         heat_tmplt_path = pkg_resources.resource_filename(
665             'snaps.openstack.tests.heat', 'keypair_heat_template.yaml')
666         self.stack_settings = StackConfig(
667             name=stack_name, template_path=heat_tmplt_path,
668             env_values=env_values)
669         self.stack = None
670         self.heat_client = heat_utils.heat_client(self.os_creds)
671         self.nova = nova_utils.nova_client(self.os_creds)
672
673     def tearDown(self):
674         """
675         Cleans the stack
676         """
677         if self.stack:
678             try:
679                 heat_utils.delete_stack(self.heat_client, self.stack)
680             except:
681                 pass
682
683     def test_create_keypair_with_stack(self):
684         """
685         Tests the creation of an OpenStack keypair with Heat.
686         """
687         self.stack = heat_utils.create_stack(
688             self.heat_client, self.stack_settings)
689         self.assertTrue(stack_active(self.heat_client, self.stack))
690
691         keypairs = heat_utils.get_stack_keypairs(
692             self.heat_client, self.nova, self.stack)
693
694         self.assertEqual(1, len(keypairs))
695         keypair = keypairs[0]
696
697         self.assertEqual(self.keypair_name, keypair.name)
698
699         outputs = heat_utils.get_outputs(self.heat_client, self.stack)
700
701         for output in outputs:
702             if output.key == 'private_key':
703                 self.assertTrue(output.value.startswith(
704                     '-----BEGIN RSA PRIVATE KEY-----'))
705
706         keypair = nova_utils.get_keypair_by_id(self.nova, keypair.id)
707         self.assertIsNotNone(keypair)
708
709         self.assertEqual(self.keypair_name, keypair.name)
710
711
712 class HeatUtilsSecurityGroupTests(OSComponentTestCase):
713     """
714     Test Heat volume functionality
715     """
716
717     def setUp(self):
718         """
719         Instantiates OpenStack instances that cannot be spawned by Heat
720         """
721         guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
722         stack_name = guid + '-stack'
723         self.sec_grp_name = guid + '-sec-grp'
724
725         env_values = {'security_group_name': self.sec_grp_name}
726
727         heat_tmplt_path = pkg_resources.resource_filename(
728             'snaps.openstack.tests.heat', 'security_group_heat_template.yaml')
729         self.stack_settings = StackConfig(
730             name=stack_name, template_path=heat_tmplt_path,
731             env_values=env_values)
732         self.stack = None
733         self.heat_client = heat_utils.heat_client(self.os_creds)
734         self.neutron = neutron_utils.neutron_client(self.os_creds)
735
736     def tearDown(self):
737         """
738         Cleans the stack
739         """
740         if self.stack:
741             try:
742                 heat_utils.delete_stack(self.heat_client, self.stack)
743             except:
744                 pass
745
746     def test_create_security_group_with_stack(self):
747         """
748         Tests the creation of an OpenStack SecurityGroup with Heat.
749         """
750         self.stack = heat_utils.create_stack(
751             self.heat_client, self.stack_settings)
752         self.assertTrue(stack_active(self.heat_client, self.stack))
753
754         sec_grp = heat_utils.get_stack_security_groups(
755             self.heat_client, self.neutron, self.stack)[0]
756
757         self.assertEqual(self.sec_grp_name, sec_grp.name)
758         self.assertEqual('Test description', sec_grp.description)
759         self.assertEqual(2, len(sec_grp.rules))
760
761         has_ssh_rule = False
762         has_icmp_rule = False
763
764         for rule in sec_grp.rules:
765             if (rule.security_group_id == sec_grp.id
766                     and rule.direction == 'egress'
767                     and rule.ethertype == 'IPv4'
768                     and rule.port_range_min == 22
769                     and rule.port_range_max == 22
770                     and rule.protocol == 'tcp'
771                     and rule.remote_group_id is None
772                     and rule.remote_ip_prefix == '0.0.0.0/0'):
773                 has_ssh_rule = True
774             if (rule.security_group_id == sec_grp.id
775                     and rule.direction == 'ingress'
776                     and rule.ethertype == 'IPv4'
777                     and rule.port_range_min is None
778                     and rule.port_range_max is None
779                     and rule.protocol == 'icmp'
780                     and rule.remote_group_id is None
781                     and rule.remote_ip_prefix == '0.0.0.0/0'):
782                 has_icmp_rule = True
783
784         self.assertTrue(has_ssh_rule)
785         self.assertTrue(has_icmp_rule)
786
787
788 def stack_active(heat_cli, stack):
789     """
790     Blocks until stack application has successfully completed or failed
791     :param heat_cli: the Heat client
792     :param stack: the Stack domain object
793     :return: T/F
794     """
795     # Wait until stack deployment has completed
796     end_time = time.time() + stack_config.STACK_COMPLETE_TIMEOUT
797     is_active = False
798     while time.time() < end_time:
799         status = heat_utils.get_stack_status(heat_cli, stack.id)
800         if status == stack_config.STATUS_CREATE_COMPLETE:
801             is_active = True
802             break
803         elif status == stack_config.STATUS_CREATE_FAILED:
804             is_active = False
805             break
806
807         time.sleep(3)
808
809     return is_active