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"
 
 
 import six
 import logging
+
+from collections import Mapping
 from six.moves import range
 
 
 
         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"""
                     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]