3 ##############################################################################
4 # Copyright (c) 2017 Intel Corporation
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 ##############################################################################
12 # Unittest for yardstick.benchmark.orchestrator.heat
13 from contextlib import contextmanager
14 from itertools import count
15 from tempfile import NamedTemporaryFile
22 from yardstick.benchmark.contexts import node
23 from yardstick.orchestrator import heat
26 TARGET_MODULE = 'yardstick.orchestrator.heat'
29 def mock_patch_target_module(inner_import):
30 return mock.patch('.'.join([TARGET_MODULE, inner_import]))
36 data = {'start': start}
40 data['end'] = end = time.time()
41 data['delta'] = end - start
44 def index_value_iter(index, index_value, base_value=None):
45 for current_index in count():
46 if current_index == index:
52 def get_error_message(error):
56 except AttributeError:
58 return next((arg for arg in error.args if isinstance(arg, str)), None)
61 class HeatContextTestCase(unittest.TestCase):
63 def test_get_short_key_uuid(self):
65 k = heat.get_short_key_uuid(u)
66 self.assertEqual(heat.HEAT_KEY_UUID_LENGTH, len(k))
67 self.assertIn(k, str(u))
70 class HeatTemplateTestCase(unittest.TestCase):
73 self.template = heat.HeatTemplate('test')
75 def test_add_tenant_network(self):
76 self.template.add_network('some-network')
79 self.template.resources['some-network']['type'],
82 def test_add_provider_network(self):
83 self.template.add_network('some-network', 'physnet2', 'sriov')
86 self.template.resources['some-network']['type'],
87 'OS::Neutron::ProviderNet')
89 self.template.resources['some-network']['properties']['physical_network'],
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'])
99 self.template.resources['some-subnet']['type'],
100 'OS::Neutron::Subnet')
102 self.template.resources['some-subnet']['properties']['cidr'],
105 def test_add_router(self):
106 self.template.add_router('some-router', 'ext-net', 'some-subnet')
109 self.template.resources['some-router']['type'],
110 'OS::Neutron::Router')
113 self.template.resources['some-router']['depends_on'])
115 def test_add_router_interface(self):
116 self.template.add_router_interface(
117 'some-router-if', 'some-router', 'some-subnet')
120 self.template.resources['some-router-if']['type'],
121 'OS::Neutron::RouterInterface')
124 self.template.resources['some-router-if']['depends_on'])
126 def test_add_servergroup(self):
127 self.template.add_servergroup('some-server-group', 'anti-affinity')
130 self.template.resources['some-server-group']['type'],
131 'OS::Nova::ServerGroup')
133 self.template.resources['some-server-group']['properties']['policies'],
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()
147 heat_stack = heat.HeatStack("tmpStack")
148 self.assertTrue(heat_stack.stacks_exist())
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 = {}
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(
171 sec_group_id="sec_group1",
172 provider="not-sriov")
173 heat_template.add_port(
178 sec_group_id="sec_group1",
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(
205 scheduler_hints="hints1",
212 additional_properties={
215 heat_template.add_server(
228 scheduler_hints="hints1",
235 additional_properties={
238 heat_template.add_server(
251 scheduler_hints="hints1",
258 additional_properties={
261 heat_template.add_flavor(
269 heat_template.add_flavor(name=None, vcpus=1, ram=2048)
270 heat_template.add_server(
281 scheduler_hints="hints1",
288 additional_properties={
291 heat_template.add_network("network1")
293 heat_template.add_flavor("test")
295 heat_template.resources['test']['type'], 'OS::Nova::Flavor')
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
303 # populate attributes of the constructed mock
304 mock_heat_client.stacks.get().stack_status_reason = 'the reason'
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
311 with mock.patch.object(self.template, 'status', return_value=None) as mock_status:
312 # block with timeout hit
314 with self.assertRaises(RuntimeError) as raised, timer():
315 self.template.create(block=True, timeout=timeout)
317 # ensure op_utils was used
318 expected_op_utils_usage += 1
320 mock_op_utils.get_session.call_count, expected_op_utils_usage)
322 mock_op_utils.get_endpoint.call_count, expected_op_utils_usage)
324 mock_op_utils.get_heat_api_version.call_count,
325 expected_op_utils_usage)
327 # ensure the constructor and instance were used
328 self.assertEqual(mock_heat_client_class.call_count,
329 expected_constructor_calls)
331 mock_heat_client.stacks.create.call_count,
332 expected_create_calls)
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
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)
343 # block with create failed
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)
349 # ensure the existing heat_client was used and op_utils was used
352 mock_op_utils.get_session.call_count, expected_op_utils_usage)
354 mock_op_utils.get_endpoint.call_count, expected_op_utils_usage)
356 mock_op_utils.get_heat_api_version.call_count,
357 expected_op_utils_usage)
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)
363 mock_heat_client.stacks.create.call_count,
364 expected_create_calls)
366 # ensure that the status was used three times
367 expected_status_calls += 3
368 self.assertEqual(mock_status.call_count, expected_status_calls)
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
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()
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'},
385 expected_outputs = { # pylint: disable=unused-variable
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
396 with mock.patch.object(self.template, 'status') as mock_status:
397 self.template.name = 'no block test'
398 mock_status.return_value = None
401 self.assertIsInstance(self.template.create(
402 block=False, timeout=2), heat.HeatStack)
404 # ensure op_utils was used
405 expected_op_utils_usage += 1
407 mock_op_utils.get_session.call_count, expected_op_utils_usage)
409 mock_op_utils.get_endpoint.call_count, expected_op_utils_usage)
411 mock_op_utils.get_heat_api_version.call_count,
412 expected_op_utils_usage)
414 # ensure the constructor and instance were used
415 self.assertEqual(mock_heat_client_class.call_count,
416 expected_constructor_calls)
418 mock_heat_client.stacks.create.call_count,
419 expected_create_calls)
421 # ensure that the status was not used
422 self.assertEqual(mock_status.call_count, expected_status_calls)
424 # ensure no outputs because this requires blocking
425 self.assertEqual(self.template.outputs, {})
427 # block with immediate complete
428 self.template.name = 'block, immediate complete test'
430 mock_status.return_value = self.template.HEAT_CREATE_COMPLETE_STATUS
431 self.assertIsInstance(self.template.create(
432 block=True, timeout=2), heat.HeatStack)
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)
438 mock_heat_client.stacks.create.call_count,
439 expected_create_calls)
441 # ensure status was checked once
442 expected_status_calls += 1
443 self.assertEqual(mock_status.call_count, expected_status_calls)
445 # reset template outputs
446 self.template.outputs = None
448 # block with delayed complete
449 self.template.name = 'block, delayed complete test'
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)
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)
461 mock_heat_client.stacks.create.call_count,
462 expected_create_calls)
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)
469 class HeatStackTestCase(unittest.TestCase):
471 def test_delete_calls__delete_multiple_times(self):
472 stack = heat.HeatStack('test')
474 with mock.patch.object(stack, "_delete") as delete_mock:
476 # call once and then call again if uuid is not none
477 self.assertGreater(delete_mock.call_count, 1)
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')
486 self.assertGreater(delete_mock.call_count, 0)