Make security group configurable - dovetail
[yardstick.git] / yardstick / tests / unit / orchestrator / test_heat.py
1 ##############################################################################
2 # Copyright (c) 2017 Intel Corporation
3 #
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
9
10 import tempfile
11
12 import munch
13 import mock
14 from oslo_serialization import jsonutils
15 from oslo_utils import uuidutils
16 import shade
17 import unittest
18
19 from yardstick.benchmark.contexts import node
20 from yardstick.common import constants
21 from yardstick.common import exceptions
22 from yardstick.orchestrator import heat
23
24
25 class FakeStack(object):
26
27     def __init__(self, outputs=None, status=None, id=None):
28         self.outputs = outputs
29         self.status = status
30         self.id = id
31
32
33 class HeatStackTestCase(unittest.TestCase):
34
35     def setUp(self):
36         self.stack_name = 'STACK NAME'
37         with mock.patch.object(shade, 'openstack_cloud'):
38             self.heatstack = heat.HeatStack(self.stack_name)
39         self._mock_stack_create = mock.patch.object(self.heatstack._cloud,
40                                                     'create_stack')
41         self.mock_stack_create = self._mock_stack_create.start()
42         self._mock_stack_delete = mock.patch.object(self.heatstack._cloud,
43                                                     'delete_stack')
44         self.mock_stack_delete = self._mock_stack_delete.start()
45         self._mock_stack_get = mock.patch.object(self.heatstack._cloud,
46                                                  'get_stack')
47         self.mock_stack_get = self._mock_stack_get.start()
48
49         self.addCleanup(self._cleanup)
50
51     def _cleanup(self):
52         self._mock_stack_create.stop()
53         self._mock_stack_delete.stop()
54         self._mock_stack_get.stop()
55         heat._DEPLOYED_STACKS = {}
56
57     @mock.patch.object(shade, 'openstack_cloud')
58     def test__init(self, mock_openstack_cloud):
59         os_cloud_config = {'key': 'value'}
60         heatstack = heat.HeatStack('name', os_cloud_config=os_cloud_config)
61         self.assertEqual('name', heatstack.name)
62         os_cloud_config.update(constants.OS_CLOUD_DEFAULT_CONFIG)
63         mock_openstack_cloud.assert_called_once_with(**os_cloud_config)
64
65     def test_create(self):
66         template = {'tkey': 'tval'}
67         heat_parameters = {'pkey': 'pval'}
68         outputs = [{'output_key': 'okey', 'output_value': 'oval'}]
69         id = uuidutils.generate_uuid()
70         self.mock_stack_create.return_value = FakeStack(
71             outputs=outputs, status=mock.Mock(), id=id)
72         mock_tfile = mock.Mock()
73         with mock.patch.object(tempfile._TemporaryFileWrapper, '__enter__',
74                                return_value=mock_tfile):
75             self.heatstack.create(template, heat_parameters, True, 100)
76             mock_tfile.write.assert_called_once_with(jsonutils.dump_as_bytes(template))
77             mock_tfile.close.assert_called_once()
78
79         self.mock_stack_create.assert_called_once_with(
80             self.stack_name, template_file=mock_tfile.name, wait=True,
81             timeout=100, pkey='pval')
82         self.assertEqual({'okey': 'oval'}, self.heatstack.outputs)
83         self.assertEqual(heat._DEPLOYED_STACKS[id], self.heatstack._stack)
84
85     def test_stacks_exist(self):
86         self.assertEqual(0, self.heatstack.stacks_exist())
87         heat._DEPLOYED_STACKS['id'] = 'stack'
88         self.assertEqual(1, self.heatstack.stacks_exist())
89
90     def test_delete_not_uuid(self):
91         self.assertIsNone(self.heatstack.delete())
92
93     def test_delete_existing_uuid(self):
94         id = uuidutils.generate_uuid()
95         self.heatstack._stack = FakeStack(
96             outputs=mock.Mock(), status=mock.Mock(), id=id)
97         heat._DEPLOYED_STACKS[id] = self.heatstack._stack
98         delete_return = mock.Mock()
99         self.mock_stack_delete.return_value = delete_return
100
101         ret = self.heatstack.delete(wait=True)
102         self.assertEqual(delete_return, ret)
103         self.assertFalse(heat._DEPLOYED_STACKS)
104         self.mock_stack_delete.assert_called_once_with(id, wait=True)
105
106     def test_delete_bug_in_shade(self):
107         id = uuidutils.generate_uuid()
108         self.heatstack._stack = FakeStack(
109             outputs=mock.Mock(), status=mock.Mock(), id=id)
110         heat._DEPLOYED_STACKS[id] = self.heatstack._stack
111         self.mock_stack_delete.side_effect = TypeError()
112
113         ret = self.heatstack.delete(wait=True)
114         self.assertTrue(ret)
115         self.assertFalse(heat._DEPLOYED_STACKS)
116         self.mock_stack_delete.assert_called_once_with(id, wait=True)
117
118     def test_get(self):
119         # make sure shade/get_stack is called with the appropriate vars
120         self.mock_stack_get.return_value = munch.Munch(
121             id="my-existing-stack-id",
122             outputs=[
123                 {
124                  u'output_value': u'b734d06a-dec7-...',
125                  u'output_key': u'ares.demo-test-port-network_id',
126                  u'description': u''
127                 },
128                 {u'output_value': u'b08da78c-2218-...',
129                  u'output_key': u'ares.demo-test-port-subnet_id',
130                  u'description': u''
131                 },
132                 {u'output_value': u'10.0.1.0/24',
133                  u'output_key': u'demo-test-subnet-cidr',
134                  u'description': u''
135                 },
136                 {u'output_value': u'b08da78c-2218-...',
137                  u'output_key': u'demo-test-subnet',
138                  u'description': u''
139                 },
140                 {u'output_value': u'b1a03624-aefc-...',
141                  u'output_key': u'ares.demo',
142                  u'description': u''
143                 },
144                 {u'output_value': u'266a8088-c630-...',
145                  u'output_key': u'demo-secgroup',
146                  u'description': u''
147                 },
148                 {u'output_value': u'10.0.1.5',
149                  u'output_key': u'ares.demo-test-port',
150                  u'description': u''
151                 },
152                 {u'output_value': u'10.0.1.1',
153                  u'output_key': u'demo-test-subnet-gateway_ip',
154                  u'description': u''
155                 },
156                 {u'output_value': u'',
157                  u'output_key': u'ares.demo-test-port-device_id',
158                  u'description': u''
159                 },
160                 {u'output_value': u'172.24.4.7',
161                  u'output_key': u'ares.demo-fip',
162                  u'description': u''
163                 },
164                 {u'output_value': u'fa:16:3e:6c:c3:0f',
165                  u'output_key': u'ares.demo-test-port-mac_address',
166                  u'description': u''}
167             ]
168         )
169         expected_outputs = {
170             'ares.demo-test-port-network_id': 'b734d06a-dec7-...',
171             'ares.demo-test-port-subnet_id': 'b08da78c-2218-...',
172             'demo-test-subnet-cidr': '10.0.1.0/24',
173             'demo-test-subnet': 'b08da78c-2218-...',
174             'ares.demo': 'b1a03624-aefc-...',
175             'demo-secgroup': '266a8088-c630-...',
176             'ares.demo-test-port': '10.0.1.5',
177             'demo-test-subnet-gateway_ip': '10.0.1.1',
178             'ares.demo-test-port-device_id': '',
179             'ares.demo-fip': '172.24.4.7',
180             'ares.demo-test-port-mac_address': 'fa:16:3e:6c:c3:0f',
181         }
182
183         stack_id = "my-existing-stack-id"
184         self.heatstack.name = "my-existing-stack"
185         self.heatstack.get()
186
187         self.mock_stack_get.assert_called_once_with(self.heatstack.name)
188         self.assertEqual(expected_outputs, self.heatstack.outputs)
189         self.assertEqual(1, len(heat._DEPLOYED_STACKS))
190         self.assertEqual(self.heatstack._stack,
191                          heat._DEPLOYED_STACKS[stack_id])
192
193     def test_get_invalid_name(self):
194         # No context matching this name exists
195         self.mock_stack_get.return_value = []
196         self.heatstack.name = 'not-a-stack'
197         self.heatstack.get()
198         self.assertEqual(0, len(heat._DEPLOYED_STACKS))
199
200
201 class HeatTemplateTestCase(unittest.TestCase):
202
203     def setUp(self):
204         self._os_cloud_config = {'key1': 'value1'}
205         self.template = heat.HeatTemplate(
206             'test', os_cloud_config=self._os_cloud_config)
207
208     def test_add_tenant_network(self):
209         self.template.add_network('some-network')
210
211         self.assertEqual('OS::Neutron::Net',
212                          self.template.resources['some-network']['type'])
213
214     def test_add_provider_network(self):
215         self.template.add_network('some-network', 'physnet2', 'sriov')
216
217         self.assertEqual(self.template.resources['some-network']['type'],
218                          'OS::Neutron::ProviderNet')
219         self.assertEqual(self.template.resources['some-network'][
220                              'properties']['physical_network'], 'physnet2')
221
222     def test_add_subnet(self):
223         netattrs = {'cidr': '10.0.0.0/24',
224                     'provider': None,
225                     'external_network': 'ext_net'}
226         self.template.add_subnet('some-subnet', "some-network",
227                                  netattrs['cidr'])
228
229         self.assertEqual(self.template.resources['some-subnet']['type'],
230                          'OS::Neutron::Subnet')
231         self.assertEqual(self.template.resources['some-subnet']['properties'][
232                              'cidr'], '10.0.0.0/24')
233
234     def test_add_router(self):
235         self.template.add_router('some-router', 'ext-net', 'some-subnet')
236
237         self.assertEqual(self.template.resources['some-router']['type'],
238                          'OS::Neutron::Router')
239         self.assertIn('some-subnet',
240                       self.template.resources['some-router']['depends_on'])
241
242     def test_add_router_interface(self):
243         self.template.add_router_interface('some-router-if', 'some-router',
244                                            'some-subnet')
245
246         self.assertEqual(self.template.resources['some-router-if']['type'],
247                          'OS::Neutron::RouterInterface')
248         self.assertIn('some-subnet',
249                       self.template.resources['some-router-if']['depends_on'])
250
251     def test_add_servergroup(self):
252         self.template.add_servergroup('some-server-group', 'anti-affinity')
253
254         self.assertEqual(self.template.resources['some-server-group']['type'],
255                          'OS::Nova::ServerGroup')
256         self.assertEqual(self.template.resources['some-server-group'][
257                              'properties']['policies'], ['anti-affinity'])
258
259     def test_add_security_group(self):
260         security_group = {
261             'rules': [
262                 {'remote_ip_prefix': '0.0.0.0/0',
263                  'port_range_max': 65535,
264                  'port_range_min': 1,
265                  'protocol': 'custom'},
266             ]
267         }
268         self.template.add_security_group('some-security-group', security_group)
269
270         secgroup_rsc = self.template.resources['some-security-group']
271
272         self.assertEqual(secgroup_rsc['type'], "OS::Neutron::SecurityGroup")
273         self.assertEqual(secgroup_rsc['properties']['description'],
274                          "Custom security group rules defined by the user")
275         self.assertEqual(secgroup_rsc['properties']['rules'][0]['protocol'],
276                          'custom')
277
278     def test__add_resources_to_template_raw(self):
279         test_context = node.NodeContext()
280         self.addCleanup(test_context._delete_context)
281         test_context._name = 'foo'
282         test_context.template_file = '/tmp/some-heat-file'
283         test_context.heat_parameters = {'image': 'cirros'}
284         test_context.key_filename = "/tmp/1234"
285         test_context.keypair_name = "foo-key"
286         test_context.secgroup_name = "foo-secgroup"
287         test_context.key_uuid = "2f2e4997-0a8e-4eb7-9fa4-f3f8fbbc393b"
288
289         test_context.tmpfile = tempfile.NamedTemporaryFile(
290             delete=True, mode='w+t')
291         test_context.tmpfile.write("heat_template_version: 2015-04-30")
292         test_context.tmpfile.flush()
293         test_context.tmpfile.seek(0)
294         heat_template = heat.HeatTemplate('template name')
295         heat_template.resources = {}
296
297         heat_template.add_network("network1")
298         heat_template.add_network("network2")
299         heat_template.add_security_group("sec_group1")
300         heat_template.add_security_group("sec_group2")
301         heat_template.add_subnet("subnet1", "network1", "cidr1")
302         heat_template.add_subnet("subnet2", "network2", "cidr2")
303         heat_template.add_router("router1", "gw1", "subnet1")
304         heat_template.add_router_interface("router_if1", "router1", "subnet1")
305         network1 = mock.MagicMock()
306         network1.stack_name = "network1"
307         network1.subnet_stack_name = "subnet1"
308         network1.vnic_type = "normal"
309         network2 = mock.MagicMock()
310         network2.stack_name = "network2"
311         network2.subnet_stack_name = "subnet2"
312         network2.vnic_type = "normal"
313         heat_template.add_port("port1", network1)
314         heat_template.add_port("port2", network2,
315                                sec_group_id="sec_group1", provider="not-sriov")
316         heat_template.add_port("port3", network2,
317                                sec_group_id="sec_group1", provider="sriov")
318         heat_template.add_floating_ip("floating_ip1", "network1", "port1",
319                                       "router_if1")
320         heat_template.add_floating_ip("floating_ip2", "network2", "port2",
321                                       "router_if2", "foo-secgroup")
322         heat_template.add_floating_ip_association("floating_ip1_association",
323                                                   "floating_ip1", "port1")
324         heat_template.add_servergroup("server_grp2", "affinity")
325         heat_template.add_servergroup("server_grp3", "anti-affinity")
326         heat_template.add_security_group("security_group")
327         heat_template.add_server(name="server1", image="image1",
328                                  flavor="flavor1", flavors=[])
329         heat_template.add_server_group(name="servergroup",
330                                        policies=["policy1", "policy2"])
331         heat_template.add_server_group(name="servergroup",
332                                        policies="policy1")
333         heat_template.add_server(
334             name="server2", image="image1", flavor="flavor1", flavors=[],
335             ports=["port1", "port2"], networks=["network1", "network2"],
336             scheduler_hints="hints1", user="user1", key_name="foo-key",
337             user_data="user", metadata={"cat": 1, "doc": 2},
338             additional_properties={"prop1": 1, "prop2": 2})
339         heat_template.add_server(
340             name="server2", image="image1", flavor="flavor1",
341             flavors=["flavor1", "flavor2"], ports=["port1", "port2"],
342             networks=["network1", "network2"], scheduler_hints="hints1",
343             user="user1", key_name="foo-key", user_data="user",
344             metadata={"cat": 1, "doc": 2},
345             additional_properties={"prop1": 1, "prop2": 2})
346         heat_template.add_server(
347             name="server2", image="image1", flavor="flavor1",
348             flavors=["flavor3", "flavor4"], ports=["port1", "port2"],
349             networks=["network1", "network2"], scheduler_hints="hints1",
350             user="user1", key_name="foo-key", user_data="user",
351             metadata={"cat": 1, "doc": 2},
352             additional_properties={"prop1": 1, "prop2": 2})
353         heat_template.add_flavor(name="flavor1", vcpus=1, ram=2048, disk=1,
354                                  extra_specs={"cat": 1, "dog": 2})
355         heat_template.add_flavor(name=None, vcpus=1, ram=2048)
356         heat_template.add_server(
357             name="server1", image="image1", flavor="flavor1", flavors=[],
358             ports=["port1", "port2"], networks=["network1", "network2"],
359             scheduler_hints="hints1", user="user1", key_name="foo-key",
360             user_data="user", metadata={"cat": 1, "doc": 2},
361             additional_properties={"prop1": 1, "prop2": 2})
362         heat_template.add_network("network1")
363
364         heat_template.add_flavor("test")
365         self.assertEqual(heat_template.resources['test']['type'],
366                          'OS::Nova::Flavor')
367
368     def test_create_not_block(self):
369         heat_stack = mock.Mock()
370         with mock.patch.object(heat, 'HeatStack', return_value=heat_stack) \
371                 as mock_heatstack:
372             ret = self.template.create(block=False)
373
374         mock_heatstack.assert_called_once_with(
375             self.template.name, os_cloud_config=self.template._os_cloud_config)
376         heat_stack.create.assert_called_once_with(
377             self.template._template, self.template.heat_parameters, False,
378             3600)
379         self.assertEqual(heat_stack, ret)
380
381     def test_create_block(self):
382         heat_stack = mock.Mock()
383         heat_stack.status = self.template.HEAT_STATUS_COMPLETE
384         with mock.patch.object(heat, 'HeatStack', return_value=heat_stack):
385             ret = self.template.create(block=False)
386         heat_stack.create.assert_called_once_with(
387             self.template._template, self.template.heat_parameters, False,
388             3600)
389         self.assertEqual(heat_stack, ret)
390
391     def test_create_block_status_no_complete(self):
392         heat_stack = mock.Mock()
393         heat_stack.status = 'other status'
394         heat_stack.get_failures.return_value = []
395         with mock.patch.object(heat, 'HeatStack', return_value=heat_stack):
396             self.assertRaises(exceptions.HeatTemplateError,
397                               self.template.create, block=True)
398         heat_stack.create.assert_called_once_with(
399             self.template._template, self.template.heat_parameters, True,
400             3600)
401
402     def test_create_block_status_no_complete_with_reasons(self):
403         heat_stack = mock.Mock()
404         heat_stack.status = 'other status'
405         heat_stack.get_failures.return_value = [
406             mock.Mock(resource_status_reason="A reason"),
407             mock.Mock(resource_status_reason="Something else")
408         ]
409         with mock.patch.object(heat, 'HeatStack', return_value=heat_stack):
410             with mock.patch.object(heat, 'log') as mock_log:
411                 self.assertRaises(exceptions.HeatTemplateError,
412                                   self.template.create, block=True)
413                 mock_log.error.assert_any_call("%s", "A reason")
414                 mock_log.error.assert_any_call("%s", "Something else")
415         heat_stack.create.assert_called_once_with(
416             self.template._template, self.template.heat_parameters, True,
417             3600)