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