diff --git a/releasenotes/notes/enrich-nodes-json-ironic-port-data-0905da3f7b13d149.yaml b/releasenotes/notes/enrich-nodes-json-ironic-port-data-0905da3f7b13d149.yaml
new file mode 100644
index 000000000..175b173c6
--- /dev/null
+++ b/releasenotes/notes/enrich-nodes-json-ironic-port-data-0905da3f7b13d149.yaml
@@ -0,0 +1,38 @@
+---
+features:
+  - |
+    Adds support to specify additional parameters for Bare Metal ports when
+    registering nodes.
+
+    The  ``mac`` key in nodes_json (instackenv.json) is replaced by the new
+    ``ports`` key. Each port-entry supports the following keys: ``address``,
+    ``physical_network`` and ``local_link_connection``. (The keys in ``ports``
+    mirror a subset off the `Bare Metal service API <https://developer.openstack.org/api-ref/baremetal/#ports-ports>`_
+    .)
+
+    Example specifying port mac address only::
+
+      "ports": [
+        {
+          "address": "52:54:00:87:c8:2e"
+        }
+      ]
+
+    Example specifying additional parameters::
+
+      "ports": [
+        {
+          "address": "52:54:00:87:c8:2f",
+          "physical_network": "network",
+          "local_link_connection": {
+            "switch_info": "switch",
+            "port_id": "gi1/0/11",
+            "switch_id": "a6:18:66:33:cb:49"
+          }
+        }
+      ]
+deprecations:
+  - |
+    The ``mac`` key in nodes_json is replaced by ``ports``. The ``ports`` key
+    expect a list of dictionaries specifying ``address`` (mac address), and
+    optional keys  ``physical_network`` and ``local_link_connection``.
diff --git a/tripleo_common/actions/baremetal.py b/tripleo_common/actions/baremetal.py
index dc73355ec..4ed67b9b1 100644
--- a/tripleo_common/actions/baremetal.py
+++ b/tripleo_common/actions/baremetal.py
@@ -46,7 +46,7 @@ class RegisterOrUpdateNodes(base.TripleOAction):
     def __init__(self, nodes_json, remove=False, kernel_name=None,
                  ramdisk_name=None, instance_boot_option='local'):
         super(RegisterOrUpdateNodes, self).__init__()
-        self.nodes_json = nodes_json
+        self.nodes_json = nodes.convert_nodes_json_mac_to_ports(nodes_json)
         self.remove = remove
         self.instance_boot_option = instance_boot_option
         self.kernel_name = kernel_name
@@ -83,7 +83,7 @@ class ValidateNodes(base.TripleOAction):
 
     def __init__(self, nodes_json):
         super(ValidateNodes, self).__init__()
-        self.nodes_json = nodes_json
+        self.nodes_json = nodes.convert_nodes_json_mac_to_ports(nodes_json)
 
     def run(self, context):
         try:
diff --git a/tripleo_common/actions/parameters.py b/tripleo_common/actions/parameters.py
index 1f3ca3a3e..cc27fbb6f 100644
--- a/tripleo_common/actions/parameters.py
+++ b/tripleo_common/actions/parameters.py
@@ -345,7 +345,7 @@ class GenerateFencingParametersAction(base.TripleOAction):
     def __init__(self, nodes_json, os_auth, delay,
                  ipmi_level, ipmi_cipher, ipmi_lanplus):
         super(GenerateFencingParametersAction, self).__init__()
-        self.nodes_json = nodes_json
+        self.nodes_json = nodes.convert_nodes_json_mac_to_ports(nodes_json)
         self.os_auth = os_auth
         self.delay = delay
         self.ipmi_level = ipmi_level
@@ -362,10 +362,10 @@ class GenerateFencingParametersAction(base.TripleOAction):
         for node in self.nodes_json:
             node_data = {}
             params = {}
-            if "mac" in node:
+            if "ports" in node:
                 # Not all Ironic drivers present a MAC address, so we only
                 # capture it if it's present
-                mac_addr = node["mac"][0]
+                mac_addr = node['ports'][0]['address']
                 node_data["host_mac"] = mac_addr
 
                 # If the MAC isn't in the hostmap, this node hasn't been
