a2b221534b56a2dc44aff6fbc516b3c6102c2798
[snaps.git] / snaps / openstack / tests / create_stack_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 time
16
17 import pkg_resources
18 from heatclient.exc import HTTPBadRequest
19 from snaps import file_utils
20 from snaps.openstack.create_flavor import OpenStackFlavor, FlavorSettings
21 from snaps.openstack.create_image import OpenStackImage
22
23 try:
24     from urllib.request import URLError
25 except ImportError:
26     from urllib2 import URLError
27
28 import logging
29 import unittest
30 import uuid
31
32 from snaps.openstack import create_stack
33 from snaps.openstack.create_stack import (
34     StackSettings, StackSettingsError, StackCreationError)
35 from snaps.openstack.tests import openstack_tests, create_instance_tests
36 from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase
37 from snaps.openstack.utils import heat_utils, neutron_utils, nova_utils
38
39 __author__ = 'spisarski'
40
41 logger = logging.getLogger('create_stack_tests')
42
43
44 class StackSettingsUnitTests(unittest.TestCase):
45     """
46     Tests the construction of the StackSettings class
47     """
48
49     def test_no_params(self):
50         with self.assertRaises(StackSettingsError):
51             StackSettings()
52
53     def test_empty_config(self):
54         with self.assertRaises(StackSettingsError):
55             StackSettings(**dict())
56
57     def test_name_only(self):
58         with self.assertRaises(StackSettingsError):
59             StackSettings(name='foo')
60
61     def test_config_with_name_only(self):
62         with self.assertRaises(StackSettingsError):
63             StackSettings(**{'name': 'foo'})
64
65     def test_config_minimum_template(self):
66         settings = StackSettings(**{'name': 'stack', 'template': 'foo'})
67         self.assertEqual('stack', settings.name)
68         self.assertEqual('foo', settings.template)
69         self.assertIsNone(settings.template_path)
70         self.assertIsNone(settings.env_values)
71         self.assertEqual(create_stack.STACK_COMPLETE_TIMEOUT,
72                          settings.stack_create_timeout)
73
74     def test_config_minimum_template_path(self):
75         settings = StackSettings(**{'name': 'stack', 'template_path': 'foo'})
76         self.assertEqual('stack', settings.name)
77         self.assertIsNone(settings.template)
78         self.assertEqual('foo', settings.template_path)
79         self.assertIsNone(settings.env_values)
80         self.assertEqual(create_stack.STACK_COMPLETE_TIMEOUT,
81                          settings.stack_create_timeout)
82
83     def test_minimum_template(self):
84         settings = StackSettings(name='stack', template='foo')
85         self.assertEqual('stack', settings.name)
86         self.assertEqual('foo', settings.template)
87         self.assertIsNone(settings.template_path)
88         self.assertIsNone(settings.env_values)
89         self.assertEqual(create_stack.STACK_COMPLETE_TIMEOUT,
90                          settings.stack_create_timeout)
91
92     def test_minimum_template_path(self):
93         settings = StackSettings(name='stack', template_path='foo')
94         self.assertEqual('stack', settings.name)
95         self.assertEqual('foo', settings.template_path)
96         self.assertIsNone(settings.template)
97         self.assertIsNone(settings.env_values)
98         self.assertEqual(create_stack.STACK_COMPLETE_TIMEOUT,
99                          settings.stack_create_timeout)
100
101     def test_all(self):
102         env_values = {'foo': 'bar'}
103         settings = StackSettings(name='stack', template='bar',
104                                  template_path='foo', env_values=env_values,
105                                  stack_create_timeout=999)
106         self.assertEqual('stack', settings.name)
107         self.assertEqual('bar', settings.template)
108         self.assertEqual('foo', settings.template_path)
109         self.assertEqual(env_values, settings.env_values)
110         self.assertEqual(999, settings.stack_create_timeout)
111
112     def test_config_all(self):
113         env_values = {'foo': 'bar'}
114         settings = StackSettings(
115             **{'name': 'stack', 'template': 'bar', 'template_path': 'foo',
116                'env_values': env_values, 'stack_create_timeout': 999})
117         self.assertEqual('stack', settings.name)
118         self.assertEqual('bar', settings.template)
119         self.assertEqual('foo', settings.template_path)
120         self.assertEqual(env_values, settings.env_values)
121         self.assertEqual(999, settings.stack_create_timeout)
122
123
124 class CreateStackSuccessTests(OSIntegrationTestCase):
125     """
126     Tests for the CreateStack class defined in create_stack.py
127     """
128
129     def setUp(self):
130         """
131         Instantiates the CreateStack object that is responsible for downloading
132         and creating an OS stack file within OpenStack
133         """
134         super(self.__class__, self).__start__()
135
136         self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
137
138         self.heat_creds = self.admin_os_creds
139         self.heat_creds.project_name = self.admin_os_creds.project_name
140
141         self.heat_cli = heat_utils.heat_client(self.heat_creds)
142         self.stack_creator = None
143
144         self.image_creator = OpenStackImage(
145             self.heat_creds, openstack_tests.cirros_image_settings(
146                 name=self.guid + '-image',
147                 image_metadata=self.image_metadata))
148         self.image_creator.create()
149
150         # Create Flavor
151         self.flavor_creator = OpenStackFlavor(
152             self.admin_os_creds,
153             FlavorSettings(name=self.guid + '-flavor-name', ram=256, disk=10,
154                            vcpus=1))
155         self.flavor_creator.create()
156
157         self.network_name = self.guid + '-net'
158         self.subnet_name = self.guid + '-subnet'
159         self.vm_inst_name = self.guid + '-inst'
160
161         self.env_values = {
162             'image_name': self.image_creator.image_settings.name,
163             'flavor_name': self.flavor_creator.flavor_settings.name,
164             'net_name': self.network_name,
165             'subnet_name': self.subnet_name,
166             'inst_name': self.vm_inst_name}
167
168         self.heat_tmplt_path = pkg_resources.resource_filename(
169             'snaps.openstack.tests.heat', 'test_heat_template.yaml')
170
171     def tearDown(self):
172         """
173         Cleans the stack and downloaded stack file
174         """
175         if self.stack_creator:
176             try:
177                 self.stack_creator.clean()
178             except:
179                 pass
180
181         if self.image_creator:
182             try:
183                 self.image_creator.clean()
184             except:
185                 pass
186
187         if self.flavor_creator:
188             try:
189                 self.flavor_creator.clean()
190             except:
191                 pass
192
193         super(self.__class__, self).__clean__()
194
195     def test_create_stack_template_file(self):
196         """
197         Tests the creation of an OpenStack stack from Heat template file.
198         """
199         # Create Stack
200         # Set the default stack settings, then set any custom parameters sent
201         # from the app
202         stack_settings = StackSettings(
203             name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
204             template_path=self.heat_tmplt_path,
205             env_values=self.env_values)
206         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
207                                                              stack_settings)
208         created_stack = self.stack_creator.create()
209         self.assertIsNotNone(created_stack)
210
211         retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli,
212                                                      created_stack.id)
213         self.assertIsNotNone(retrieved_stack)
214         self.assertEqual(created_stack.name, retrieved_stack.name)
215         self.assertEqual(created_stack.id, retrieved_stack.id)
216         self.assertEqual(0, len(self.stack_creator.get_outputs()))
217
218     def test_create_stack_short_timeout(self):
219         """
220         Tests the creation of an OpenStack stack from Heat template file.
221         """
222         # Create Stack
223         # Set the default stack settings, then set any custom parameters sent
224         # from the app
225         stack_settings = StackSettings(
226             name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
227             template_path=self.heat_tmplt_path,
228             env_values=self.env_values, stack_create_timeout=0)
229
230         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
231                                                              stack_settings)
232         with self.assertRaises(StackCreationError):
233             self.stack_creator.create()
234
235     def test_create_stack_template_dict(self):
236         """
237         Tests the creation of an OpenStack stack from a heat dict() object.
238         """
239         # Create Stack
240         # Set the default stack settings, then set any custom parameters sent
241         # from the app
242         template_dict = heat_utils.parse_heat_template_str(
243             file_utils.read_file(self.heat_tmplt_path))
244         stack_settings = StackSettings(
245             name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
246             template=template_dict,
247             env_values=self.env_values)
248         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
249                                                              stack_settings)
250         created_stack = self.stack_creator.create()
251         self.assertIsNotNone(created_stack)
252
253         retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli,
254                                                      created_stack.id)
255         self.assertIsNotNone(retrieved_stack)
256         self.assertEqual(created_stack.name, retrieved_stack.name)
257         self.assertEqual(created_stack.id, retrieved_stack.id)
258         self.assertEqual(0, len(self.stack_creator.get_outputs()))
259
260     def test_create_delete_stack(self):
261         """
262         Tests the creation then deletion of an OpenStack stack to ensure
263         clean() does not raise an Exception.
264         """
265         # Create Stack
266         template_dict = heat_utils.parse_heat_template_str(
267             file_utils.read_file(self.heat_tmplt_path))
268         stack_settings = StackSettings(
269             name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
270             template=template_dict,
271             env_values=self.env_values)
272         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
273                                                              stack_settings)
274         created_stack = self.stack_creator.create()
275         self.assertIsNotNone(created_stack)
276
277         retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli,
278                                                      created_stack.id)
279         self.assertIsNotNone(retrieved_stack)
280         self.assertEqual(created_stack.name, retrieved_stack.name)
281         self.assertEqual(created_stack.id, retrieved_stack.id)
282         self.assertEqual(0, len(self.stack_creator.get_outputs()))
283         self.assertEqual(create_stack.STATUS_CREATE_COMPLETE,
284                          self.stack_creator.get_status())
285
286         # Delete Stack manually
287         heat_utils.delete_stack(self.heat_cli, created_stack)
288
289         end_time = time.time() + 90
290         deleted = False
291         while time.time() < end_time:
292             status = heat_utils.get_stack_status(self.heat_cli,
293                                                  retrieved_stack.id)
294             if status == create_stack.STATUS_DELETE_COMPLETE:
295                 deleted = True
296                 break
297
298         self.assertTrue(deleted)
299
300         # Must not throw an exception when attempting to cleanup non-existent
301         # stack
302         self.stack_creator.clean()
303         self.assertIsNone(self.stack_creator.get_stack())
304
305     def test_create_same_stack(self):
306         """
307         Tests the creation of an OpenStack stack when the stack already exists.
308         """
309         # Create Stack
310         template_dict = heat_utils.parse_heat_template_str(
311             file_utils.read_file(self.heat_tmplt_path))
312         stack_settings = StackSettings(
313             name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
314             template=template_dict,
315             env_values=self.env_values)
316         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
317                                                              stack_settings)
318         created_stack1 = self.stack_creator.create()
319
320         retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli,
321                                                      created_stack1.id)
322         self.assertIsNotNone(retrieved_stack)
323         self.assertEqual(created_stack1.name, retrieved_stack.name)
324         self.assertEqual(created_stack1.id, retrieved_stack.id)
325         self.assertEqual(0, len(self.stack_creator.get_outputs()))
326
327         # Should be retrieving the instance data
328         stack_creator2 = create_stack.OpenStackHeatStack(self.heat_creds,
329                                                          stack_settings)
330         stack2 = stack_creator2.create()
331         self.assertEqual(created_stack1.id, stack2.id)
332
333     def test_retrieve_network_creators(self):
334         """
335         Tests the creation of an OpenStack stack from Heat template file and
336         the retrieval of the network creator.
337         """
338         stack_settings = StackSettings(
339             name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
340             template_path=self.heat_tmplt_path,
341             env_values=self.env_values)
342         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
343                                                              stack_settings)
344         created_stack = self.stack_creator.create()
345         self.assertIsNotNone(created_stack)
346
347         net_creators = self.stack_creator.get_network_creators()
348         self.assertIsNotNone(net_creators)
349         self.assertEqual(1, len(net_creators))
350         self.assertEqual(self.network_name, net_creators[0].get_network().name)
351
352         neutron = neutron_utils.neutron_client(self.os_creds)
353         net_by_name = neutron_utils.get_network(
354             neutron, network_name=net_creators[0].get_network().name)
355         self.assertEqual(net_creators[0].get_network(), net_by_name)
356         self.assertIsNotNone(neutron_utils.get_network_by_id(
357             neutron, net_creators[0].get_network().id))
358
359         self.assertEqual(1, len(net_creators[0].get_subnets()))
360         subnet = net_creators[0].get_subnets()[0]
361         subnet_by_name = neutron_utils.get_subnet(
362             neutron, subnet_name=subnet.name)
363         self.assertEqual(subnet, subnet_by_name)
364
365         subnet_by_id = neutron_utils.get_subnet_by_id(neutron, subnet.id)
366         self.assertIsNotNone(subnet_by_id)
367         self.assertEqual(subnet_by_name, subnet_by_id)
368
369     def test_retrieve_vm_inst_creators(self):
370         """
371         Tests the creation of an OpenStack stack from Heat template file and
372         the retrieval of the network creator.
373         """
374         stack_settings = StackSettings(
375             name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
376             template_path=self.heat_tmplt_path,
377             env_values=self.env_values)
378         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
379                                                              stack_settings)
380         created_stack = self.stack_creator.create()
381         self.assertIsNotNone(created_stack)
382
383         vm_inst_creators = self.stack_creator.get_vm_inst_creators()
384         self.assertIsNotNone(vm_inst_creators)
385         self.assertEqual(1, len(vm_inst_creators))
386         self.assertEqual(self.vm_inst_name,
387                          vm_inst_creators[0].get_vm_inst().name)
388
389         nova = nova_utils.nova_client(self.admin_os_creds)
390         vm_inst_by_name = nova_utils.get_server(
391             nova, server_name=vm_inst_creators[0].get_vm_inst().name)
392         self.assertEqual(vm_inst_creators[0].get_vm_inst(), vm_inst_by_name)
393         self.assertIsNotNone(nova_utils.get_server_object_by_id(
394             nova, vm_inst_creators[0].get_vm_inst().id))
395
396
397 class CreateStackFloatingIpTests(OSIntegrationTestCase):
398     """
399     Tests for the CreateStack class defined in create_stack.py
400     """
401
402     def setUp(self):
403         """
404         Instantiates the CreateStack object that is responsible for downloading
405         and creating an OS stack file within OpenStack
406         """
407         super(self.__class__, self).__start__()
408
409         self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
410
411         self.heat_creds = self.admin_os_creds
412         self.heat_creds.project_name = self.admin_os_creds.project_name
413
414         self.heat_cli = heat_utils.heat_client(self.heat_creds)
415         self.stack_creator = None
416
417         self.image_creator = OpenStackImage(
418             self.heat_creds, openstack_tests.cirros_image_settings(
419                 name=self.guid + '-image',
420                 image_metadata=self.image_metadata))
421         self.image_creator.create()
422
423         self.network_name = self.guid + '-net'
424         self.subnet_name = self.guid + '-subnet'
425         self.flavor1_name = self.guid + '-flavor1'
426         self.flavor2_name = self.guid + '-flavor2'
427         self.sec_grp_name = self.guid + '-sec_grp'
428         self.vm_inst1_name = self.guid + '-inst1'
429         self.vm_inst2_name = self.guid + '-inst2'
430         self.keypair_name = self.guid + '-kp'
431
432         self.env_values = {
433             'image1_name': self.image_creator.image_settings.name,
434             'image2_name': self.image_creator.image_settings.name,
435             'flavor1_name': self.flavor1_name,
436             'flavor2_name': self.flavor2_name,
437             'net_name': self.network_name,
438             'subnet_name': self.subnet_name,
439             'inst1_name': self.vm_inst1_name,
440             'inst2_name': self.vm_inst2_name,
441             'keypair_name': self.keypair_name,
442             'external_net_name': self.ext_net_name,
443             'security_group_name': self.sec_grp_name}
444
445         self.heat_tmplt_path = pkg_resources.resource_filename(
446             'snaps.openstack.tests.heat', 'floating_ip_heat_template.yaml')
447
448     def tearDown(self):
449         """
450         Cleans the stack and downloaded stack file
451         """
452         if self.stack_creator:
453             try:
454                 self.stack_creator.clean()
455             except:
456                 pass
457
458         if self.image_creator:
459             try:
460                 self.image_creator.clean()
461             except:
462                 pass
463
464         super(self.__class__, self).__clean__()
465
466     def test_connect_via_ssh_heat_vm(self):
467         """
468         Tests the creation of an OpenStack stack from Heat template file and
469         the retrieval of two VM instance creators and attempt to connect via
470         SSH to the first one with a floating IP.
471         """
472         stack_settings = StackSettings(
473             name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
474             template_path=self.heat_tmplt_path,
475             env_values=self.env_values)
476         self.stack_creator = create_stack.OpenStackHeatStack(
477             self.heat_creds, stack_settings,
478             [self.image_creator.image_settings])
479         created_stack = self.stack_creator.create()
480         self.assertIsNotNone(created_stack)
481
482         vm_inst_creators = self.stack_creator.get_vm_inst_creators(
483             heat_keypair_option='private_key')
484         self.assertIsNotNone(vm_inst_creators)
485         self.assertEqual(2, len(vm_inst_creators))
486
487         for vm_inst_creator in vm_inst_creators:
488             if vm_inst_creator.get_vm_inst().name == self.vm_inst1_name:
489                 self.assertTrue(
490                     create_instance_tests.validate_ssh_client(vm_inst_creator))
491             else:
492                 vm_settings = vm_inst_creator.instance_settings
493                 self.assertEqual(0, len(vm_settings.floating_ip_settings))
494
495
496 class CreateStackVolumeTests(OSIntegrationTestCase):
497     """
498     Tests for the CreateStack class defined in create_stack.py
499     """
500
501     def setUp(self):
502         """
503         Instantiates the CreateStack object that is responsible for downloading
504         and creating an OS stack file within OpenStack
505         """
506         super(self.__class__, self).__start__()
507
508         self.guid = self.__class__.__name__ + '-' + str(uuid.uuid4())
509
510         self.heat_creds = self.admin_os_creds
511         self.heat_creds.project_name = self.admin_os_creds.project_name
512
513         self.heat_cli = heat_utils.heat_client(self.heat_creds)
514         self.stack_creator = None
515
516         self.volume_name = self.guid + '-volume'
517         self.volume_type_name = self.guid + '-volume-type'
518
519         self.env_values = {
520             'volume_name': self.volume_name,
521             'volume_type_name': self.volume_type_name}
522
523         self.heat_tmplt_path = pkg_resources.resource_filename(
524             'snaps.openstack.tests.heat', 'volume_heat_template.yaml')
525
526         stack_settings = StackSettings(
527             name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
528             template_path=self.heat_tmplt_path,
529             env_values=self.env_values)
530         self.stack_creator = create_stack.OpenStackHeatStack(
531             self.heat_creds, stack_settings)
532         self.created_stack = self.stack_creator.create()
533         self.assertIsNotNone(self.created_stack)
534
535     def tearDown(self):
536         """
537         Cleans the stack and downloaded stack file
538         """
539         if self.stack_creator:
540             try:
541                 self.stack_creator.clean()
542             except:
543                 pass
544
545         super(self.__class__, self).__clean__()
546
547     def test_retrieve_volume_creator(self):
548         """
549         Tests the creation of an OpenStack stack from Heat template file and
550         the retrieval of an OpenStackVolume creator/state machine instance
551         """
552         volume_creators = self.stack_creator.get_volume_creators()
553         self.assertEqual(1, len(volume_creators))
554
555         creator = volume_creators[0]
556         self.assertEqual(self.volume_name, creator.volume_settings.name)
557         self.assertEqual(self.volume_name, creator.get_volume().name)
558         self.assertEqual(self.volume_type_name,
559                          creator.volume_settings.type_name)
560         self.assertEqual(self.volume_type_name, creator.get_volume().type)
561         self.assertEqual(1, creator.volume_settings.size)
562         self.assertEqual(1, creator.get_volume().size)
563
564     def test_retrieve_volume_type_creator(self):
565         """
566         Tests the creation of an OpenStack stack from Heat template file and
567         the retrieval of an OpenStackVolume creator/state machine instance
568         """
569         volume_type_creators = self.stack_creator.get_volume_type_creators()
570         self.assertEqual(1, len(volume_type_creators))
571
572         creator = volume_type_creators[0]
573         self.assertIsNotNone(creator)
574
575         volume_type = creator.get_volume_type()
576         self.assertIsNotNone(volume_type)
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         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 CreateStackNegativeTests(OSIntegrationTestCase):
593     """
594     Negative test cases for the CreateStack class
595     """
596
597     def setUp(self):
598         super(self.__class__, self).__start__()
599
600         self.heat_creds = self.admin_os_creds
601         self.heat_creds.project_name = self.admin_os_creds.project_name
602
603         self.stack_name = self.__class__.__name__ + '-' + str(uuid.uuid4())
604         self.stack_creator = None
605         self.heat_tmplt_path = pkg_resources.resource_filename(
606             'snaps.openstack.tests.heat', 'test_heat_template.yaml')
607
608     def tearDown(self):
609         if self.stack_creator:
610             self.stack_creator.clean()
611         super(self.__class__, self).__clean__()
612
613     def test_missing_dependencies(self):
614         """
615         Expect an StackCreationError when the stack file does not exist
616         """
617         stack_settings = StackSettings(name=self.stack_name,
618                                        template_path=self.heat_tmplt_path)
619         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
620                                                              stack_settings)
621         with self.assertRaises(HTTPBadRequest):
622             self.stack_creator.create()
623
624     def test_bad_stack_file(self):
625         """
626         Expect an StackCreationError when the stack file does not exist
627         """
628         stack_settings = StackSettings(name=self.stack_name,
629                                        template_path='foo')
630         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
631                                                              stack_settings)
632         with self.assertRaises(IOError):
633             self.stack_creator.create()