Added support for applying Heat Templates
[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 from heatclient.exc import HTTPBadRequest
18 from snaps import file_utils
19
20 from snaps.openstack.create_flavor import OpenStackFlavor, FlavorSettings
21
22 from snaps.openstack.create_image import OpenStackImage
23
24 try:
25     from urllib.request import URLError
26 except ImportError:
27     from urllib2 import URLError
28
29 import logging
30 import unittest
31 import uuid
32
33 from snaps.openstack import create_stack
34 from snaps.openstack.create_stack import StackSettings, StackSettingsError
35 from snaps.openstack.tests import openstack_tests
36 from snaps.openstack.tests.os_source_file_test import OSIntegrationTestCase
37 from snaps.openstack.utils import heat_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     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(config=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(config={'name': 'foo'})
63
64     def test_config_minimum_template(self):
65         settings = StackSettings(config={'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, settings.stack_create_timeout)
71
72     def test_config_minimum_template_path(self):
73         settings = StackSettings(config={'name': 'stack', 'template_path': 'foo'})
74         self.assertEqual('stack', settings.name)
75         self.assertIsNone(settings.template)
76         self.assertEqual('foo', settings.template_path)
77         self.assertIsNone(settings.env_values)
78         self.assertEqual(create_stack.STACK_COMPLETE_TIMEOUT, settings.stack_create_timeout)
79
80     def test_minimum_template(self):
81         settings = StackSettings(name='stack', template='foo')
82         self.assertEqual('stack', settings.name)
83         self.assertEqual('foo', settings.template)
84         self.assertIsNone(settings.template_path)
85         self.assertIsNone(settings.env_values)
86         self.assertEqual(create_stack.STACK_COMPLETE_TIMEOUT, settings.stack_create_timeout)
87
88     def test_minimum_template_path(self):
89         settings = StackSettings(name='stack', template_path='foo')
90         self.assertEqual('stack', settings.name)
91         self.assertEqual('foo', settings.template_path)
92         self.assertIsNone(settings.template)
93         self.assertIsNone(settings.env_values)
94         self.assertEqual(create_stack.STACK_COMPLETE_TIMEOUT, settings.stack_create_timeout)
95
96     def test_all(self):
97         env_values = {'foo': 'bar'}
98         settings = StackSettings(name='stack', template='bar', template_path='foo', env_values=env_values,
99                                  stack_create_timeout=999)
100         self.assertEqual('stack', settings.name)
101         self.assertEqual('bar', settings.template)
102         self.assertEqual('foo', settings.template_path)
103         self.assertEqual(env_values, settings.env_values)
104         self.assertEqual(999, settings.stack_create_timeout)
105
106     def test_config_all(self):
107         env_values = {'foo': 'bar'}
108         settings = StackSettings(
109             config={'name': 'stack', 'template': 'bar', 'template_path': 'foo',
110                     'env_values': env_values, 'stack_create_timeout': 999})
111         self.assertEqual('stack', settings.name)
112         self.assertEqual('bar', settings.template)
113         self.assertEqual('foo', settings.template_path)
114         self.assertEqual(env_values, settings.env_values)
115         self.assertEqual(999, settings.stack_create_timeout)
116
117
118 class CreateStackSuccessTests(OSIntegrationTestCase):
119     """
120     Test for the CreateStack class defined in create_stack.py
121     """
122
123     def setUp(self):
124         """
125         Instantiates the CreateStack object that is responsible for downloading and creating an OS stack file
126         within OpenStack
127         """
128         super(self.__class__, self).__start__()
129
130         self.guid = str(uuid.uuid4())
131         self.heat_cli = heat_utils.heat_client(self.os_creds)
132         self.stack_creator = None
133
134         self.image_creator = OpenStackImage(
135             self.os_creds, openstack_tests.cirros_image_settings(
136                 name=self.__class__.__name__ + '-' + str(self.guid) + '-image'))
137         self.image_creator.create()
138
139         # Create Flavor
140         self.flavor_creator = OpenStackFlavor(
141             self.admin_os_creds,
142             FlavorSettings(name=self.guid + '-flavor-name', ram=128, disk=10, vcpus=1))
143         self.flavor_creator.create()
144
145         self.env_values = {'image_name': self.image_creator.image_settings.name,
146                            'flavor_name': self.flavor_creator.flavor_settings.name}
147
148     def tearDown(self):
149         """
150         Cleans the stack and downloaded stack file
151         """
152         if self.stack_creator:
153             self.stack_creator.clean()
154
155         if self.image_creator:
156             try:
157                 self.image_creator.clean()
158             except:
159                 pass
160
161         if self.flavor_creator:
162             try:
163                 self.flavor_creator.clean()
164             except:
165                 pass
166
167         super(self.__class__, self).__clean__()
168
169     def test_create_stack_template_file(self):
170         """
171         Tests the creation of an OpenStack stack from Heat template file.
172         """
173         # Create Stack
174         # Set the default stack settings, then set any custom parameters sent from the app
175         stack_settings = StackSettings(name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
176                                        template_path='../examples/heat/test_heat_template.yaml',
177                                        env_values=self.env_values)
178         self.stack_creator = create_stack.OpenStackHeatStack(self.os_creds, stack_settings)
179         created_stack = self.stack_creator.create()
180         self.assertIsNotNone(created_stack)
181
182         retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli, created_stack.id)
183         self.assertIsNotNone(retrieved_stack)
184         self.assertEqual(created_stack.name, retrieved_stack.name)
185         self.assertEqual(created_stack.id, retrieved_stack.id)
186         self.assertIsNotNone(self.stack_creator.get_outputs())
187         self.assertEquals(0, len(self.stack_creator.get_outputs()))
188
189     def test_create_stack_template_dict(self):
190         """
191         Tests the creation of an OpenStack stack from a heat dict() object.
192         """
193         # Create Stack
194         # Set the default stack settings, then set any custom parameters sent from the app
195         template_dict = heat_utils.parse_heat_template_str(
196             file_utils.read_file('../examples/heat/test_heat_template.yaml'))
197         stack_settings = StackSettings(name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
198                                        template=template_dict,
199                                        env_values=self.env_values)
200         self.stack_creator = create_stack.OpenStackHeatStack(self.os_creds, stack_settings)
201         created_stack = self.stack_creator.create()
202         self.assertIsNotNone(created_stack)
203
204         retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli, created_stack.id)
205         self.assertIsNotNone(retrieved_stack)
206         self.assertEqual(created_stack.name, retrieved_stack.name)
207         self.assertEqual(created_stack.id, retrieved_stack.id)
208         self.assertIsNotNone(self.stack_creator.get_outputs())
209         self.assertEquals(0, len(self.stack_creator.get_outputs()))
210
211     def test_create_delete_stack(self):
212         """
213         Tests the creation then deletion of an OpenStack stack to ensure clean() does not raise an Exception.
214         """
215         # Create Stack
216         template_dict = heat_utils.parse_heat_template_str(
217             file_utils.read_file('../examples/heat/test_heat_template.yaml'))
218         stack_settings = StackSettings(name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
219                                        template=template_dict,
220                                        env_values=self.env_values)
221         self.stack_creator = create_stack.OpenStackHeatStack(self.os_creds, stack_settings)
222         created_stack = self.stack_creator.create()
223         self.assertIsNotNone(created_stack)
224
225         retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli, created_stack.id)
226         self.assertIsNotNone(retrieved_stack)
227         self.assertEqual(created_stack.name, retrieved_stack.name)
228         self.assertEqual(created_stack.id, retrieved_stack.id)
229         self.assertIsNotNone(self.stack_creator.get_outputs())
230         self.assertEquals(0, len(self.stack_creator.get_outputs()))
231         self.assertEqual(create_stack.STATUS_CREATE_COMPLETE, self.stack_creator.get_status())
232
233         # Delete Stack manually
234         heat_utils.delete_stack(self.heat_cli, created_stack)
235
236         end_time = time.time() + 90
237         deleted = False
238         while time.time() < end_time:
239             status = heat_utils.get_stack_status(self.heat_cli, retrieved_stack.id)
240             if status == create_stack.STATUS_DELETE_COMPLETE:
241                 deleted = True
242                 break
243
244         self.assertTrue(deleted)
245
246         # Must not throw an exception when attempting to cleanup non-existent stack
247         self.stack_creator.clean()
248         self.assertIsNone(self.stack_creator.get_stack())
249
250     def test_create_same_stack(self):
251         """
252         Tests the creation of an OpenStack stack when the stack already exists.
253         """
254         # Create Stack
255         template_dict = heat_utils.parse_heat_template_str(
256             file_utils.read_file('../examples/heat/test_heat_template.yaml'))
257         stack_settings = StackSettings(name=self.__class__.__name__ + '-' + str(self.guid) + '-stack',
258                                        template=template_dict,
259                                        env_values=self.env_values)
260         self.stack_creator = create_stack.OpenStackHeatStack(self.os_creds, stack_settings)
261         created_stack1 = self.stack_creator.create()
262
263         retrieved_stack = heat_utils.get_stack_by_id(self.heat_cli, created_stack1.id)
264         self.assertIsNotNone(retrieved_stack)
265         self.assertEqual(created_stack1.name, retrieved_stack.name)
266         self.assertEqual(created_stack1.id, retrieved_stack.id)
267         self.assertIsNotNone(self.stack_creator.get_outputs())
268         self.assertEqual(0, len(self.stack_creator.get_outputs()))
269
270         # Should be retrieving the instance data
271         stack_creator2 = create_stack.OpenStackHeatStack(self.os_creds, stack_settings)
272         stack2 = stack_creator2.create()
273         self.assertEqual(created_stack1.id, stack2.id)
274
275
276 class CreateStackNegativeTests(OSIntegrationTestCase):
277     """
278     Negative test cases for the CreateStack class
279     """
280
281     def setUp(self):
282         super(self.__class__, self).__start__()
283
284         self.stack_name = self.__class__.__name__ + '-' + str(uuid.uuid4())
285         self.stack_creator = None
286
287     def tearDown(self):
288         if self.stack_creator:
289             self.stack_creator.clean()
290         super(self.__class__, self).__clean__()
291
292     def test_missing_dependencies(self):
293         """
294         Expect an StackCreationError when the stack file does not exist
295         """
296         stack_settings = StackSettings(name=self.stack_name, template_path='../examples/heat/test_heat_template.yaml')
297         self.stack_creator = create_stack.OpenStackHeatStack(self.os_creds, stack_settings)
298         with self.assertRaises(HTTPBadRequest):
299             self.stack_creator.create()
300
301     def test_bad_stack_file(self):
302         """
303         Expect an StackCreationError when the stack file does not exist
304         """
305         stack_settings = StackSettings(name=self.stack_name, template_path='foo')
306         self.stack_creator = create_stack.OpenStackHeatStack(self.os_creds, stack_settings)
307         with self.assertRaises(IOError):
308             self.stack_creator.create()