From ec6a90d449f8b1ab2b17083188ec65f75ab7818b Mon Sep 17 00:00:00 2001
From: Ross Brattain <ross.b.brattain@intel.com>
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 <ross.b.brattain@intel.com>
---
 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