diff --git a/tripleo_common/tests/utils/test_nodes.py b/tripleo_common/tests/utils/test_nodes.py
index 53b2ee872..d2d6bc495 100644
--- a/tripleo_common/tests/utils/test_nodes.py
+++ b/tripleo_common/tests/utils/test_nodes.py
@@ -258,9 +258,9 @@ class NodesTest(base.TestCase):
 
     def _get_node(self):
         return {'cpu': '1', 'memory': '2048', 'disk': '30', 'arch': 'amd64',
-                'mac': ['aaa'], 'pm_addr': 'foo.bar', 'pm_user': 'test',
-                'pm_password': 'random', 'pm_type': 'ipmi', 'name': 'node1',
-                'capabilities': 'num_nics:6'}
+                'ports': [{'address': 'aaa'}], 'pm_addr': 'foo.bar',
+                'pm_user': 'test', 'pm_password': 'random', 'pm_type': 'ipmi',
+                'name': 'node1', 'capabilities': 'num_nics:6'}
 
     def test_register_all_nodes_ironic_no_hw_stats(self):
         node_list = [self._get_node()]
@@ -287,7 +287,8 @@ class NodesTest(base.TestCase):
                              resource_class='baremetal',
                              properties=node_properties)
         port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid,
-                              address='aaa', physical_network='ctlplane')
+                              address='aaa', physical_network='ctlplane',
+                              local_link_connection=None)
         ironic.node.create.assert_has_calls([pxe_node, mock.ANY])
         ironic.port.create.assert_has_calls([port_call])
 
@@ -311,7 +312,8 @@ class NodesTest(base.TestCase):
                              resource_class='baremetal',
                              properties=node_properties)
         port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid,
-                              address='aaa', physical_network='ctlplane')
+                              address='aaa', physical_network='ctlplane',
+                              local_link_connection=None)
         ironic.node.create.assert_has_calls([pxe_node, mock.ANY])
         ironic.port.create.assert_has_calls([port_call])
 
@@ -341,7 +343,8 @@ class NodesTest(base.TestCase):
                              resource_class='baremetal',
                              properties=node_properties)
         port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid,
-                              address='aaa', physical_network='ctlplane')
+                              address='aaa', physical_network='ctlplane',
+                              local_link_connection=None)
         ironic.node.create.assert_has_calls([pxe_node, mock.ANY])
         ironic.port.create.assert_has_calls([port_call])
 
@@ -365,7 +368,8 @@ class NodesTest(base.TestCase):
                              resource_class='baremetal',
                              uuid="abcdef")
         port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid,
-                              address='aaa', physical_network='ctlplane')
+                              address='aaa', physical_network='ctlplane',
+                              local_link_connection=None)
         ironic.node.create.assert_has_calls([pxe_node, mock.ANY])
         ironic.port.create.assert_has_calls([port_call])
 
@@ -390,7 +394,8 @@ class NodesTest(base.TestCase):
                              resource_class='baremetal',
                              properties=node_properties)
         port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid,
-                              address='aaa', physical_network='ctlplane')
+                              address='aaa', physical_network='ctlplane',
+                              local_link_connection=None)
         ironic.node.create.assert_has_calls([pxe_node, mock.ANY])
         ironic.port.create.assert_has_calls([port_call])
 
@@ -425,7 +430,8 @@ class NodesTest(base.TestCase):
                              resource_class='baremetal',
                              **interfaces)
         port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid,
-                              address='aaa', physical_network='ctlplane')
+                              address='aaa', local_link_connection=None,
+                              physical_network='ctlplane')
         ironic.node.create.assert_has_calls([pxe_node, mock.ANY])
         ironic.port.create.assert_has_calls([port_call])
 
@@ -589,7 +595,7 @@ class NodesTest(base.TestCase):
 
     def test_register_node_update(self):
         node = self._get_node()
-        node['mac'][0] = node['mac'][0].upper()
+        node['ports'][0]['address'] = node['ports'][0]['address'].upper()
         ironic = mock.MagicMock()
         node_map = {'mac': {'aaa': 1}}
 
