Merge "Adding InfiniBand Support"
This commit is contained in:
commit
516ddc424f
@ -243,6 +243,8 @@ the ramdisk. Request body: JSON dictionary with at least these keys:
|
||||
|
||||
* ``mac_address`` MAC (physical) address of the interface.
|
||||
|
||||
* ``client_id`` InfiniBand Client-ID, for Ethernet is None.
|
||||
|
||||
* ``root_disk`` default deployment root disk as calculated by the
|
||||
ironic-python-agent algorithm.
|
||||
|
||||
|
@ -354,3 +354,33 @@ a CPU flag and a capability, for example::
|
||||
cpu_flags = aes:cpu_aes,svm:cpu_vt,vmx:cpu_vt
|
||||
|
||||
See the default value of this option for a more detail example.
|
||||
|
||||
InfiniBand support
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
Starting with the Ocata release, **Ironic Inspector** supports detection of
|
||||
InfiniBand network interfaces. A recent (Ocata or newer) IPA image is required
|
||||
for that to work. When an InfiniBand network interface is discovered, the
|
||||
**Ironic Inspector** adds a ``client-id`` attribute to the ``extra`` attribute
|
||||
in the ironic port. The **Ironic Inspector** should be configured with
|
||||
``firewall.ethoib_interfaces`` to indicate the Ethernet Over InfiniBand (EoIB)
|
||||
which are used for physical access access to the DHCP network.
|
||||
For example if **Ironic Inspector** DHCP server is using ``br-inspector`` and
|
||||
the ``br-inspector`` has EoIB port e.g. ``eth0``,
|
||||
the ``firewall.ethoib_interfaces`` should be set to ``eth0``.
|
||||
The ``firewall.ethoib_interfaces`` allows to map the baremetal GUID to it's
|
||||
EoIB MAC based on the neighs files. This is needed for blocking DHCP traffic
|
||||
of the nodes (MACs) which are not part of the introspection.
|
||||
|
||||
The format of the ``/sys/class/net/<ethoib>/eth/neighs`` file::
|
||||
|
||||
# EMAC=<ethernet mac of the ethoib> IMAC=<qp number:lid:GUID>
|
||||
# For example:
|
||||
IMAC=97:fe:80:00:00:00:00:00:00:7c:fe:90:03:00:29:26:52
|
||||
qp number=97:fe
|
||||
lid=80:00:00:00:00:00:00
|
||||
GUID=7c:fe:90:03:00:29:26:52
|
||||
|
||||
Example of content::
|
||||
|
||||
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
|
||||
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
|
||||
|
@ -400,6 +400,10 @@
|
||||
# iptables chain name to use. (string value)
|
||||
#firewall_chain = ironic-inspector
|
||||
|
||||
# List of Etherent Over InfiniBand interfaces on the Ironic host which
|
||||
# are used for Inspector DHCP (list value)
|
||||
#ethoib_interfaces =
|
||||
|
||||
|
||||
[ironic]
|
||||
|
||||
|
@ -41,9 +41,18 @@ FIREWALL_OPTS = [
|
||||
cfg.StrOpt('firewall_chain',
|
||||
default='ironic-inspector',
|
||||
help=_('iptables chain name to use.')),
|
||||
cfg.ListOpt('ethoib_interfaces',
|
||||
default=[],
|
||||
help=_('List of Etherent 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 '
|
||||
'dnsmasq_interface. The MACs of the InfiniBand nodes '
|
||||
'which are not in desired state are going to be '
|
||||
'blacklisted based on the list of neighbor MACs '
|
||||
'on these interfaces.')),
|
||||
]
|
||||
|
||||
|
||||
PROCESSING_OPTS = [
|
||||
cfg.StrOpt('add_ports',
|
||||
default='pxe',
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from eventlet import semaphore
|
||||
@ -33,6 +34,7 @@ LOCK = semaphore.BoundedSemaphore()
|
||||
BASE_COMMAND = None
|
||||
BLACKLIST_CACHE = None
|
||||
ENABLED = True
|
||||
EMAC_REGEX = 'EMAC=([0-9a-f]{2}(:[0-9a-f]{2}){5}) IMAC=.*'
|
||||
|
||||
|
||||
def _iptables(*args, **kwargs):
|
||||
@ -177,15 +179,19 @@ def update_filters(ironic=None):
|
||||
|
||||
assert INTERFACE is not None
|
||||
ironic = ir_utils.get_client() if ironic is None else ironic
|
||||
|
||||
with LOCK:
|
||||
if not _should_enable_dhcp():
|
||||
_disable_dhcp()
|
||||
return
|
||||
|
||||
macs_active = set(p.address for p in ironic.port.list(limit=0))
|
||||
ports_active = ironic.port.list(limit=0, fields=['address', 'extra'])
|
||||
macs_active = set(p.address for p in ports_active)
|
||||
to_blacklist = macs_active - node_cache.active_macs()
|
||||
if BLACKLIST_CACHE is not None and to_blacklist == BLACKLIST_CACHE:
|
||||
ib_mac_mapping = (
|
||||
_ib_mac_to_rmac_mapping(to_blacklist, ports_active))
|
||||
|
||||
if (BLACKLIST_CACHE is not None and
|
||||
to_blacklist == BLACKLIST_CACHE and not ib_mac_mapping):
|
||||
LOG.debug('Not updating iptables - no changes in MAC list %s',
|
||||
to_blacklist)
|
||||
return
|
||||
@ -197,6 +203,7 @@ def update_filters(ironic=None):
|
||||
with _temporary_chain(NEW_CHAIN, CHAIN):
|
||||
# - Blacklist active macs, so that nova can boot them
|
||||
for mac in to_blacklist:
|
||||
mac = ib_mac_mapping.get(mac) or mac
|
||||
_iptables('-A', NEW_CHAIN, '-m', 'mac',
|
||||
'--mac-source', mac, '-j', 'DROP')
|
||||
# - Whitelist everything else
|
||||
@ -205,3 +212,48 @@ def update_filters(ironic=None):
|
||||
# Cache result of successful iptables update
|
||||
ENABLED = True
|
||||
BLACKLIST_CACHE = to_blacklist
|
||||
|
||||
|
||||
def _ib_mac_to_rmac_mapping(blacklist_macs, ports_active):
|
||||
"""Mapping between host InfiniBand MAC 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 firewall
|
||||
rules based on those MACs get applied to the dnsmasq_interface by the
|
||||
update_filters function.
|
||||
|
||||
:param blacklist_macs: List of InfiniBand baremetal hosts macs to
|
||||
blacklist.
|
||||
:param ports_active: list of active ironic ports
|
||||
:return baremetal InfiniBand to remote mac on ironic node mapping
|
||||
"""
|
||||
ethoib_interfaces = CONF.firewall.ethoib_interfaces
|
||||
ib_mac_to_remote_mac = {}
|
||||
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(
|
||||
_LE('Interface %s is not Ethernet Over InfiniBand; '
|
||||
'Skipping ...'), interface)
|
||||
continue
|
||||
for port in ports_active:
|
||||
if port.address in blacklist_macs:
|
||||
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:
|
||||
ib_mac_to_remote_mac[port.address] = match.group(1)
|
||||
return ib_mac_to_remote_mac
|
||||
|
@ -343,17 +343,31 @@ class NodeInfo(object):
|
||||
self._node = ir_utils.get_node(self.uuid, ironic=ironic)
|
||||
return self._node
|
||||
|
||||
def create_ports(self, macs, ironic=None):
|
||||
def create_ports(self, ports, ironic=None):
|
||||
"""Create one or several ports for this node.
|
||||
|
||||
:param ports: List of ports with all their attributes
|
||||
e.g [{'mac': xx, 'ip': xx, 'client_id': None},
|
||||
{'mac': xx, 'ip': None, 'client_id': None}]
|
||||
It also support the old style of list of macs.
|
||||
A warning is issued if port already exists on a node.
|
||||
:param ironic: Ironic client to use instead of self.ironic
|
||||
"""
|
||||
existing_macs = []
|
||||
for mac in macs:
|
||||
for port in ports:
|
||||
mac = port
|
||||
extra = {}
|
||||
if isinstance(port, dict):
|
||||
mac = port['mac']
|
||||
client_id = port.get('client_id')
|
||||
if client_id:
|
||||
extra = {'client-id': client_id}
|
||||
|
||||
if mac not in self.ports():
|
||||
self._create_port(mac, ironic)
|
||||
self._create_port(mac, ironic=ironic, extra=extra)
|
||||
else:
|
||||
existing_macs.append(mac)
|
||||
|
||||
if existing_macs:
|
||||
LOG.warning(_LW('Did not create ports %s as they already exist'),
|
||||
existing_macs, node_info=self)
|
||||
@ -371,10 +385,11 @@ class NodeInfo(object):
|
||||
ironic.node.list_ports(self.uuid, limit=0)}
|
||||
return self._ports
|
||||
|
||||
def _create_port(self, mac, ironic=None):
|
||||
def _create_port(self, mac, ironic=None, extra=None):
|
||||
ironic = ironic or self.ironic
|
||||
try:
|
||||
port = ironic.port.create(node_uuid=self.uuid, address=mac)
|
||||
port = ironic.port.create(
|
||||
node_uuid=self.uuid, address=mac, extra=extra)
|
||||
except exceptions.Conflict:
|
||||
LOG.warning(_LW('Port %s already exists, skipping'),
|
||||
mac, node_info=self)
|
||||
|
@ -153,6 +153,7 @@ class ValidateInterfacesHook(base.ProcessingHook):
|
||||
name = iface.get('name')
|
||||
mac = iface.get('mac_address')
|
||||
ip = iface.get('ipv4_address')
|
||||
client_id = iface.get('client_id')
|
||||
|
||||
if not name:
|
||||
LOG.error(_LE('Malformed interface record: %s'),
|
||||
@ -173,10 +174,11 @@ class ValidateInterfacesHook(base.ProcessingHook):
|
||||
|
||||
mac = mac.lower()
|
||||
|
||||
LOG.debug('Found interface %(name)s with MAC "%(mac)s" and '
|
||||
'IP address "%(ip)s"',
|
||||
{'name': name, 'mac': mac, 'ip': ip}, data=data)
|
||||
result[name] = {'ip': ip, 'mac': mac}
|
||||
LOG.debug('Found interface %(name)s with MAC "%(mac)s", '
|
||||
'IP address "%(ip)s" and client_id "%(client_id)s"',
|
||||
{'name': name, 'mac': mac, 'ip': ip,
|
||||
'client_id': client_id}, data=data)
|
||||
result[name] = {'ip': ip, 'mac': mac, 'client_id': client_id}
|
||||
|
||||
return result
|
||||
|
||||
@ -199,6 +201,7 @@ class ValidateInterfacesHook(base.ProcessingHook):
|
||||
for name, iface in interfaces.items():
|
||||
mac = iface.get('mac')
|
||||
ip = iface.get('ip')
|
||||
client_id = iface.get('client_id')
|
||||
|
||||
if name == 'lo' or (ip and netaddr.IPAddress(ip).is_loopback()):
|
||||
LOG.debug('Skipping local interface %s', name, data=data)
|
||||
@ -215,7 +218,8 @@ class ValidateInterfacesHook(base.ProcessingHook):
|
||||
name, data=data)
|
||||
continue
|
||||
|
||||
result[name] = {'ip': ip, 'mac': mac.lower()}
|
||||
result[name] = {'ip': ip, 'mac': mac.lower(),
|
||||
'client_id': client_id}
|
||||
|
||||
if not result:
|
||||
raise utils.Error(_('No suitable interfaces found in %s') %
|
||||
|
@ -268,9 +268,8 @@ def _run_post_hooks(node_info, introspection_data):
|
||||
def _process_node(node_info, node, introspection_data):
|
||||
# NOTE(dtantsur): repeat the check in case something changed
|
||||
ir_utils.check_provision_state(node)
|
||||
|
||||
node_info.create_ports(introspection_data.get('macs') or ())
|
||||
|
||||
interfaces = introspection_data.get('interfaces')
|
||||
node_info.create_ports(list(interfaces.values()))
|
||||
_run_post_hooks(node_info, introspection_data)
|
||||
_store_data(node_info, introspection_data)
|
||||
|
||||
@ -434,7 +433,8 @@ def _reapply_with_data(node_info, introspection_data):
|
||||
'introspection on stored data:\n%s') %
|
||||
'\n'.join(failures), node_info=node_info)
|
||||
|
||||
node_info.create_ports(introspection_data.get('macs') or ())
|
||||
interfaces = introspection_data.get('interfaces')
|
||||
node_info.create_ports(list(interfaces.values()))
|
||||
_run_post_hooks(node_info, introspection_data)
|
||||
_store_data(node_info, introspection_data)
|
||||
node_info.invalidate_cache()
|
||||
|
@ -89,12 +89,21 @@ class InventoryTest(BaseTest):
|
||||
# Prepare some realistic inventory
|
||||
# https://github.com/openstack/ironic-inspector/blob/master/HTTP-API.rst # noqa
|
||||
self.bmc_address = '1.2.3.4'
|
||||
self.macs = ['11:22:33:44:55:66', '66:55:44:33:22:11']
|
||||
self.ips = ['1.2.1.2', '1.2.1.1']
|
||||
self.macs = (
|
||||
['11:22:33:44:55:66', '66:55:44:33:22:11', '7c:fe:90:29:26:52'])
|
||||
self.ips = ['1.2.1.2', '1.2.1.1', '1.2.1.3']
|
||||
self.inactive_mac = '12:12:21:12:21:12'
|
||||
self.pxe_mac = self.macs[0]
|
||||
self.all_macs = self.macs + [self.inactive_mac]
|
||||
self.pxe_iface_name = 'eth1'
|
||||
self.client_id = (
|
||||
'ff:00:00:00:00:00:02:00:00:02:c9:00:7c:fe:90:03:00:29:26:52')
|
||||
self.valid_interfaces = {
|
||||
self.pxe_iface_name: {'ip': self.ips[0], 'mac': self.macs[0],
|
||||
'client_id': None},
|
||||
'ib0': {'ip': self.ips[2], 'mac': self.macs[2],
|
||||
'client_id': self.client_id}
|
||||
}
|
||||
self.data = {
|
||||
'boot_interface': '01-' + self.pxe_mac.replace(':', '-'),
|
||||
'inventory': {
|
||||
@ -104,6 +113,9 @@ class InventoryTest(BaseTest):
|
||||
{'name': 'eth2', 'mac_address': self.inactive_mac},
|
||||
{'name': 'eth3', 'mac_address': self.macs[1],
|
||||
'ipv4_address': self.ips[1]},
|
||||
{'name': 'ib0', 'mac_address': self.macs[2],
|
||||
'ipv4_address': self.ips[2],
|
||||
'client_id': self.client_id}
|
||||
],
|
||||
'disks': [
|
||||
{'name': '/dev/sda', 'model': 'Big Data Disk',
|
||||
@ -123,16 +135,25 @@ class InventoryTest(BaseTest):
|
||||
'root_disk': {'name': '/dev/sda', 'model': 'Big Data Disk',
|
||||
'size': 1000 * units.Gi,
|
||||
'wwn': None},
|
||||
'interfaces': self.valid_interfaces,
|
||||
}
|
||||
self.inventory = self.data['inventory']
|
||||
self.all_interfaces = {
|
||||
'eth1': {'mac': self.macs[0], 'ip': self.ips[0]},
|
||||
'eth2': {'mac': self.inactive_mac, 'ip': None},
|
||||
'eth3': {'mac': self.macs[1], 'ip': self.ips[1]}
|
||||
'eth1': {'mac': self.macs[0], 'ip': self.ips[0],
|
||||
'client_id': None},
|
||||
'eth2': {'mac': self.inactive_mac, 'ip': None, 'client_id': None},
|
||||
'eth3': {'mac': self.macs[1], 'ip': self.ips[1],
|
||||
'client_id': None},
|
||||
'ib0': {'mac': self.macs[2], 'ip': self.ips[2],
|
||||
'client_id': self.client_id}
|
||||
}
|
||||
self.active_interfaces = {
|
||||
'eth1': {'mac': self.macs[0], 'ip': self.ips[0]},
|
||||
'eth3': {'mac': self.macs[1], 'ip': self.ips[1]}
|
||||
'eth1': {'mac': self.macs[0], 'ip': self.ips[0],
|
||||
'client_id': None},
|
||||
'eth3': {'mac': self.macs[1], 'ip': self.ips[1],
|
||||
'client_id': None},
|
||||
'ib0': {'mac': self.macs[2], 'ip': self.ips[2],
|
||||
'client_id': self.client_id}
|
||||
}
|
||||
self.pxe_interfaces = {
|
||||
self.pxe_iface_name: self.all_interfaces[self.pxe_iface_name]
|
||||
|
@ -249,7 +249,35 @@ class Test(Base):
|
||||
self.cli.node.update.assert_called_once_with(self.uuid, mock.ANY)
|
||||
self.assertCalledWithPatch(self.patch, self.cli.node.update)
|
||||
self.cli.port.create.assert_called_once_with(
|
||||
node_uuid=self.uuid, address='11:22:33:44:55:66')
|
||||
node_uuid=self.uuid, address='11:22:33:44:55:66', extra={})
|
||||
|
||||
status = self.call_get_status(self.uuid)
|
||||
self.check_status(status, finished=True)
|
||||
|
||||
def test_bmc_with_client_id(self):
|
||||
self.pxe_mac = self.macs[2]
|
||||
self.data['boot_interface'] = ('20-' + self.pxe_mac.replace(':', '-'))
|
||||
self.pxe_iface_name = 'ib0'
|
||||
self.pxe_interfaces = {
|
||||
self.pxe_iface_name: self.all_interfaces[self.pxe_iface_name]
|
||||
}
|
||||
self.call_introspect(self.uuid)
|
||||
eventlet.greenthread.sleep(DEFAULT_SLEEP)
|
||||
self.cli.node.set_power_state.assert_called_once_with(self.uuid,
|
||||
'reboot')
|
||||
|
||||
status = self.call_get_status(self.uuid)
|
||||
self.check_status(status, finished=False)
|
||||
|
||||
res = self.call_continue(self.data)
|
||||
self.assertEqual({'uuid': self.uuid}, res)
|
||||
eventlet.greenthread.sleep(DEFAULT_SLEEP)
|
||||
|
||||
self.cli.node.update.assert_called_once_with(self.uuid, mock.ANY)
|
||||
self.assertCalledWithPatch(self.patch, self.cli.node.update)
|
||||
self.cli.port.create.assert_called_once_with(
|
||||
node_uuid=self.uuid, address=self.macs[2],
|
||||
extra={'client-id': self.client_id})
|
||||
|
||||
status = self.call_get_status(self.uuid)
|
||||
self.check_status(status, finished=True)
|
||||
@ -279,7 +307,7 @@ class Test(Base):
|
||||
self.assertCalledWithPatch(self.patch + patch_credentials,
|
||||
self.cli.node.update)
|
||||
self.cli.port.create.assert_called_once_with(
|
||||
node_uuid=self.uuid, address='11:22:33:44:55:66')
|
||||
node_uuid=self.uuid, address='11:22:33:44:55:66', extra={})
|
||||
|
||||
status = self.call_get_status(self.uuid)
|
||||
self.check_status(status, finished=True)
|
||||
@ -482,7 +510,7 @@ class Test(Base):
|
||||
|
||||
self.assertCalledWithPatch(self.patch_root_hints, self.cli.node.update)
|
||||
self.cli.port.create.assert_called_once_with(
|
||||
node_uuid=self.uuid, address='11:22:33:44:55:66')
|
||||
node_uuid=self.uuid, address='11:22:33:44:55:66', extra={})
|
||||
|
||||
status = self.call_get_status(self.uuid)
|
||||
self.check_status(status, finished=True)
|
||||
@ -708,7 +736,7 @@ class Test(Base):
|
||||
self.cli.node.update.assert_called_once_with(self.uuid, mock.ANY)
|
||||
self.assertCalledWithPatch(self.patch, self.cli.node.update)
|
||||
self.cli.port.create.assert_called_once_with(
|
||||
node_uuid=self.uuid, address='11:22:33:44:55:66')
|
||||
node_uuid=self.uuid, extra={}, address='11:22:33:44:55:66')
|
||||
|
||||
status = self.call_get_status(self.uuid)
|
||||
self.check_status(status, finished=True)
|
||||
|
@ -26,12 +26,18 @@ from ironic_inspector.test import base as test_base
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
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
|
||||
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
|
||||
"""
|
||||
|
||||
|
||||
@mock.patch.object(firewall, '_iptables')
|
||||
@mock.patch.object(ir_utils, 'get_client')
|
||||
@mock.patch.object(subprocess, 'check_call')
|
||||
class TestFirewall(test_base.NodeTest):
|
||||
CLIENT_ID = 'ff:00:00:00:00:00:02:00:00:02:c9:00:7c:fe:90:03:00:29:24:4f'
|
||||
|
||||
def test_update_filters_without_manage_firewall(self, mock_call,
|
||||
mock_get_client,
|
||||
mock_iptables):
|
||||
@ -341,3 +347,99 @@ class TestFirewall(test_base.NodeTest):
|
||||
|
||||
mock_iptables.assert_any_call('-A', firewall.NEW_CHAIN, '-j', 'ACCEPT')
|
||||
self.assertEqual({'foobar'}, firewall.BLACKLIST_CACHE)
|
||||
|
||||
def test_update_filters_infiniband(
|
||||
self, mock_call, mock_get_client, mock_iptables):
|
||||
|
||||
CONF.set_override('ethoib_interfaces', ['eth0'], 'firewall')
|
||||
active_macs = ['11:22:33:44:55:66', '66:55:44:33:22:11']
|
||||
expected_rmac = '02:00:00:61:00:02'
|
||||
ports = [mock.Mock(address=m) for m in active_macs]
|
||||
ports.append(mock.Mock(address='7c:fe:90:29:24:4f',
|
||||
extra={'client-id': self.CLIENT_ID},
|
||||
spec=['address', 'extra']))
|
||||
mock_get_client.port.list.return_value = ports
|
||||
node_cache.add_node(self.node.uuid, mac=active_macs,
|
||||
state=istate.States.finished,
|
||||
bmc_address='1.2.3.4', foo=None)
|
||||
firewall.init()
|
||||
|
||||
update_filters_expected_args = [
|
||||
('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
|
||||
'67', '-j', CONF.firewall.firewall_chain),
|
||||
('-F', CONF.firewall.firewall_chain),
|
||||
('-X', CONF.firewall.firewall_chain),
|
||||
('-N', CONF.firewall.firewall_chain),
|
||||
('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
|
||||
'67', '-j', firewall.NEW_CHAIN),
|
||||
('-F', firewall.NEW_CHAIN),
|
||||
('-X', firewall.NEW_CHAIN),
|
||||
('-N', firewall.NEW_CHAIN),
|
||||
# Blacklist
|
||||
('-A', firewall.NEW_CHAIN, '-m', 'mac', '--mac-source',
|
||||
expected_rmac, '-j', 'DROP'),
|
||||
('-A', firewall.NEW_CHAIN, '-j', 'ACCEPT'),
|
||||
('-I', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
|
||||
'67', '-j', firewall.NEW_CHAIN),
|
||||
('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
|
||||
'67', '-j', CONF.firewall.firewall_chain),
|
||||
('-F', CONF.firewall.firewall_chain),
|
||||
('-X', CONF.firewall.firewall_chain),
|
||||
('-E', firewall.NEW_CHAIN, CONF.firewall.firewall_chain)
|
||||
]
|
||||
|
||||
fileobj = mock.mock_open(read_data=IB_DATA)
|
||||
with mock.patch('six.moves.builtins.open', fileobj, create=True):
|
||||
firewall.update_filters(mock_get_client)
|
||||
call_args_list = mock_iptables.call_args_list
|
||||
|
||||
for (args, call) in zip(update_filters_expected_args,
|
||||
call_args_list):
|
||||
self.assertEqual(args, call[0])
|
||||
|
||||
def test_update_filters_infiniband_no_such_file(
|
||||
self, mock_call, mock_get_client, mock_iptables):
|
||||
|
||||
CONF.set_override('ethoib_interfaces', ['eth0'], 'firewall')
|
||||
active_macs = ['11:22:33:44:55:66', '66:55:44:33:22:11']
|
||||
ports = [mock.Mock(address=m) for m in active_macs]
|
||||
ports.append(mock.Mock(address='7c:fe:90:29:24:4f',
|
||||
extra={'client-id': self.CLIENT_ID},
|
||||
spec=['address', 'extra']))
|
||||
mock_get_client.port.list.return_value = ports
|
||||
node_cache.add_node(self.node.uuid, mac=active_macs,
|
||||
state=istate.States.finished,
|
||||
bmc_address='1.2.3.4', foo=None)
|
||||
firewall.init()
|
||||
|
||||
update_filters_expected_args = [
|
||||
('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
|
||||
'67', '-j', CONF.firewall.firewall_chain),
|
||||
('-F', CONF.firewall.firewall_chain),
|
||||
('-X', CONF.firewall.firewall_chain),
|
||||
('-N', CONF.firewall.firewall_chain),
|
||||
('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
|
||||
'67', '-j', firewall.NEW_CHAIN),
|
||||
('-F', firewall.NEW_CHAIN),
|
||||
('-X', firewall.NEW_CHAIN),
|
||||
('-N', firewall.NEW_CHAIN),
|
||||
# Blacklist
|
||||
('-A', firewall.NEW_CHAIN, '-m', 'mac', '--mac-source',
|
||||
'7c:fe:90:29:24:4f', '-j', 'DROP'),
|
||||
('-A', firewall.NEW_CHAIN, '-j', 'ACCEPT'),
|
||||
('-I', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
|
||||
'67', '-j', firewall.NEW_CHAIN),
|
||||
('-D', 'INPUT', '-i', 'br-ctlplane', '-p', 'udp', '--dport',
|
||||
'67', '-j', CONF.firewall.firewall_chain),
|
||||
('-F', CONF.firewall.firewall_chain),
|
||||
('-X', CONF.firewall.firewall_chain),
|
||||
('-E', firewall.NEW_CHAIN, CONF.firewall.firewall_chain)
|
||||
]
|
||||
|
||||
with mock.patch('six.moves.builtins.open', side_effect=IOError()):
|
||||
firewall.update_filters(mock_get_client)
|
||||
call_args_list = mock_iptables.call_args_list
|
||||
|
||||
for (args, call) in zip(update_filters_expected_args,
|
||||
call_args_list):
|
||||
self.assertEqual(args, call[0])
|
||||
|
@ -70,7 +70,8 @@ class TestNodeCache(test_base.NodeTest):
|
||||
order_by(db.Attribute.name, db.Attribute.value).all())
|
||||
self.assertEqual([('bmc_address', '1.2.3.4', self.uuid),
|
||||
('mac', self.macs[0], self.uuid),
|
||||
('mac', self.macs[1], self.uuid)],
|
||||
('mac', self.macs[1], self.uuid),
|
||||
('mac', self.macs[2], self.uuid)],
|
||||
[(row.name, row.value, row.uuid) for row in res])
|
||||
|
||||
def test__delete_node(self):
|
||||
|
@ -347,6 +347,10 @@ class TestProcessNode(BaseTest):
|
||||
'processing')
|
||||
self.validate_attempts = 5
|
||||
self.data['macs'] = self.macs # validate_interfaces hook
|
||||
self.valid_interfaces['eth3'] = {
|
||||
'mac': self.macs[1], 'ip': self.ips[1], 'extra': {}
|
||||
}
|
||||
self.data['interfaces'] = self.valid_interfaces
|
||||
self.ports = self.all_ports
|
||||
|
||||
self.new_creds = ('user', 'password')
|
||||
@ -398,9 +402,11 @@ class TestProcessNode(BaseTest):
|
||||
process._process_node(self.node_info, self.node, self.data)
|
||||
|
||||
self.cli.port.create.assert_any_call(node_uuid=self.uuid,
|
||||
address=self.macs[0])
|
||||
address=self.macs[0],
|
||||
extra={})
|
||||
self.cli.port.create.assert_any_call(node_uuid=self.uuid,
|
||||
address=self.macs[1])
|
||||
address=self.macs[1],
|
||||
extra={})
|
||||
self.cli.node.set_power_state.assert_called_once_with(self.uuid, 'off')
|
||||
self.assertFalse(self.cli.node.validate.called)
|
||||
|
||||
@ -414,9 +420,11 @@ class TestProcessNode(BaseTest):
|
||||
process._process_node(self.node_info, self.node, self.data)
|
||||
|
||||
self.cli.port.create.assert_any_call(node_uuid=self.uuid,
|
||||
address=self.macs[0])
|
||||
address=self.macs[0],
|
||||
extra={})
|
||||
self.cli.port.create.assert_any_call(node_uuid=self.uuid,
|
||||
address=self.macs[1])
|
||||
address=self.macs[1],
|
||||
extra={})
|
||||
|
||||
def test_set_ipmi_credentials(self):
|
||||
self.node_info.set_option('new_ipmi_credentials', self.new_creds)
|
||||
@ -653,7 +661,8 @@ class TestReapplyNode(BaseTest):
|
||||
# behind validate_interfaces
|
||||
self.cli.port.create.assert_called_once_with(
|
||||
node_uuid=self.uuid,
|
||||
address=swifted_data['macs'][0]
|
||||
address=swifted_data['macs'][0],
|
||||
extra={}
|
||||
)
|
||||
|
||||
@prepare_mocks
|
||||
@ -707,7 +716,6 @@ class TestReapplyNode(BaseTest):
|
||||
swift_mock.get_object.return_value = json.dumps(self.data)
|
||||
exc = Exception('Oops')
|
||||
self.cli.port.create.side_effect = exc
|
||||
|
||||
self.call()
|
||||
|
||||
finished_mock.assert_called_once_with(self.node_info, error=str(exc))
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
InfiniBand interface discovery is supported in the introspection.
|
||||
Therefore the ironic-inspector will add the client-id to the corresponding
|
||||
ironic port that represent InfiniBand interface. The ironic-inspector
|
||||
should be configured with ``firewall.ethoib_interfaces`` to indicate what are
|
||||
Ethernet Over InfiniBand Interfaces that are used for DHCP.
|
Loading…
Reference in New Issue
Block a user