Permalink
Browse files

Add support for Infiniband network interfaces

OpenStack ironic references Infiniband interfaces via a 6 byte 'MAC
address' formed from bytes 13-15 and 18-20 of interface's hardware
address. This address is used as the ethernet_mac_address of Infiniband
links in network_data.json in configdrives generated by OpenStack nova.
We can use this address to map links in network_data.json to their
corresponding interface names.

When generating interface configuration files, we need to use the
interface's full hardware address as the HWADDR, rather than the 6 byte
MAC address provided by network_data.json.

This change allows IB interfaces to be referenced in this dual mode - by
MAC address and hardware address, depending on the context.

This change does not implement support for writing out ifcfg files on
RHEL systems with TYPE=Infiniband - these files are generated with
TYPE=Ethernet. There may be some downsides to this.

Conflicts:
	cloudinit/net/__init__.py
	tests/unittests/test_net.py
  • Loading branch information...
markgoddard committed Feb 16, 2018
1 parent ca321ae commit 0fec13275831c857ff4c1c0bb0c14f8fef9abb28
Showing with 144 additions and 7 deletions.
  1. +54 −6 cloudinit/net/__init__.py
  2. +10 −0 cloudinit/sources/helpers/openstack.py
  3. +80 −1 tests/unittests/test_net.py
