Merge "tg: speedup unittests, mock time.sleep"
[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 = 0
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 = 0
200             with self.assertRaises(RuntimeError) as raised, timer() as time_data:
201                 self.template.create(block=True, timeout=timeout)
202
203             # ensure op_utils was used
204             expected_op_utils_usage += 1
205             self.assertEqual(mock_op_utils.get_session.call_count, expected_op_utils_usage)
206             self.assertEqual(mock_op_utils.get_endpoint.call_count, expected_op_utils_usage)
207             self.assertEqual(mock_op_utils.get_heat_api_version.call_count, expected_op_utils_usage)
208
209             # ensure the constructor and instance were used
210             expected_constructor_calls += 1
211             expected_create_calls += 1
212             self.assertEqual(mock_heat_client_class.call_count, expected_constructor_calls)
213             self.assertEqual(mock_heat_client.stacks.create.call_count, expected_create_calls)
214
215             # ensure that the status was used
216             self.assertGreater(mock_status.call_count, expected_status_calls)
217             expected_status_calls = mock_status.call_count  # synchronize the value
218
219             # ensure the expected exception was raised
220             error_message = get_error_message(raised.exception)
221             self.assertIn('timeout', error_message)
222             self.assertNotIn('the reason', error_message)
223
224             # block with create failed
225             timeout = 10
226             mock_status.side_effect = iter([None, None, u'CREATE_FAILED'])
227             with self.assertRaises(RuntimeError) as raised, timer() as time_data:
228                 self.template.create(block=True, timeout=timeout)
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 = 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             self.template.name = 'no block test'
274             mock_status.return_value = None
275
276             # no block
277             self.assertIsInstance(self.template.create(block=False, timeout=2), heat.HeatStack)
278
279             # ensure op_utils was used
280             expected_op_utils_usage += 1
281             self.assertEqual(mock_op_utils.get_session.call_count, expected_op_utils_usage)
282             self.assertEqual(mock_op_utils.get_endpoint.call_count, expected_op_utils_usage)
283             self.assertEqual(mock_op_utils.get_heat_api_version.call_count, expected_op_utils_usage)
284
285             # ensure the constructor and instance were used
286             expected_constructor_calls += 1
287             expected_create_calls += 1
288             self.assertEqual(mock_heat_client_class.call_count, expected_constructor_calls)
289             self.assertEqual(mock_heat_client.stacks.create.call_count, expected_create_calls)
290
291             # ensure that the status was not used
292             self.assertEqual(mock_status.call_count, expected_status_calls)
293
294             # ensure no outputs because this requires blocking
295             self.assertEqual(self.template.outputs, {})
296
297             # block with immediate complete
298             self.template.name = 'block, immediate complete test'
299
300             mock_status.return_value = self.template.HEAT_CREATE_COMPLETE_STATUS
301             self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack)
302
303             # ensure existing instance was re-used and op_utils was not used
304             expected_create_calls += 1
305             self.assertEqual(mock_heat_client_class.call_count, expected_constructor_calls)
306             self.assertEqual(mock_heat_client.stacks.create.call_count, expected_create_calls)
307
308             # ensure status was checked once
309             expected_status_calls += 1
310             self.assertEqual(mock_status.call_count, expected_status_calls)
311
312             # ensure the expected outputs are present
313             self.assertDictEqual(self.template.outputs, expected_outputs)
314
315             # reset template outputs
316             self.template.outputs = None
317
318             # block with delayed complete
319             self.template.name = 'block, delayed complete test'
320
321             success_index = 2
322             mock_status.side_effect = index_value_iter(success_index,
323                                                        self.template.HEAT_CREATE_COMPLETE_STATUS)
324             self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack)
325
326             # ensure existing instance was re-used and op_utils was not used
327             expected_create_calls += 1
328             self.assertEqual(mock_heat_client_class.call_count, expected_constructor_calls)
329             self.assertEqual(mock_heat_client.stacks.create.call_count, expected_create_calls)
330
331             # ensure status was checked three more times
332             expected_status_calls += 1 + success_index
333             self.assertEqual(mock_status.call_count, expected_status_calls)
334
335
336 class HeatStackTestCase(unittest.TestCase):
337
338     def test_delete_calls__delete_multiple_times(self):
339         stack = heat.HeatStack('test')
340         stack.uuid = 1
341         with mock.patch.object(stack, "_delete") as delete_mock:
342             stack.delete()
343         # call once and then call again if uuid is not none
344         self.assertGreater(delete_mock.call_count, 1)
345
346     @mock.patch('yardstick.orchestrator.heat.op_utils')
347     def test_delete_all_calls_delete(self, mock_op):
348         # we must patch the object before we create an instance
349         # so we can override delete() in all the instances
350         with mock.patch.object(heat.HeatStack, "delete") as delete_mock:
351             stack = heat.HeatStack('test')
352             stack.uuid = 1
353             stack.delete_all()
354             self.assertGreater(delete_mock.call_count, 0)