@@ -781,6 +787,38 @@ class NodesTest(base.TestCase):
                          'redfish_username': 'test',
                          'redfish_system_id': '/redfish/v1/Systems/1'})
 
+    def test_register_ironic_node_with_physical_network(self):
+        node = self._get_node()
+        node['ports'] = [{'physical_network': 'subnet1', 'address': 'aaa'}]
+        ironic = mock.MagicMock()
+        nodes.register_ironic_node(node, client=ironic)
+        port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid,
+                              address='aaa', physical_network='subnet1',
+                              local_link_connection=None)
+        ironic.port.create.assert_has_calls([port_call])
+
+    def test_register_ironic_node_with_local_link_connection(self):
+        node = self._get_node()
+        node['ports'] = [
+            {
+                'local_link_connection': {
+                    "switch_info": "switch",
+                    "port_id": "port1",
+                    "switch_id": "bbb"
+                },
+                'physical_network': 'subnet1',
+                'address': 'aaa'
+            }
+        ]
+        ironic = mock.MagicMock()
+        nodes.register_ironic_node(node, client=ironic)
+        port_call = mock.call(node_uuid=ironic.node.create.return_value.uuid,
+                              address='aaa', physical_network='subnet1',
+                              local_link_connection={"switch_info": "switch",
+                                                     "port_id": "port1",
+                                                     "switch_id": "bbb"})
+        ironic.port.create.assert_has_calls([port_call])
+
     def test_clean_up_extra_nodes_ironic(self):
         node = collections.namedtuple('node', ['uuid'])
         client = mock.MagicMock()
