From ec6a90d449f8b1ab2b17083188ec65f75ab7818b Mon Sep 17 00:00:00 2001 From: Ross Brattain Date: Tue, 17 Oct 2017 14:30:37 -0700 Subject: [PATCH] heat: allow overriding Heat/Neutron private IP for DPDK tests For some L2/L3 DPDK testcases we need to use a custom IP address space different from what Heat provides. These testcases require port_security_enabled = False so Neutron should allow for unrestricted L2 traffic. This will work because we bind the ports to DPDK and thus don't need DHCP. vnf_0: floating_ip: true placement: "pgrp1" network_ports: mgmt: - mgmt uplink_0: - xe0: local_ip: 10.44.0.20 netmask: 255.255.255.0 downlink_0: - xe1: local_ip: 10.44.0.30 netmask: 255.255.255.0 Also fixup flake8 errors in unittests Change-Id: Id29dfffa692f16fb1f526d208db43e476e2f7830 Signed-off-by: Ross Brattain --- tests/unit/benchmark/contexts/test_heat.py | 8 +- tests/unit/benchmark/contexts/test_model.py | 172 ++++++++++++++++++++++++++++ yardstick/benchmark/contexts/heat.py | 1 + yardstick/benchmark/contexts/model.py | 32 +++++- 4 files changed, 208 insertions(+), 5 deletions(-) diff --git a/tests/unit/benchmark/contexts/test_heat.py b/tests/unit/benchmark/contexts/test_heat.py index 223d64060..f2e725df2 100644 --- a/tests/unit/benchmark/contexts/test_heat.py +++ b/tests/unit/benchmark/contexts/test_heat.py @@ -119,8 +119,12 @@ class HeatContextTestCase(unittest.TestCase): "2f2e4997-0a8e-4eb7-9fa4-f3f8fbbc393b") mock_template.add_security_group.assert_called_with("foo-secgroup") # mock_template.add_network.assert_called_with("bar-fool-network", 'physnet1', None) - mock_template.add_router.assert_called_with("bar-fool-network-router", netattrs["external_network"], "bar-fool-network-subnet") - mock_template.add_router_interface.assert_called_with("bar-fool-network-router-if0", "bar-fool-network-router", "bar-fool-network-subnet") + mock_template.add_router.assert_called_with("bar-fool-network-router", + netattrs["external_network"], + "bar-fool-network-subnet") + mock_template.add_router_interface.assert_called_with("bar-fool-network-router-if0", + "bar-fool-network-router", + "bar-fool-network-subnet") @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate') def test_attrs_get(self, mock_template): diff --git a/tests/unit/benchmark/contexts/test_model.py b/tests/unit/benchmark/contexts/test_model.py index 48ee01cf0..53b035b82 100644 --- a/tests/unit/benchmark/contexts/test_model.py +++ b/tests/unit/benchmark/contexts/test_model.py @@ -282,6 +282,178 @@ class ServerTestCase(unittest.TestCase): scheduler_hints='hints', availability_zone='zone') + def test_override_ip(self): + network_ports = { + 'mgmt': ['mgmt'], + 'uplink_0': [ + {'xe0': {'local_ip': '10.44.0.20', 'netmask': '255.255.255.0'}}, + ], + 'downlink_0': [ + {'xe1': {'local_ip': '10.44.0.30', 'netmask': '255.255.255.0'}}, + ], + } + attrs = { + 'image': 'some-image', 'flavor': 'some-flavor', + } + test_server = model.Server('foo', self.mock_context, attrs) + test_server.interfaces = { + "xe0": { + "local_ip": "1.2.3.4", + "netmask": "255.255.255.0", + }, + "xe1": { + "local_ip": "1.2.3.5", + "netmask": "255.255.255.0" + } + } + test_server.network_ports = network_ports + + test_server.override_ip("uplink_0", {"port": "xe0"}) + self.assertEqual(test_server.interfaces["xe0"], network_ports["uplink_0"][0]["xe0"]) + + def test_override_ip_multiple(self): + network_ports = { + 'mgmt': ['mgmt'], + 'uplink_0': [ + {'xe0': {'local_ip': '10.44.0.20', 'netmask': '255.255.255.0'}}, + {'xe0': {'local_ip': '10.44.0.21', 'netmask': '255.255.255.0'}}, + ], + 'downlink_0': [ + {'xe1': {'local_ip': '10.44.0.30', 'netmask': '255.255.255.0'}}, + ], + } + attrs = { + 'image': 'some-image', 'flavor': 'some-flavor', + } + test_server = model.Server('foo', self.mock_context, attrs) + test_server.interfaces = { + "xe0": { + "local_ip": "1.2.3.4", + "netmask": "255.255.255.0", + }, + "xe1": { + "local_ip": "1.2.3.5", + "netmask": "255.255.255.0" + } + } + test_server.network_ports = network_ports + test_server.override_ip("uplink_0", {"port": "xe0"}) + self.assertEqual(test_server.interfaces["xe0"], network_ports["uplink_0"][0]["xe0"]) + + def test_override_ip_mixed(self): + network_ports = { + 'mgmt': ['mgmt'], + 'uplink_0': [ + 'xe0', + {'xe0': {'local_ip': '10.44.0.21', 'netmask': '255.255.255.0'}}, + ], + 'downlink_0': [ + {'xe1': {'local_ip': '10.44.0.30', 'netmask': '255.255.255.0'}}, + ], + } + attrs = { + 'image': 'some-image', 'flavor': 'some-flavor', + } + test_server = model.Server('foo', self.mock_context, attrs) + test_server.interfaces = { + "xe0": { + "local_ip": "1.2.3.4", + "netmask": "255.255.255.0", + }, + "xe1": { + "local_ip": "1.2.3.5", + "netmask": "255.255.255.0" + } + } + test_server.network_ports = network_ports + test_server.override_ip("uplink_0", {"port": "xe0"}) + self.assertEqual(test_server.interfaces["xe0"], network_ports["uplink_0"][1]["xe0"]) + + @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate') + def test__add_instance_with_ip_override_invalid_syntax(self, mock_template): + network_ports = { + 'mgmt': ['mgmt'], + 'uplink_0': 'xe0', + 'downlink_0': [ + {'xe1': {'local_ip': '10.44.0.30', 'netmask': '255.255.255.0'}}, + ], + } + attrs = { + 'image': 'some-image', 'flavor': 'some-flavor', + } + test_server = model.Server('foo', self.mock_context, attrs) + test_server.network_ports = network_ports + context = type("Context", (object,), {}) + # can't use Mock because Mock.name is reserved + context.name = "context" + networks = [model.Network(n, context, {}) for n in network_ports] + + with self.assertRaises(SyntaxError): + test_server._add_instance(mock_template, 'some-server', + networks, 'hints') + + @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate') + def test__add_instance_with_ip_override(self, mock_template): + network_ports = { + 'mgmt': ['mgmt'], + 'uplink_0': [ + {'xe0': {'local_ip': '10.44.0.20', 'netmask': '255.255.255.0'}}, + ], + 'downlink_0': [ + {'xe1': {'local_ip': '10.44.0.30', 'netmask': '255.255.255.0'}}, + ], + } + attrs = { + 'image': 'some-image', 'flavor': 'some-flavor', + } + test_server = model.Server('foo', self.mock_context, attrs) + test_server.network_ports = network_ports + context = type("Context", (object,), {}) + # can't use Mock because Mock.name is reserved + context.name = "context" + networks = [model.Network(n, context, {}) for n in network_ports] + + test_server._add_instance(mock_template, 'some-server', + networks, 'hints') + self.assertEqual(test_server.ports, { + 'downlink_0': [{'port': 'xe1', 'stack_name': 'some-server-xe1-port'}], + 'mgmt': [{'port': 'mgmt', 'stack_name': 'some-server-mgmt-port'}], + 'uplink_0': [{'port': 'xe0', 'stack_name': 'some-server-xe0-port'}] + }) + + @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate') + def test__add_instance_with_multiple_ip_override(self, mock_template): + network_ports = { + 'mgmt': ['mgmt'], + 'uplink_0': [ + {'xe0': {'local_ip': '10.44.0.20', 'netmask': '255.255.255.0'}}, + {'xe0': {'local_ip': '10.44.0.21', 'netmask': '255.255.255.0'}}, + ], + 'downlink_0': [ + {'xe1': {'local_ip': '10.44.0.30', 'netmask': '255.255.255.0'}}, + ], + } + attrs = { + 'image': 'some-image', 'flavor': 'some-flavor', + } + test_server = model.Server('foo', self.mock_context, attrs) + test_server.network_ports = network_ports + context = type("Context", (object,), {}) + # can't use Mock because Mock.name is reserved + context.name = "context" + networks = [model.Network(n, context, {}) for n in network_ports] + + test_server._add_instance(mock_template, 'some-server', + networks, 'hints') + self.assertEqual(test_server.ports, { + 'downlink_0': [{'port': 'xe1', 'stack_name': 'some-server-xe1-port'}], + 'mgmt': [{'port': 'mgmt', 'stack_name': 'some-server-mgmt-port'}], + 'uplink_0': [{'port': 'xe0', 'stack_name': 'some-server-xe0-port'}, + # this is not an error, we can produce this, it is left to Heat + # to detect duplicate ports and error + {'port': 'xe0', 'stack_name': 'some-server-xe0-port'}] + }) + @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate') def test__add_instance_with_user_data(self, mock_template): user_data = "USER_DATA" diff --git a/yardstick/benchmark/contexts/heat.py b/yardstick/benchmark/contexts/heat.py index ff3e5f801..4ba543b9e 100644 --- a/yardstick/benchmark/contexts/heat.py +++ b/yardstick/benchmark/contexts/heat.py @@ -348,6 +348,7 @@ class HeatContext(Context): port['port'], port['stack_name'], self.stack.outputs) + server.override_ip(network_name, port) def make_interface_dict(self, network_name, port, stack_name, outputs): private_ip = outputs[stack_name] diff --git a/yardstick/benchmark/contexts/model.py b/yardstick/benchmark/contexts/model.py index d3c03e100..ae56066ee 100644 --- a/yardstick/benchmark/contexts/model.py +++ b/yardstick/benchmark/contexts/model.py @@ -14,6 +14,8 @@ from __future__ import absolute_import import six import logging + +from collections import Mapping from six.moves import range @@ -241,6 +243,26 @@ class Server(Object): # pragma: no cover Server.list.append(self) + def override_ip(self, network_name, port): + def find_port_overrides(): + for p in ports: + # p can be string or dict + # we can't just use p[port['port'] in case p is a string + # and port['port'] is an int? + if isinstance(p, Mapping): + g = p.get(port['port']) + # filter out empty dicts + if g: + yield g + + ports = self.network_ports.get(network_name, []) + intf = self.interfaces[port['port']] + for override in find_port_overrides(): + intf['local_ip'] = override.get('local_ip', intf['local_ip']) + intf['netmask'] = override.get('netmask', intf['netmask']) + # only use the first value + break + @property def image(self): """returns a server's image name""" @@ -270,9 +292,13 @@ class Server(Object): # pragma: no cover continue else: if isinstance(ports, six.string_types): - if ports.startswith('-'): - LOG.warning("possible YAML error, port name starts with - '%s", ports) - ports = [ports] + # because strings are iterable we have to check specifically + raise SyntaxError("network_port must be a list '{}'".format(ports)) + # convert port subdicts into their just port name + # port subdicts are used to override Heat IP address, + # but we just need the port name + # we allow duplicates here and let Heat raise the error + ports = [next(iter(p)) if isinstance(p, dict) else p for p in ports] # otherwise add a port for every network with port name as network name else: ports = [network.name] -- 2.16.6