Merge "SNAPS Stack creators can now return SNAPS network creators."
[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
35 from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase
36 from snaps.openstack.utils import heat_utils, neutron_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     Test 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.env_values = {
159             'image_name': self.image_creator.image_settings.name,
160             'flavor_name': self.flavor_creator.flavor_settings.name,
161             'net_name': self.network_name,
162             'subnet_name': self.subnet_name}
163
164         self.heat_tmplt_path = pkg_resources.resource_filename(
165             'snaps.openstack.tests.heat', 'test_heat_template.yaml')
166
167     def tearDown(self):
168         """
169         Cleans the stack and downloaded stack file
170         """
171         if self.stack_creator:
172             try:
173                 self.stack_creator.clean()
174             except:
175                 pass
176
177         if self.image_creator:
178             try:
179                 self.image_creator.clean()
180             except:
181                 pass
182
183         if self.flavor_creator:
184             try:
185                 self.flavor_creator.clean()
186             except:
187                 pass
188
189         super(self.__class__, self).__clean__()
190
191     def test_create_stack_template_file(self):
192         """
193         Tests the creation of an OpenStack stack from Heat template file.
194         """
195         # Create Stack
196         # Set the default stack settings, then set any custom parameters sent
197         # from the app
198         stack_settings = StackSettings(
199             name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
200             template_path=self.heat_tmplt_path,
201             env_values=self.env_values)
202         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
203                                                              stack_settings)
204         created_stack = self.stack_creator.create()
205         self.assertIsNotNone(created_stack)
206
207         retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli,
208                                                      created_stack.id)
209         self.assertIsNotNone(retrieved_stack)
210         self.assertEqual(created_stack.name, retrieved_stack.name)
211         self.assertEqual(created_stack.id, retrieved_stack.id)
212         self.assertIsNotNone(self.stack_creator.get_outputs())
213         self.assertEquals(0, len(self.stack_creator.get_outputs()))
214
215         resources = heat_utils.get_resources(
216             self.heat_cli, self.stack_creator.get_stack())
217         self.assertIsNotNone(resources)
218         self.assertEqual(4, len(resources))
219
220     def test_create_stack_template_dict(self):
221         """
222         Tests the creation of an OpenStack stack from a heat dict() object.
223         """
224         # Create Stack
225         # Set the default stack settings, then set any custom parameters sent
226         # from the app
227         template_dict = heat_utils.parse_heat_template_str(
228             file_utils.read_file(self.heat_tmplt_path))
229         stack_settings = StackSettings(
230             name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
231             template=template_dict,
232             env_values=self.env_values)
233         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
234                                                              stack_settings)
235         created_stack = self.stack_creator.create()
236         self.assertIsNotNone(created_stack)
237
238         retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli,
239                                                      created_stack.id)
240         self.assertIsNotNone(retrieved_stack)
241         self.assertEqual(created_stack.name, retrieved_stack.name)
242         self.assertEqual(created_stack.id, retrieved_stack.id)
243         self.assertIsNotNone(self.stack_creator.get_outputs())
244         self.assertEquals(0, len(self.stack_creator.get_outputs()))
245
246     def test_create_delete_stack(self):
247         """
248         Tests the creation then deletion of an OpenStack stack to ensure
249         clean() does not raise an Exception.
250         """
251         # Create Stack
252         template_dict = heat_utils.parse_heat_template_str(
253             file_utils.read_file(self.heat_tmplt_path))
254         stack_settings = StackSettings(
255             name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
256             template=template_dict,
257             env_values=self.env_values)
258         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
259                                                              stack_settings)
260         created_stack = self.stack_creator.create()
261         self.assertIsNotNone(created_stack)
262
263         retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli,
264                                                      created_stack.id)
265         self.assertIsNotNone(retrieved_stack)
266         self.assertEqual(created_stack.name, retrieved_stack.name)
267         self.assertEqual(created_stack.id, retrieved_stack.id)
268         self.assertIsNotNone(self.stack_creator.get_outputs())
269         self.assertEquals(0, len(self.stack_creator.get_outputs()))
270         self.assertEqual(create_stack.STATUS_CREATE_COMPLETE,
271                          self.stack_creator.get_status())
272
273         # Delete Stack manually
274         heat_utils.delete_stack(self.heat_cli, created_stack)
275
276         end_time = time.time() + 90
277         deleted = False
278         while time.time() < end_time:
279             status = heat_utils.get_stack_status(self.heat_cli,
280                                                  retrieved_stack.id)
281             if status == create_stack.STATUS_DELETE_COMPLETE:
282                 deleted = True
283                 break
284
285         self.assertTrue(deleted)
286
287         # Must not throw an exception when attempting to cleanup non-existent
288         # stack
289         self.stack_creator.clean()
290         self.assertIsNone(self.stack_creator.get_stack())
291
292     def test_create_same_stack(self):
293         """
294         Tests the creation of an OpenStack stack when the stack already exists.
295         """
296         # Create Stack
297         template_dict = heat_utils.parse_heat_template_str(
298             file_utils.read_file(self.heat_tmplt_path))
299         stack_settings = StackSettings(
300             name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
301             template=template_dict,
302             env_values=self.env_values)
303         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
304                                                              stack_settings)
305         created_stack1 = self.stack_creator.create()
306
307         retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli,
308                                                      created_stack1.id)
309         self.assertIsNotNone(retrieved_stack)
310         self.assertEqual(created_stack1.name, retrieved_stack.name)
311         self.assertEqual(created_stack1.id, retrieved_stack.id)
312         self.assertIsNotNone(self.stack_creator.get_outputs())
313         self.assertEqual(0, len(self.stack_creator.get_outputs()))
314
315         # Should be retrieving the instance data
316         stack_creator2 = create_stack.OpenStackHeatStack(self.heat_creds,
317                                                          stack_settings)
318         stack2 = stack_creator2.create()
319         self.assertEqual(created_stack1.id, stack2.id)
320
321     def test_retrieve_network_creators(self):
322         """
323         Tests the creation of an OpenStack stack from Heat template file and
324         the retrieval of the network creator.
325         """
326         stack_settings = StackSettings(
327             name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
328             template_path=self.heat_tmplt_path,
329             env_values=self.env_values)
330         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
331                                                              stack_settings)
332         created_stack = self.stack_creator.create()
333         self.assertIsNotNone(created_stack)
334
335         net_creators = self.stack_creator.get_network_creators()
336         self.assertIsNotNone(net_creators)
337         self.assertEqual(1, len(net_creators))
338         self.assertEqual(self.network_name, net_creators[0].get_network().name)
339
340         neutron = neutron_utils.neutron_client(self.os_creds)
341         net_by_name = neutron_utils.get_network(
342             neutron, network_name=net_creators[0].get_network().name)
343         self.assertEqual(net_creators[0].get_network(), net_by_name)
344         self.assertIsNotNone(neutron_utils.get_network_by_id(
345             neutron, net_creators[0].get_network().id))
346
347         self.assertEqual(1, len(net_creators[0].get_subnets()))
348         subnet = net_creators[0].get_subnets()[0]
349         subnet_by_name = neutron_utils.get_subnet(
350             neutron, subnet_name=subnet.name)
351         self.assertEqual(subnet, subnet_by_name)
352
353         subnet_by_id = neutron_utils.get_subnet_by_id(neutron, subnet.id)
354         self.assertIsNotNone(subnet_by_id)
355         self.assertEqual(subnet_by_name, subnet_by_id)
356
357
358 class CreateStackNegativeTests(OSIntegrationTestCase):
359     """
360     Negative test cases for the CreateStack class
361     """
362
363     def setUp(self):
364         super(self.__class__, self).__start__()
365
366         self.heat_creds = self.admin_os_creds
367         self.heat_creds.project_name = self.admin_os_creds.project_name
368
369         self.stack_name = self.__class__.__name__ + '-' + str(uuid.uuid4())
370         self.stack_creator = None
371         self.heat_tmplt_path = pkg_resources.resource_filename(
372             'snaps.openstack.tests.heat', 'test_heat_template.yaml')
373
374     def tearDown(self):
375         if self.stack_creator:
376             self.stack_creator.clean()
377         super(self.__class__, self).__clean__()
378
379     def test_missing_dependencies(self):
380         """
381         Expect an StackCreationError when the stack file does not exist
382         """
383         stack_settings = StackSettings(name=self.stack_name,
384                                        template_path=self.heat_tmplt_path)
385         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
386                                                              stack_settings)
387         with self.assertRaises(HTTPBadRequest):
388             self.stack_creator.create()
389
390     def test_bad_stack_file(self):
391         """
392         Expect an StackCreationError when the stack file does not exist
393         """
394         stack_settings = StackSettings(name=self.stack_name,
395                                        template_path='foo')
396         self.stack_creator = create_stack.OpenStackHeatStack(self.heat_creds,
397                                                              stack_settings)
398         with self.assertRaises(IOError):
399             self.stack_creator.create()