Add reason for stack creation failure
[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 CreateComplexStackTests(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.vm_inst1_name = self.guid + '-inst1'
428         self.vm_inst2_name = self.guid + '-inst2'
429         self.keypair_name = self.guid + '-kp'
430
431         self.env_values = {
432             'image1_name': self.image_creator.image_settings.name,
433             'image2_name': self.image_creator.image_settings.name,
434             'flavor1_name': self.flavor1_name,
435             'flavor2_name': self.flavor2_name,
436             'net_name': self.network_name,
437             'subnet_name': self.subnet_name,
438             'inst1_name': self.vm_inst1_name,
439             'inst2_name': self.vm_inst2_name,
440             'keypair_name': self.keypair_name}
441
442         self.heat_tmplt_path = pkg_resources.resource_filename(
443             'snaps.openstack.tests.heat', 'floating_ip_heat_template.yaml')
444
445     def tearDown(self):
446         """
447         Cleans the stack and downloaded stack file
448         """
449         if self.stack_creator:
450             try:
451                 self.stack_creator.clean()
452             except:
453                 pass
454
455         if self.image_creator:
456             try:
457                 self.image_creator.clean()
458             except:
459                 pass
460
461         super(self.__class__, self).__clean__()
462
463     def test_connect_via_ssh_heat_vm(self):
464         """
465         Tests the creation of an OpenStack stack from Heat template file and
466         the retrieval of two VM instance creators and attempt to connect via
467         SSH to the first one with a floating IP.
468         """
469         stack_settings = StackSettings(
470             name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
471             template_path=self.heat_tmplt_path,
472             env_values=self.env_values)
473         self.stack_creator = create_stack.OpenStackHeatStack(
474             self.heat_creds, stack_settings,
475             [self.image_creator.image_settings])
476         created_stack = self.stack_creator.create()
477         self.assertIsNotNone(created_stack)
478
479         vm_inst_creators = self.stack_creator.get_vm_inst_creators(
480             heat_keypair_option='private_key')
481         self.assertIsNotNone(vm_inst_creators)
482         self.assertEqual(2, len(vm_inst_creators))
483
484         for vm_inst_creator in vm_inst_creators:
485             if vm_inst_creator.get_vm_inst().name == self.vm_inst1_name:
486                 self.assertTrue(
487                     create_instance_tests.validate_ssh_client(vm_inst_creator))
488             else:
489                 vm_settings = vm_inst_creator.instance_settings
490                 self.assertEqual(0, len(vm_settings.floating_ip_settings))
491
492
493 class CreateStackNegativeTests(OSIntegrationTestCase):
494     """
495     Negative test cases for the CreateStack class
496     """
497
498     def setUp(self):
499         super(self.__class__, self).__start__()
500
501         self.heat_creds = self.admin_os_creds
502         self.heat_creds.project_name = self.admin_os_creds.project_name
503
504         self.stack_name = self.__class__.__name__ + '-' + str(uuid.uuid4())
505         self.stack_creator = None
506         self.heat_tmplt_path = pkg_resources.resource_filename(
507             'snaps.openstack.tests.heat', 'test_heat_template.yaml')
508
509     def tearDown(self):
510         if self.stack_creator:
511             self.stack_creator.clean()
512         super(self.__class__, self).__clean__()
513
514     def test_missing_dependencies(self):
515         """
516         Expect an StackCreationError when the stack file does not exist
517         """
518         stack_settings = StackSettings(name=self.stack_name,
519                                        template_path=self.heat_tmplt_path)
520         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
521                                                              stack_settings)
522         with self.assertRaises(HTTPBadRequest):
523             self.stack_creator.create()
524
525     def test_bad_stack_file(self):
526         """
527         Expect an StackCreationError when the stack file does not exist
528         """
529         stack_settings = StackSettings(name=self.stack_name,
530                                        template_path='foo')
531         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
532                                                              stack_settings)
533         with self.assertRaises(IOError):
534             self.stack_creator.create()