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