diff options
author | Edward MacGillivray <edward.s.macgillivray@intel.com> | 2017-06-12 11:06:45 -0700 |
---|---|---|
committer | Ross Brattain <ross.b.brattain@intel.com> | 2017-06-20 13:19:25 +0000 |
commit | 653902770572c780777d1dc7a371794b670585b1 (patch) | |
tree | 6fcb520a711836eb10c2dd4759666d3b39858150 /tests/unit | |
parent | 37921fcd232cd2fbba9f45ef9fa5d8c912f54af6 (diff) |
Acquire NSB specific data from Heat.
First we add mac_address, subnet_cidr to Heat template outputs
Then we convert those into a form for NSB and add vld_id.
NSB also requires PCI Bus ID, kernel driver
and dpdk_port_num. We get this by ssh-ing
into instance and dumping sysfs
We also need to fix allow for ssh key auth,
and implement relative path file loading
so NSB can find all its YAML files
JIRA: YARDSTICK-580
Change history:
don't hide heat create tracebacks we need tracebacks for debug
vnf_generic: add task_path to scenario so we can load relative paths
for vnf_generic we want to be able to load yaml relative to the
task path
For example:
traffic_profile: ../../traffic_profiles/fixed.yaml
topology: ping_tg_topology.yaml # TODO: look in relative path where the tc.yaml is found
These need to be relative to samples/vnf_samples/nsut/ping/tc_ping_heat_context.yaml
Add a scenario["task_path"] entry
heat: log actual exception
vnf_generic: replace list with set and iterate over values()
some general refactors to remove redundact lookups and
type conversions
heat: provide mac_address, device_id and network_id from outputs
We may need more information to dynamically
determine test topology.
Towards this end return more info in the heat template.
We can return mac_address, device_id and network_id.
Once we have this info we can add it to the context_cfg
as an interfaces dict.
add sample vnf ping multi-network test
this test requires 3 network, one for mgmt
and the other two for NSB traffic tests
We have to make sure we don't use DPDK
on mgmt interface because DPDK unbinds
the driver
heat: convert networks to OrderedDict
so we can lookups networks as well as
iterate over them in consisitent order
heat: and vld_id to networks for vnf_generic
vnf_generic uses vld_id Virtual Link Descriptor ID
to identify interfaces
Add the key to the networks dict
and store in Networks object
implement relative path file loading in vnf_generic
in multiple places we need to load a file
relative to the task path, so add
open_relative_file_path
and modify load_vnf_model to include the scenario_cfg
parameter so we have access to task_path
DRAFT: heat timeout support
Heat stack in CI job failed due to some Nova issue.
But then apparently yardstick kept running and took 180mins to timeout
https://build.opnfv.org/ci/view/bottlenecks/job/bottlenecks-compass-posca_stress_ping-baremetal-daily-master/16/console
We can add a Heat create timeout and fail faster if there is an error.
The question is how long should we wait for a Heat stack to deploy. We
can set a default and allow override in the heat context config, if
users make complicated stacks
heat: get netmask and gateway from heat outputs
we have do some tricky business with finding
the subnet cidr and converting it into netmask
vnf_generic: get vpci, driver and dpdk_port_num
use a big old find command to dump all the sysfs
netdev info nicely. This was re-used from autotest FCoE tests.
r"""find /sys/devices/pci* -type d -name net -exec sh -c '{ grep -sH ^ \
+$1/ifindex $1/address $1/operstate $1/device/vendor $1/device/device \
+$1/device/subsystem_vendor $1/device/subsystem_device ; \
+printf "%s/driver:" $1 ; basename $(readlink -s $1/device/driver); } \
+' sh \{\}/* \;
This finds all PCI devices that are network devices, then
dumps all the relevant info using /bin/sh.
Then we parse this into a 'netdevs' dict inside the node_dict
and also convert into VNF fields we need.
vnf_generic: set node name for kpis
node is a dict, so we have to use node_name
vnfdgen: we CANNOT use TaskTemplate.render
because it does not allow for missing variables, we need to allow
password for key_filename to be undefined
remove default ssh password hack, once rendering is fixed
add new example tc_external_ping_heat_context
Change-Id: If1fe0c1a2ab0a5be17e40790a66f28f706fa44d6
Signed-off-by: Ross Brattain <ross.b.brattain@intel.com>
Signed-off-by: Edward MacGillivray <edward.s.macgillivray@intel.com>
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): |