diff options
author | Ross Brattain <ross.b.brattain@intel.com> | 2017-06-22 15:10:21 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@opnfv.org> | 2017-06-22 15:10:21 +0000 |
commit | e80c35164e7dfee4fe4a3652b71b8775c1c0857a (patch) | |
tree | b62143208b719c39de73c001a547258ab19bfa98 /tests/unit | |
parent | 6b3ee75dc0b5fc0e66c914d0b72b4396411526fd (diff) | |
parent | 653902770572c780777d1dc7a371794b670585b1 (diff) |
Merge "Acquire NSB specific data from Heat."
Diffstat (limited to 'tests/unit')
-rw-r--r-- | tests/unit/benchmark/contexts/test_heat.py | 8 | ||||
-rw-r--r-- | tests/unit/benchmark/scenarios/networking/test_vnf_generic.py | 150 | ||||
-rw-r--r-- | tests/unit/orchestrator/test_heat.py | 328 |
3 files changed, 409 insertions, 77 deletions
diff --git a/tests/unit/benchmark/contexts/test_heat.py b/tests/unit/benchmark/contexts/test_heat.py index d878ebe97..3dadd48eb 100644 --- a/tests/unit/benchmark/contexts/test_heat.py +++ b/tests/unit/benchmark/contexts/test_heat.py @@ -17,6 +17,7 @@ import logging import os import unittest import uuid +from collections import OrderedDict import mock @@ -37,7 +38,7 @@ class HeatContextTestCase(unittest.TestCase): self.assertIsNone(self.test_context.name) self.assertIsNone(self.test_context.stack) - self.assertEqual(self.test_context.networks, []) + self.assertEqual(self.test_context.networks, OrderedDict()) self.assertEqual(self.test_context.servers, []) self.assertEqual(self.test_context.placement_groups, []) self.assertEqual(self.test_context.server_groups, []) @@ -105,7 +106,9 @@ class HeatContextTestCase(unittest.TestCase): self.test_context.key_uuid = "2f2e4997-0a8e-4eb7-9fa4-f3f8fbbc393b" netattrs = {'cidr': '10.0.0.0/24', 'provider': None, 'external_network': 'ext_net'} self.mock_context.name = 'bar' - self.test_context.networks = [model.Network("fool-network", self.mock_context, netattrs)] + self.test_context.networks = OrderedDict( + {"fool-network": model.Network("fool-network", self.mock_context, + netattrs)}) self.test_context._add_resources_to_template(mock_template) mock_template.add_keypair.assert_called_with( @@ -122,6 +125,7 @@ class HeatContextTestCase(unittest.TestCase): self.test_context.name = 'foo' self.test_context.template_file = '/bar/baz/some-heat-file' self.test_context.heat_parameters = {'image': 'cirros'} + self.test_context.heat_timeout = 5 self.test_context.deploy() mock_template.assert_called_with(self.test_context.name, diff --git a/tests/unit/benchmark/scenarios/networking/test_vnf_generic.py b/tests/unit/benchmark/scenarios/networking/test_vnf_generic.py index 4167d6f3b..111e7812e 100644 --- a/tests/unit/benchmark/scenarios/networking/test_vnf_generic.py +++ b/tests/unit/benchmark/scenarios/networking/test_vnf_generic.py @@ -20,12 +20,13 @@ from __future__ import absolute_import import os +import errno import unittest - import mock from yardstick.benchmark.scenarios.networking.vnf_generic import \ - SshManager, NetworkServiceTestCase, IncorrectConfig, IncorrectSetup + SshManager, NetworkServiceTestCase, IncorrectConfig, \ + IncorrectSetup, open_relative_file from yardstick.network_services.collector.subscriber import Collector from yardstick.network_services.vnf_generic.vnf.base import \ GenericTrafficGen, GenericVNF @@ -288,6 +289,7 @@ class TestNetworkServiceTestCase(unittest.TestCase): } self.scenario_cfg = { + 'task_path': "", 'tc_options': {'rfc2544': {'allowed_drop_rate': '0.8 - 1'}}, 'task_id': 'a70bdf4a-8e67-47a3-9dc1-273c14506eb7', 'tc': 'tc_ipv4_1Mflow_64B_packetsize', @@ -350,7 +352,8 @@ class TestNetworkServiceTestCase(unittest.TestCase): vnf = mock.Mock(autospec=GenericVNF) self.s.get_vnf_impl = mock.Mock(return_value=vnf) - self.assertIsNotNone(self.s.load_vnf_models(self.context_cfg)) + self.assertIsNotNone( + self.s.load_vnf_models(self.scenario_cfg, self.context_cfg)) def test_map_topology_to_infrastructure(self): with mock.patch("yardstick.ssh.SSH") as ssh: @@ -488,3 +491,144 @@ class TestNetworkServiceTestCase(unittest.TestCase): self.s.collector.stop = \ mock.Mock(return_value=True) self.assertIsNone(self.s.teardown()) + + SAMPLE_NETDEVS = { + 'enp11s0': { + 'address': '0a:de:ad:be:ef:f5', + 'device': '0x1533', + 'driver': 'igb', + 'ifindex': '2', + 'interface_name': 'enp11s0', + 'operstate': 'down', + 'pci_bus_id': '0000:0b:00.0', + 'subsystem_device': '0x1533', + 'subsystem_vendor': '0x15d9', + 'vendor': '0x8086' + }, + 'lan': { + 'address': '0a:de:ad:be:ef:f4', + 'device': '0x153a', + 'driver': 'e1000e', + 'ifindex': '3', + 'interface_name': 'lan', + 'operstate': 'up', + 'pci_bus_id': '0000:00:19.0', + 'subsystem_device': '0x153a', + 'subsystem_vendor': '0x15d9', + 'vendor': '0x8086' + } + } + SAMPLE_VM_NETDEVS = { + 'eth1': { + 'address': 'fa:de:ad:be:ef:5b', + 'device': '0x0001', + 'driver': 'virtio_net', + 'ifindex': '3', + 'interface_name': 'eth1', + 'operstate': 'down', + 'pci_bus_id': '0000:00:04.0', + 'vendor': '0x1af4' + } + } + + def test_parse_netdev_info(self): + output = """\ +/sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/ifindex:2 +/sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/address:0a:de:ad:be:ef:f5 +/sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/operstate:down +/sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/device/vendor:0x8086 +/sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/device/device:0x1533 +/sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/device/subsystem_vendor:0x15d9 +/sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/device/subsystem_device:0x1533 +/sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/driver:igb +/sys/devices/pci0000:00/0000:00:1c.3/0000:0b:00.0/net/enp11s0/pci_bus_id:0000:0b:00.0 +/sys/devices/pci0000:00/0000:00:19.0/net/lan/ifindex:3 +/sys/devices/pci0000:00/0000:00:19.0/net/lan/address:0a:de:ad:be:ef:f4 +/sys/devices/pci0000:00/0000:00:19.0/net/lan/operstate:up +/sys/devices/pci0000:00/0000:00:19.0/net/lan/device/vendor:0x8086 +/sys/devices/pci0000:00/0000:00:19.0/net/lan/device/device:0x153a +/sys/devices/pci0000:00/0000:00:19.0/net/lan/device/subsystem_vendor:0x15d9 +/sys/devices/pci0000:00/0000:00:19.0/net/lan/device/subsystem_device:0x153a +/sys/devices/pci0000:00/0000:00:19.0/net/lan/driver:e1000e +/sys/devices/pci0000:00/0000:00:19.0/net/lan/pci_bus_id:0000:00:19.0 +""" + res = NetworkServiceTestCase.parse_netdev_info(output) + assert res == self.SAMPLE_NETDEVS + + def test_parse_netdev_info_virtio(self): + output = """\ +/sys/devices/pci0000:00/0000:00:04.0/virtio1/net/eth1/ifindex:3 +/sys/devices/pci0000:00/0000:00:04.0/virtio1/net/eth1/address:fa:de:ad:be:ef:5b +/sys/devices/pci0000:00/0000:00:04.0/virtio1/net/eth1/operstate:down +/sys/devices/pci0000:00/0000:00:04.0/virtio1/net/eth1/device/vendor:0x1af4 +/sys/devices/pci0000:00/0000:00:04.0/virtio1/net/eth1/device/device:0x0001 +/sys/devices/pci0000:00/0000:00:04.0/virtio1/net/eth1/driver:virtio_net +""" + res = NetworkServiceTestCase.parse_netdev_info(output) + assert res == self.SAMPLE_VM_NETDEVS + + def test_sort_dpdk_port_num(self): + netdevs = self.SAMPLE_NETDEVS.copy() + NetworkServiceTestCase._sort_dpdk_port_num(netdevs) + assert netdevs['lan']['dpdk_port_num'] == 1 + assert netdevs['enp11s0']['dpdk_port_num'] == 2 + + def test_probe_missing_values(self): + netdevs = self.SAMPLE_NETDEVS.copy() + NetworkServiceTestCase._sort_dpdk_port_num(netdevs) + network = {'local_mac': '0a:de:ad:be:ef:f5'} + NetworkServiceTestCase._probe_missing_values(netdevs, network, set()) + assert network['dpdk_port_num'] == 2 + + network = {'local_mac': '0a:de:ad:be:ef:f4'} + NetworkServiceTestCase._probe_missing_values(netdevs, network, set()) + assert network['dpdk_port_num'] == 1 + + def test_open_relative_path(self): + mock_open = mock.mock_open() + mock_open_result = mock_open() + mock_open_call_count = 1 # initial call to get result + + module_name = \ + 'yardstick.benchmark.scenarios.networking.vnf_generic.open' + + # test + with mock.patch(module_name, mock_open, create=True): + self.assertEqual(open_relative_file('foo', 'bar'), mock_open_result) + + mock_open_call_count += 1 # one more call expected + self.assertEqual(mock_open.call_count, mock_open_call_count) + self.assertIn('foo', mock_open.call_args_list[-1][0][0]) + self.assertNotIn('bar', mock_open.call_args_list[-1][0][0]) + + def open_effect(*args, **kwargs): + if kwargs.get('name', args[0]) == os.path.join('bar', 'foo'): + return mock_open_result + raise IOError(errno.ENOENT, 'not found') + + mock_open.side_effect = open_effect + self.assertEqual(open_relative_file('foo', 'bar'), mock_open_result) + + mock_open_call_count += 2 # two more calls expected + self.assertEqual(mock_open.call_count, mock_open_call_count) + self.assertIn('foo', mock_open.call_args_list[-1][0][0]) + self.assertIn('bar', mock_open.call_args_list[-1][0][0]) + + # test an IOError of type ENOENT + mock_open.side_effect = IOError(errno.ENOENT, 'not found') + with self.assertRaises(IOError): + # the second call still raises + open_relative_file('foo', 'bar') + + mock_open_call_count += 2 # two more calls expected + self.assertEqual(mock_open.call_count, mock_open_call_count) + self.assertIn('foo', mock_open.call_args_list[-1][0][0]) + self.assertIn('bar', mock_open.call_args_list[-1][0][0]) + + # test an IOError other than ENOENT + mock_open.side_effect = IOError(errno.EBUSY, 'busy') + with self.assertRaises(IOError): + open_relative_file('foo', 'bar') + + mock_open_call_count += 1 # one more call expected + self.assertEqual(mock_open.call_count, mock_open_call_count) diff --git a/tests/unit/orchestrator/test_heat.py b/tests/unit/orchestrator/test_heat.py index 4892f98f8..3b3873301 100644 --- a/tests/unit/orchestrator/test_heat.py +++ b/tests/unit/orchestrator/test_heat.py @@ -10,16 +10,43 @@ ############################################################################## # Unittest for yardstick.benchmark.orchestrator.heat - +from contextlib import contextmanager from tempfile import NamedTemporaryFile import unittest import uuid +import time import mock from yardstick.benchmark.contexts import node from yardstick.orchestrator import heat +TARGET_MODULE = 'yardstick.orchestrator.heat' + + +def mock_patch_target_module(inner_import): + return mock.patch('.'.join([TARGET_MODULE, inner_import])) + + +@contextmanager +def timer(): + start = time.time() + data = {'start': start} + try: + yield data + finally: + data['end'] = end = time.time() + data['delta'] = end - start + +def get_error_message(error): + try: + # py2 + return error.message + except AttributeError: + # py3 + return next((arg for arg in error.args if isinstance(arg, str)), None) + + class HeatContextTestCase(unittest.TestCase): def test_get_short_key_uuid(self): @@ -70,88 +97,245 @@ class HeatTemplateTestCase(unittest.TestCase): self.assertEqual(self.template.resources['some-server-group']['properties']['policies'], ['anti-affinity']) def test__add_resources_to_template_raw(self): - - self.test_context = node.NodeContext() - self.test_context.name = 'foo' - self.test_context.template_file = '/tmp/some-heat-file' - self.test_context.heat_parameters = {'image': 'cirros'} - self.test_context.key_filename = "/tmp/1234" - self.test_context.keypair_name = "foo-key" - self.test_context.secgroup_name = "foo-secgroup" - self.test_context.key_uuid = "2f2e4997-0a8e-4eb7-9fa4-f3f8fbbc393b" - self._template = { - 'outputs' : {}, - 'resources' : {} - } - - self.heat_object = heat.HeatObject() - self.heat_tmp_object = heat.HeatObject() - - self.heat_stack = heat.HeatStack("tmpStack") - self.heat_stack.stacks_exist() - - self.test_context.tmpfile = NamedTemporaryFile(delete=True, mode='w+t') - self.test_context.tmpfile.write("heat_template_version: 2015-04-30") - self.test_context.tmpfile.flush() - self.test_context.tmpfile.seek(0) - self.heat_tmp_template = heat.HeatTemplate(self.heat_tmp_object, self.test_context.tmpfile.name, - heat_parameters= {"dict1": 1, "dict2": 2}) - - self.heat_template = heat.HeatTemplate(self.heat_object) - self.heat_template.resources = {} - - self.heat_template.add_network("network1") - self.heat_template.add_network("network2") - self.heat_template.add_security_group("sec_group1") - self.heat_template.add_security_group("sec_group2") - self.heat_template.add_subnet("subnet1", "network1", "cidr1") - self.heat_template.add_subnet("subnet2", "network2", "cidr2") - self.heat_template.add_router("router1", "gw1", "subnet1") - self.heat_template.add_router_interface("router_if1", "router1", "subnet1") - self.heat_template.add_port("port1", "network1", "subnet1") - self.heat_template.add_port("port2", "network2", "subnet2", sec_group_id="sec_group1",provider="not-sriov") - self.heat_template.add_port("port3", "network2", "subnet2", sec_group_id="sec_group1",provider="sriov") - self.heat_template.add_floating_ip("floating_ip1", "network1", "port1", "router_if1") - self.heat_template.add_floating_ip("floating_ip2", "network2", "port2", "router_if2", "foo-secgroup") - self.heat_template.add_floating_ip_association("floating_ip1_association", "floating_ip1", "port1") - self.heat_template.add_servergroup("server_grp2", "affinity") - self.heat_template.add_servergroup("server_grp3", "anti-affinity") - self.heat_template.add_security_group("security_group") - self.heat_template.add_server(name="server1", image="image1", flavor="flavor1", flavors=[]) - self.heat_template.add_server_group(name="servergroup", policies=["policy1","policy2"]) - self.heat_template.add_server_group(name="servergroup", policies="policy1") - self.heat_template.add_server(name="server2", image="image1", flavor="flavor1", flavors=[], ports=["port1", "port2"], + test_context = node.NodeContext() + test_context.name = 'foo' + test_context.template_file = '/tmp/some-heat-file' + test_context.heat_parameters = {'image': 'cirros'} + test_context.key_filename = "/tmp/1234" + test_context.keypair_name = "foo-key" + test_context.secgroup_name = "foo-secgroup" + test_context.key_uuid = "2f2e4997-0a8e-4eb7-9fa4-f3f8fbbc393b" + heat_object = heat.HeatObject() + + heat_stack = heat.HeatStack("tmpStack") + self.assertTrue(heat_stack.stacks_exist()) + + test_context.tmpfile = NamedTemporaryFile(delete=True, mode='w+t') + test_context.tmpfile.write("heat_template_version: 2015-04-30") + test_context.tmpfile.flush() + test_context.tmpfile.seek(0) + heat_template = heat.HeatTemplate(heat_object) + heat_template.resources = {} + + heat_template.add_network("network1") + heat_template.add_network("network2") + heat_template.add_security_group("sec_group1") + heat_template.add_security_group("sec_group2") + heat_template.add_subnet("subnet1", "network1", "cidr1") + heat_template.add_subnet("subnet2", "network2", "cidr2") + heat_template.add_router("router1", "gw1", "subnet1") + heat_template.add_router_interface("router_if1", "router1", "subnet1") + heat_template.add_port("port1", "network1", "subnet1") + heat_template.add_port("port2", "network2", "subnet2", sec_group_id="sec_group1",provider="not-sriov") + heat_template.add_port("port3", "network2", "subnet2", sec_group_id="sec_group1",provider="sriov") + heat_template.add_floating_ip("floating_ip1", "network1", "port1", "router_if1") + heat_template.add_floating_ip("floating_ip2", "network2", "port2", "router_if2", "foo-secgroup") + heat_template.add_floating_ip_association("floating_ip1_association", "floating_ip1", "port1") + heat_template.add_servergroup("server_grp2", "affinity") + heat_template.add_servergroup("server_grp3", "anti-affinity") + heat_template.add_security_group("security_group") + heat_template.add_server(name="server1", image="image1", flavor="flavor1", flavors=[]) + heat_template.add_server_group(name="servergroup", policies=["policy1","policy2"]) + heat_template.add_server_group(name="servergroup", policies="policy1") + heat_template.add_server(name="server2", image="image1", flavor="flavor1", flavors=[], ports=["port1", "port2"], networks=["network1", "network2"], scheduler_hints="hints1", user="user1", key_name="foo-key", user_data="user", metadata={"cat": 1, "doc": 2}, additional_properties={"prop1": 1, "prop2": 2}) - self.heat_template.add_server(name="server2", image="image1", flavor="flavor1", flavors=["flavor1", "flavor2"], + heat_template.add_server(name="server2", image="image1", flavor="flavor1", flavors=["flavor1", "flavor2"], ports=["port1", "port2"], networks=["network1", "network2"], scheduler_hints="hints1", user="user1", key_name="foo-key", user_data="user", metadata={"cat": 1, "doc": 2}, additional_properties={"prop1": 1, "prop2": 2} ) - self.heat_template.add_server(name="server2", image="image1", flavor="flavor1", flavors=["flavor3", "flavor4"], + heat_template.add_server(name="server2", image="image1", flavor="flavor1", flavors=["flavor3", "flavor4"], ports=["port1", "port2"], networks=["network1", "network2"], scheduler_hints="hints1", user="user1", key_name="foo-key", user_data="user", metadata={"cat": 1, "doc": 2}, additional_properties={"prop1": 1, "prop2": 2}) - self.heat_template.add_flavor(name="flavor1", vcpus=1, ram=2048, disk=1,extra_specs={"cat": 1, "dog": 2}) - self.heat_template.add_flavor(name=None, vcpus=1, ram=2048) - self.heat_template.add_server(name="server1", - image="image1", - flavor="flavor1", - flavors=[], - ports=["port1", "port2"], - networks=["network1", "network2"], - scheduler_hints="hints1", - user="user1", - key_name="foo-key", - user_data="user", - metadata={"cat": 1, "doc": 2}, - additional_properties= {"prop1": 1, "prop2": 2} ) - self.heat_template.add_network("network1") - - self.heat_template.add_flavor("test") - self.assertEqual(self.heat_template.resources['test']['type'], 'OS::Nova::Flavor') + heat_template.add_flavor(name="flavor1", vcpus=1, ram=2048, disk=1,extra_specs={"cat": 1, "dog": 2}) + heat_template.add_flavor(name=None, vcpus=1, ram=2048) + heat_template.add_server(name="server1", + image="image1", + flavor="flavor1", + flavors=[], + ports=["port1", "port2"], + networks=["network1", "network2"], + scheduler_hints="hints1", + user="user1", + key_name="foo-key", + user_data="user", + metadata={"cat": 1, "doc": 2}, + additional_properties= {"prop1": 1, "prop2": 2} ) + heat_template.add_network("network1") + + heat_template.add_flavor("test") + self.assertEqual(heat_template.resources['test']['type'], 'OS::Nova::Flavor') + + @mock_patch_target_module('op_utils') + @mock_patch_target_module('heatclient.client.Client') + def test_create_negative(self, mock_heat_client_class, mock_op_utils): + self.template.HEAT_WAIT_LOOP_INTERVAL = interval = 0.2 + mock_heat_client = mock_heat_client_class() # get the constructed mock + + # populate attributes of the constructed mock + mock_heat_client.stacks.get().stack_status_reason = 'the reason' + + expected_status_calls = 0 + expected_constructor_calls = 1 # above, to get the instance + expected_create_calls = 0 + expected_op_utils_usage = 0 + + with mock.patch.object(self.template, 'status', return_value=None) as mock_status: + # block with timeout hit + timeout = 2 + with self.assertRaises(RuntimeError) as raised, timer() as time_data: + self.template.create(block=True, timeout=timeout) + + # ensure runtime is approximately the timeout value + expected_time_low = timeout - interval * 0.2 + expected_time_high = timeout + interval * 0.2 + self.assertTrue(expected_time_low < time_data['delta'] < expected_time_high) + + # ensure op_utils was used + expected_op_utils_usage += 1 + self.assertEqual(mock_op_utils.get_session.call_count, expected_op_utils_usage) + self.assertEqual(mock_op_utils.get_endpoint.call_count, expected_op_utils_usage) + self.assertEqual(mock_op_utils.get_heat_api_version.call_count, expected_op_utils_usage) + + # ensure the constructor and instance were used + expected_constructor_calls += 1 + expected_create_calls += 1 + self.assertEqual(mock_heat_client_class.call_count, expected_constructor_calls) + self.assertEqual(mock_heat_client.stacks.create.call_count, expected_create_calls) + + # ensure that the status was used + self.assertGreater(mock_status.call_count, expected_status_calls) + expected_status_calls = mock_status.call_count # synchronize the value + + # ensure the expected exception was raised + error_message = get_error_message(raised.exception) + self.assertIn('timeout', error_message) + self.assertNotIn('the reason', error_message) + + # block with create failed + timeout = 10 + mock_status.side_effect = iter([None, None, u'CREATE_FAILED']) + with self.assertRaises(RuntimeError) as raised, timer() as time_data: + self.template.create(block=True, timeout=timeout) + + # ensure runtime is approximately two intervals + expected_time_low = interval * 1.8 + expected_time_high = interval * 2.2 + self.assertTrue(expected_time_low < time_data['delta'] < expected_time_high) + + # ensure the existing heat_client was used and op_utils was used again + self.assertEqual(mock_op_utils.get_session.call_count, expected_op_utils_usage) + self.assertEqual(mock_op_utils.get_endpoint.call_count, expected_op_utils_usage) + self.assertEqual(mock_op_utils.get_heat_api_version.call_count, expected_op_utils_usage) + + # ensure the constructor was not used but the instance was used + expected_create_calls += 1 + self.assertEqual(mock_heat_client_class.call_count, expected_constructor_calls) + self.assertEqual(mock_heat_client.stacks.create.call_count, expected_create_calls) + + # ensure that the status was used three times + expected_status_calls += 3 + self.assertEqual(mock_status.call_count, expected_status_calls) + + # ensure the expected exception was raised + error_message = get_error_message(raised.exception) + self.assertNotIn('timeout', error_message) + self.assertIn('the reason', error_message) + + @mock_patch_target_module('op_utils') + @mock_patch_target_module('heatclient.client.Client') + def test_create(self, mock_heat_client_class, mock_op_utils): + self.template.HEAT_WAIT_LOOP_INTERVAL = interval = 0.2 + mock_heat_client = mock_heat_client_class() + + # populate attributes of the constructed mock + mock_heat_client.stacks.get().outputs = [ + {'output_key': 'key1', 'output_value': 'value1'}, + {'output_key': 'key2', 'output_value': 'value2'}, + {'output_key': 'key3', 'output_value': 'value3'}, + ] + expected_outputs = { + 'key1': 'value1', + 'key2': 'value2', + 'key3': 'value3', + } + + expected_status_calls = 0 + expected_constructor_calls = 1 # above, to get the instance + expected_create_calls = 0 + expected_op_utils_usage = 0 + + with mock.patch.object(self.template, 'status') as mock_status: + # no block + with timer() as time_data: + self.assertIsInstance(self.template.create(block=False, timeout=2), heat.HeatStack) + + # ensure runtime is much less than one interval + self.assertLess(time_data['delta'], interval * 0.2) + + # ensure op_utils was used + expected_op_utils_usage += 1 + self.assertEqual(mock_op_utils.get_session.call_count, expected_op_utils_usage) + self.assertEqual(mock_op_utils.get_endpoint.call_count, expected_op_utils_usage) + self.assertEqual(mock_op_utils.get_heat_api_version.call_count, expected_op_utils_usage) + + # ensure the constructor and instance were used + expected_constructor_calls += 1 + expected_create_calls += 1 + self.assertEqual(mock_heat_client_class.call_count, expected_constructor_calls) + self.assertEqual(mock_heat_client.stacks.create.call_count, expected_create_calls) + + # ensure that the status was not used + self.assertEqual(mock_status.call_count, expected_status_calls) + + # ensure no outputs because this requires blocking + self.assertEqual(self.template.outputs, {}) + + # block with immediate complete + mock_status.return_value = u'CREATE_COMPLETE' + with timer() as time_data: + self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack) + + # ensure runtime is less than one interval + self.assertLess(time_data['delta'], interval * 0.2) + + # ensure existing instance was re-used and op_utils was not used + expected_create_calls += 1 + self.assertEqual(mock_heat_client_class.call_count, expected_constructor_calls) + self.assertEqual(mock_heat_client.stacks.create.call_count, expected_create_calls) + + # ensure status was checked once + expected_status_calls += 1 + self.assertEqual(mock_status.call_count, expected_status_calls) + + # ensure the expected outputs are present + self.assertDictEqual(self.template.outputs, expected_outputs) + + # reset template outputs + self.template.outputs = None + + # block with delayed complete + mock_status.side_effect = iter([None, None, u'CREATE_COMPLETE']) + with timer() as time_data: + self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack) + + # ensure runtime is approximately two intervals + expected_time_low = interval * 1.8 + expected_time_high = interval * 2.2 + self.assertTrue(expected_time_low < time_data['delta'] < expected_time_high) + + # ensure existing instance was re-used and op_utils was not used + expected_create_calls += 1 + self.assertEqual(mock_heat_client_class.call_count, expected_constructor_calls) + self.assertEqual(mock_heat_client.stacks.create.call_count, expected_create_calls) + + # ensure status was checked three more times + expected_status_calls += 3 + self.assertEqual(mock_status.call_count, expected_status_calls) class HeatStackTestCase(unittest.TestCase): |