@@ -376,6 +376,20 @@ def get_interface_mac(ifname):
return read_sys_net_safe(ifname, path)
def get_ib_interface_hwaddr(ifname, ethernet_format):
"""Returns the string value of an Infiniband interface's hardware
address. If ethernet_format is True, an Ethernet MAC-style 6 byte
representation of the address will be returned.
"""
# Type 32 is Infiniband.
if read_sys_net_safe(ifname, 'type') == '32':
mac = get_interface_mac(ifname)
if mac and ethernet_format:
# Use bytes 13-15 and 18-20 of the hardware address.
mac = mac[36:-14] + mac[51:]
return mac
def get_interfaces_by_mac():
"""Build a dictionary of tuples {mac: name}.
@@ -395,13 +409,47 @@ def get_interfaces_by_mac():
continue
mac = get_interface_mac(name)
# some devices may not have a mac (tun0)
if not mac:
if mac:
if mac in ret:
raise RuntimeError(
"duplicate mac found! both '%s' and '%s' have mac '%s'" %
(name, ret[mac], mac))
ret[mac] = name
# Try to get an Infiniband hardware address (in 6 byte Ethernet format)
# for the interface.
ib_mac = get_ib_interface_hwaddr(name, True)
if ib_mac:
if ib_mac in ret:
raise RuntimeError(
"duplicate mac found! both '%s' and '%s' have mac '%s'" %
(name, ret[mac], mac))
ret[ib_mac] = name
return ret
def get_ib_hwaddrs_by_interface():
"""Build a dictionary mapping Infiniband interface names to their hardware
address."""
try:
devs = get_devicelist()
except OSError as e:
if e.errno == errno.ENOENT:
devs = []
else:
raise
ret = {}
for name in devs:
if not interface_has_own_mac(name):
continue
if is_bridge(name):
continue
if mac in ret:
raise RuntimeError(
"duplicate mac found! both '%s' and '%s' have mac '%s'" %
(name, ret[mac], mac))
ret[mac] = name
ib_mac = get_ib_interface_hwaddr(name, False)
if ib_mac:
if ib_mac in ret:
raise RuntimeError(
"duplicate mac found! both '%s' and '%s' have mac '%s'" %
(name, ret[mac], mac))
ret[name] = ib_mac
return ret
# vi: ts=4 expandtab
@@ -658,6 +658,16 @@ def convert_net_json(network_json=None, known_macs=None):
else:
cfg[key] = fmt % link_id_info[target]['name']
# Infiniband interfaces may be referenced in network_data.json by a 6 byte
# Ethernet MAC-style address, and we use that address to look up the
# interface name above. Now ensure that the hardware address is set to the
# full 20 byte address.
ib_known_hwaddrs = net.get_ib_hwaddrs_by_interface()
if ib_known_hwaddrs:
for cfg in config:
if cfg['name'] in ib_known_hwaddrs:
cfg['mac_address'] = ib_known_hwaddrs[cfg['name']]
for service in services:
cfg = service
cfg.update({'type': 'nameserver'})
@@ -1193,10 +1193,14 @@ def _se_is_bridge(self, name):
def _se_interface_has_own_mac(self, name):
return name in self.data['own_macs']
def _se_get_ib_interface_hwaddr(self, name, ethernet_format):
ib_hwaddr = self.data.get('ib_hwaddr', {})
return ib_hwaddr.get(name, {}).get(ethernet_format)
def _mock_setup(self):
self.data = copy.deepcopy(self._data)
mocks = ('get_devicelist', 'get_interface_mac', 'is_bridge',
'interface_has_own_mac')
'interface_has_own_mac', 'get_ib_interface_hwaddr')
self.mocks = {}
for n in mocks:
m = mock.patch('cloudinit.net.' + n,
@@ -1242,6 +1246,81 @@ def test_excludes_bridges(self):
mock.call('b1')],
any_order=True)
def test_ib(self):
ib_addr = '80:00:00:28:fe:80:00:00:00:00:00:00:00:11:22:03:00:33:44:56'
ib_addr_eth_format = '00:11:22:33:44:56'
self._mock_setup()
self.data['devices'] = ['enp0s1', 'ib0']
self.data['own_macs'].append('ib0')
self.data['macs']['ib0'] = ib_addr
self.data['ib_hwaddr'] = {'ib0': {True: ib_addr_eth_format,
False: ib_addr}}
result = net.get_interfaces_by_mac()
expected = {'aa:aa:aa:aa:aa:01': 'enp0s1',
ib_addr_eth_format: 'ib0', ib_addr: 'ib0'}
self.assertEqual(expected, result)
class TestGetIBHwaddrsByInterface(TestCase):
_ib_addr = '80:00:00:28:fe:80:00:00:00:00:00:00:00:11:22:03:00:33:44:56'
_ib_addr_eth_format = '00:11:22:33:44:56'
_data = {'devices': ['enp0s1', 'enp0s2', 'bond1', 'bridge1',
'bridge1-nic', 'tun0', 'ib0'],
'bonds': ['bond1'],
'bridges': ['bridge1'],
'own_macs': ['enp0s1', 'enp0s2', 'bridge1-nic', 'bridge1', 'ib0'],
'macs': {'enp0s1': 'aa:aa:aa:aa:aa:01',
'enp0s2': 'aa:aa:aa:aa:aa:02',
'bond1': 'aa:aa:aa:aa:aa:01',
'bridge1': 'aa:aa:aa:aa:aa:03',
'bridge1-nic': 'aa:aa:aa:aa:aa:03',
'tun0': None,
'ib0': _ib_addr},
'ib_hwaddr': {'ib0': {True: _ib_addr_eth_format,
False: _ib_addr}}}
data = {}
def _mock_setup(self):
self.data = copy.deepcopy(self._data)
mocks = ('get_devicelist', 'get_interface_mac', 'is_bridge',
'interface_has_own_mac', 'get_ib_interface_hwaddr')
self.mocks = {}
for n in mocks:
m = mock.patch('cloudinit.net.' + n,
side_effect=getattr(self, '_se_' + n))
self.addCleanup(m.stop)
self.mocks[n] = m.start()
def _se_get_devicelist(self):
return self.data['devices']
def _se_get_interface_mac(self, name):
return self.data['macs'][name]
def _se_is_bridge(self, name):
return name in self.data['bridges']
def _se_interface_has_own_mac(self, name):
return name in self.data['own_macs']
def _se_get_ib_interface_hwaddr(self, name, ethernet_format):
ib_hwaddr = self.data.get('ib_hwaddr', {})
return ib_hwaddr.get(name, {}).get(ethernet_format)
def test_ethernet(self):
self._mock_setup()
self.data['devices'].remove('ib0')
result = net.get_ib_hwaddrs_by_interface()
expected = {}
self.assertEqual(expected, result)
def test_ib(self):
self._mock_setup()
result = net.get_ib_hwaddrs_by_interface()
expected = {'ib0': self._ib_addr}
self.assertEqual(expected, result)
def _gzip_data(data):
with io.BytesIO() as iobuf:

0 comments on commit 0fec132

Please sign in to comment.