From 1681ba53783835e500e602a1b7500e0221eb08f9 Mon Sep 17 00:00:00 2001 From: Emma Foley Date: Thu, 22 Feb 2018 19:11:12 +0000 Subject: Update TaskParser to deal with qualified name in Context The context name depends on the defined name in the testcase input file, the task ID and the flags of the context. If the context is going to be undeployed at the end of the test, the task ID is suffixed to the name to avoid interferences with previous deployments. If the context needs to be deployed at the end of the test, the name assigned is kept. This patch makes this process transparent to the developer. This patch modifies how TaskParser determines the correct context name, taking into account that the name might change based on context flags. JIRA: YARDSTICK-886 Change-Id: I44da30dac562c1a4166e084645ae91c17798651d Signed-off-by: Rodolfo Alonso Hernandez Signed-off-by: Emma Foley --- yardstick/benchmark/core/task.py | 107 ++++++++++++--------- yardstick/common/exceptions.py | 4 + yardstick/tests/unit/benchmark/core/test_task.py | 115 +++++++++++++++++------ 3 files changed, 154 insertions(+), 72 deletions(-) diff --git a/yardstick/benchmark/core/task.py b/yardstick/benchmark/core/task.py index 5fcc9182c..270800a99 100644 --- a/yardstick/benchmark/core/task.py +++ b/yardstick/benchmark/core/task.py @@ -7,10 +7,7 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -""" Handler for yardstick command 'task' """ -from __future__ import absolute_import -from __future__ import print_function import sys import os from collections import OrderedDict @@ -34,6 +31,7 @@ from yardstick.dispatcher.base import Base as DispatcherBase from yardstick.common.task_template import TaskTemplate from yardstick.common import utils from yardstick.common import constants +from yardstick.common import exceptions from yardstick.common.html_template import report_template output_file_default = "/tmp/yardstick.out" @@ -518,13 +516,9 @@ class TaskParser(object): # pragma: no cover context_cfgs = [{"type": "Dummy"}] contexts = [] - name_suffix = '-{}'.format(task_id[:8]) for cfg_attrs in context_cfgs: - try: - cfg_attrs['name'] = '{}{}'.format(cfg_attrs['name'], - name_suffix) - except KeyError: - pass + + cfg_attrs['task_id'] = task_id # default to Heat context because we are testing OpenStack context_type = cfg_attrs.get("type", "Heat") context = Context.get(context_type) @@ -542,17 +536,71 @@ class TaskParser(object): # pragma: no cover # relative to task path scenario["task_path"] = os.path.dirname(self.path) - change_server_name(scenario, name_suffix) - - try: - for node in scenario['nodes']: - scenario['nodes'][node] += name_suffix - except KeyError: - pass + self._change_node_names(scenario, contexts) # TODO we need something better here, a class that represent the file return cfg["scenarios"], run_in_parallel, meet_precondition, contexts + @staticmethod + def _change_node_names(scenario, contexts): + """Change the node names in a scenario, depending on the context config + + The nodes (VMs or physical servers) are referred in the context section + with the name of the server and the name of the context: + . + + If the context is going to be undeployed at the end of the test, the + task ID is suffixed to the name to avoid interferences with previous + deployments. If the context needs to be deployed at the end of the + test, the name assigned is kept. + + There are several places where a node name could appear in the scenario + configuration: + scenario: + host: athena.demo + target: kratos.demo + targets: + - athena.demo + - kratos.demo + + scenario: + options: + server_name: # JIRA: YARDSTICK-810 + host: athena.demo + target: kratos.demo + + scenario: + nodes: + tg__0: tg_0.yardstick + vnf__0: vnf_0.yardstick + """ + def qualified_name(name): + node_name, context_name = name.split('.') + try: + ctx = next((context for context in contexts + if context.assigned_name == context_name)) + except StopIteration: + raise exceptions.ScenarioConfigContextNameNotFound( + context_name=context_name) + + return '{}.{}'.format(node_name, ctx.name) + + if 'host' in scenario: + scenario['host'] = qualified_name(scenario['host']) + if 'target' in scenario: + scenario['target'] = qualified_name(scenario['target']) + server_name = scenario.get('options', {}).get('server_name', {}) + if 'host' in server_name: + server_name['host'] = qualified_name(server_name['host']) + if 'target' in server_name: + server_name['target'] = qualified_name(server_name['target']) + if 'targets' in scenario: + for idx, target in enumerate(scenario['targets']): + scenario['targets'][idx] = qualified_name(target) + if 'nodes' in scenario: + for scenario_node, target in scenario['nodes'].items(): + scenario['nodes'][scenario_node] = qualified_name(target) + def _check_schema(self, cfg_schema, schema_type): """Check if config file is using the correct schema type""" @@ -685,30 +733,3 @@ def parse_task_args(src_name, args): % {"src": src_name, "src_type": type(kw)}) raise TypeError() return kw - - -def change_server_name(scenario, suffix): - - def add_suffix(cfg, key): - try: - value = cfg[key] - except KeyError: - pass - else: - try: - value['name'] += suffix - except TypeError: - cfg[key] += suffix - - server_name = scenario.get('options', {}).get('server_name', {}) - - add_suffix(scenario, 'host') - add_suffix(scenario, 'target') - add_suffix(server_name, 'host') - add_suffix(server_name, 'target') - - try: - key = 'targets' - scenario[key] = ['{}{}'.format(a, suffix) for a in scenario[key]] - except KeyError: - pass diff --git a/yardstick/common/exceptions.py b/yardstick/common/exceptions.py index 3e0635e46..4f89fed32 100644 --- a/yardstick/common/exceptions.py +++ b/yardstick/common/exceptions.py @@ -70,3 +70,7 @@ class IPv6RangeError(YardstickException): class DPDKSetupDriverError(YardstickException): message = '"igb_uio" driver is not loaded' + + +class ScenarioConfigContextNameNotFound(YardstickException): + message = 'Context name "%(context_name)s" not found' diff --git a/yardstick/tests/unit/benchmark/core/test_task.py b/yardstick/tests/unit/benchmark/core/test_task.py index bac035fb9..2420df2d8 100644 --- a/yardstick/tests/unit/benchmark/core/test_task.py +++ b/yardstick/tests/unit/benchmark/core/test_task.py @@ -7,13 +7,16 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## +import copy import os import mock import unittest +from yardstick.benchmark.contexts import dummy from yardstick.benchmark.core import task from yardstick.common import constants as consts +from yardstick.common import exceptions class TaskTestCase(unittest.TestCase): @@ -249,31 +252,6 @@ class TaskTestCase(unittest.TestCase): actual_result = t._parse_options(options) self.assertEqual(expected_result, actual_result) - - def test_change_server_name_host_str(self): - scenario = {'host': 'demo'} - suffix = '-8' - task.change_server_name(scenario, suffix) - self.assertEqual('demo-8', scenario['host']) - - def test_change_server_name_host_dict(self): - scenario = {'host': {'name': 'demo'}} - suffix = '-8' - task.change_server_name(scenario, suffix) - self.assertEqual('demo-8', scenario['host']['name']) - - def test_change_server_name_target_str(self): - scenario = {'target': 'demo'} - suffix = '-8' - task.change_server_name(scenario, suffix) - self.assertEqual('demo-8', scenario['target']) - - def test_change_server_name_target_dict(self): - scenario = {'target': {'name': 'demo'}} - suffix = '-8' - task.change_server_name(scenario, suffix) - self.assertEqual('demo-8', scenario['target']['name']) - @mock.patch('six.moves.builtins.open', side_effect=mock.mock_open()) @mock.patch.object(task, 'utils') @mock.patch('logging.root') @@ -292,9 +270,88 @@ class TaskTestCase(unittest.TestCase): return os.path.join(consts.YARDSTICK_ROOT_PATH, filepath) -def main(): - unittest.main() +class TaskParserTestCase(unittest.TestCase): + + def setUp(self): + self.parser = task.TaskParser('fake/path') + self.scenario = { + 'host': 'athena.demo', + 'target': 'kratos.demo', + 'targets': [ + 'ares.demo', 'mars.demo' + ], + 'options': { + 'server_name': { + 'host': 'jupiter.demo', + 'target': 'saturn.demo', + }, + }, + 'nodes': { + 'tg__0': 'tg_0.demo', + 'vnf__0': 'vnf_0.demo', + } + } + + def test__change_node_names(self): + + ctx_attrs = { + 'name': 'demo', + 'task_id': '1234567890', + 'servers': [ + 'athena', 'kratos', + 'ares', 'mars', + 'jupiter', 'saturn', + 'tg_0', 'vnf_0' + ] + } + + my_context = dummy.DummyContext() + my_context.init(ctx_attrs) + + expected_scenario = { + 'host': 'athena.demo-12345678', + 'target': 'kratos.demo-12345678', + 'targets': [ + 'ares.demo-12345678', 'mars.demo-12345678' + ], + 'options': { + 'server_name': { + 'host': 'jupiter.demo-12345678', + 'target': 'saturn.demo-12345678', + }, + }, + 'nodes': { + 'tg__0': 'tg_0.demo-12345678', + 'vnf__0': 'vnf_0.demo-12345678', + } + } + + scenario = copy.deepcopy(self.scenario) + + self.parser._change_node_names(scenario, [my_context]) + self.assertEqual(scenario, expected_scenario) + + def test__change_node_names_context_not_found(self): + scenario = copy.deepcopy(self.scenario) + self.assertRaises(exceptions.ScenarioConfigContextNameNotFound, + self.parser._change_node_names, + scenario, []) + + def test__change_node_names_context_name_unchanged(self): + ctx_attrs = { + 'name': 'demo', + 'task_id': '1234567890', + 'flags': { + 'no_setup': True, + 'no_teardown': True + } + } + + my_context = dummy.DummyContext() + my_context.init(ctx_attrs) + scenario = copy.deepcopy(self.scenario) + expected_scenario = copy.deepcopy(self.scenario) -if __name__ == '__main__': - main() + self.parser._change_node_names(scenario, [my_context]) + self.assertEqual(scenario, expected_scenario) -- cgit 1.2.3-korg