diff options
-rw-r--r-- | ansible/nsb_setup.yml | 1 | ||||
-rwxr-xr-x | tests/ci/yardstick-verify | 4 | ||||
-rw-r--r-- | tests/unit/benchmark/contexts/test_heat.py | 38 | ||||
-rw-r--r-- | tests/unit/benchmark/core/test_task.py | 52 | ||||
-rw-r--r-- | tests/unit/network_services/vnf_generic/vnf/test_iniparser.py | 52 | ||||
-rw-r--r-- | tests/unit/network_services/vnf_generic/vnf/test_sample_vnf.py | 14 | ||||
-rw-r--r-- | tests/unit/test_ssh.py | 2 | ||||
-rw-r--r-- | yardstick/benchmark/contexts/heat.py | 25 | ||||
-rw-r--r-- | yardstick/benchmark/contexts/model.py | 15 | ||||
-rw-r--r-- | yardstick/benchmark/core/task.py | 10 | ||||
-rw-r--r-- | yardstick/benchmark/scenarios/compute/qemu_migrate.py | 2 | ||||
-rw-r--r-- | yardstick/benchmark/scenarios/compute/qemu_migrate_benchmark.bash | 6 | ||||
-rw-r--r-- | yardstick/benchmark/scenarios/networking/vnf_generic.py | 29 | ||||
-rw-r--r-- | yardstick/network_services/vnf_generic/vnf/iniparser.py | 299 | ||||
-rw-r--r-- | yardstick/ssh.py | 3 |
15 files changed, 335 insertions, 217 deletions
diff --git a/ansible/nsb_setup.yml b/ansible/nsb_setup.yml index e79ccabea..78cf87ec7 100644 --- a/ansible/nsb_setup.yml +++ b/ansible/nsb_setup.yml @@ -48,7 +48,6 @@ recreate: yes state: started restart_policy: always - network_mode: host privileged: True interactive: True volumes: diff --git a/tests/ci/yardstick-verify b/tests/ci/yardstick-verify index ca8a0b27a..f3e7a49e3 100755 --- a/tests/ci/yardstick-verify +++ b/tests/ci/yardstick-verify @@ -87,7 +87,9 @@ error_exit() exitcode=$rc fi - cleanup + if [[ "${DEPLOY_SCENARIO:0:2}" == 'os' ]];then + source "${YARDSTICK_REPO_DIR}/tests/ci/clean_images.sh" + fi echo "Exiting with RC=$exitcode" diff --git a/tests/unit/benchmark/contexts/test_heat.py b/tests/unit/benchmark/contexts/test_heat.py index cc0c7bc8e..582d9ab99 100644 --- a/tests/unit/benchmark/contexts/test_heat.py +++ b/tests/unit/benchmark/contexts/test_heat.py @@ -13,7 +13,6 @@ from __future__ import absolute_import -import ipaddress import logging import os import unittest @@ -147,30 +146,6 @@ class HeatContextTestCase(unittest.TestCase): self.test_context.user = 'foo' @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate') - @mock.patch('yardstick.benchmark.contexts.heat.get_neutron_client') - def test_attrs_get(self, mock_neutron, mock_template): - image, flavor, user = expected_tuple = 'foo1', 'foo2', 'foo3' - self.assertNotEqual(self.test_context.image, image) - self.assertNotEqual(self.test_context.flavor, flavor) - self.assertNotEqual(self.test_context.user, user) - self.test_context._image = image - self.test_context._flavor = flavor - self.test_context._user = user - attr_tuple = self.test_context.image, self.test_context.flavor, self.test_context.user - self.assertEqual(attr_tuple, expected_tuple) - - @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate') - def test_attrs_set_negative(self, mock_template): - with self.assertRaises(AttributeError): - self.test_context.image = 'foo' - - with self.assertRaises(AttributeError): - self.test_context.flavor = 'foo' - - with self.assertRaises(AttributeError): - self.test_context.user = 'foo' - - @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate') def test_deploy(self, mock_template): self.test_context.name = 'foo' self.test_context.template_file = '/bar/baz/some-heat-file' @@ -210,8 +185,8 @@ class HeatContextTestCase(unittest.TestCase): } server = mock.MagicMock() server.ports = OrderedDict([ - ('a', {'stack_name': 'b'}), - ('c', {'stack_name': 'd'}), + ('a', {'stack_name': 'b', 'port': 'port_a'}), + ('c', {'stack_name': 'd', 'port': 'port_c'}), ]) expected = { @@ -231,7 +206,7 @@ class HeatContextTestCase(unittest.TestCase): self.test_context.add_server_port(server) self.assertEqual(server.private_ip, '10.20.30.45') self.assertEqual(len(server.interfaces), 2) - self.assertDictEqual(server.interfaces['a'], expected) + self.assertDictEqual(server.interfaces['port_a'], expected) @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate') def test_undeploy(self, mock_template): @@ -472,7 +447,6 @@ class HeatContextTestCase(unittest.TestCase): network2 = mock.MagicMock() network2.name = 'net_2' - network2.vld_id = 'vld999' network2.segmentation_id = 'seg45' network2.network_type = 'type_b' network2.physical_network = 'virt' @@ -488,16 +462,15 @@ class HeatContextTestCase(unittest.TestCase): attr_name = {} self.assertIsNone(self.test_context._get_network(attr_name)) - attr_name = {'vld_id': 'vld777'} + attr_name = {'network_type': 'nosuch'} self.assertIsNone(self.test_context._get_network(attr_name)) attr_name = 'vld777' self.assertIsNone(self.test_context._get_network(attr_name)) - attr_name = {'vld_id': 'vld999'} + attr_name = {'segmentation_id': 'seg45'} expected = { "name": 'net_2', - "vld_id": 'vld999', "segmentation_id": 'seg45', "network_type": 'type_b', "physical_network": 'virt', @@ -508,7 +481,6 @@ class HeatContextTestCase(unittest.TestCase): attr_name = 'a' expected = { "name": 'net_1', - "vld_id": 'vld111', "segmentation_id": 'seg54', "network_type": 'type_a', "physical_network": 'phys', diff --git a/tests/unit/benchmark/core/test_task.py b/tests/unit/benchmark/core/test_task.py index 14027e43c..e3917b5b2 100644 --- a/tests/unit/benchmark/core/test_task.py +++ b/tests/unit/benchmark/core/test_task.py @@ -66,31 +66,27 @@ class TaskTestCase(unittest.TestCase): nodes = { 'node1': { 'interfaces': { - 'eth0': { - 'name': 'mgmt', + 'mgmt': { + 'network_name': 'mgmt', }, - 'eth1': { - 'name': 'external', - 'vld_id': '23', + 'xe0': { + 'network_name': 'private_0', }, - 'eth10': { - 'name': 'internal', - 'vld_id': '55', + 'xe1': { + 'network_name': 'public_0', }, }, }, 'node2': { 'interfaces': { - 'eth4': { - 'name': 'mgmt', + 'mgmt': { + 'network_name': 'mgmt', }, - 'eth2': { - 'name': 'external', - 'vld_id': '32', + 'private_0': { + 'network_name': 'private_0', }, - 'eth11': { - 'name': 'internal', - 'vld_id': '55', + 'public_0': { + 'network_name': 'public_0', }, }, }, @@ -99,30 +95,30 @@ class TaskTestCase(unittest.TestCase): mock_context.get_network.side_effect = iter([ None, { - 'name': 'a', - 'network_type': 'private', + 'name': 'mgmt', + 'network_type': 'flat', }, {}, { - 'name': 'b', - 'vld_id': 'y', + 'name': 'private_0', 'subnet_cidr': '10.20.0.0/16', }, { - 'name': 'c', - 'vld_id': 'x', + 'name': 'public_0', + 'segmentation_id': '1001', }, { - 'name': 'd', - 'vld_id': 'w', + 'name': 'private_1', }, ]) - # once for each vld_id in the nodes dict - expected_get_network_calls = 4 + # one for each interface + expected_get_network_calls = 6 expected = { - 'a': {'name': 'a', 'network_type': 'private'}, - 'b': {'name': 'b', 'vld_id': 'y', 'subnet_cidr': '10.20.0.0/16'}, + 'mgmt': {'name': 'mgmt', 'network_type': 'flat'}, + 'private_0': {'name': 'private_0', 'subnet_cidr': '10.20.0.0/16'}, + 'private_1': {'name': 'private_1'}, + 'public_0': {'name': 'public_0', 'segmentation_id': '1001'}, } networks = task.get_networks_from_nodes(nodes) diff --git a/tests/unit/network_services/vnf_generic/vnf/test_iniparser.py b/tests/unit/network_services/vnf_generic/vnf/test_iniparser.py index 15d6adea1..1ad8df9c6 100644 --- a/tests/unit/network_services/vnf_generic/vnf/test_iniparser.py +++ b/tests/unit/network_services/vnf_generic/vnf/test_iniparser.py @@ -28,6 +28,7 @@ stl_patch.start() if stl_patch: from yardstick.network_services.vnf_generic.vnf.iniparser import ParseError + from yardstick.network_services.vnf_generic.vnf.iniparser import LineParser from yardstick.network_services.vnf_generic.vnf.iniparser import BaseParser from yardstick.network_services.vnf_generic.vnf.iniparser import ConfigParser @@ -38,16 +39,18 @@ key1=value1 list1: value2 value3 value4 -key2="double quote value" key3='single quote value' ; comment here key4= -[section2] +[section2] ; comment with #2 other symbol # here is a comment line list2: value5 -key with no value +key with no value # mixed comment ; symbols ; another comment line key5= + +[section1] ; reopen a section! +key2="double quote value" """ PARSE_TEXT_2 = """\ @@ -86,6 +89,17 @@ class TestParseError(unittest.TestCase): self.assertEqual(str(error), "at line 2, a: 'c'") +class TestLineParser(unittest.TestCase): + + def test___repr__(self): + line_parser = LineParser('', 101) + self.assertIsNotNone(repr(line_parser)) + + def test_error_invalid_assignment(self): + line_parser = LineParser('', 101) + self.assertIsNotNone(line_parser.error_invalid_assignment()) + + class TestBaseParser(unittest.TestCase): @staticmethod @@ -96,23 +110,26 @@ class TestBaseParser(unittest.TestCase): return internal_open - @mock.patch('yardstick.network_services.vnf_generic.vnf.iniparser.open') - def test_parse_none(self, mock_open): - mock_open.side_effect = self.make_open('') - + def test_parse(self): parser = BaseParser() + parser.parse() - parser.parse([]) + def test_parse_empty_string(self): + parser = BaseParser() + self.assertIsNone(parser.parse('')) def test_not_implemented_methods(self): parser = BaseParser() with self.assertRaises(NotImplementedError): - parser.assignment('key', 'value') + parser.assignment('key', 'value', LineParser('', 100)) with self.assertRaises(NotImplementedError): parser.new_section('section') + with self.assertRaises(NotImplementedError): + parser.comment('comment') + class TestConfigParser(unittest.TestCase): @@ -128,18 +145,25 @@ class TestConfigParser(unittest.TestCase): def test_parse(self, mock_open): mock_open.side_effect = self.make_open(PARSE_TEXT_1) - config_parser = ConfigParser('my_file', []) + existing_data = [['section0', [['key0', 'value0']]]] + config_parser = ConfigParser('my_file', existing_data) config_parser.parse() expected = [ [ + 'section0', + [ + ['key0', 'value0'], + ], + ], + [ 'section1', [ ['key1', 'value1'], ['list1', 'value2\nvalue3\nvalue4'], - ['key2', 'double quote value'], ['key3', 'single quote value'], ['key4', ''], + ['key2', 'double quote value'], ], ], [ @@ -153,12 +177,16 @@ class TestConfigParser(unittest.TestCase): ] self.assertEqual(config_parser.sections, expected) + self.assertIsNotNone(config_parser.find_section('section1')) + self.assertIsNone(config_parser.find_section('section3')) + self.assertEqual(config_parser.find_section_index('section1'), 1) + self.assertEqual(config_parser.find_section_index('section3'), -1) @mock.patch('yardstick.network_services.vnf_generic.vnf.iniparser.open') def test_parse_2(self, mock_open): mock_open.side_effect = self.make_open(PARSE_TEXT_2) - config_parser = ConfigParser('my_file', []) + config_parser = ConfigParser('my_file') config_parser.parse() expected = [ diff --git a/tests/unit/network_services/vnf_generic/vnf/test_sample_vnf.py b/tests/unit/network_services/vnf_generic/vnf/test_sample_vnf.py index 0264facf5..983c21e61 100644 --- a/tests/unit/network_services/vnf_generic/vnf/test_sample_vnf.py +++ b/tests/unit/network_services/vnf_generic/vnf/test_sample_vnf.py @@ -186,6 +186,7 @@ class TestVnfSshHelper(unittest.TestCase): @mock.patch('yardstick.ssh.paramiko') def test_upload_config_file(self, mock_paramiko): ssh_helper = VnfSshHelper(self.VNFD_0['mgmt-interface'], 'my/bin/path') + ssh_helper._run = mock.MagicMock() self.assertFalse(ssh_helper.is_connected) cfg_file = ssh_helper.upload_config_file('my/prefix', 'my content') @@ -227,6 +228,7 @@ class TestVnfSshHelper(unittest.TestCase): @mock.patch('yardstick.ssh.provision_tool') def test_provision_tool(self, mock_provision_tool, mock_paramiko): ssh_helper = VnfSshHelper(self.VNFD_0['mgmt-interface'], 'my/bin/path') + ssh_helper._run = mock.MagicMock() self.assertFalse(ssh_helper.is_connected) ssh_helper.provision_tool() @@ -412,6 +414,7 @@ class TestDpdkVnfSetupEnvHelper(unittest.TestCase): 'virtual-interface': { 'dst_mac': '00:00:00:00:00:03', 'vpci': '0000:05:00.0', + 'dpdk_port_num': '0', 'driver': 'i40e', 'local_ip': '152.16.100.19', 'type': 'PCI-PASSTHROUGH', @@ -419,7 +422,9 @@ class TestDpdkVnfSetupEnvHelper(unittest.TestCase): 'dpdk_port_num': '0', 'bandwidth': '10 Gbps', 'dst_ip': '152.16.100.20', - 'local_mac': '00:00:00:00:00:01' + 'local_mac': '00:00:00:00:00:01', + 'vld_id': 'private_0', + 'ifname': 'xe0', }, 'vnfd-connection-point-ref': 'xe0', 'name': 'xe0' @@ -428,6 +433,7 @@ class TestDpdkVnfSetupEnvHelper(unittest.TestCase): 'virtual-interface': { 'dst_mac': '00:00:00:00:00:04', 'vpci': '0000:05:00.1', + 'dpdk_port_num': '1', 'driver': 'ixgbe', 'local_ip': '152.16.40.19', 'type': 'PCI-PASSTHROUGH', @@ -435,7 +441,9 @@ class TestDpdkVnfSetupEnvHelper(unittest.TestCase): 'dpdk_port_num': '1', 'bandwidth': '10 Gbps', 'dst_ip': '152.16.40.20', - 'local_mac': '00:00:00:00:00:02' + 'local_mac': '00:00:00:00:00:02', + 'vld_id': 'public_0', + 'ifname': 'xe1', }, 'vnfd-connection-point-ref': 'xe1', 'name': 'xe1' @@ -600,8 +608,8 @@ class TestDpdkVnfSetupEnvHelper(unittest.TestCase): dpdk_setup_helper = DpdkVnfSetupEnvHelper(vnfd_helper, ssh_helper, scenario_helper) dpdk_setup_helper.CFG_CONFIG = 'config' dpdk_setup_helper.CFG_SCRIPT = 'script' - dpdk_setup_helper.all_ports = [3, 4, 5] dpdk_setup_helper.pipeline_kwargs = {} + dpdk_setup_helper.all_ports = [0, 1, 2] expected = { 'cfg_file': 'config', diff --git a/tests/unit/test_ssh.py b/tests/unit/test_ssh.py index 27ed68c7b..b298c745b 100644 --- a/tests/unit/test_ssh.py +++ b/tests/unit/test_ssh.py @@ -527,7 +527,7 @@ class TestAutoConnectSSH(unittest.TestCase): 'key_filename': None, 'password': None, 'name': None, - 'wait': False, + 'wait': True, } result = auto_connect_ssh._make_dict() self.assertDictEqual(result, expected) diff --git a/yardstick/benchmark/contexts/heat.py b/yardstick/benchmark/contexts/heat.py index 575467f8f..94a3824a7 100644 --- a/yardstick/benchmark/contexts/heat.py +++ b/yardstick/benchmark/contexts/heat.py @@ -329,19 +329,24 @@ class HeatContext(Context): def add_server_port(self, server): # TODO(hafe) can only handle one internal network for now - port = next(iter(server.ports.values())) - server.private_ip = self.stack.outputs[port["stack_name"]] + # use private ip from first port + private_port = next(iter(server.ports.values())) + server.private_ip = self.stack.outputs[private_port["stack_name"]] server.interfaces = {} for network_name, port in server.ports.items(): - server.interfaces[network_name] = self.make_interface_dict( - network_name, port['stack_name'], self.stack.outputs) + # port['port'] is either port name from mapping or default network_name + server.interfaces[port['port']] = self.make_interface_dict(network_name, port['port'], + port['stack_name'], + self.stack.outputs) - def make_interface_dict(self, network_name, stack_name, outputs): + def make_interface_dict(self, network_name, port, stack_name, outputs): private_ip = outputs[stack_name] mac_address = outputs[h_join(stack_name, "mac_address")] + # these are attributes of the network, not the port output_subnet_cidr = outputs[h_join(self.name, network_name, 'subnet', 'cidr')] + # these are attributes of the network, not the port output_subnet_gateway = outputs[h_join(self.name, network_name, 'subnet', 'gateway_ip')] @@ -355,6 +360,7 @@ class HeatContext(Context): "mac_address": mac_address, "device_id": outputs[h_join(stack_name, "device_id")], "network_id": outputs[h_join(stack_name, "network_id")], + # this should be == vld_id for NSB tests "network_name": network_name, # to match vnf_generic "local_mac": mac_address, @@ -438,9 +444,11 @@ class HeatContext(Context): network = self.networks.get(attr_name, None) else: - # Don't generalize too much Just support vld_id - vld_id = attr_name.get('vld_id', {}) - network_iter = (n for n in self.networks.values() if n.vld_id == vld_id) + # Only take the first key, value + key, value = next(iter(attr_name.items()), (None, None)) + if key is None: + return None + network_iter = (n for n in self.networks.values() if getattr(n, key) == value) network = next(network_iter, None) if network is None: @@ -448,7 +456,6 @@ class HeatContext(Context): result = { "name": network.name, - "vld_id": network.vld_id, "segmentation_id": network.segmentation_id, "network_type": network.network_type, "physical_network": network.physical_network, diff --git a/yardstick/benchmark/contexts/model.py b/yardstick/benchmark/contexts/model.py index 0b8197ce9..da2b74e1c 100644 --- a/yardstick/benchmark/contexts/model.py +++ b/yardstick/benchmark/contexts/model.py @@ -208,6 +208,7 @@ class Server(Object): # pragma: no cover self.instances = attrs["instances"] # dict with key network name, each item is a dict with port name and ip + self.network_ports = attrs.get("network_ports", {}) self.ports = {} self.floating_ip = None @@ -253,8 +254,18 @@ class Server(Object): # pragma: no cover """adds to the template one server and corresponding resources""" port_name_list = [] for network in networks: - port_name = server_name + "-" + network.name + "-port" - self.ports[network.name] = {"stack_name": port_name} + # if explicit mapping skip unused networks + if self.network_ports: + try: + port = self.network_ports[network.name] + except KeyError: + # no port for this network + continue + # otherwise add a port for every network with port name as network name + else: + port = network.name + port_name = "{0}-{1}-port".format(server_name, port) + self.ports[network.name] = {"stack_name": port_name, "port": port} # we can't use secgroups if port_security_enabled is False if network.port_security_enabled is False: sec_group_id = None diff --git a/yardstick/benchmark/core/task.py b/yardstick/benchmark/core/task.py index a49a2cb71..29295537a 100644 --- a/yardstick/benchmark/core/task.py +++ b/yardstick/benchmark/core/task.py @@ -377,8 +377,6 @@ class Task(object): # pragma: no cover target_attr: either a name for a server created by yardstick or a dict with attribute name mapping when using external heat templates """ - host = None - target = None for context in self.contexts: if context.__context_type__ != "Heat": continue @@ -628,11 +626,11 @@ def get_networks_from_nodes(nodes): continue interfaces = node.get('interfaces', {}) for interface in interfaces.values(): - vld_id = interface.get('vld_id') - # mgmt network doesn't have vld_id - if not vld_id: + # vld_id is network_name + network_name = interface.get('network_name') + if not network_name: continue - network = Context.get_network({"vld_id": vld_id}) + network = Context.get_network(network_name) if network: networks[network['name']] = network return networks diff --git a/yardstick/benchmark/scenarios/compute/qemu_migrate.py b/yardstick/benchmark/scenarios/compute/qemu_migrate.py index 6c0446bb7..6cfedc17a 100644 --- a/yardstick/benchmark/scenarios/compute/qemu_migrate.py +++ b/yardstick/benchmark/scenarios/compute/qemu_migrate.py @@ -106,7 +106,7 @@ class QemuMigrate(base.Scenario): cmd_args = " %s %s %s %s %s %s" %\ (smp, qmp_sock_src, qmp_sock_dst, incoming_ip, migrate_to_port, max_down_time) - cmd = "bash migrate_benchmark.sh %s" % (cmd_args) + cmd = "bash qemu_migrate_benchmark.sh %s" % (cmd_args) LOG.debug("Executing command: %s", cmd) status, stdout, stderr = self.host.execute(cmd) if status: diff --git a/yardstick/benchmark/scenarios/compute/qemu_migrate_benchmark.bash b/yardstick/benchmark/scenarios/compute/qemu_migrate_benchmark.bash index 552098103..d9a440c89 100644 --- a/yardstick/benchmark/scenarios/compute/qemu_migrate_benchmark.bash +++ b/yardstick/benchmark/scenarios/compute/qemu_migrate_benchmark.bash @@ -14,6 +14,7 @@ set -e # Commandline arguments src=$2 +dst=$3 dst_ip=$4 migrate_to_port=$5 max_down_time=$6 @@ -22,7 +23,6 @@ OUTPUT_FILE=/tmp/output-qemu.log do_migrate() { -# local src=`echo $OPTIONS | cut -d ':' -f 2 | cut -d ',' -f 1` echo "info status" | nc -U $src # with no speed limit echo "migrate_set_speed 0" |nc -U $src @@ -45,7 +45,9 @@ output_qemu() # print detail information echo "info migrate" | nc -U $src echo "quit" | nc -U $src + echo "quit" | nc -u $dst sleep 5 + echo "Migration executed successfully" } > $OUTPUT_FILE @@ -64,5 +66,7 @@ echo -e "{ \ main() { do_migrate + output_qemu + output_json } main diff --git a/yardstick/benchmark/scenarios/networking/vnf_generic.py b/yardstick/benchmark/scenarios/networking/vnf_generic.py index f7b2915a2..0e6ceab6e 100644 --- a/yardstick/benchmark/scenarios/networking/vnf_generic.py +++ b/yardstick/benchmark/scenarios/networking/vnf_generic.py @@ -216,7 +216,26 @@ class NetworkServiceTestCase(base.Scenario): @staticmethod def get_vld_networks(networks): - return {n['vld_id']: n for n in networks.values()} + # network name is vld_id + vld_map = {} + for name, n in networks.items(): + try: + vld_map[n['vld_id']] = n + except KeyError: + vld_map[name] = n + return vld_map + + @staticmethod + def find_node_if(nodes, name, if_name, vld_id): + try: + # check for xe0, xe1 + intf = nodes[name]["interfaces"][if_name] + except KeyError: + # if not xe0, then maybe vld_id, private_0, public_0 + # pop it and re-insert with the correct name from topology + intf = nodes[name]["interfaces"].pop(vld_id) + nodes[name]["interfaces"][if_name] = intf + return intf def _resolve_topology(self): for vld in self.topology["vld"]: @@ -234,8 +253,8 @@ class NetworkServiceTestCase(base.Scenario): try: nodes = self.context_cfg["nodes"] - node0_if = nodes[node0_name]["interfaces"][node0_if_name] - node1_if = nodes[node1_name]["interfaces"][node1_if_name] + node0_if = self.find_node_if(nodes, node0_name, node0_if_name, vld["id"]) + node1_if = self.find_node_if(nodes, node1_name, node1_if_name, vld["id"]) # names so we can do reverse lookups node0_if["ifname"] = node0_if_name @@ -285,8 +304,8 @@ class NetworkServiceTestCase(base.Scenario): node1_if_name = node1_data["vnfd-connection-point-ref"] nodes = self.context_cfg["nodes"] - node0_if = nodes[node0_name]["interfaces"][node0_if_name] - node1_if = nodes[node1_name]["interfaces"][node1_if_name] + node0_if = self.find_node_if(nodes, node0_name, node0_if_name, vld["id"]) + node1_if = self.find_node_if(nodes, node1_name, node1_if_name, vld["id"]) # add peer interface dict, but remove circular link # TODO: don't waste memory diff --git a/yardstick/network_services/vnf_generic/vnf/iniparser.py b/yardstick/network_services/vnf_generic/vnf/iniparser.py index 70e24de5b..98256e08a 100644 --- a/yardstick/network_services/vnf_generic/vnf/iniparser.py +++ b/yardstick/network_services/vnf_generic/vnf/iniparser.py @@ -14,163 +14,236 @@ class ParseError(Exception): - def __init__(self, message, lineno, line): + + def __init__(self, message, line_no, line): self.msg = message self.line = line - self.lineno = lineno + self.line_no = line_no def __str__(self): - return 'at line %d, %s: %r' % (self.lineno, self.msg, self.line) + return 'at line %d, %s: %r' % (self.line_no, self.msg, self.line) + + +class SectionParseError(ParseError): + + pass + + +class LineParser(object): + + PARSE_EXC = ParseError + + @staticmethod + def strip_key_value(key, value): + key = key.strip() + value = value.strip() + if value and value[0] == value[-1] and value.startswith(('"', "'")): + value = value[1:-1] + return key, [value] + + def __init__(self, line, line_no): + super(LineParser, self).__init__() + self.line = line + self.line_no = line_no + self.continuation = line != line.lstrip() + semi_active, _, semi_comment = line.partition(';') + pound_active, _, pound_comment = line.partition('#') + if not semi_comment and not pound_comment: + self.active = line.strip() + self.comment = '' + elif len(semi_comment) > len(pound_comment): + self.active = semi_active.strip() + self.comment = semi_comment.strip() + else: + self.active = pound_active.strip() + self.comment = pound_comment.strip() + self._section_name = None + + def __repr__(self): + template = "line %d: active '%s' comment '%s'\n%s" + return template % (self.line_no, self.active, self.comment, self.line) + + @property + def section_name(self): + if self._section_name is None: + if not self.active.startswith('['): + raise self.error_no_section_start_bracket() + if not self.active.endswith(']'): + raise self.error_no_section_end_bracket() + self._section_name = '' + if self.active: + self._section_name = self.active[1:-1] + if not self._section_name: + raise self.error_no_section_name() + return self._section_name + + def is_active_line(self): + return bool(self.active) + + def is_continuation(self): + return self.continuation + + def split_key_value(self): + for sep in ['=', ':']: + words = self.active.split(sep, 1) + try: + return self.strip_key_value(*words) + except TypeError: + pass + + return self.active.rstrip(), '@' + + def error_invalid_assignment(self): + return self.PARSE_EXC("No ':' or '=' found in assignment", self.line_no, self.line) + + def error_empty_key(self): + return self.PARSE_EXC('Key cannot be empty', self.line_no, self.line) + + def error_unexpected_continuation(self): + return self.PARSE_EXC('Unexpected continuation line', self.line_no, self.line) + + def error_no_section_start_bracket(self): + return SectionParseError('Invalid section (must start with [)', self.line_no, self.line) + + def error_no_section_end_bracket(self): + return self.PARSE_EXC('Invalid section (must end with ])', self.line_no, self.line) + + def error_no_section_name(self): + return self.PARSE_EXC('Empty section name', self.line_no, self.line) class BaseParser(object): - lineno = 0 - parse_exc = ParseError - def _assignment(self, key, value): - self.assignment(key, value) - return None, [] + def parse(self, data=None): + if data is not None: + return self._parse(data.splitlines()) - def _get_section(self, line): - if not line.endswith(']'): - return self.error_no_section_end_bracket(line) - if len(line) <= 2: - return self.error_no_section_name(line) + def _next_key_value(self, line_parser, key, value): + self.comment(line_parser) - return line[1:-1] + if not line_parser.is_active_line(): + # Blank line, ends multi-line values + if key: + key, value = self.assignment(key, value, line_parser) + return key, value - def _split_key_value(self, line): - colon = line.find(':') - equal = line.find('=') - if colon < 0 and equal < 0: - return line.strip(), '@' + if line_parser.is_continuation(): + # Continuation of previous assignment + if key is None: + raise line_parser.error_unexpected_continuation() - if colon < 0 or (equal >= 0 and equal < colon): - key, value = line[:equal], line[equal + 1:] + value.append(line_parser.active.lstrip()) + return key, value + + if key: + # Flush previous assignment, if any + key, value = self.assignment(key, value, line_parser) + + try: + # Section start + self.new_section(line_parser) + except SectionParseError: + pass else: - key, value = line[:colon], line[colon + 1:] + return key, value - value = value.strip() - if value and value[0] == value[-1] and value.startswith(("\"", "'")): - value = value[1:-1] - return key.strip(), [value] + key, value = line_parser.split_key_value() + if not key: + raise line_parser.error_empty_key() + return key, value - def parse(self, lineiter): + def _parse(self, line_iter): key = None value = [] - for line in lineiter: - self.lineno += 1 - - line = line.rstrip() - lines = line.split(';') - line = lines[0] - if not line: - # Blank line, ends multi-line values - if key: - key, value = self._assignment(key, value) - continue - elif line.startswith((' ', '\t')): - # Continuation of previous assignment - if key is None: - self.error_unexpected_continuation(line) - else: - value.append(line.lstrip()) - continue - - if key: - # Flush previous assignment, if any - key, value = self._assignment(key, value) - - if line.startswith('['): - # Section start - section = self._get_section(line) - if section: - self.new_section(section) - elif line.startswith(('#', ';')): - self.comment(line[1:].lstrip()) - else: - key, value = self._split_key_value(line) - if not key: - return self.error_empty_key(line) + parse_iter = (LineParser(line, line_no) for line_no, line in enumerate(line_iter)) + for line_parser in parse_iter: + key, value = self._next_key_value(line_parser, key, value) if key: # Flush previous assignment, if any - self._assignment(key, value) + self.assignment(key, value, LineParser('EOF', -1)) - def assignment(self, key, value): + def _assignment(self, key, value, line_parser): """Called when a full assignment is parsed.""" raise NotImplementedError() - def new_section(self, section): + def assignment(self, key, value, line_parser): + self._assignment(key, value, line_parser) + return None, [] + + def new_section(self, line_parser): """Called when a new section is started.""" raise NotImplementedError() - def comment(self, comment): + def comment(self, line_parser): """Called when a comment is parsed.""" - pass - - def error_invalid_assignment(self, line): - raise self.parse_exc("No ':' or '=' found in assignment", - self.lineno, line) - - def error_empty_key(self, line): - raise self.parse_exc('Key cannot be empty', self.lineno, line) - - def error_unexpected_continuation(self, line): - raise self.parse_exc('Unexpected continuation line', - self.lineno, line) - - def error_no_section_end_bracket(self, line): - raise self.parse_exc('Invalid section (must end with ])', - self.lineno, line) - - def error_no_section_name(self, line): - raise self.parse_exc('Empty section name', self.lineno, line) + raise NotImplementedError() class ConfigParser(BaseParser): """Parses a single config file, populating 'sections' to look like: - {'DEFAULT': {'key': [value, ...], ...}, - ...} + [ + [ + 'section1', + [ + ['key1', 'value1\nvalue2'], + ['key2', 'value3\nvalue4'], + ], + ], + [ + 'section2', + [ + ['key3', 'value5\nvalue6'], + ], + ], + ] """ - def __init__(self, filename, sections): + def __init__(self, filename, sections=None): super(ConfigParser, self).__init__() self.filename = filename - self.sections = sections + if sections is not None: + self.sections = sections + else: + self.sections = [] + self.section_name = None self.section = None - def parse(self): - with open(self.filename) as f: - return super(ConfigParser, self).parse(f) + def parse(self, data=None): + if not data: + data = self.filename + with open(data) as f: + return self._parse(f) - def find_section(self, sections, section): - return next((i for i, sect in enumerate(sections) if sect == section), -1) + def __iter__(self): + return iter(self.sections) - def new_section(self, section): - self.section = section - index = self.find_section(self.sections, section) - if index == -1: - self.sections.append([section, []]) + def find_section_index(self, section_name): + return next((i for i, (name, value) in enumerate(self) if name == section_name), -1) - def assignment(self, key, value): - if not self.section: - raise self.error_no_section() + def find_section(self, section_name): + return next((value for name, value in self.sections if name == section_name), None) - value = '\n'.join(value) - - def append(sections, section): - entry = [key, value] - index = self.find_section(sections, section) - sections[index][1].append(entry) + def new_section(self, line_parser): + section_name = line_parser.section_name + index = self.find_section_index(section_name) + self.section_name = section_name + if index == -1: + self.section = [section_name, []] + self.sections.append(self.section) + else: + self.section = self.sections[index] - append(self.sections, self.section) + def _assignment(self, key, value, line_parser): + if not self.section_name: + raise line_parser.error_no_section_name() - def parse_exc(self, msg, lineno, line=None): - return ParseError(msg, lineno, line) + value = '\n'.join(value) + entry = [key, value] + self.section[1].append(entry) - def error_no_section(self): - return self.parse_exc('Section must be started before assignment', - self.lineno) + def comment(self, line_parser): + """Called when a comment is parsed.""" + pass diff --git a/yardstick/ssh.py b/yardstick/ssh.py index a024cf64a..bb715e4b4 100644 --- a/yardstick/ssh.py +++ b/yardstick/ssh.py @@ -432,8 +432,9 @@ class SSH(object): class AutoConnectSSH(SSH): + # always wait or we will get OpenStack SSH errors def __init__(self, user, host, port=None, pkey=None, - key_filename=None, password=None, name=None, wait=False): + key_filename=None, password=None, name=None, wait=True): super(AutoConnectSSH, self).__init__(user, host, port, pkey, key_filename, password, name) self._wait = wait |