@@ -859,8 +897,10 @@ VALID_NODE_JSON = [
      'pm_password': 'p@$$w0rd',
      'pm_port': 1234,
      'ipmi_priv_level': 'USER',
-     'mac': ['aa:bb:cc:dd:ee:ff',
-             '11:22:33:44:55:66'],
+     'ports': [
+         {'address': 'aa:bb:cc:dd:ee:ff'},
+         {'address': '11:22:33:44:55:66'}
+     ],
      'name': 'foobar1',
      'capabilities': {'foo': 'bar'},
      'kernel_id': 'kernel1',
@@ -871,8 +911,10 @@ VALID_NODE_JSON = [
      'pm_password': 'p@$$w0rd',
      'pm_port': 1234,
      'ipmi_priv_level': 'USER',
-     'mac': ['dd:ee:ff:aa:bb:cc',
-             '44:55:66:11:22:33'],
+     'ports': [
+         {'address': 'dd:ee:ff:aa:bb:cc'},
+         {'address': '44:55:66:11:22:33'}
+     ],
      'name': 'foobar2',
      'capabilities': {'foo': 'bar'},
      'kernel_id': 'kernel1',
@@ -881,7 +923,9 @@ VALID_NODE_JSON = [
      'pm_addr': '1.2.3.4',
      'pm_user': 'root',
      'pm_password': 'p@$$w0rd',
-     'mac': ['22:22:22:22:22:22'],
+     'ports': [
+         {'address': '22:22:22:22:22:22'}
+     ],
      'capabilities': 'foo:bar,foo1:bar1',
      'cpu': 2,
      'memory': 1024,
@@ -932,7 +976,9 @@ class TestValidateNodes(base.TestCase):
              'pm_addr': '1.1.1.1',
              'pm_user': 'root',
              'pm_password': 'p@$$w0rd',
-             'mac': ['42']},
+             'ports': [
+                 {'address': '42'}]
+             },
         ]
         self.assertRaisesRegex(exception.InvalidNode,
                                'MAC address 42 is invalid',
@@ -944,12 +990,16 @@ class TestValidateNodes(base.TestCase):
              'pm_addr': '1.1.1.1',
              'pm_user': 'root',
              'pm_password': 'p@$$w0rd',
-             'mac': ['11:22:33:44:55:66']},
+             'ports': [
+                 {'address': '11:22:33:44:55:66'}
+             ]},
             {'pm_type': 'ipmi',
              'pm_addr': '1.2.1.1',
              'pm_user': 'user',
              'pm_password': 'p@$$w0rd',
-             'mac': ['11:22:33:44:55:66']},
+             'ports': [
+                 {'address': '11:22:33:44:55:66'}
+             ]},
         ]
         self.assertRaisesRegex(exception.InvalidNode,
                                'MAC 11:22:33:44:55:66 is not unique',
diff --git a/tripleo_common/utils/nodes.py b/tripleo_common/utils/nodes.py
index 76d4d056c..4f08eb2d7 100644
--- a/tripleo_common/utils/nodes.py
+++ b/tripleo_common/utils/nodes.py
@@ -34,6 +34,20 @@ _KNOWN_INTERFACE_FIELDS = [
 CTLPLANE_NETWORK = 'ctlplane'
 
 
+def convert_nodes_json_mac_to_ports(nodes_json):
+    for node in nodes_json:
+        if node.get('mac'):
+            LOG.warning('Key mac is deprecated, please use ports.')
+            for address in node['mac']:
+                try:
+                    node['ports'].append({'address': address})
+                except KeyError:
+                    node['ports'] = [{'address': address}]
+            del node['mac']
+
+    return nodes_json
+
+
 class DriverInfo(object):
     """Class encapsulating field conversion logic."""
     DEFAULTS = {}
@@ -232,9 +246,9 @@ class SshDriverInfo(DriverInfo):
 
     def validate(self, node):
         super(SshDriverInfo, self).validate(node)
-        if not node.get('mac'):
+        if not node.get('ports')[0]['address']:
             raise exception.InvalidNode(
-                'Nodes with SSH drivers require at least one MAC')
+                'Nodes with SSH drivers require at least one PORT')
 
 
 class iBootDriverInfo(PrefixedDriverInfo):
@@ -356,9 +370,14 @@ def register_ironic_node(node, client):
     LOG.debug('Registering node %s with ironic.', node_id)
     ironic_node = client.node.create(**create_map)
 
-    for mac in node.get("mac", []):
-        client.port.create(address=mac, physical_network=CTLPLANE_NETWORK,
-                           node_uuid=ironic_node.uuid)
+    for port in node.get('ports', []):
+        LOG.debug('Creating Bare Metal port for node: %s, with properties: %s.'
+                  % (ironic_node.uuid, port))
+        client.port.create(
+            address=port.get('address'),
+            physical_network=port.get('physical_network', 'ctlplane'),
+            local_link_connection=port.get('local_link_connection'),
+            node_uuid=ironic_node.uuid)
 
     validation = client.node.validate(ironic_node.uuid)
     if not validation.power['result']:
@@ -388,9 +407,9 @@ def _populate_node_mapping(client):
 
 def _get_node_id(node, handler, node_map):
     candidates = set()
-    for mac in node.get('mac', []):
+    for port in node.get('ports', []):
         try:
-            candidates.add(node_map['mac'][mac.lower()])
+            candidates.add(node_map['mac'][port['address'].lower()])
         except KeyError:
             pass
 
@@ -527,15 +546,16 @@ def validate_nodes(nodes_list):
         except exception.InvalidNode as exc:
             failures.append((index, exc))
 
-        for mac in node.get('mac', ()):
-            if not netutils.is_valid_mac(mac):
-                failures.append((index, 'MAC address %s is invalid' % mac))
+        for port in node.get('ports', ()):
+            if not netutils.is_valid_mac(port['address']):
+                failures.append((index, 'MAC address %s is invalid' %
+                                 port['address']))
 
-            if mac in macs:
+            if port['address'] in macs:
                 failures.append(
-                    (index, 'MAC %s is not unique' % mac))
+                    (index, 'MAC %s is not unique' % port['address']))
             else:
-                macs.add(mac)
+                macs.add(port['address'])
 
         unique_id = handler.unique_id_from_fields(node)
         if unique_id:
@@ -569,7 +589,7 @@ def validate_nodes(nodes_list):
         for field in node:
             converted = handler.convert_key(field)
             if (converted is None and field not in _NON_DRIVER_FIELDS and
-                    field not in ('mac', 'pm_type')):
+                    field not in ('ports', 'pm_type')):
                 failures.append((index, 'Unknown field %s' % field))
 
     if failures: