Remove references to "dpdk_nic_bind" utility
[yardstick.git] / yardstick / 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 time
17 import uuid
18
19 import mock
20 import unittest
21
22 from yardstick.benchmark.contexts import node
23 from yardstick.orchestrator import heat
24
25
26 TARGET_MODULE = 'yardstick.orchestrator.heat'
27
28
29 def mock_patch_target_module(inner_import):
30     return mock.patch('.'.join([TARGET_MODULE, inner_import]))
31
32
33 @contextmanager
34 def timer():
35     start = time.time()
36     data = {'start': start}
37     try:
38         yield data
39     finally:
40         data['end'] = end = time.time()
41         data['delta'] = end - start
42
43
44 def index_value_iter(index, index_value, base_value=None):
45     for current_index in count():
46         if current_index == index:
47             yield index_value
48         else:
49             yield base_value
50
51
52 def get_error_message(error):
53     try:
54         # py2
55         return error.message
56     except AttributeError:
57         # py3
58         return next((arg for arg in error.args if isinstance(arg, str)), None)
59
60
61 class HeatContextTestCase(unittest.TestCase):
62
63     def test_get_short_key_uuid(self):
64         u = uuid.uuid4()
65         k = heat.get_short_key_uuid(u)
66         self.assertEqual(heat.HEAT_KEY_UUID_LENGTH, len(k))
67         self.assertIn(k, str(u))
68
69
70 class HeatTemplateTestCase(unittest.TestCase):
71
72     def setUp(self):
73         self.template = heat.HeatTemplate('test')
74
75     def test_add_tenant_network(self):
76         self.template.add_network('some-network')
77
78         self.assertEqual(
79             self.template.resources['some-network']['type'],
80             'OS::Neutron::Net')
81
82     def test_add_provider_network(self):
83         self.template.add_network('some-network', 'physnet2', 'sriov')
84
85         self.assertEqual(
86             self.template.resources['some-network']['type'],
87             'OS::Neutron::ProviderNet')
88         self.assertEqual(
89             self.template.resources['some-network']['properties']['physical_network'],
90             'physnet2')
91
92     def test_add_subnet(self):
93         netattrs = {'cidr': '10.0.0.0/24',
94                     'provider': None, 'external_network': 'ext_net'}
95         self.template.add_subnet(
96             'some-subnet', "some-network", netattrs['cidr'])
97
98         self.assertEqual(
99             self.template.resources['some-subnet']['type'],
100             'OS::Neutron::Subnet')
101         self.assertEqual(
102             self.template.resources['some-subnet']['properties']['cidr'],
103             '10.0.0.0/24')
104
105     def test_add_router(self):
106         self.template.add_router('some-router', 'ext-net', 'some-subnet')
107
108         self.assertEqual(
109             self.template.resources['some-router']['type'],
110             'OS::Neutron::Router')
111         self.assertIn(
112             'some-subnet',
113             self.template.resources['some-router']['depends_on'])
114
115     def test_add_router_interface(self):
116         self.template.add_router_interface(
117             'some-router-if', 'some-router', 'some-subnet')
118
119         self.assertEqual(
120             self.template.resources['some-router-if']['type'],
121             'OS::Neutron::RouterInterface')
122         self.assertIn(
123             'some-subnet',
124             self.template.resources['some-router-if']['depends_on'])
125
126     def test_add_servergroup(self):
127         self.template.add_servergroup('some-server-group', 'anti-affinity')
128
129         self.assertEqual(
130             self.template.resources['some-server-group']['type'],
131             'OS::Nova::ServerGroup')
132         self.assertEqual(
133             self.template.resources['some-server-group']['properties']['policies'],
134             ['anti-affinity'])
135
136     def test__add_resources_to_template_raw(self):
137         test_context = node.NodeContext()
138         test_context.name = 'foo'
139         test_context.template_file = '/tmp/some-heat-file'
140         test_context.heat_parameters = {'image': 'cirros'}
141         test_context.key_filename = "/tmp/1234"
142         test_context.keypair_name = "foo-key"
143         test_context.secgroup_name = "foo-secgroup"
144         test_context.key_uuid = "2f2e4997-0a8e-4eb7-9fa4-f3f8fbbc393b"
145         heat_object = heat.HeatObject()
146
147         heat_stack = heat.HeatStack("tmpStack")
148         self.assertTrue(heat_stack.stacks_exist())
149
150         test_context.tmpfile = NamedTemporaryFile(delete=True, mode='w+t')
151         test_context.tmpfile.write("heat_template_version: 2015-04-30")
152         test_context.tmpfile.flush()
153         test_context.tmpfile.seek(0)
154         heat_template = heat.HeatTemplate(heat_object)
155         heat_template.resources = {}
156
157         heat_template.add_network("network1")
158         heat_template.add_network("network2")
159         heat_template.add_security_group("sec_group1")
160         heat_template.add_security_group("sec_group2")
161         heat_template.add_subnet("subnet1", "network1", "cidr1")
162         heat_template.add_subnet("subnet2", "network2", "cidr2")
163         heat_template.add_router("router1", "gw1", "subnet1")
164         heat_template.add_router_interface("router_if1", "router1", "subnet1")
165         heat_template.add_port("port1", "network1", "subnet1", "normal")
166         heat_template.add_port(
167             "port2",
168             "network2",
169             "subnet2",
170             "normal",
171             sec_group_id="sec_group1",
172             provider="not-sriov")
173         heat_template.add_port(
174             "port3",
175             "network2",
176             "subnet2",
177             "normal",
178             sec_group_id="sec_group1",
179             provider="sriov")
180         heat_template.add_floating_ip(
181             "floating_ip1", "network1", "port1", "router_if1")
182         heat_template.add_floating_ip(
183             "floating_ip2", "network2", "port2", "router_if2", "foo-secgroup")
184         heat_template.add_floating_ip_association(
185             "floating_ip1_association", "floating_ip1", "port1")
186         heat_template.add_servergroup("server_grp2", "affinity")
187         heat_template.add_servergroup("server_grp3", "anti-affinity")
188         heat_template.add_security_group("security_group")
189         heat_template.add_server(
190             name="server1", image="image1", flavor="flavor1", flavors=[])
191         heat_template.add_server_group(
192             name="servergroup", policies=["policy1", "policy2"])
193         heat_template.add_server_group(name="servergroup", policies="policy1")
194         heat_template.add_server(
195             name="server2",
196             image="image1",
197             flavor="flavor1",
198             flavors=[],
199             ports=[
200                 "port1",
201                 "port2"],
202             networks=[
203                 "network1",
204                 "network2"],
205             scheduler_hints="hints1",
206             user="user1",
207             key_name="foo-key",
208             user_data="user",
209             metadata={
210                 "cat": 1,
211                 "doc": 2},
212             additional_properties={
213                 "prop1": 1,
214                 "prop2": 2})
215         heat_template.add_server(
216             name="server2",
217             image="image1",
218             flavor="flavor1",
219             flavors=[
220                 "flavor1",
221                 "flavor2"],
222             ports=[
223                 "port1",
224                 "port2"],
225             networks=[
226                 "network1",
227                 "network2"],
228             scheduler_hints="hints1",
229             user="user1",
230             key_name="foo-key",
231             user_data="user",
232             metadata={
233                 "cat": 1,
234                 "doc": 2},
235             additional_properties={
236                 "prop1": 1,
237                 "prop2": 2})
238         heat_template.add_server(
239             name="server2",
240             image="image1",
241             flavor="flavor1",
242             flavors=[
243                 "flavor3",
244                 "flavor4"],
245             ports=[
246                 "port1",
247                 "port2"],
248             networks=[
249                 "network1",
250                 "network2"],
251             scheduler_hints="hints1",
252             user="user1",
253             key_name="foo-key",
254             user_data="user",
255             metadata={
256                 "cat": 1,
257                 "doc": 2},
258             additional_properties={
259                 "prop1": 1,
260                 "prop2": 2})
261         heat_template.add_flavor(
262             name="flavor1",
263             vcpus=1,
264             ram=2048,
265             disk=1,
266             extra_specs={
267                 "cat": 1,
268                 "dog": 2})
269         heat_template.add_flavor(name=None, vcpus=1, ram=2048)
270         heat_template.add_server(
271             name="server1",
272             image="image1",
273             flavor="flavor1",
274             flavors=[],
275             ports=[
276                 "port1",
277                 "port2"],
278             networks=[
279                 "network1",
280                 "network2"],
281             scheduler_hints="hints1",
282             user="user1",
283             key_name="foo-key",
284             user_data="user",
285             metadata={
286                 "cat": 1,
287                 "doc": 2},
288             additional_properties={
289                 "prop1": 1,
290                 "prop2": 2})
291         heat_template.add_network("network1")
292
293         heat_template.add_flavor("test")
294         self.assertEqual(
295             heat_template.resources['test']['type'], 'OS::Nova::Flavor')
296
297     @mock_patch_target_module('op_utils')
298     @mock_patch_target_module('heatclient')
299     def test_create_negative(self, mock_heat_client_class, mock_op_utils):
300         self.template.HEAT_WAIT_LOOP_INTERVAL = 0
301         mock_heat_client = mock_heat_client_class()  # get the constructed mock
302
303         # populate attributes of the constructed mock
304         mock_heat_client.stacks.get().stack_status_reason = 'the reason'
305
306         expected_status_calls = 0
307         expected_constructor_calls = 1  # above, to get the instance
308         expected_create_calls = 0
309         expected_op_utils_usage = 0
310
311         with mock.patch.object(self.template, 'status', return_value=None) as mock_status:
312             # block with timeout hit
313             timeout = 0
314             with self.assertRaises(RuntimeError) as raised, timer():
315                 self.template.create(block=True, timeout=timeout)
316
317             # ensure op_utils was used
318             expected_op_utils_usage += 1
319             self.assertEqual(
320                 mock_op_utils.get_session.call_count, expected_op_utils_usage)
321             self.assertEqual(
322                 mock_op_utils.get_endpoint.call_count, expected_op_utils_usage)
323             self.assertEqual(
324                 mock_op_utils.get_heat_api_version.call_count,
325                 expected_op_utils_usage)
326
327             # ensure the constructor and instance were used
328             self.assertEqual(mock_heat_client_class.call_count,
329                              expected_constructor_calls)
330             self.assertEqual(
331                 mock_heat_client.stacks.create.call_count,
332                 expected_create_calls)
333
334             # ensure that the status was used
335             self.assertGreater(mock_status.call_count, expected_status_calls)
336             expected_status_calls = mock_status.call_count  # synchronize the value
337
338             # ensure the expected exception was raised
339             error_message = get_error_message(raised.exception)
340             self.assertIn('timeout', error_message)
341             self.assertNotIn('the reason', error_message)
342
343             # block with create failed
344             timeout = 10
345             mock_status.side_effect = iter([None, None, u'CREATE_FAILED'])
346             with self.assertRaises(RuntimeError) as raised, timer():
347                 self.template.create(block=True, timeout=timeout)
348
349             # ensure the existing heat_client was used and op_utils was used
350             # again
351             self.assertEqual(
352                 mock_op_utils.get_session.call_count, expected_op_utils_usage)
353             self.assertEqual(
354                 mock_op_utils.get_endpoint.call_count, expected_op_utils_usage)
355             self.assertEqual(
356                 mock_op_utils.get_heat_api_version.call_count,
357                 expected_op_utils_usage)
358
359             # ensure the constructor was not used but the instance was used
360             self.assertEqual(mock_heat_client_class.call_count,
361                              expected_constructor_calls)
362             self.assertEqual(
363                 mock_heat_client.stacks.create.call_count,
364                 expected_create_calls)
365
366             # ensure that the status was used three times
367             expected_status_calls += 3
368             self.assertEqual(mock_status.call_count, expected_status_calls)
369
370     # NOTE(elfoley): This needs to be split into multiple tests.
371     # The lines where the template is reset should serve as a guide for where
372     # to split.
373     @mock_patch_target_module('op_utils')
374     @mock_patch_target_module('heatclient')
375     def test_create(self, mock_heat_client_class, mock_op_utils):
376         self.template.HEAT_WAIT_LOOP_INTERVAL = 0.2
377         mock_heat_client = mock_heat_client_class()
378
379         # populate attributes of the constructed mock
380         mock_heat_client.stacks.get().outputs = [
381             {'output_key': 'key1', 'output_value': 'value1'},
382             {'output_key': 'key2', 'output_value': 'value2'},
383             {'output_key': 'key3', 'output_value': 'value3'},
384         ]
385         expected_outputs = {  # pylint: disable=unused-variable
386             'key1': 'value1',
387             'key2': 'value2',
388             'key3': 'value3',
389         }
390
391         expected_status_calls = 0
392         expected_constructor_calls = 1  # above, to get the instance
393         expected_create_calls = 0
394         expected_op_utils_usage = 0
395
396         with mock.patch.object(self.template, 'status') as mock_status:
397             self.template.name = 'no block test'
398             mock_status.return_value = None
399
400             # no block
401             self.assertIsInstance(self.template.create(
402                 block=False, timeout=2), heat.HeatStack)
403
404             # ensure op_utils was used
405             expected_op_utils_usage += 1
406             self.assertEqual(
407                 mock_op_utils.get_session.call_count, expected_op_utils_usage)
408             self.assertEqual(
409                 mock_op_utils.get_endpoint.call_count, expected_op_utils_usage)
410             self.assertEqual(
411                 mock_op_utils.get_heat_api_version.call_count,
412                 expected_op_utils_usage)
413
414             # ensure the constructor and instance were used
415             self.assertEqual(mock_heat_client_class.call_count,
416                              expected_constructor_calls)
417             self.assertEqual(
418                 mock_heat_client.stacks.create.call_count,
419                 expected_create_calls)
420
421             # ensure that the status was not used
422             self.assertEqual(mock_status.call_count, expected_status_calls)
423
424             # ensure no outputs because this requires blocking
425             self.assertEqual(self.template.outputs, {})
426
427             # block with immediate complete
428             self.template.name = 'block, immediate complete test'
429
430             mock_status.return_value = self.template.HEAT_CREATE_COMPLETE_STATUS
431             self.assertIsInstance(self.template.create(
432                 block=True, timeout=2), heat.HeatStack)
433
434             # ensure existing instance was re-used and op_utils was not used
435             self.assertEqual(mock_heat_client_class.call_count,
436                              expected_constructor_calls)
437             self.assertEqual(
438                 mock_heat_client.stacks.create.call_count,
439                 expected_create_calls)
440
441             # ensure status was checked once
442             expected_status_calls += 1
443             self.assertEqual(mock_status.call_count, expected_status_calls)
444
445             # reset template outputs
446             self.template.outputs = None
447
448             # block with delayed complete
449             self.template.name = 'block, delayed complete test'
450
451             success_index = 2
452             mock_status.side_effect = index_value_iter(
453                 success_index, self.template.HEAT_CREATE_COMPLETE_STATUS)
454             self.assertIsInstance(self.template.create(
455                 block=True, timeout=2), heat.HeatStack)
456
457             # ensure existing instance was re-used and op_utils was not used
458             self.assertEqual(mock_heat_client_class.call_count,
459                              expected_constructor_calls)
460             self.assertEqual(
461                 mock_heat_client.stacks.create.call_count,
462                 expected_create_calls)
463
464             # ensure status was checked three more times
465             expected_status_calls += 1 + success_index
466             self.assertEqual(mock_status.call_count, expected_status_calls)
467
468
469 class HeatStackTestCase(unittest.TestCase):
470
471     def test_delete_calls__delete_multiple_times(self):
472         stack = heat.HeatStack('test')
473         stack.uuid = 1
474         with mock.patch.object(stack, "_delete") as delete_mock:
475             stack.delete()
476         # call once and then call again if uuid is not none
477         self.assertGreater(delete_mock.call_count, 1)
478
479     def test_delete_all_calls_delete(self):
480         # we must patch the object before we create an instance
481         # so we can override delete() in all the instances
482         with mock.patch.object(heat.HeatStack, "delete") as delete_mock:
483             stack = heat.HeatStack('test')
484             stack.uuid = 1
485             stack.delete_all()
486             self.assertGreater(delete_mock.call_count, 0)