pxe filter - option to always block unknown hosts
It is not always desired to open the DHCP server for any host just because introspection is active. Add an option to ensure that only nodes being introspected are added to the DHCP servers allow list. Also adds ethoib support in the dnsmasq PXE filter. Also fix a typo in ethoib_interfaces option help text. Change-Id: I4cd7f4f0a449dcc23897ec9288cb57ec9bd647d7
This commit is contained in:
parent
275ba3b574
commit
7a067a97a8
@ -26,7 +26,7 @@ _OPTS = [
|
||||
help=_('iptables chain name to use.')),
|
||||
cfg.ListOpt('ethoib_interfaces',
|
||||
default=[],
|
||||
help=_('List of Etherent Over InfiniBand interfaces '
|
||||
help=_('List of Ethernet Over InfiniBand interfaces '
|
||||
'on the Inspector host which are used for physical '
|
||||
'access to the DHCP network. Multiple interfaces would '
|
||||
'be attached to a bond or bridge specified in '
|
||||
|
@ -24,6 +24,15 @@ _OPTS = [
|
||||
cfg.IntOpt('sync_period', default=15, min=0,
|
||||
help=_('Amount of time in seconds, after which repeat periodic '
|
||||
'update of the filter.')),
|
||||
cfg.BoolOpt('deny_unknown_macs', default=False,
|
||||
help=_('By default inspector will open the DHCP server for '
|
||||
'any node when introspection is active. Opening DHCP '
|
||||
'for unknown MAC addresses when introspection is '
|
||||
'active allow for users to add nodes with no ports to '
|
||||
'ironic and have ironic-inspector enroll ports based '
|
||||
'on node introspection results. NOTE: If this option '
|
||||
'is True, nodes must have at least one enrolled port '
|
||||
'prior to introspection.'))
|
||||
]
|
||||
|
||||
|
||||
|
@ -15,6 +15,8 @@
|
||||
|
||||
import contextlib
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
|
||||
from automaton import exceptions as automaton_errors
|
||||
from automaton import machines
|
||||
@ -27,13 +29,17 @@ import stevedore
|
||||
|
||||
from ironic_inspector.common.i18n import _
|
||||
from ironic_inspector.common import ironic as ir_utils
|
||||
from ironic_inspector import node_cache
|
||||
from ironic_inspector.pxe_filter import interface
|
||||
from ironic_inspector import utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
_STEVEDORE_DRIVER_NAMESPACE = 'ironic_inspector.pxe_filter'
|
||||
|
||||
_EMAC_REGEX = 'EMAC=([0-9a-f]{2}(:[0-9a-f]{2}){5}) IMAC=.*'
|
||||
|
||||
|
||||
class InvalidFilterDriverState(RuntimeError):
|
||||
"""The fsm of the filter driver raised an error."""
|
||||
@ -96,6 +102,14 @@ class BaseFilter(interface.FilterDriver):
|
||||
|
||||
def __init__(self):
|
||||
super(BaseFilter, self).__init__()
|
||||
# Configuration check
|
||||
if (CONF.pxe_filter.deny_unknown_macs
|
||||
and CONF.processing.node_not_found_hook):
|
||||
msg = _('Configuration error: [pxe_filter]deny_unknown_macs is'
|
||||
'enabled and [processing]node_not_found_hook is enabled.'
|
||||
'These options cannot both be enabled simultaneously.')
|
||||
raise utils.Error(msg)
|
||||
|
||||
self.lock = semaphore.BoundedSemaphore()
|
||||
self.fsm.initialize(start_state=States.uninitialized)
|
||||
|
||||
@ -235,3 +249,66 @@ def driver():
|
||||
:returns: the singleton PXE filter driver object.
|
||||
"""
|
||||
return _driver_manager().driver
|
||||
|
||||
|
||||
def _ib_mac_to_rmac_mapping(ports):
|
||||
"""Update port InfiniBand MAC address to EthernetOverInfiniBand MAC
|
||||
|
||||
On InfiniBand deployment we need to map between the baremetal host
|
||||
InfiniBand MAC to the EoIB MAC. The EoIB MAC addresses are learned
|
||||
automatically by the EoIB interfaces and those MACs are recorded to the
|
||||
/sys/class/net/<ethoib_interface>/eth/neighs file. The InfiniBand GUID is
|
||||
taken from the ironic port client-id extra attribute. The InfiniBand GUID
|
||||
is the last 8 bytes of the client-id. The file format allows to map the
|
||||
GUID to EoIB MAC. The filter rules based on those MACs get applied during a
|
||||
driver.update() call
|
||||
|
||||
:param ports: list of ironic ports
|
||||
:returns: Nothing.
|
||||
"""
|
||||
ethoib_interfaces = CONF.iptables.ethoib_interfaces
|
||||
for iface in ethoib_interfaces:
|
||||
neighs_file = (os.path.join('/sys/class/net', iface, 'eth/neighs'))
|
||||
try:
|
||||
with open(neighs_file, 'r') as fd:
|
||||
data = fd.read()
|
||||
except IOError:
|
||||
LOG.error('Interface %s is not Ethernet Over InfiniBand; '
|
||||
'Skipping ...', iface)
|
||||
continue
|
||||
for port in ports:
|
||||
client_id = port.extra.get('client-id')
|
||||
if client_id:
|
||||
# Note(moshele): The last 8 bytes in the client-id is
|
||||
# the baremetal node InfiniBand GUID
|
||||
guid = client_id[-23:]
|
||||
p = re.compile(_EMAC_REGEX + guid)
|
||||
match = p.search(data)
|
||||
if match:
|
||||
port.address = match.group(1)
|
||||
|
||||
|
||||
def get_ironic_macs(ironic):
|
||||
ports = [port for port in
|
||||
ir_utils.call_with_retries(ironic.ports, limit=None,
|
||||
fields=['address', 'extra'])]
|
||||
_ib_mac_to_rmac_mapping(ports)
|
||||
return {port.address for port in ports}
|
||||
|
||||
|
||||
def get_inactive_macs(ironic):
|
||||
ports = [port for port in
|
||||
ir_utils.call_with_retries(ironic.ports, limit=None,
|
||||
fields=['address', 'extra'])
|
||||
if port.address not in node_cache.active_macs()]
|
||||
_ib_mac_to_rmac_mapping(ports)
|
||||
return {port.address for port in ports}
|
||||
|
||||
|
||||
def get_active_macs(ironic):
|
||||
ports = [port for port in
|
||||
ir_utils.call_with_retries(ironic.ports, limit=None,
|
||||
fields=['address', 'extra'])
|
||||
if port.address in node_cache.active_macs()]
|
||||
_ib_mac_to_rmac_mapping(ports)
|
||||
return {port.address for port in ports}
|
||||
|
@ -84,11 +84,11 @@ class DnsmasqFilter(pxe_filter.BaseFilter):
|
||||
timestamp_start = timeutils.utcnow()
|
||||
|
||||
# active_macs are the MACs for which introspection is active
|
||||
active_macs = node_cache.active_macs()
|
||||
active_macs = pxe_filter.get_active_macs(ironic)
|
||||
|
||||
# ironic_macs are all the MACs know to ironic (all ironic ports)
|
||||
ironic_macs = set(port.address for port in
|
||||
ir_utils.call_with_retries(ironic.ports, limit=None,
|
||||
fields=['address']))
|
||||
ironic_macs = pxe_filter.get_ironic_macs(ironic)
|
||||
|
||||
denylist, allowlist = _get_deny_allow_lists()
|
||||
# removedlist are the MACs that are in either in allow or denylist,
|
||||
# but not kept in ironic (ironic_macs) any more
|
||||
@ -233,7 +233,8 @@ def _configure_removedlist(macs):
|
||||
|
||||
hostsdir = CONF.dnsmasq_pxe_filter.dhcp_hostsdir
|
||||
|
||||
if _should_enable_unknown_hosts():
|
||||
if (_should_enable_unknown_hosts()
|
||||
and not CONF.pxe_filter.deny_unknown_macs):
|
||||
for mac in macs:
|
||||
if os.stat(os.path.join(hostsdir, mac)).st_size != _MAC_ALLOW_LEN:
|
||||
_add_mac_to_allowlist(mac)
|
||||
@ -253,7 +254,8 @@ def _configure_unknown_hosts():
|
||||
path = os.path.join(CONF.dnsmasq_pxe_filter.dhcp_hostsdir,
|
||||
_UNKNOWN_HOSTS_FILE)
|
||||
|
||||
if _should_enable_unknown_hosts():
|
||||
if (_should_enable_unknown_hosts()
|
||||
and not CONF.pxe_filter.deny_unknown_macs):
|
||||
wildcard_filter = _ALLOW_UNKNOWN_HOSTS
|
||||
log_wildcard_filter = 'allow'
|
||||
else:
|
||||
|
@ -12,23 +12,17 @@
|
||||
# limitations under the License.
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import re
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
from ironic_inspector.common import ironic as ir_utils
|
||||
from ironic_inspector import node_cache
|
||||
from ironic_inspector.pxe_filter import base as pxe_filter
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
_EMAC_REGEX = 'EMAC=([0-9a-f]{2}(:[0-9a-f]{2}){5}) IMAC=.*'
|
||||
|
||||
|
||||
def _should_enable_dhcp():
|
||||
"""Check whether we should enable DHCP at all.
|
||||
@ -46,6 +40,7 @@ class IptablesFilter(pxe_filter.BaseFilter):
|
||||
def __init__(self):
|
||||
super(IptablesFilter, self).__init__()
|
||||
self.denylist_cache = None
|
||||
self.allowlist_cache = None
|
||||
self.enabled = True
|
||||
self.interface = CONF.iptables.dnsmasq_interface
|
||||
self.chain = CONF.iptables.firewall_chain
|
||||
@ -66,6 +61,7 @@ class IptablesFilter(pxe_filter.BaseFilter):
|
||||
def reset(self):
|
||||
self.enabled = True
|
||||
self.denylist_cache = None
|
||||
self.allowlist_cache = None
|
||||
for chain in (self.chain, self.new_chain):
|
||||
try:
|
||||
self._clean_up(chain)
|
||||
@ -114,26 +110,44 @@ class IptablesFilter(pxe_filter.BaseFilter):
|
||||
self._disable_dhcp()
|
||||
return
|
||||
|
||||
to_deny = _get_denylist(ironic)
|
||||
if to_deny == self.denylist_cache:
|
||||
LOG.debug('Not updating iptables - no changes in MAC list %s',
|
||||
to_deny)
|
||||
if CONF.pxe_filter.deny_unknown_macs:
|
||||
to_allow = pxe_filter.get_active_macs(ironic)
|
||||
to_deny = None
|
||||
else:
|
||||
to_deny = pxe_filter.get_inactive_macs(ironic)
|
||||
to_allow = None
|
||||
|
||||
if to_deny == self.denylist_cache and to_allow == self.allowlist_cache:
|
||||
LOG.debug('Not updating iptables - no changes in MAC lists %s %s',
|
||||
to_deny, to_allow)
|
||||
return
|
||||
|
||||
LOG.debug('Adding active MAC\'s %s to the deny list', to_deny)
|
||||
if CONF.pxe_filter.deny_unknown_macs:
|
||||
LOG.debug('Adding MAC\'s %s to the allow list', to_allow)
|
||||
else:
|
||||
LOG.debug('Adding MAC\'s %s to the deny list', to_deny)
|
||||
with self._temporary_chain(self.new_chain, self.chain):
|
||||
# Force update on the next iteration if this attempt fails
|
||||
self.denylist_cache = None
|
||||
# - Add active macs to the deny list, so that nova can boot them
|
||||
for mac in to_deny:
|
||||
self._iptables('-A', self.new_chain, '-m', 'mac',
|
||||
'--mac-source', mac, '-j', 'DROP')
|
||||
# - Add everything else to the allow list
|
||||
self._iptables('-A', self.new_chain, '-j', 'ACCEPT')
|
||||
self.allowlist_cache = None
|
||||
# - Add active macs to allow list, so that they are introspected
|
||||
if CONF.pxe_filter.deny_unknown_macs:
|
||||
for mac in to_allow:
|
||||
self._iptables('-A', self.new_chain, '-m', 'mac',
|
||||
'--mac-source', mac, '-j', 'ACCEPT')
|
||||
# - Add inactive macs to the deny list, so that nova can boot them
|
||||
if not CONF.pxe_filter.deny_unknown_macs:
|
||||
for mac in to_deny:
|
||||
self._iptables('-A', self.new_chain, '-m', 'mac',
|
||||
'--mac-source', mac, '-j', 'DROP')
|
||||
# - Add everything else to the unknown list
|
||||
policy = 'DROP' if CONF.pxe_filter.deny_unknown_macs else 'ACCEPT'
|
||||
self._iptables('-A', self.new_chain, '-j', policy)
|
||||
|
||||
# Cache result of successful iptables update
|
||||
self.enabled = True
|
||||
self.denylist_cache = to_deny
|
||||
self.allowlist_cache = to_allow
|
||||
LOG.debug('The iptables filter was synchronized')
|
||||
|
||||
@contextlib.contextmanager
|
||||
@ -190,50 +204,3 @@ class IptablesFilter(pxe_filter.BaseFilter):
|
||||
# deny everything
|
||||
self._iptables('-A', self.new_chain, '-j', 'REJECT')
|
||||
self.enabled = False
|
||||
|
||||
|
||||
def _ib_mac_to_rmac_mapping(ports):
|
||||
"""Update port InfiniBand MAC address to EthernetOverInfiniBand MAC
|
||||
|
||||
On InfiniBand deployment we need to map between the baremetal host
|
||||
InfiniBand MAC to the EoIB MAC. The EoIB MAC addresses are learned
|
||||
automatically by the EoIB interfaces and those MACs are recorded to the
|
||||
/sys/class/net/<ethoib_interface>/eth/neighs file. The InfiniBand GUID is
|
||||
taken from the ironic port client-id extra attribute. The InfiniBand GUID
|
||||
is the last 8 bytes of the client-id. The file format allows to map the
|
||||
GUID to EoIB MAC. The filter rules based on those MACs get applied during a
|
||||
driver.update() call
|
||||
|
||||
:param ports: list of ironic ports
|
||||
:returns: Nothing.
|
||||
"""
|
||||
ethoib_interfaces = CONF.iptables.ethoib_interfaces
|
||||
for interface in ethoib_interfaces:
|
||||
neighs_file = (
|
||||
os.path.join('/sys/class/net', interface, 'eth/neighs'))
|
||||
try:
|
||||
with open(neighs_file, 'r') as fd:
|
||||
data = fd.read()
|
||||
except IOError:
|
||||
LOG.error('Interface %s is not Ethernet Over InfiniBand; '
|
||||
'Skipping ...', interface)
|
||||
continue
|
||||
for port in ports:
|
||||
client_id = port.extra.get('client-id')
|
||||
if client_id:
|
||||
# Note(moshele): The last 8 bytes in the client-id is
|
||||
# the baremetal node InfiniBand GUID
|
||||
guid = client_id[-23:]
|
||||
p = re.compile(_EMAC_REGEX + guid)
|
||||
match = p.search(data)
|
||||
if match:
|
||||
port.address = match.group(1)
|
||||
|
||||
|
||||
def _get_denylist(ironic):
|
||||
ports = [port for port in
|
||||
ir_utils.call_with_retries(ironic.ports, limit=None,
|
||||
fields=['address', 'extra'])
|
||||
if port.address not in node_cache.active_macs()]
|
||||
_ib_mac_to_rmac_mapping(ports)
|
||||
return [port.address for port in ports]
|
||||
|
@ -26,8 +26,10 @@ from oslo_config import cfg
|
||||
|
||||
from ironic_inspector.common import ironic as ir_utils
|
||||
from ironic_inspector import node_cache
|
||||
from ironic_inspector.pxe_filter import base as pxe_filter
|
||||
from ironic_inspector.pxe_filter import dnsmasq
|
||||
from ironic_inspector.test import base as test_base
|
||||
from ironic_inspector import utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
@ -38,6 +40,15 @@ class DnsmasqTestBase(test_base.BaseTest):
|
||||
self.driver = dnsmasq.DnsmasqFilter()
|
||||
|
||||
|
||||
class TestConfiguration(DnsmasqTestBase):
|
||||
|
||||
def test_deny_unknown_macs_and_node_not_found_hook_bad(self):
|
||||
CONF.set_override('deny_unknown_macs', True, 'pxe_filter')
|
||||
CONF.set_override('node_not_found_hook', True, 'processing')
|
||||
self.assertRaisesRegex(utils.Error, 'Configuration error:',
|
||||
self.driver.__init__)
|
||||
|
||||
|
||||
class TestShouldEnableUnknownHosts(DnsmasqTestBase):
|
||||
def setUp(self):
|
||||
super(TestShouldEnableUnknownHosts, self).setUp()
|
||||
@ -251,6 +262,23 @@ class TestMACHandlers(test_base.BaseTest):
|
||||
'A %s record for all unknown hosts using wildcard mac '
|
||||
'created', 'deny')
|
||||
|
||||
def test__macs_unknown_hosts_deny_unknown(self):
|
||||
self.mock_join.return_value = "%s/%s" % (self.dhcp_hostsdir,
|
||||
dnsmasq._UNKNOWN_HOSTS_FILE)
|
||||
self.mock_introspection_active.return_value = True
|
||||
CONF.set_override('deny_unknown_macs', True, 'pxe_filter')
|
||||
|
||||
dnsmasq._configure_unknown_hosts()
|
||||
|
||||
self.mock_join.assert_called_once_with(self.dhcp_hostsdir,
|
||||
dnsmasq._UNKNOWN_HOSTS_FILE)
|
||||
self.mock__exclusive_write_or_pass.assert_called_once_with(
|
||||
self.mock_join.return_value,
|
||||
'%s' % dnsmasq._DENY_UNKNOWN_HOSTS)
|
||||
self.mock_log.debug.assert_called_once_with(
|
||||
'A %s record for all unknown hosts using wildcard mac '
|
||||
'created', 'deny')
|
||||
|
||||
def test__configure_removedlist_allowlist(self):
|
||||
self.mock_introspection_active.return_value = True
|
||||
self.mock_stat.return_value.st_size = dnsmasq._MAC_DENY_LEN
|
||||
@ -271,6 +299,17 @@ class TestMACHandlers(test_base.BaseTest):
|
||||
self.mock__exclusive_write_or_pass.assert_called_once_with(
|
||||
self.mock_join.return_value, '%s,ignore\n' % self.mac)
|
||||
|
||||
def test__configure_removedlist_denylist_deny_unknown(self):
|
||||
self.mock_introspection_active.return_value = True
|
||||
self.mock_stat.return_value.st_size = dnsmasq._MAC_ALLOW_LEN
|
||||
CONF.set_override('deny_unknown_macs', True, 'pxe_filter')
|
||||
|
||||
dnsmasq._configure_removedlist({self.mac})
|
||||
|
||||
self.mock_join.assert_called_with(self.dhcp_hostsdir, self.mac)
|
||||
self.mock__exclusive_write_or_pass.assert_called_once_with(
|
||||
self.mock_join.return_value, '%s,ignore\n' % self.mac)
|
||||
|
||||
def test__allowlist_mac(self):
|
||||
dnsmasq._add_mac_to_allowlist(self.mac)
|
||||
|
||||
@ -372,6 +411,9 @@ class TestSync(DnsmasqTestBase):
|
||||
get_client_mock = self.useFixture(
|
||||
fixtures.MockPatchObject(ir_utils, 'get_client')).mock
|
||||
get_client_mock.return_value = self.mock_ironic
|
||||
self.mock_get_active_macs = self.useFixture(
|
||||
fixtures.MockPatchObject(pxe_filter, 'get_active_macs')).mock
|
||||
self.mock_get_active_macs.return_value = set()
|
||||
self.mock_active_macs = self.useFixture(
|
||||
fixtures.MockPatchObject(node_cache, 'active_macs')).mock
|
||||
self.ironic_macs = {'new_mac', 'active_mac'}
|
||||
@ -401,14 +443,15 @@ class TestSync(DnsmasqTestBase):
|
||||
self.mock__configure_unknown_hosts.assert_called_once_with()
|
||||
|
||||
def test__sync(self):
|
||||
self.mock_get_active_macs.return_value = {'active_mac'}
|
||||
self.driver._sync(self.mock_ironic)
|
||||
|
||||
self.mock_get_active_macs.assert_called_once_with(self.mock_ironic)
|
||||
self.mock__add_mac_to_allowlist.assert_called_once_with('active_mac')
|
||||
self.mock__add_mac_to_denylist.assert_called_once_with('new_mac')
|
||||
|
||||
self.mock_ironic.ports.assert_called_once_with(
|
||||
limit=None, fields=['address'])
|
||||
self.mock_active_macs.assert_called_once_with()
|
||||
fields=['address', 'extra'], limit=None)
|
||||
self.mock__get_deny_allow_lists.assert_called_once_with()
|
||||
self.mock__configure_unknown_hosts.assert_called_once_with()
|
||||
self.mock__configure_removedlist.assert_called_once_with({'gone_mac'})
|
||||
@ -424,14 +467,15 @@ class TestSync(DnsmasqTestBase):
|
||||
os_exc.SDKException('boom'),
|
||||
[mock.Mock(address=address) for address in self.ironic_macs]
|
||||
]
|
||||
self.mock_get_active_macs.return_value = {'active_mac'}
|
||||
self.driver._sync(self.mock_ironic)
|
||||
|
||||
self.mock__add_mac_to_allowlist.assert_called_once_with('active_mac')
|
||||
self.mock__add_mac_to_denylist.assert_called_once_with('new_mac')
|
||||
|
||||
self.mock_ironic.ports.assert_called_with(
|
||||
limit=None, fields=['address'])
|
||||
self.mock_active_macs.assert_called_once_with()
|
||||
self.mock_ironic.ports.assert_called_with(fields=['address', 'extra'],
|
||||
limit=None)
|
||||
self.mock_get_active_macs.assert_called_once_with(self.mock_ironic)
|
||||
self.mock__get_deny_allow_lists.assert_called_once_with()
|
||||
self.mock__configure_removedlist.assert_called_once_with({'gone_mac'})
|
||||
self.mock_log.debug.assert_has_calls([
|
||||
|
@ -16,14 +16,13 @@
|
||||
from unittest import mock
|
||||
|
||||
import fixtures
|
||||
from openstack import exceptions as os_exc
|
||||
from oslo_config import cfg
|
||||
|
||||
from ironic_inspector import node_cache
|
||||
from ironic_inspector.pxe_filter import base as pxe_filter
|
||||
from ironic_inspector.pxe_filter import iptables
|
||||
from ironic_inspector.test import base as test_base
|
||||
|
||||
from ironic_inspector import utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
@ -44,10 +43,14 @@ class TestIptablesDriver(test_base.NodeTest):
|
||||
fixtures.MockPatchObject(self.driver, '_iptables')).mock
|
||||
self.mock_should_enable_dhcp = self.useFixture(
|
||||
fixtures.MockPatchObject(iptables, '_should_enable_dhcp')).mock
|
||||
self.mock__get_denylist = self.useFixture(
|
||||
fixtures.MockPatchObject(iptables, '_get_denylist')).mock
|
||||
self.mock__get_denylist.return_value = []
|
||||
self.mock_get_inactive_macs = self.useFixture(
|
||||
fixtures.MockPatchObject(pxe_filter, 'get_inactive_macs')).mock
|
||||
self.mock_get_inactive_macs.return_value = set()
|
||||
self.mock_get_active_macs = self.useFixture(
|
||||
fixtures.MockPatchObject(pxe_filter, 'get_active_macs')).mock
|
||||
self.mock_get_active_macs.return_value = set()
|
||||
self.mock_ironic = mock.Mock()
|
||||
self.mock_ironic.ports.return_value = []
|
||||
|
||||
def check_fsm(self, events):
|
||||
# assert the iptables.fsm.process_event() was called with the events
|
||||
@ -143,7 +146,7 @@ class TestIptablesDriver(test_base.NodeTest):
|
||||
for (args, call) in zip(_iptables_expected_args,
|
||||
call_args_list):
|
||||
self.assertEqual(args, call[0])
|
||||
self.mock__get_denylist.assert_called_once_with(self.mock_ironic)
|
||||
self.mock_get_inactive_macs.assert_called_once_with(self.mock_ironic)
|
||||
self.check_fsm([pxe_filter.Events.sync])
|
||||
|
||||
def test__iptables_args_ipv4(self):
|
||||
@ -179,7 +182,8 @@ class TestIptablesDriver(test_base.NodeTest):
|
||||
self.driver = iptables.IptablesFilter()
|
||||
self.mock_iptables = self.useFixture(
|
||||
fixtures.MockPatchObject(self.driver, '_iptables')).mock
|
||||
self.mock__get_denylist.return_value = ['AA:BB:CC:DD:EE:FF']
|
||||
self.mock_get_active_macs.return_value = ['AA:BB:CC:DD:EE:FF']
|
||||
self.mock_get_inactive_macs.return_value = ['FF:EE:DD:CC:BB:AA']
|
||||
self.mock_should_enable_dhcp.return_value = True
|
||||
|
||||
_iptables_expected_args = [
|
||||
@ -190,7 +194,7 @@ class TestIptablesDriver(test_base.NodeTest):
|
||||
('-N', self.driver.new_chain),
|
||||
# deny
|
||||
('-A', self.driver.new_chain, '-m', 'mac', '--mac-source',
|
||||
self.mock__get_denylist.return_value[0], '-j', 'DROP'),
|
||||
self.mock_get_inactive_macs.return_value[0], '-j', 'DROP'),
|
||||
('-A', self.driver.new_chain, '-j', 'ACCEPT'),
|
||||
('-I', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
|
||||
expected_port, '-j', self.driver.new_chain),
|
||||
@ -208,14 +212,14 @@ class TestIptablesDriver(test_base.NodeTest):
|
||||
for (args, call) in zip(_iptables_expected_args,
|
||||
call_args_list):
|
||||
self.assertEqual(args, call[0])
|
||||
self.mock__get_denylist.assert_called_once_with(self.mock_ironic)
|
||||
self.mock_get_inactive_macs.assert_called_once_with(self.mock_ironic)
|
||||
|
||||
# check caching
|
||||
|
||||
self.mock_iptables.reset_mock()
|
||||
self.mock__get_denylist.reset_mock()
|
||||
self.mock_get_inactive_macs.reset_mock()
|
||||
self.driver.sync(self.mock_ironic)
|
||||
self.mock__get_denylist.assert_called_once_with(self.mock_ironic)
|
||||
self.mock_get_inactive_macs.assert_called_once_with(self.mock_ironic)
|
||||
self.assertFalse(self.mock_iptables.called)
|
||||
|
||||
def test_sync_with_denylist_ipv4(self):
|
||||
@ -226,18 +230,71 @@ class TestIptablesDriver(test_base.NodeTest):
|
||||
CONF.set_override('ip_version', '6', 'iptables')
|
||||
self._test_sync_with_denylist('547')
|
||||
|
||||
def _test_sync_with_allowlist(self, expected_port):
|
||||
CONF.set_override('deny_unknown_macs', True, 'pxe_filter')
|
||||
self.driver = iptables.IptablesFilter()
|
||||
self.mock_iptables = self.useFixture(
|
||||
fixtures.MockPatchObject(self.driver, '_iptables')).mock
|
||||
self.mock_get_active_macs.return_value = ['AA:BB:CC:DD:EE:FF']
|
||||
self.mock_get_inactive_macs.return_value = ['FF:EE:DD:CC:BB:AA']
|
||||
self.mock_should_enable_dhcp.return_value = True
|
||||
|
||||
_iptables_expected_args = [
|
||||
('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
|
||||
expected_port, '-j', self.driver.new_chain),
|
||||
('-F', self.driver.new_chain),
|
||||
('-X', self.driver.new_chain),
|
||||
('-N', self.driver.new_chain),
|
||||
# deny
|
||||
('-A', self.driver.new_chain, '-m', 'mac', '--mac-source',
|
||||
self.mock_get_active_macs.return_value[0], '-j', 'ACCEPT'),
|
||||
('-A', self.driver.new_chain, '-j', 'DROP'),
|
||||
('-I', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
|
||||
expected_port, '-j', self.driver.new_chain),
|
||||
('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
|
||||
expected_port, '-j', self.driver.chain),
|
||||
('-F', self.driver.chain),
|
||||
('-X', self.driver.chain),
|
||||
('-E', self.driver.new_chain, self.driver.chain)
|
||||
]
|
||||
|
||||
self.driver.sync(self.mock_ironic)
|
||||
self.check_fsm([pxe_filter.Events.sync])
|
||||
call_args_list = self.mock_iptables.call_args_list
|
||||
|
||||
for (args, call) in zip(_iptables_expected_args,
|
||||
call_args_list):
|
||||
self.assertEqual(args, call[0])
|
||||
self.mock_get_active_macs.assert_called_once_with(self.mock_ironic)
|
||||
|
||||
# check caching
|
||||
|
||||
self.mock_iptables.reset_mock()
|
||||
self.mock_get_active_macs.reset_mock()
|
||||
self.driver.sync(self.mock_ironic)
|
||||
self.mock_get_active_macs.assert_called_once_with(self.mock_ironic)
|
||||
self.assertFalse(self.mock_iptables.called)
|
||||
|
||||
def test_sync_with_allowlist_ipv4(self):
|
||||
CONF.set_override('ip_version', '4', 'iptables')
|
||||
self._test_sync_with_allowlist('67')
|
||||
|
||||
def test_sync_with_allowlist_ipv6(self):
|
||||
CONF.set_override('ip_version', '6', 'iptables')
|
||||
self._test_sync_with_allowlist('547')
|
||||
|
||||
def _test__iptables_clean_cache_on_error(self, expected_port):
|
||||
self.driver = iptables.IptablesFilter()
|
||||
self.mock_iptables = self.useFixture(
|
||||
fixtures.MockPatchObject(self.driver, '_iptables')).mock
|
||||
self.mock__get_denylist.return_value = ['AA:BB:CC:DD:EE:FF']
|
||||
self.mock_get_inactive_macs.return_value = ['AA:BB:CC:DD:EE:FF']
|
||||
self.mock_should_enable_dhcp.return_value = True
|
||||
|
||||
self.mock_iptables.side_effect = [None, None, RuntimeError('Oops!'),
|
||||
None, None, None, None, None, None]
|
||||
self.assertRaises(RuntimeError, self.driver.sync, self.mock_ironic)
|
||||
self.check_fsm([pxe_filter.Events.sync, pxe_filter.Events.reset])
|
||||
self.mock__get_denylist.assert_called_once_with(self.mock_ironic)
|
||||
self.mock_get_inactive_macs.assert_called_once_with(self.mock_ironic)
|
||||
|
||||
# check caching
|
||||
syncs_expected_args = [
|
||||
@ -249,7 +306,7 @@ class TestIptablesDriver(test_base.NodeTest):
|
||||
('-N', self.driver.new_chain),
|
||||
# deny
|
||||
('-A', self.driver.new_chain, '-m', 'mac', '--mac-source',
|
||||
self.mock__get_denylist.return_value[0], '-j', 'DROP'),
|
||||
self.mock_get_inactive_macs.return_value[0], '-j', 'DROP'),
|
||||
('-A', self.driver.new_chain, '-j', 'ACCEPT'),
|
||||
('-I', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
|
||||
expected_port, '-j', self.driver.new_chain),
|
||||
@ -262,7 +319,7 @@ class TestIptablesDriver(test_base.NodeTest):
|
||||
|
||||
self.mock_iptables.reset_mock()
|
||||
self.mock_iptables.side_effect = None
|
||||
self.mock__get_denylist.reset_mock()
|
||||
self.mock_get_inactive_macs.reset_mock()
|
||||
self.mock_fsm.reset_mock()
|
||||
self.driver.sync(self.mock_ironic)
|
||||
self.check_fsm([pxe_filter.Events.sync])
|
||||
@ -271,7 +328,7 @@ class TestIptablesDriver(test_base.NodeTest):
|
||||
for (idx, (args, call)) in enumerate(zip(syncs_expected_args,
|
||||
call_args_list)):
|
||||
self.assertEqual(args, call[0], 'idx: %s' % idx)
|
||||
self.mock__get_denylist.assert_called_once_with(self.mock_ironic)
|
||||
self.mock_get_inactive_macs.assert_called_once_with(self.mock_ironic)
|
||||
|
||||
def test__iptables_clean_cache_on_error_ipv4(self):
|
||||
CONF.set_override('ip_version', '4', 'iptables')
|
||||
@ -291,6 +348,12 @@ class TestIptablesDriver(test_base.NodeTest):
|
||||
driver = iptables.IptablesFilter()
|
||||
self.assertEqual(driver._cmd_iptables, 'ip6tables')
|
||||
|
||||
def test_deny_unknown_macs_and_node_not_found_hook_bad(self):
|
||||
CONF.set_override('deny_unknown_macs', True, 'pxe_filter')
|
||||
CONF.set_override('node_not_found_hook', True, 'processing')
|
||||
self.assertRaisesRegex(utils.Error, 'Configuration error:',
|
||||
self.driver.__init__)
|
||||
|
||||
|
||||
class Test_ShouldEnableDhcp(test_base.BaseTest):
|
||||
def setUp(self):
|
||||
@ -311,113 +374,3 @@ class Test_ShouldEnableDhcp(test_base.BaseTest):
|
||||
def test__should_enable_dhcp_false(self):
|
||||
self.mock_introspection_active.return_value = False
|
||||
self.assertIs(False, iptables._should_enable_dhcp())
|
||||
|
||||
|
||||
class TestIBMapping(test_base.BaseTest):
|
||||
def setUp(self):
|
||||
super(TestIBMapping, self).setUp()
|
||||
CONF.set_override('ethoib_interfaces', ['eth0'], 'iptables')
|
||||
self.ib_data = (
|
||||
'EMAC=02:00:02:97:00:01 IMAC=97:fe:80:00:00:00:00:00:00:7c:fe:90:'
|
||||
'03:00:29:26:52\n'
|
||||
'EMAC=02:00:00:61:00:02 IMAC=61:fe:80:00:00:00:00:00:00:7c:fe:90:'
|
||||
'03:00:29:24:4f\n'
|
||||
)
|
||||
self.client_id = ('ff:00:00:00:00:00:02:00:00:02:c9:00:7c:fe:90:03:00:'
|
||||
'29:24:4f')
|
||||
self.ib_address = '7c:fe:90:29:24:4f'
|
||||
self.ib_port = mock.Mock(address=self.ib_address,
|
||||
extra={'client-id': self.client_id},
|
||||
spec=['address', 'extra'])
|
||||
self.port = mock.Mock(address='aa:bb:cc:dd:ee:ff',
|
||||
extra={}, spec=['address', 'extra'])
|
||||
self.ports = [self.ib_port, self.port]
|
||||
self.expected_rmac = '02:00:00:61:00:02'
|
||||
self.fileobj = mock.mock_open(read_data=self.ib_data)
|
||||
|
||||
def test_matching_ib(self):
|
||||
with mock.patch('builtins.open', self.fileobj,
|
||||
create=True) as mock_open:
|
||||
iptables._ib_mac_to_rmac_mapping(self.ports)
|
||||
|
||||
self.assertEqual(self.expected_rmac, self.ib_port.address)
|
||||
self.assertEqual(self.ports, [self.ib_port, self.port])
|
||||
mock_open.assert_called_once_with('/sys/class/net/eth0/eth/neighs',
|
||||
'r')
|
||||
|
||||
def test_ib_not_match(self):
|
||||
self.ports[0].extra['client-id'] = 'foo'
|
||||
with mock.patch('builtins.open', self.fileobj,
|
||||
create=True) as mock_open:
|
||||
iptables._ib_mac_to_rmac_mapping(self.ports)
|
||||
|
||||
self.assertEqual(self.ib_address, self.ib_port.address)
|
||||
self.assertEqual(self.ports, [self.ib_port, self.port])
|
||||
mock_open.assert_called_once_with('/sys/class/net/eth0/eth/neighs',
|
||||
'r')
|
||||
|
||||
def test_open_no_such_file(self):
|
||||
with mock.patch('builtins.open',
|
||||
side_effect=IOError(), autospec=True) as mock_open:
|
||||
iptables._ib_mac_to_rmac_mapping(self.ports)
|
||||
|
||||
self.assertEqual(self.ib_address, self.ib_port.address)
|
||||
self.assertEqual(self.ports, [self.ib_port, self.port])
|
||||
mock_open.assert_called_once_with('/sys/class/net/eth0/eth/neighs',
|
||||
'r')
|
||||
|
||||
def test_no_interfaces(self):
|
||||
CONF.set_override('ethoib_interfaces', [], 'iptables')
|
||||
with mock.patch('builtins.open', self.fileobj,
|
||||
create=True) as mock_open:
|
||||
iptables._ib_mac_to_rmac_mapping(self.ports)
|
||||
|
||||
self.assertEqual(self.ib_address, self.ib_port.address)
|
||||
self.assertEqual(self.ports, [self.ib_port, self.port])
|
||||
mock_open.assert_not_called()
|
||||
|
||||
|
||||
class TestGetDenylist(test_base.BaseTest):
|
||||
def setUp(self):
|
||||
super(TestGetDenylist, self).setUp()
|
||||
self.mock__ib_mac_to_rmac_mapping = self.useFixture(
|
||||
fixtures.MockPatchObject(iptables, '_ib_mac_to_rmac_mapping')).mock
|
||||
self.mock_active_macs = self.useFixture(
|
||||
fixtures.MockPatchObject(node_cache, 'active_macs')).mock
|
||||
self.mock_ironic = mock.Mock()
|
||||
|
||||
def test_active_port(self):
|
||||
mock_ports_list = [
|
||||
mock.Mock(address='foo'),
|
||||
mock.Mock(address='bar'),
|
||||
]
|
||||
self.mock_ironic.ports.return_value = mock_ports_list
|
||||
self.mock_active_macs.return_value = {'foo'}
|
||||
|
||||
ports = iptables._get_denylist(self.mock_ironic)
|
||||
# foo is an active address so we expect the denylist contain only bar
|
||||
self.assertEqual(['bar'], ports)
|
||||
self.mock_ironic.ports.assert_called_once_with(
|
||||
limit=None, fields=['address', 'extra'])
|
||||
self.mock__ib_mac_to_rmac_mapping.assert_called_once_with(
|
||||
[mock_ports_list[1]])
|
||||
|
||||
@mock.patch('time.sleep', lambda _x: None)
|
||||
def test_retry_on_port_list_failure(self):
|
||||
mock_ports_list = [
|
||||
mock.Mock(address='foo'),
|
||||
mock.Mock(address='bar'),
|
||||
]
|
||||
self.mock_ironic.ports.side_effect = [
|
||||
os_exc.SDKException('boom'),
|
||||
mock_ports_list
|
||||
]
|
||||
self.mock_active_macs.return_value = {'foo'}
|
||||
|
||||
ports = iptables._get_denylist(self.mock_ironic)
|
||||
# foo is an active address so we expect the denylist contain only bar
|
||||
self.assertEqual(['bar'], ports)
|
||||
self.mock_ironic.ports.assert_called_with(
|
||||
limit=None, fields=['address', 'extra'])
|
||||
self.mock__ib_mac_to_rmac_mapping.assert_called_once_with(
|
||||
[mock_ports_list[1]])
|
||||
|
@ -17,10 +17,12 @@ from automaton import exceptions as automaton_errors
|
||||
from eventlet import semaphore
|
||||
import fixtures
|
||||
from futurist import periodics
|
||||
from openstack import exceptions as os_exc
|
||||
from oslo_config import cfg
|
||||
import stevedore
|
||||
|
||||
from ironic_inspector.common import ironic as ir_utils
|
||||
from ironic_inspector import node_cache
|
||||
from ironic_inspector.pxe_filter import base as pxe_filter
|
||||
from ironic_inspector.pxe_filter import interface
|
||||
from ironic_inspector.test import base as test_base
|
||||
@ -292,3 +294,198 @@ class TestDriver(test_base.BaseTest):
|
||||
|
||||
self.assertIs(self.mock_driver, ret)
|
||||
self.mock__driver_manager.assert_called_once_with()
|
||||
|
||||
|
||||
class TestIBMapping(test_base.BaseTest):
|
||||
def setUp(self):
|
||||
super(TestIBMapping, self).setUp()
|
||||
CONF.set_override('ethoib_interfaces', ['eth0'], 'iptables')
|
||||
self.ib_data = (
|
||||
'EMAC=02:00:02:97:00:01 IMAC=97:fe:80:00:00:00:00:00:00:7c:fe:90:'
|
||||
'03:00:29:26:52\n'
|
||||
'EMAC=02:00:00:61:00:02 IMAC=61:fe:80:00:00:00:00:00:00:7c:fe:90:'
|
||||
'03:00:29:24:4f\n'
|
||||
)
|
||||
self.client_id = ('ff:00:00:00:00:00:02:00:00:02:c9:00:7c:fe:90:03:00:'
|
||||
'29:24:4f')
|
||||
self.ib_address = '7c:fe:90:29:24:4f'
|
||||
self.ib_port = mock.Mock(address=self.ib_address,
|
||||
extra={'client-id': self.client_id},
|
||||
spec=['address', 'extra'])
|
||||
self.port = mock.Mock(address='aa:bb:cc:dd:ee:ff',
|
||||
extra={}, spec=['address', 'extra'])
|
||||
self.ports = [self.ib_port, self.port]
|
||||
self.expected_rmac = '02:00:00:61:00:02'
|
||||
self.fileobj = mock.mock_open(read_data=self.ib_data)
|
||||
|
||||
def test_matching_ib(self):
|
||||
with mock.patch('builtins.open', self.fileobj,
|
||||
create=True) as mock_open:
|
||||
pxe_filter._ib_mac_to_rmac_mapping(self.ports)
|
||||
|
||||
self.assertEqual(self.expected_rmac, self.ib_port.address)
|
||||
self.assertEqual(self.ports, [self.ib_port, self.port])
|
||||
mock_open.assert_called_once_with('/sys/class/net/eth0/eth/neighs',
|
||||
'r')
|
||||
|
||||
def test_ib_not_match(self):
|
||||
self.ports[0].extra['client-id'] = 'foo'
|
||||
with mock.patch('builtins.open', self.fileobj,
|
||||
create=True) as mock_open:
|
||||
pxe_filter._ib_mac_to_rmac_mapping(self.ports)
|
||||
|
||||
self.assertEqual(self.ib_address, self.ib_port.address)
|
||||
self.assertEqual(self.ports, [self.ib_port, self.port])
|
||||
mock_open.assert_called_once_with('/sys/class/net/eth0/eth/neighs',
|
||||
'r')
|
||||
|
||||
def test_open_no_such_file(self):
|
||||
with mock.patch('builtins.open',
|
||||
side_effect=IOError(), autospec=True) as mock_open:
|
||||
pxe_filter._ib_mac_to_rmac_mapping(self.ports)
|
||||
|
||||
self.assertEqual(self.ib_address, self.ib_port.address)
|
||||
self.assertEqual(self.ports, [self.ib_port, self.port])
|
||||
mock_open.assert_called_once_with('/sys/class/net/eth0/eth/neighs',
|
||||
'r')
|
||||
|
||||
def test_no_interfaces(self):
|
||||
CONF.set_override('ethoib_interfaces', [], 'iptables')
|
||||
with mock.patch('builtins.open', self.fileobj,
|
||||
create=True) as mock_open:
|
||||
pxe_filter._ib_mac_to_rmac_mapping(self.ports)
|
||||
|
||||
self.assertEqual(self.ib_address, self.ib_port.address)
|
||||
self.assertEqual(self.ports, [self.ib_port, self.port])
|
||||
mock_open.assert_not_called()
|
||||
|
||||
|
||||
class TestGetInactiveMacs(test_base.BaseTest):
|
||||
def setUp(self):
|
||||
super(TestGetInactiveMacs, self).setUp()
|
||||
self.mock__ib_mac_to_rmac_mapping = self.useFixture(
|
||||
fixtures.MockPatchObject(pxe_filter,
|
||||
'_ib_mac_to_rmac_mapping')).mock
|
||||
self.mock_active_macs = self.useFixture(
|
||||
fixtures.MockPatchObject(node_cache, 'active_macs')).mock
|
||||
self.mock_ironic = mock.Mock()
|
||||
|
||||
def test_inactive_port(self):
|
||||
mock_ports_list = [
|
||||
mock.Mock(address='foo'),
|
||||
mock.Mock(address='bar'),
|
||||
]
|
||||
self.mock_ironic.ports.return_value = mock_ports_list
|
||||
self.mock_active_macs.return_value = {'foo'}
|
||||
|
||||
ports = pxe_filter.get_inactive_macs(self.mock_ironic)
|
||||
self.assertEqual({'bar'}, ports)
|
||||
self.mock_ironic.ports.assert_called_once_with(
|
||||
limit=None, fields=['address', 'extra'])
|
||||
self.mock__ib_mac_to_rmac_mapping.assert_called_once_with(
|
||||
[mock_ports_list[1]])
|
||||
|
||||
@mock.patch('time.sleep', lambda _x: None)
|
||||
def test_retry_on_port_list_failure(self):
|
||||
mock_ports_list = [
|
||||
mock.Mock(address='foo'),
|
||||
mock.Mock(address='bar'),
|
||||
]
|
||||
self.mock_ironic.ports.side_effect = [
|
||||
os_exc.SDKException('boom'),
|
||||
mock_ports_list
|
||||
]
|
||||
self.mock_active_macs.return_value = {'foo'}
|
||||
|
||||
ports = pxe_filter.get_inactive_macs(self.mock_ironic)
|
||||
self.assertEqual({'bar'}, ports)
|
||||
self.mock_ironic.ports.assert_called_with(
|
||||
limit=None, fields=['address', 'extra'])
|
||||
self.mock__ib_mac_to_rmac_mapping.assert_called_once_with(
|
||||
[mock_ports_list[1]])
|
||||
|
||||
|
||||
class TestGetActiveMacs(test_base.BaseTest):
|
||||
def setUp(self):
|
||||
super(TestGetActiveMacs, self).setUp()
|
||||
self.mock__ib_mac_to_rmac_mapping = self.useFixture(
|
||||
fixtures.MockPatchObject(pxe_filter,
|
||||
'_ib_mac_to_rmac_mapping')).mock
|
||||
self.mock_active_macs = self.useFixture(
|
||||
fixtures.MockPatchObject(node_cache, 'active_macs')).mock
|
||||
self.mock_ironic = mock.Mock()
|
||||
|
||||
def test_active_port(self):
|
||||
mock_ports_list = [
|
||||
mock.Mock(address='foo'),
|
||||
mock.Mock(address='bar'),
|
||||
]
|
||||
self.mock_ironic.ports.return_value = mock_ports_list
|
||||
self.mock_active_macs.return_value = {'foo'}
|
||||
|
||||
ports = pxe_filter.get_active_macs(self.mock_ironic)
|
||||
self.assertEqual({'foo'}, ports)
|
||||
self.mock_ironic.ports.assert_called_once_with(
|
||||
limit=None, fields=['address', 'extra'])
|
||||
self.mock__ib_mac_to_rmac_mapping.assert_called_once_with(
|
||||
[mock_ports_list[0]])
|
||||
|
||||
@mock.patch('time.sleep', lambda _x: None)
|
||||
def test_retry_on_port_list_failure(self):
|
||||
mock_ports_list = [
|
||||
mock.Mock(address='foo'),
|
||||
mock.Mock(address='bar'),
|
||||
]
|
||||
self.mock_ironic.ports.side_effect = [
|
||||
os_exc.SDKException('boom'),
|
||||
mock_ports_list
|
||||
]
|
||||
self.mock_active_macs.return_value = {'foo'}
|
||||
|
||||
ports = pxe_filter.get_active_macs(self.mock_ironic)
|
||||
self.assertEqual({'foo'}, ports)
|
||||
self.mock_ironic.ports.assert_called_with(
|
||||
limit=None, fields=['address', 'extra'])
|
||||
self.mock__ib_mac_to_rmac_mapping.assert_called_once_with(
|
||||
[mock_ports_list[0]])
|
||||
|
||||
|
||||
class TestGetIronicMacs(test_base.BaseTest):
|
||||
def setUp(self):
|
||||
super(TestGetIronicMacs, self).setUp()
|
||||
self.mock__ib_mac_to_rmac_mapping = self.useFixture(
|
||||
fixtures.MockPatchObject(pxe_filter,
|
||||
'_ib_mac_to_rmac_mapping')).mock
|
||||
self.mock_ironic = mock.Mock()
|
||||
|
||||
def test_active_port(self):
|
||||
mock_ports_list = [
|
||||
mock.Mock(address='foo'),
|
||||
mock.Mock(address='bar'),
|
||||
]
|
||||
self.mock_ironic.ports.return_value = mock_ports_list
|
||||
|
||||
ports = pxe_filter.get_ironic_macs(self.mock_ironic)
|
||||
self.assertEqual({'foo', 'bar'}, ports)
|
||||
self.mock_ironic.ports.assert_called_once_with(
|
||||
limit=None, fields=['address', 'extra'])
|
||||
self.mock__ib_mac_to_rmac_mapping.assert_called_once_with(
|
||||
mock_ports_list)
|
||||
|
||||
@mock.patch('time.sleep', lambda _x: None)
|
||||
def test_retry_on_port_list_failure(self):
|
||||
mock_ports_list = [
|
||||
mock.Mock(address='foo'),
|
||||
mock.Mock(address='bar'),
|
||||
]
|
||||
self.mock_ironic.ports.side_effect = [
|
||||
os_exc.SDKException('boom'),
|
||||
mock_ports_list
|
||||
]
|
||||
|
||||
ports = pxe_filter.get_ironic_macs(self.mock_ironic)
|
||||
self.assertEqual({'foo', 'bar'}, ports)
|
||||
self.mock_ironic.ports.assert_called_with(
|
||||
limit=None, fields=['address', 'extra'])
|
||||
self.mock__ib_mac_to_rmac_mapping.assert_called_once_with(
|
||||
mock_ports_list)
|
||||
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
The dnsmasq pxe-filter now supports mapping between host InfiniBand MAC to
|
||||
EthernetOverInfiniBand MAC. (This was previously only supported by the
|
||||
iptables pxe-filter.)
|
@ -0,0 +1,17 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
By default the DHCP filtering will open the DHCP server for any node when
|
||||
introspection is active. It will only block DHCP for enrolled nodes that
|
||||
are not being introspected. Doing so is required to support interface
|
||||
discovery (which by default will enroll the pxe port to ironic if not
|
||||
present). This behaviour is not always wanted, as nodes not managed by
|
||||
ironic may boot the inspection image.
|
||||
|
||||
A new option was added ``[pxe_filter]deny_unknown_macs`` which allow
|
||||
changeing this behaviour so that the DHCP server only allow enrolled nodes
|
||||
being introspected and deny everything else.
|
||||
|
||||
.. Note:: If this option is ``True``, nodes must have at least one
|
||||
enrolled port prior to introspection.
|
||||
|
Loading…
Reference in New Issue
Block a user