Merge "Yardstick output format unified"
[yardstick.git] / tests / unit / orchestrator / test_heat.py
1 #!/usr/bin/env python
2
3 ##############################################################################
4 # Copyright (c) 2017 Intel Corporation
5 #
6 # All rights reserved. This program and the accompanying materials
7 # are made available under the terms of the Apache License, Version 2.0
8 # which accompanies this distribution, and is available at
9 # http://www.apache.org/licenses/LICENSE-2.0
10 ##############################################################################
11
12 # Unittest for yardstick.benchmark.orchestrator.heat
13 from contextlib import contextmanager
14 from tempfile import NamedTemporaryFile
15 import unittest
16 import uuid
17 import time
18 import mock
19
20 from yardstick.benchmark.contexts import node
21 from yardstick.orchestrator import heat
22
23
24 TARGET_MODULE = 'yardstick.orchestrator.heat'
25
26
27 def mock_patch_target_module(inner_import):
28     return mock.patch('.'.join([TARGET_MODULE, inner_import]))
29
30
31 @contextmanager
32 def timer():
33     start = time.time()
34     data = {'start': start}
35     try:
36         yield data
37     finally:
38         data['end'] = end = time.time()
39         data['delta'] = end - start
40
41 def get_error_message(error):
42     try:
43         # py2
44         return error.message
45     except AttributeError:
46         # py3
47         return next((arg for arg in error.args if isinstance(arg, str)), None)
48
49
50 class HeatContextTestCase(unittest.TestCase):
51
52     def test_get_short_key_uuid(self):
53         u = uuid.uuid4()
54         k = heat.get_short_key_uuid(u)
55         self.assertEqual(heat.HEAT_KEY_UUID_LENGTH, len(k))
56         self.assertIn(k, str(u))
57
58 class HeatTemplateTestCase(unittest.TestCase):
59
60     def setUp(self):
61         self.template = heat.HeatTemplate('test')
62
63     def test_add_tenant_network(self):
64         self.template.add_network('some-network')
65
66         self.assertEqual(self.template.resources['some-network']['type'], 'OS::Neutron::Net')
67
68     def test_add_provider_network(self):
69         self.template.add_network('some-network', 'physnet2', 'sriov')
70
71         self.assertEqual(self.template.resources['some-network']['type'], 'OS::Neutron::ProviderNet')
72         self.assertEqual(self.template.resources['some-network']['properties']['physical_network'], 'physnet2')
73
74     def test_add_subnet(self):
75         netattrs = {'cidr': '10.0.0.0/24', 'provider': None, 'external_network': 'ext_net'}
76         self.template.add_subnet('some-subnet', "some-network", netattrs['cidr'])
77
78         self.assertEqual(self.template.resources['some-subnet']['type'], 'OS::Neutron::Subnet')
79         self.assertEqual(self.template.resources['some-subnet']['properties']['cidr'], '10.0.0.0/24')
80
81     def test_add_router(self):
82         self.template.add_router('some-router', 'ext-net', 'some-subnet')
83
84         self.assertEqual(self.template.resources['some-router']['type'], 'OS::Neutron::Router')
85         self.assertIn('some-subnet', self.template.resources['some-router']['depends_on'])
86
87     def test_add_router_interface(self):
88         self.template.add_router_interface('some-router-if', 'some-router', 'some-subnet')
89
90         self.assertEqual(self.template.resources['some-router-if']['type'], 'OS::Neutron::RouterInterface')
91         self.assertIn('some-subnet', self.template.resources['some-router-if']['depends_on'])
92
93     def test_add_servergroup(self):
94         self.template.add_servergroup('some-server-group', 'anti-affinity')
95
96         self.assertEqual(self.template.resources['some-server-group']['type'], 'OS::Nova::ServerGroup')
97         self.assertEqual(self.template.resources['some-server-group']['properties']['policies'], ['anti-affinity'])
98
99     def test__add_resources_to_template_raw(self):
100         test_context = node.NodeContext()
101         test_context.name = 'foo'
102         test_context.template_file = '/tmp/some-heat-file'
103         test_context.heat_parameters = {'image': 'cirros'}
104         test_context.key_filename = "/tmp/1234"
105         test_context.keypair_name = "foo-key"
106         test_context.secgroup_name = "foo-secgroup"
107         test_context.key_uuid = "2f2e4997-0a8e-4eb7-9fa4-f3f8fbbc393b"
108         heat_object = heat.HeatObject()
109
110         heat_stack = heat.HeatStack("tmpStack")
111         self.assertTrue(heat_stack.stacks_exist())
112
113         test_context.tmpfile = NamedTemporaryFile(delete=True, mode='w+t')
114         test_context.tmpfile.write("heat_template_version: 2015-04-30")
115         test_context.tmpfile.flush()
116         test_context.tmpfile.seek(0)
117         heat_template = heat.HeatTemplate(heat_object)
118         heat_template.resources = {}
119
120         heat_template.add_network("network1")
121         heat_template.add_network("network2")
122         heat_template.add_security_group("sec_group1")
123         heat_template.add_security_group("sec_group2")
124         heat_template.add_subnet("subnet1", "network1", "cidr1")
125         heat_template.add_subnet("subnet2", "network2", "cidr2")
126         heat_template.add_router("router1", "gw1", "subnet1")
127         heat_template.add_router_interface("router_if1", "router1", "subnet1")
128         heat_template.add_port("port1", "network1", "subnet1")
129         heat_template.add_port("port2", "network2", "subnet2", sec_group_id="sec_group1",provider="not-sriov")
130         heat_template.add_port("port3", "network2", "subnet2", sec_group_id="sec_group1",provider="sriov")
131         heat_template.add_floating_ip("floating_ip1", "network1", "port1", "router_if1")
132         heat_template.add_floating_ip("floating_ip2", "network2", "port2", "router_if2", "foo-secgroup")
133         heat_template.add_floating_ip_association("floating_ip1_association", "floating_ip1", "port1")
134         heat_template.add_servergroup("server_grp2", "affinity")
135         heat_template.add_servergroup("server_grp3", "anti-affinity")
136         heat_template.add_security_group("security_group")
137         heat_template.add_server(name="server1", image="image1", flavor="flavor1", flavors=[])
138         heat_template.add_server_group(name="servergroup", policies=["policy1","policy2"])
139         heat_template.add_server_group(name="servergroup", policies="policy1")
140         heat_template.add_server(name="server2", image="image1", flavor="flavor1", flavors=[], ports=["port1", "port2"],
141                                  networks=["network1", "network2"], scheduler_hints="hints1", user="user1",
142                                  key_name="foo-key", user_data="user", metadata={"cat": 1, "doc": 2},
143                                  additional_properties={"prop1": 1, "prop2": 2})
144         heat_template.add_server(name="server2", image="image1", flavor="flavor1", flavors=["flavor1", "flavor2"],
145                                  ports=["port1", "port2"],
146                                  networks=["network1", "network2"], scheduler_hints="hints1", user="user1",
147                                  key_name="foo-key", user_data="user", metadata={"cat": 1, "doc": 2},
148                                  additional_properties={"prop1": 1, "prop2": 2} )
149         heat_template.add_server(name="server2", image="image1", flavor="flavor1", flavors=["flavor3", "flavor4"],
150                                  ports=["port1", "port2"],
151                                  networks=["network1", "network2"], scheduler_hints="hints1", user="user1",
152                                  key_name="foo-key", user_data="user", metadata={"cat": 1, "doc": 2},
153                                  additional_properties={"prop1": 1, "prop2": 2})
154         heat_template.add_flavor(name="flavor1", vcpus=1, ram=2048, disk=1,extra_specs={"cat": 1, "dog": 2})
155         heat_template.add_flavor(name=None, vcpus=1, ram=2048)
156         heat_template.add_server(name="server1",
157                                  image="image1",
158                                  flavor="flavor1",
159                                  flavors=[],
160                                  ports=["port1", "port2"],
161                                  networks=["network1", "network2"],
162                                  scheduler_hints="hints1",
163                                  user="user1",
164                                  key_name="foo-key",
165                                  user_data="user",
166                                  metadata={"cat": 1, "doc": 2},
167                                  additional_properties= {"prop1": 1, "prop2": 2} )
168         heat_template.add_network("network1")
169
170         heat_template.add_flavor("test")
171         self.assertEqual(heat_template.resources['test']['type'], 'OS::Nova::Flavor')
172
173     @mock_patch_target_module('op_utils')
174     @mock_patch_target_module('heatclient.client.Client')
175     def test_create_negative(self, mock_heat_client_class, mock_op_utils):
176         self.template.HEAT_WAIT_LOOP_INTERVAL = interval = 0.2
177         mock_heat_client = mock_heat_client_class()  # get the constructed mock
178
179         # populate attributes of the constructed mock
180         mock_heat_client.stacks.get().stack_status_reason = 'the reason'
181
182         expected_status_calls = 0
183         expected_constructor_calls = 1  # above, to get the instance
184         expected_create_calls = 0
185         expected_op_utils_usage = 0
186
187         with mock.patch.object(self.template, 'status', return_value=None) as mock_status:
188             # block with timeout hit
189             timeout = 2
190             with self.assertRaises(RuntimeError) as raised, timer() as time_data:
191                 self.template.create(block=True, timeout=timeout)
192
193             # ensure runtime is approximately the timeout value
194             expected_time_low = timeout - interval * 0.2
195             expected_time_high = timeout + interval * 0.2
196             self.assertTrue(expected_time_low < time_data['delta'] < expected_time_high)
197
198             # ensure op_utils was used
199             expected_op_utils_usage += 1
200             self.assertEqual(mock_op_utils.get_session.call_count, expected_op_utils_usage)
201             self.assertEqual(mock_op_utils.get_endpoint.call_count, expected_op_utils_usage)
202             self.assertEqual(mock_op_utils.get_heat_api_version.call_count, expected_op_utils_usage)
203
204             # ensure the constructor and instance were used
205             expected_constructor_calls += 1
206             expected_create_calls += 1
207             self.assertEqual(mock_heat_client_class.call_count, expected_constructor_calls)
208             self.assertEqual(mock_heat_client.stacks.create.call_count, expected_create_calls)
209
210             # ensure that the status was used
211             self.assertGreater(mock_status.call_count, expected_status_calls)
212             expected_status_calls = mock_status.call_count  # synchronize the value
213
214             # ensure the expected exception was raised
215             error_message = get_error_message(raised.exception)
216             self.assertIn('timeout', error_message)
217             self.assertNotIn('the reason', error_message)
218
219             # block with create failed
220             timeout = 10
221             mock_status.side_effect = iter([None, None, u'CREATE_FAILED'])
222             with self.assertRaises(RuntimeError) as raised, timer() as time_data:
223                 self.template.create(block=True, timeout=timeout)
224
225             # ensure runtime is approximately two intervals
226             expected_time_low = interval * 1.8
227             expected_time_high = interval * 2.2
228             self.assertTrue(expected_time_low < time_data['delta'] < expected_time_high)
229
230             # ensure the existing heat_client was used and op_utils was used again
231             self.assertEqual(mock_op_utils.get_session.call_count, expected_op_utils_usage)
232             self.assertEqual(mock_op_utils.get_endpoint.call_count, expected_op_utils_usage)
233             self.assertEqual(mock_op_utils.get_heat_api_version.call_count, expected_op_utils_usage)
234
235             # ensure the constructor was not used but the instance was used
236             expected_create_calls += 1
237             self.assertEqual(mock_heat_client_class.call_count, expected_constructor_calls)
238             self.assertEqual(mock_heat_client.stacks.create.call_count, expected_create_calls)
239
240             # ensure that the status was used three times
241             expected_status_calls += 3
242             self.assertEqual(mock_status.call_count, expected_status_calls)
243
244             # ensure the expected exception was raised
245             error_message = get_error_message(raised.exception)
246             self.assertNotIn('timeout', error_message)
247             self.assertIn('the reason', error_message)
248
249     @mock_patch_target_module('op_utils')
250     @mock_patch_target_module('heatclient.client.Client')
251     def test_create(self, mock_heat_client_class, mock_op_utils):
252         self.template.HEAT_WAIT_LOOP_INTERVAL = interval = 0.2
253         mock_heat_client = mock_heat_client_class()
254
255         # populate attributes of the constructed mock
256         mock_heat_client.stacks.get().outputs = [
257             {'output_key': 'key1', 'output_value': 'value1'},
258             {'output_key': 'key2', 'output_value': 'value2'},
259             {'output_key': 'key3', 'output_value': 'value3'},
260         ]
261         expected_outputs = {
262             'key1': 'value1',
263             'key2': 'value2',
264             'key3': 'value3',
265         }
266
267         expected_status_calls = 0
268         expected_constructor_calls = 1  # above, to get the instance
269         expected_create_calls = 0
270         expected_op_utils_usage = 0
271
272         with mock.patch.object(self.template, 'status') as mock_status:
273             # no block
274             with timer() as time_data:
275                 self.assertIsInstance(self.template.create(block=False, timeout=2), heat.HeatStack)
276
277             # ensure runtime is much less than one interval
278             self.assertLess(time_data['delta'], interval * 0.2)
279
280             # ensure op_utils was used
281             expected_op_utils_usage += 1
282             self.assertEqual(mock_op_utils.get_session.call_count, expected_op_utils_usage)
283             self.assertEqual(mock_op_utils.get_endpoint.call_count, expected_op_utils_usage)
284             self.assertEqual(mock_op_utils.get_heat_api_version.call_count, expected_op_utils_usage)
285
286             # ensure the constructor and instance were used
287             expected_constructor_calls += 1
288             expected_create_calls += 1
289             self.assertEqual(mock_heat_client_class.call_count, expected_constructor_calls)
290             self.assertEqual(mock_heat_client.stacks.create.call_count, expected_create_calls)
291
292             # ensure that the status was not used
293             self.assertEqual(mock_status.call_count, expected_status_calls)
294
295             # ensure no outputs because this requires blocking
296             self.assertEqual(self.template.outputs, {})
297
298             # block with immediate complete
299             mock_status.return_value = u'CREATE_COMPLETE'
300             with timer() as time_data:
301                 self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack)
302
303             # ensure runtime is less than one interval
304             self.assertLess(time_data['delta'], interval * 0.2)
305
306             # ensure existing instance was re-used and op_utils was not used
307             expected_create_calls += 1
308             self.assertEqual(mock_heat_client_class.call_count, expected_constructor_calls)
309             self.assertEqual(mock_heat_client.stacks.create.call_count, expected_create_calls)
310
311             # ensure status was checked once
312             expected_status_calls += 1
313             self.assertEqual(mock_status.call_count, expected_status_calls)
314
315             # ensure the expected outputs are present
316             self.assertDictEqual(self.template.outputs, expected_outputs)
317
318             # reset template outputs
319             self.template.outputs = None
320
321             # block with delayed complete
322             mock_status.side_effect = iter([None, None, u'CREATE_COMPLETE'])
323             with timer() as time_data:
324                 self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack)
325
326             # ensure runtime is approximately two intervals
327             expected_time_low = interval * 1.8
328             expected_time_high = interval * 2.2
329             self.assertTrue(expected_time_low < time_data['delta'] < expected_time_high)
330
331             # ensure existing instance was re-used and op_utils was not used
332             expected_create_calls += 1
333             self.assertEqual(mock_heat_client_class.call_count, expected_constructor_calls)
334             self.assertEqual(mock_heat_client.stacks.create.call_count, expected_create_calls)
335
336             # ensure status was checked three more times
337             expected_status_calls += 3
338             self.assertEqual(mock_status.call_count, expected_status_calls)
339
340
341 class HeatStackTestCase(unittest.TestCase):
342
343     def test_delete_calls__delete_multiple_times(self):
344         stack = heat.HeatStack('test')
345         stack.uuid = 1
346         with mock.patch.object(stack, "_delete") as delete_mock:
347             stack.delete()
348         # call once and then call again if uuid is not none
349         self.assertGreater(delete_mock.call_count, 1)
350
351     def test_delete_all_calls_delete(self):
352         stack = heat.HeatStack('test')
353         stack.uuid = 1
354         with mock.patch.object(stack, "delete") as delete_mock:
355             stack.delete_all()
356         self.assertGreater(delete_mock.call_count, 0)