Merge "Port physical network CIDR map hook"
This commit is contained in:
commit
48391d62a4
@ -249,6 +249,14 @@ Here are some plugins that can be additionally enabled:
|
||||
Processes LLDP data returned from inspection and parses TLVs from the
|
||||
Basic Management (802.1AB), 802.1Q, and 802.3 sets and stores the
|
||||
processed data back to the Ironic inspector data in Swift.
|
||||
``physnet_cidr_map``
|
||||
Configures the ``physical_network`` property of the nodes Ironic port when
|
||||
the IP address is in a configured CIDR mapping. CIDR to physical network
|
||||
mappings is set in configuration using the ``[port_physnet]/cidr_map``
|
||||
option, for example::
|
||||
|
||||
[port_physnet]
|
||||
cidr_map = 10.10.10.0/24:physnet_a, 2001:db8::/64:physnet_b
|
||||
|
||||
Refer to :ref:`contributing_link` for information on how to write your
|
||||
own plugin.
|
||||
|
@ -20,6 +20,7 @@ from ironic_inspector.conf import dnsmasq_pxe_filter
|
||||
from ironic_inspector.conf import iptables
|
||||
from ironic_inspector.conf import ironic
|
||||
from ironic_inspector.conf import pci_devices
|
||||
from ironic_inspector.conf import port_physnet
|
||||
from ironic_inspector.conf import processing
|
||||
from ironic_inspector.conf import pxe_filter
|
||||
from ironic_inspector.conf import service_catalog
|
||||
@ -37,6 +38,7 @@ dnsmasq_pxe_filter.register_opts(CONF)
|
||||
iptables.register_opts(CONF)
|
||||
ironic.register_opts(CONF)
|
||||
pci_devices.register_opts(CONF)
|
||||
port_physnet.register_opts(CONF)
|
||||
processing.register_opts(CONF)
|
||||
pxe_filter.register_opts(CONF)
|
||||
service_catalog.register_opts(CONF)
|
||||
|
@ -68,6 +68,7 @@ def list_opts():
|
||||
('swift', ironic_inspector.conf.swift.list_opts()),
|
||||
('ironic', ironic_inspector.conf.ironic.list_opts()),
|
||||
('iptables', ironic_inspector.conf.iptables.list_opts()),
|
||||
('port_physnet', ironic_inspector.conf.port_physnet.list_opts()),
|
||||
('processing', ironic_inspector.conf.processing.list_opts()),
|
||||
('pci_devices', ironic_inspector.conf.pci_devices.list_opts()),
|
||||
('pxe_filter', ironic_inspector.conf.pxe_filter.list_opts()),
|
||||
|
36
ironic_inspector/conf/port_physnet.py
Normal file
36
ironic_inspector/conf/port_physnet.py
Normal file
@ -0,0 +1,36 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from ironic_inspector.common.i18n import _
|
||||
|
||||
|
||||
_OPTS = [
|
||||
cfg.ListOpt('cidr_map',
|
||||
default=[],
|
||||
sample_default=('10.10.10.0/24:physnet_a,'
|
||||
'2001:db8::/64:physnet_b'),
|
||||
help=_('Mapping of IP subnet CIDR to physical network. When '
|
||||
'the physnet_cidr_map processing hook is enabled the '
|
||||
'physical_network property of baremetal ports is '
|
||||
'populated based on this mapping.')),
|
||||
]
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_opts(_OPTS, group='port_physnet')
|
||||
|
||||
|
||||
def list_opts():
|
||||
return _OPTS
|
65
ironic_inspector/plugins/physnet_cidr_map.py
Normal file
65
ironic_inspector/plugins/physnet_cidr_map.py
Normal file
@ -0,0 +1,65 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Port Physical Network Hook"""
|
||||
|
||||
import ipaddress
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from ironic_inspector.plugins import base_physnet
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class PhysnetCidrMapHook(base_physnet.BasePhysnetHook):
|
||||
"""Process port physical network
|
||||
|
||||
Set the physical_network field of baremetal ports based on a cidr to
|
||||
physical network mapping in the configuration.
|
||||
"""
|
||||
|
||||
def get_physnet(self, port, iface_name, introspection_data):
|
||||
"""Return a physical network to apply to a port.
|
||||
|
||||
:param port: The ironic port to patch.
|
||||
:param iface_name: Name of the interface.
|
||||
:param introspection_data: Introspection data.
|
||||
:returns: The physical network to set, or None.
|
||||
"""
|
||||
|
||||
def get_iface_ips(iface):
|
||||
ips = []
|
||||
for addr_version in ['ipv4_address', 'ipv6_address']:
|
||||
try:
|
||||
ips.append(ipaddress.ip_address(iface.get(addr_version)))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return ips
|
||||
|
||||
# Convert list config to a dict with ip_networks as keys
|
||||
cidr_map = {
|
||||
ipaddress.ip_network(x.rsplit(':', 1)[0]): x.rsplit(':', 1)[1]
|
||||
for x in CONF.port_physnet.cidr_map}
|
||||
|
||||
iface = [i for i in introspection_data['inventory']['interfaces']
|
||||
if i['name'] == iface_name][0]
|
||||
ips = get_iface_ips(iface)
|
||||
|
||||
for ip in ips:
|
||||
try:
|
||||
return [cidr_map[cidr] for cidr in cidr_map if ip in cidr][0]
|
||||
except IndexError:
|
||||
# No mapping found for any of the ip addresses
|
||||
return None
|
155
ironic_inspector/test/unit/test_plugins_physnet_cidr_map.py
Normal file
155
ironic_inspector/test/unit/test_plugins_physnet_cidr_map.py
Normal file
@ -0,0 +1,155 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import mock
|
||||
from openstack import exceptions
|
||||
from oslo_config import cfg
|
||||
|
||||
from ironic_inspector import node_cache
|
||||
from ironic_inspector.plugins import physnet_cidr_map
|
||||
from ironic_inspector.test import base as test_base
|
||||
from ironic_inspector import utils
|
||||
|
||||
|
||||
class TestPhysnetCidrMapHook(test_base.NodeTest):
|
||||
hook = physnet_cidr_map.PhysnetCidrMapHook()
|
||||
|
||||
def setUp(self):
|
||||
super(TestPhysnetCidrMapHook, self).setUp()
|
||||
self.data = {
|
||||
'inventory': {
|
||||
'interfaces': [{
|
||||
'name': 'em1',
|
||||
'mac_address': '11:11:11:11:11:11',
|
||||
'ipv4_address': '1.1.1.1',
|
||||
}],
|
||||
'cpu': 1,
|
||||
'disks': 1,
|
||||
'memory': 1
|
||||
},
|
||||
'all_interfaces': {
|
||||
'em1': {},
|
||||
}
|
||||
}
|
||||
|
||||
ports = [mock.Mock(spec=['address', 'uuid', 'physical_network'],
|
||||
address=a) for a in ('11:11:11:11:11:11',)]
|
||||
self.node_info = node_cache.NodeInfo(uuid=self.uuid, started_at=0,
|
||||
node=self.node, ports=ports)
|
||||
|
||||
@mock.patch.object(node_cache.NodeInfo, 'patch_port')
|
||||
def test_expected_data(self, mock_patch):
|
||||
cfg.CONF.set_override('cidr_map', '1.1.1.0/24:physnet_a',
|
||||
group='port_physnet')
|
||||
patches = [{'path': '/physical_network',
|
||||
'value': 'physnet_a',
|
||||
'op': 'add'}]
|
||||
self.hook.before_update(self.data, self.node_info)
|
||||
self.assertCalledWithPatch(patches, mock_patch)
|
||||
|
||||
@mock.patch.object(node_cache.NodeInfo, 'patch_port')
|
||||
def test_no_matching_mapping_config(self, mock_patch):
|
||||
cfg.CONF.set_override('cidr_map', '2.2.2.0/24:physnet_b',
|
||||
group='port_physnet')
|
||||
self.hook.before_update(self.data, self.node_info)
|
||||
self.assertFalse(mock_patch.called)
|
||||
|
||||
@mock.patch.object(node_cache.NodeInfo, 'patch_port')
|
||||
def test_expected_data_ipv6_lowercase(self, mock_patch):
|
||||
self.data['inventory']['interfaces'][0].pop('ipv4_address')
|
||||
self.data['inventory']['interfaces'][0]['ipv6_address'] = '2001:db8::1'
|
||||
cfg.CONF.set_override('cidr_map', '2001:db8::/64:physnet_b',
|
||||
group='port_physnet')
|
||||
patches = [{'path': '/physical_network',
|
||||
'value': 'physnet_b',
|
||||
'op': 'add'}]
|
||||
self.hook.before_update(self.data, self.node_info)
|
||||
self.assertCalledWithPatch(patches, mock_patch)
|
||||
|
||||
@mock.patch.object(node_cache.NodeInfo, 'patch_port')
|
||||
def test_expected_data_ipv6_uppercase(self, mock_patch):
|
||||
self.data['inventory']['interfaces'][0].pop('ipv4_address')
|
||||
self.data['inventory']['interfaces'][0]['ipv6_address'] = '2001:db8::1'
|
||||
cfg.CONF.set_override('cidr_map', '2001:DB8::/64:physnet_b',
|
||||
group='port_physnet')
|
||||
patches = [{'path': '/physical_network',
|
||||
'value': 'physnet_b',
|
||||
'op': 'add'}]
|
||||
self.hook.before_update(self.data, self.node_info)
|
||||
self.assertCalledWithPatch(patches, mock_patch)
|
||||
|
||||
@mock.patch.object(node_cache.NodeInfo, 'patch_port')
|
||||
def test_no_mapping_in_config(self, mock_patch):
|
||||
self.hook.before_update(self.data, self.node_info)
|
||||
self.assertFalse(mock_patch.called)
|
||||
|
||||
def test_no_inventory(self):
|
||||
cfg.CONF.set_override('cidr_map', '1.1.1.0/24:physnet_a',
|
||||
group='port_physnet')
|
||||
del self.data['inventory']
|
||||
self.assertRaises(utils.Error, self.hook.before_update,
|
||||
self.data, self.node_info)
|
||||
|
||||
@mock.patch('ironic_inspector.plugins.base_physnet.LOG')
|
||||
@mock.patch.object(node_cache.NodeInfo, 'patch_port')
|
||||
def test_interface_not_in_ironic(self, mock_patch, mock_log):
|
||||
cfg.CONF.set_override('cidr_map', '1.1.1.0/24:physnet_a',
|
||||
group='port_physnet')
|
||||
self.node_info._ports = {}
|
||||
self.hook.before_update(self.data, self.node_info)
|
||||
self.assertTrue(mock_log.debug.called)
|
||||
|
||||
@mock.patch.object(node_cache.NodeInfo, 'patch_port')
|
||||
def test_no_overwrite(self, mock_patch):
|
||||
ports = [mock.Mock(spec=['address', 'uuid', 'physical_network'],
|
||||
address=a, physical_network='foo')
|
||||
for a in ('11:11:11:11:11:11',)]
|
||||
node_info = node_cache.NodeInfo(uuid=self.uuid, started_at=0,
|
||||
node=self.node, ports=ports)
|
||||
cfg.CONF.set_override('overwrite_existing', False, group='processing')
|
||||
cfg.CONF.set_override('cidr_map', '1.1.1.0/24:physnet_a',
|
||||
group='port_physnet')
|
||||
self.hook.before_update(self.data, node_info)
|
||||
self.assertFalse(mock_patch.called)
|
||||
|
||||
@mock.patch('ironic_inspector.plugins.base_physnet.LOG')
|
||||
@mock.patch.object(node_cache.NodeInfo, 'patch_port')
|
||||
def test_patch_port_exception(self, mock_patch, mock_log):
|
||||
cfg.CONF.set_override('cidr_map', '1.1.1.0/24:physnet_a',
|
||||
group='port_physnet')
|
||||
mock_patch.side_effect = exceptions.BadRequestException('invalid data')
|
||||
self.hook.before_update(self.data, self.node_info)
|
||||
log_msg = "Failed to update port %(uuid)s: %(error)s"
|
||||
mock_log.warning.assert_called_with(log_msg, mock.ANY,
|
||||
node_info=mock.ANY)
|
||||
|
||||
@mock.patch.object(node_cache.NodeInfo, 'patch_port')
|
||||
def test_no_ip_address_on_interface(self, mock_patch):
|
||||
cfg.CONF.set_override('cidr_map', '1.1.1.0/24:physnet_a',
|
||||
group='port_physnet')
|
||||
data = {
|
||||
'inventory': {
|
||||
'interfaces': [{
|
||||
'name': 'em1',
|
||||
'mac_address': '11:11:11:11:11:11',
|
||||
}],
|
||||
'cpu': 1,
|
||||
'disks': 1,
|
||||
'memory': 1
|
||||
},
|
||||
'all_interfaces': {
|
||||
'em1': {},
|
||||
}
|
||||
}
|
||||
self.hook.before_update(data, self.node_info)
|
||||
self.assertFalse(mock_patch.called)
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- Added ``physnet_cidr_map`` processing plugin, the plugin uses the IP
|
||||
address of interfaces returned during inspection and set the port
|
||||
``physical_network`` via lookup from a CIDR to physical network mapping in
|
||||
config option ``[port_physnet]/cidr_map``.
|
@ -43,6 +43,7 @@ ironic_inspector.hooks.processing =
|
||||
local_link_connection = ironic_inspector.plugins.local_link_connection:GenericLocalLinkConnectionHook
|
||||
lldp_basic = ironic_inspector.plugins.lldp_basic:LLDPBasicProcessingHook
|
||||
pci_devices = ironic_inspector.plugins.pci_devices:PciDevicesHook
|
||||
physnet_cidr_map = ironic_inspector.plugins.physnet_cidr_map:PhysnetCidrMapHook
|
||||
ironic_inspector.hooks.node_not_found =
|
||||
example = ironic_inspector.plugins.example:example_not_found_hook
|
||||
enroll = ironic_inspector.plugins.discovery:enroll_node_not_found_hook
|
||||
|
Loading…
Reference in New Issue
Block a user