Enrich integration with ironic networking features
The mac key in nodes_json is deprecated, replaced with "ports" key. New ports key is list of dicts holding a richer data set matching the properties of ports in the Bare Metal service api. In addition to mac address the physical_network and local_link_connection can be defined for Bare Metal ports when registering nodes. * address: (mandatory) The physical address (mac address) of the port. * physical_network: (otional) Defaults to: ctlplane * local_link_connection: (optional) This data enables the possibility for automatic configuration of switches via neutron plugins. e.g ML2 vendor plugins. Defaults to: None Implements: enrich-ironic-networking-integration Change-Id: I74d4178dbb0cfe8c934ce15e3e7c9bb1c469de10
This commit is contained in:
parent
7f51d0e1fe
commit
4603ef678f
@ -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``.
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user