From f8c32c5df789ab9dd698b247740cd72049e1bf65 Mon Sep 17 00:00:00 2001 From: Emma Foley Date: Mon, 19 Feb 2018 23:15:11 +0000 Subject: Add ability to reuse existing OpenStack set-up This change adds two optional flags to the context configuration for heat: * no_setup -- to re-use an existing OpenStack deployment * no_teardown -- to skip undeploying the context, so it is available in other testcases In order to achieve this, resource naming had to be made more predictable. When no_setup or no_teardown are set: * Do not add a suffix to context name * When searching, try with and without a suffix to find a server by name All behaviors remain the same unless these values are set. NOTE: This feature doesn't work with the NSPerf scenario, because the interfaces in the VNFs are bound to DPDK after the first test, so the look-up for missing topology information fails in subsequent test runs, as the interfaces are not visable to the kernel. This can be addressed either by either unbinding the interfaces from DPDK at the end of each test, or modifying the look-up for the missing topology information to query DPDK as well. The change will be made in a future patch. JIRA: YARDSTICK-886 Change-Id: I6df5e86e419c283a2bee41917e4f62179aa9c31a Signed-off-by: Emma Foley --- yardstick/benchmark/contexts/heat.py | 19 +++++- yardstick/benchmark/core/task.py | 4 +- .../tests/unit/benchmark/contexts/test_heat.py | 78 +++++++++++++++++++++- yardstick/tests/unit/benchmark/core/test_task.py | 32 +++++++++ 4 files changed, 129 insertions(+), 4 deletions(-) diff --git a/yardstick/benchmark/contexts/heat.py b/yardstick/benchmark/contexts/heat.py index ed301a998..75e26e06f 100644 --- a/yardstick/benchmark/contexts/heat.py +++ b/yardstick/benchmark/contexts/heat.py @@ -331,7 +331,14 @@ class HeatContext(Context): if self.template_file is None: self._add_resources_to_template(heat_template) - self.stack = self._create_new_stack(heat_template) + if self._flags.no_setup: + # Try to get an existing stack, returns a stack or None + self.stack = self._retrieve_existing_stack(self.name) + if not self.stack: + self.stack = self._create_new_stack(heat_template) + + else: + self.stack = self._create_new_stack(heat_template) # TODO: use Neutron to get segmentation-id self.get_neutron_info() @@ -397,6 +404,10 @@ class HeatContext(Context): def undeploy(self): """undeploys stack from cloud""" + if self._flags.no_teardown: + LOG.info("Undeploying context '%s' SKIP", self.name) + return + if self.stack: LOG.info("Undeploying context '%s' START", self.name) self.stack.delete() @@ -444,7 +455,11 @@ class HeatContext(Context): server.private_ip = self.stack.outputs.get( attr_name.get("private_ip_attr", object()), None) else: - server = self._server_map.get(attr_name, None) + try: + server = self._server_map[attr_name] + except KeyError: + attr_name_no_suffix = attr_name.split("-")[0] + server = self._server_map.get(attr_name_no_suffix, None) if server is None: return None diff --git a/yardstick/benchmark/core/task.py b/yardstick/benchmark/core/task.py index 270800a99..2c3edfe13 100644 --- a/yardstick/benchmark/core/task.py +++ b/yardstick/benchmark/core/task.py @@ -342,7 +342,7 @@ class Task(object): # pragma: no cover try: config_context_target(item) except KeyError: - pass + LOG.debug("Got a KeyError in config_context_target(%s)", item) else: break @@ -523,6 +523,8 @@ class TaskParser(object): # pragma: no cover context_type = cfg_attrs.get("type", "Heat") context = Context.get(context_type) context.init(cfg_attrs) + # Update the name in case the context has used the name_suffix + cfg_attrs['name'] = context.name contexts.append(context) run_in_parallel = cfg.get("run_in_parallel", False) diff --git a/yardstick/tests/unit/benchmark/contexts/test_heat.py b/yardstick/tests/unit/benchmark/contexts/test_heat.py index 1e31abce8..c449b2a28 100644 --- a/yardstick/tests/unit/benchmark/contexts/test_heat.py +++ b/yardstick/tests/unit/benchmark/contexts/test_heat.py @@ -16,6 +16,8 @@ import logging import mock import unittest +import shade + from yardstick.benchmark.contexts import base from yardstick.benchmark.contexts import heat from yardstick.benchmark.contexts import model @@ -120,13 +122,33 @@ class HeatContextTestCase(unittest.TestCase): self.assertEqual(self.test_context.assigned_name, 'foo') def test_name_flags(self): - self.test_context._flags = base.Flags(**{"no_setup": True, "no_teardown": True}) + self.test_context._flags = base.Flags( + **{"no_setup": True, "no_teardown": True}) self.test_context._name = 'foo' self.test_context._task_id = '1234567890' self.assertEqual(self.test_context.name, 'foo') self.assertEqual(self.test_context.assigned_name, 'foo') + @mock.patch('yardstick.ssh.SSH.gen_keys') + def test_init_no_setup_no_teardown(self, *args): + + attrs = {'name': 'foo', + 'task_id': '1234567890', + 'placement_groups': {}, + 'server_groups': {}, + 'networks': {}, + 'servers': {}, + 'flags': { + 'no_setup': True, + 'no_teardown': True, + }, + } + + self.test_context.init(attrs) + self.assertTrue(self.test_context._flags.no_setup) + self.assertTrue(self.test_context._flags.no_teardown) + @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate') def test__add_resources_to_template_no_servers(self, mock_template): self.test_context._name = 'ctx' @@ -228,6 +250,50 @@ class HeatContextTestCase(unittest.TestCase): {'image': 'cirros'}) self.assertIsNotNone(self.test_context.stack) + # TODO: patch objects + @mock.patch.object(heat, 'HeatTemplate') + @mock.patch.object(heat.HeatContext, '_retrieve_existing_stack') + @mock.patch.object(heat.HeatContext, '_create_new_stack') + def test_deploy_no_setup(self, mock_create_new_stack, mock_retrieve_existing_stack, *args): + self.test_context._name = 'foo' + self.test_context._task_id = '1234567890' + # Might be able to get rid of these + self.test_context.template_file = '/bar/baz/some-heat-file' + self.test_context.heat_parameters = {'image': 'cirros'} + self.test_context.get_neutron_info = mock.MagicMock() + self.test_context._flags.no_setup = True + self.test_context.deploy() + + # check that heat client is called... + mock_create_new_stack.assert_not_called() + mock_retrieve_existing_stack.assert_called_with(self.test_context.name) + self.assertIsNotNone(self.test_context.stack) + + @mock.patch.object(shade, 'openstack_cloud') + @mock.patch.object(heat.HeatTemplate, 'add_keypair') + @mock.patch.object(heat.HeatContext, '_create_new_stack') + @mock.patch.object(heat.HeatStack, 'get') + def test_deploy_try_retrieve_context_does_not_exist(self, + mock_get_stack, + mock_create_new_stack, + *args): + self.test_context._name = 'demo' + self.test_context._task_id = '1234567890' + self.test_context._flags.no_setup = True + self.test_context.get_neutron_info = mock.MagicMock() + + # TODo: Check is this the right value to return, should it be None instead? + mock_get_stack.return_value = [] + + self.test_context.deploy() + + mock_get_stack.assert_called() + mock_create_new_stack.assert_called() + + def test_check_for_context(self): + pass + # check that the context exists + def test_add_server_port(self): network1 = mock.MagicMock() network2 = mock.MagicMock() @@ -301,6 +367,16 @@ class HeatContextTestCase(unittest.TestCase): self.test_context.undeploy() self.assertTrue(mock_template.delete.called) + @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate') + def test_undeploy_no_teardown(self, mock_template): + self.test_context.stack = mock_template + self.test_context._name = 'foo' + self.test_context._task_id = '1234567890' + self.test_context._flags.no_teardown = True + self.test_context.undeploy() + + mock_template.delete.assert_not_called() + @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate') @mock.patch('yardstick.benchmark.contexts.heat.os') def test_undeploy_key_filename(self, mock_os, mock_template): diff --git a/yardstick/tests/unit/benchmark/core/test_task.py b/yardstick/tests/unit/benchmark/core/test_task.py index 2420df2d8..e91853ffe 100644 --- a/yardstick/tests/unit/benchmark/core/test_task.py +++ b/yardstick/tests/unit/benchmark/core/test_task.py @@ -252,6 +252,38 @@ class TaskTestCase(unittest.TestCase): actual_result = t._parse_options(options) self.assertEqual(expected_result, actual_result) + def test_parse_options_no_teardown(self): + options = { + 'openstack': { + 'EXTERNAL_NETWORK': '$network' + }, + 'nodes': ['node1', '$node'], + 'host': '$host', + 'contexts' : {'name': "my-context", + 'no_teardown': True} + } + + t = task.Task() + t.outputs = { + 'network': 'ext-net', + 'node': 'node2', + 'host': 'server.yardstick' + } + + expected_result = { + 'openstack': { + 'EXTERNAL_NETWORK': 'ext-net' + }, + 'nodes': ['node1', 'node2'], + 'host': 'server.yardstick', + 'contexts': {'name': 'my-context', + 'no_teardown': True, + } + } + + actual_result = t._parse_options(options) + self.assertEqual(expected_result, actual_result) + @mock.patch('six.moves.builtins.open', side_effect=mock.mock_open()) @mock.patch.object(task, 'utils') @mock.patch('logging.root') -- cgit 1.2.3-korg