Add a new Hardware Manager for Mellanox NICs
This patch add Mellanox Manager to support Mellanox InfiniBand NICs. It adds client_id to the NetworkInterface for the InfiniBand network interface. The Mellanox Manager provides it own implementation of get_interface_info. The mlnx get_interface_info generate InfiniBand MAC and client-id from the InfiniBand network interface address. Closes-Bug: #1532534 Change-Id: I4e7f7649a1bdeaa3ee99b2748037b0f37fea486c
This commit is contained in:
parent
f9236682f7
commit
1bdcd4449f
@ -207,10 +207,11 @@ class BlockDevice(encoding.SerializableComparable):
|
|||||||
class NetworkInterface(encoding.SerializableComparable):
|
class NetworkInterface(encoding.SerializableComparable):
|
||||||
serializable_fields = ('name', 'mac_address', 'switch_port_descr',
|
serializable_fields = ('name', 'mac_address', 'switch_port_descr',
|
||||||
'switch_chassis_descr', 'ipv4_address',
|
'switch_chassis_descr', 'ipv4_address',
|
||||||
'has_carrier', 'lldp', 'vendor', 'product')
|
'has_carrier', 'lldp', 'vendor', 'product',
|
||||||
|
'client_id')
|
||||||
|
|
||||||
def __init__(self, name, mac_addr, ipv4_address=None, has_carrier=True,
|
def __init__(self, name, mac_addr, ipv4_address=None, has_carrier=True,
|
||||||
lldp=None, vendor=None, product=None):
|
lldp=None, vendor=None, product=None, client_id=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.mac_address = mac_addr
|
self.mac_address = mac_addr
|
||||||
self.ipv4_address = ipv4_address
|
self.ipv4_address = ipv4_address
|
||||||
@ -218,6 +219,10 @@ class NetworkInterface(encoding.SerializableComparable):
|
|||||||
self.lldp = lldp
|
self.lldp = lldp
|
||||||
self.vendor = vendor
|
self.vendor = vendor
|
||||||
self.product = product
|
self.product = product
|
||||||
|
# client_id is used for InfiniBand only. we calculate the DHCP
|
||||||
|
# client identifier Option to allow DHCP to work over InfiniBand.
|
||||||
|
# see https://tools.ietf.org/html/rfc4390
|
||||||
|
self.client_id = client_id
|
||||||
# TODO(sambetts) Remove these fields in Ocata, they have been
|
# TODO(sambetts) Remove these fields in Ocata, they have been
|
||||||
# superseded by self.lldp
|
# superseded by self.lldp
|
||||||
self.switch_port_descr = None
|
self.switch_port_descr = None
|
||||||
|
0
ironic_python_agent/hardware_managers/__init__.py
Normal file
0
ironic_python_agent/hardware_managers/__init__.py
Normal file
111
ironic_python_agent/hardware_managers/mlnx.py
Normal file
111
ironic_python_agent/hardware_managers/mlnx.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
# Copyright 2016 Mellanox Technologies, Ltd
|
||||||
|
#
|
||||||
|
# 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 os
|
||||||
|
|
||||||
|
from ironic_python_agent import errors
|
||||||
|
from ironic_python_agent import hardware
|
||||||
|
from ironic_python_agent import netutils
|
||||||
|
from oslo_log import log
|
||||||
|
|
||||||
|
LOG = log.getLogger()
|
||||||
|
# Mellanox NIC Vendor ID
|
||||||
|
MLNX_VENDOR_ID = '0x15b3'
|
||||||
|
# Mellanox Prefix to generate InfiniBand CLient-ID
|
||||||
|
MLNX_INFINIBAND_CLIENT_ID_PREFIX = 'ff:00:00:00:00:00:02:00:00:02:c9:00:'
|
||||||
|
|
||||||
|
|
||||||
|
def _infiniband_address_to_mac(address):
|
||||||
|
"""Convert InfiniBand address to MAC
|
||||||
|
|
||||||
|
Convert InfiniBand address to MAC by Mellanox specific
|
||||||
|
translation. The InfiniBand address is 59 characters
|
||||||
|
composed from GID:GUID. The last 24 characters are the
|
||||||
|
GUID. The InfiniBand MAC is upper 10 characters and lower
|
||||||
|
9 characters from the GUID
|
||||||
|
Example:
|
||||||
|
address - a0:00:00:27:fe:80:00:00:00:00:00:00:7c:fe:90:03:00:29:26:52
|
||||||
|
GUID - 7c:fe:90:03:00:29:26:52
|
||||||
|
InfiniBand MAC - 7c:fe:90:29:26:52
|
||||||
|
|
||||||
|
:param address: InfiniBand Address.
|
||||||
|
:returns: InfiniBand MAC.
|
||||||
|
"""
|
||||||
|
return address[36:-14] + address[51:]
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_client_id(address):
|
||||||
|
"""Generate client id from InfiniBand address
|
||||||
|
|
||||||
|
:param address: InfiniBand address.
|
||||||
|
:returns: client id.
|
||||||
|
"""
|
||||||
|
return MLNX_INFINIBAND_CLIENT_ID_PREFIX + address[36:]
|
||||||
|
|
||||||
|
|
||||||
|
def _detect_hardware():
|
||||||
|
"""method for detection of Mellanox NICs
|
||||||
|
|
||||||
|
:return True/False
|
||||||
|
"""
|
||||||
|
iface_names = os.listdir('/sys/class/net')
|
||||||
|
for ifname in iface_names:
|
||||||
|
if (hardware._get_device_info(ifname, 'net', 'vendor') ==
|
||||||
|
MLNX_VENDOR_ID):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class MellanoxDeviceHardwareManager(hardware.HardwareManager):
|
||||||
|
"""Mellanox hardware manager to support a single device"""
|
||||||
|
|
||||||
|
HARDWARE_MANAGER_NAME = 'MellanoxDeviceHardwareManager'
|
||||||
|
HARDWARE_MANAGER_VERSION = '1'
|
||||||
|
|
||||||
|
def evaluate_hardware_support(self):
|
||||||
|
"""Declare level of hardware support provided."""
|
||||||
|
|
||||||
|
if _detect_hardware():
|
||||||
|
LOG.debug('Found Mellanox device')
|
||||||
|
return hardware.HardwareSupport.MAINLINE
|
||||||
|
else:
|
||||||
|
LOG.debug('No Mellanox devices found')
|
||||||
|
return hardware.HardwareSupport.NONE
|
||||||
|
|
||||||
|
def get_interface_info(self, interface_name):
|
||||||
|
"""Return the interface information when its Mellanox and InfiniBand
|
||||||
|
|
||||||
|
In case of Mellanox and InfiniBand interface we do the following:
|
||||||
|
1. Calculate the "InfiniBand MAC" according to InfiniBand GUID
|
||||||
|
2. Calculate the client-id according to InfiniBand GUID
|
||||||
|
"""
|
||||||
|
|
||||||
|
addr_path = '/sys/class/net/{0}/address'.format(interface_name)
|
||||||
|
with open(addr_path) as addr_file:
|
||||||
|
address = addr_file.read().strip()
|
||||||
|
vendor = hardware._get_device_info(interface_name, 'net', 'vendor')
|
||||||
|
if (len(address) != netutils.INFINIBAND_ADDR_LEN or
|
||||||
|
vendor != MLNX_VENDOR_ID):
|
||||||
|
raise errors.IncompatibleHardwareMethodError()
|
||||||
|
|
||||||
|
mac_addr = _infiniband_address_to_mac(address)
|
||||||
|
client_id = _generate_client_id(address)
|
||||||
|
|
||||||
|
return hardware.NetworkInterface(
|
||||||
|
interface_name, mac_addr,
|
||||||
|
ipv4_address=netutils.get_ipv4_addr(interface_name),
|
||||||
|
has_carrier=netutils.interface_has_carrier(interface_name),
|
||||||
|
lldp=None,
|
||||||
|
vendor=vendor,
|
||||||
|
product=hardware._get_device_info(interface_name, 'net', 'device'),
|
||||||
|
client_id=client_id)
|
@ -30,6 +30,7 @@ LLDP_ETHERTYPE = 0x88cc
|
|||||||
IFF_PROMISC = 0x100
|
IFF_PROMISC = 0x100
|
||||||
SIOCGIFFLAGS = 0x8913
|
SIOCGIFFLAGS = 0x8913
|
||||||
SIOCSIFFLAGS = 0x8914
|
SIOCSIFFLAGS = 0x8914
|
||||||
|
INFINIBAND_ADDR_LEN = 59
|
||||||
|
|
||||||
|
|
||||||
class ifreq(ctypes.Structure):
|
class ifreq(ctypes.Structure):
|
||||||
|
0
ironic_python_agent/tests/unit/hardware_managers/__init__.py
Executable file
0
ironic_python_agent/tests/unit/hardware_managers/__init__.py
Executable file
130
ironic_python_agent/tests/unit/hardware_managers/test_mlnx.py
Executable file
130
ironic_python_agent/tests/unit/hardware_managers/test_mlnx.py
Executable file
@ -0,0 +1,130 @@
|
|||||||
|
# Copyright 2016 Mellanox Technologies, Ltd
|
||||||
|
#
|
||||||
|
# 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 os
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from oslotest import base as test_base
|
||||||
|
|
||||||
|
from ironic_python_agent import errors
|
||||||
|
from ironic_python_agent import hardware
|
||||||
|
from ironic_python_agent.hardware_managers import mlnx
|
||||||
|
|
||||||
|
IB_ADDRESS = 'a0:00:00:27:fe:80:00:00:00:00:00:00:7c:fe:90:03:00:29:26:52'
|
||||||
|
CLIENT_ID = 'ff:00:00:00:00:00:02:00:00:02:c9:00:7c:fe:90:03:00:29:26:52'
|
||||||
|
|
||||||
|
|
||||||
|
class MlnxHardwareManager(test_base.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(MlnxHardwareManager, self).setUp()
|
||||||
|
self.hardware = mlnx.MellanoxDeviceHardwareManager()
|
||||||
|
self.node = {'uuid': 'dda135fb-732d-4742-8e72-df8f3199d244',
|
||||||
|
'driver_internal_info': {}}
|
||||||
|
|
||||||
|
def test_infiniband_address_to_mac(self):
|
||||||
|
self.assertEqual(
|
||||||
|
'7c:fe:90:29:26:52',
|
||||||
|
mlnx._infiniband_address_to_mac(IB_ADDRESS))
|
||||||
|
|
||||||
|
def test_generate_client_id(self):
|
||||||
|
self.assertEqual(
|
||||||
|
CLIENT_ID,
|
||||||
|
mlnx._generate_client_id(IB_ADDRESS))
|
||||||
|
|
||||||
|
@mock.patch.object(os, 'listdir')
|
||||||
|
@mock.patch('six.moves.builtins.open')
|
||||||
|
def test_detect_hardware(self, mocked_open, mock_listdir):
|
||||||
|
mock_listdir.return_value = ['eth0', 'ib0']
|
||||||
|
mocked_open.return_value.__enter__ = lambda s: s
|
||||||
|
mocked_open.return_value.__exit__ = mock.Mock()
|
||||||
|
read_mock = mocked_open.return_value.read
|
||||||
|
read_mock.side_effect = ['0x8086\n', '0x15b3\n']
|
||||||
|
self.assertTrue(mlnx._detect_hardware())
|
||||||
|
|
||||||
|
@mock.patch.object(os, 'listdir')
|
||||||
|
@mock.patch('six.moves.builtins.open')
|
||||||
|
def test_detect_hardware_no_mlnx(self, mocked_open, mock_listdir):
|
||||||
|
mock_listdir.return_value = ['eth0', 'eth1']
|
||||||
|
mocked_open.return_value.__enter__ = lambda s: s
|
||||||
|
mocked_open.return_value.__exit__ = mock.Mock()
|
||||||
|
read_mock = mocked_open.return_value.read
|
||||||
|
read_mock.side_effect = ['0x8086\n', '0x8086\n']
|
||||||
|
self.assertFalse(mlnx._detect_hardware())
|
||||||
|
|
||||||
|
@mock.patch.object(os, 'listdir')
|
||||||
|
@mock.patch('six.moves.builtins.open')
|
||||||
|
def test_detect_hardware_error(self, mocked_open, mock_listdir):
|
||||||
|
mock_listdir.return_value = ['eth0', 'ib0']
|
||||||
|
mocked_open.return_value.__enter__ = lambda s: s
|
||||||
|
mocked_open.return_value.__exit__ = mock.Mock()
|
||||||
|
read_mock = mocked_open.return_value.read
|
||||||
|
read_mock.side_effect = ['0x8086\n', OSError('boom')]
|
||||||
|
self.assertFalse(mlnx._detect_hardware())
|
||||||
|
|
||||||
|
@mock.patch.object(os, 'listdir')
|
||||||
|
@mock.patch('six.moves.builtins.open')
|
||||||
|
def test_evaluate_hardware_support(self, mocked_open, mock_listdir):
|
||||||
|
mock_listdir.return_value = ['eth0', 'ib0']
|
||||||
|
mocked_open.return_value.__enter__ = lambda s: s
|
||||||
|
mocked_open.return_value.__exit__ = mock.Mock()
|
||||||
|
read_mock = mocked_open.return_value.read
|
||||||
|
read_mock.side_effect = ['0x8086\n', '0x15b3\n']
|
||||||
|
self.assertEqual(
|
||||||
|
hardware.HardwareSupport.MAINLINE,
|
||||||
|
self.hardware.evaluate_hardware_support())
|
||||||
|
|
||||||
|
@mock.patch.object(os, 'listdir')
|
||||||
|
@mock.patch('six.moves.builtins.open')
|
||||||
|
def test_evaluate_hardware_support_no_mlnx(
|
||||||
|
self, mocked_open, mock_listdir):
|
||||||
|
mock_listdir.return_value = ['eth0', 'eth1']
|
||||||
|
mocked_open.return_value.__enter__ = lambda s: s
|
||||||
|
mocked_open.return_value.__exit__ = mock.Mock()
|
||||||
|
read_mock = mocked_open.return_value.read
|
||||||
|
read_mock.side_effect = ['0x8086\n', '0x8086\n']
|
||||||
|
self.assertEqual(
|
||||||
|
hardware.HardwareSupport.NONE,
|
||||||
|
self.hardware.evaluate_hardware_support())
|
||||||
|
|
||||||
|
@mock.patch('six.moves.builtins.open')
|
||||||
|
def test_get_interface_info(self, mocked_open):
|
||||||
|
mocked_open.return_value.__enter__ = lambda s: s
|
||||||
|
mocked_open.return_value.__exit__ = mock.Mock()
|
||||||
|
read_mock = mocked_open.return_value.read
|
||||||
|
read_mock.side_effect = [IB_ADDRESS, '0x15b3\n']
|
||||||
|
network_interface = self.hardware.get_interface_info('ib0')
|
||||||
|
self.assertEqual('ib0', network_interface.name)
|
||||||
|
self.assertEqual('7c:fe:90:29:26:52', network_interface.mac_address)
|
||||||
|
self.assertEqual('0x15b3', network_interface.vendor)
|
||||||
|
self.assertEqual(CLIENT_ID, network_interface.client_id)
|
||||||
|
|
||||||
|
@mock.patch('six.moves.builtins.open')
|
||||||
|
def test_get_interface_info_no_ib_interface(self, mocked_open):
|
||||||
|
mocked_open.return_value.__enter__ = lambda s: s
|
||||||
|
mocked_open.return_value.__exit__ = mock.Mock()
|
||||||
|
read_mock = mocked_open.return_value.read
|
||||||
|
read_mock.side_effect = ['7c:fe:90:29:26:52', '0x15b3\n']
|
||||||
|
self.assertRaises(
|
||||||
|
errors.IncompatibleHardwareMethodError,
|
||||||
|
self.hardware.get_interface_info, 'eth0')
|
||||||
|
|
||||||
|
@mock.patch('six.moves.builtins.open')
|
||||||
|
def test_get_interface_info_no_mlnx_interface(self, mocked_open):
|
||||||
|
mocked_open.return_value.__enter__ = lambda s: s
|
||||||
|
mocked_open.return_value.__exit__ = mock.Mock()
|
||||||
|
read_mock = mocked_open.return_value.read
|
||||||
|
read_mock.side_effect = [IB_ADDRESS, '0x8086\n']
|
||||||
|
self.assertRaises(
|
||||||
|
errors.IncompatibleHardwareMethodError,
|
||||||
|
self.hardware.get_interface_info, 'ib0')
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Add support for Mellanox InfiniBand NIC in IPA.
|
||||||
|
Each Mellanox InfiniBand interface returned with
|
||||||
|
"InfiniBand MAC" and InfiniBand Client-ID according
|
||||||
|
to DHCP over InfiniBand https://tools.ietf.org/html/rfc4390.
|
@ -29,6 +29,7 @@ ironic_python_agent.extensions =
|
|||||||
|
|
||||||
ironic_python_agent.hardware_managers =
|
ironic_python_agent.hardware_managers =
|
||||||
generic = ironic_python_agent.hardware:GenericHardwareManager
|
generic = ironic_python_agent.hardware:GenericHardwareManager
|
||||||
|
mlnx = ironic_python_agent.hardware_managers.mlnx:MellanoxDeviceHardwareManager
|
||||||
|
|
||||||
ironic_python_agent.inspector.collectors =
|
ironic_python_agent.inspector.collectors =
|
||||||
default = ironic_python_agent.inspector:collect_default
|
default = ironic_python_agent.inspector:collect_default
|
||||||
|
Loading…
x
Reference in New Issue
Block a user