diff options
| author | Moshe Levi <moshele@mellanox.com> | 2016-11-17 02:11:04 +0200 |
|---|---|---|
| committer | Szymon Borkowski <szymon.borkowski@intel.com> | 2016-11-22 15:46:12 +0100 |
| commit | 1bdcd4449f6dc3c39cffcefa8efba243b7c00456 (patch) | |
| tree | 7da005a297795141feb1859d0bbba4deba2cf8ac /ironic_python_agent | |
| parent | f9236682f7bb7048e607bd9235cf14d2088806ff (diff) | |
| download | ironic-python-agent-1bdcd4449f6dc3c39cffcefa8efba243b7c00456.tar.gz | |
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
Diffstat (limited to 'ironic_python_agent')
| -rw-r--r-- | ironic_python_agent/hardware.py | 9 | ||||
| -rw-r--r-- | ironic_python_agent/hardware_managers/__init__.py | 0 | ||||
| -rw-r--r-- | ironic_python_agent/hardware_managers/mlnx.py | 111 | ||||
| -rw-r--r-- | ironic_python_agent/netutils.py | 1 | ||||
| -rwxr-xr-x | ironic_python_agent/tests/unit/hardware_managers/__init__.py | 0 | ||||
| -rwxr-xr-x | ironic_python_agent/tests/unit/hardware_managers/test_mlnx.py | 130 |
6 files changed, 249 insertions, 2 deletions
diff --git a/ironic_python_agent/hardware.py b/ironic_python_agent/hardware.py index 7c878775..f3059499 100644 --- a/ironic_python_agent/hardware.py +++ b/ironic_python_agent/hardware.py @@ -207,10 +207,11 @@ class BlockDevice(encoding.SerializableComparable): class NetworkInterface(encoding.SerializableComparable): serializable_fields = ('name', 'mac_address', 'switch_port_descr', '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, - lldp=None, vendor=None, product=None): + lldp=None, vendor=None, product=None, client_id=None): self.name = name self.mac_address = mac_addr self.ipv4_address = ipv4_address @@ -218,6 +219,10 @@ class NetworkInterface(encoding.SerializableComparable): self.lldp = lldp self.vendor = vendor 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 # superseded by self.lldp self.switch_port_descr = None diff --git a/ironic_python_agent/hardware_managers/__init__.py b/ironic_python_agent/hardware_managers/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ironic_python_agent/hardware_managers/__init__.py diff --git a/ironic_python_agent/hardware_managers/mlnx.py b/ironic_python_agent/hardware_managers/mlnx.py new file mode 100644 index 00000000..63ef5b32 --- /dev/null +++ b/ironic_python_agent/hardware_managers/mlnx.py @@ -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) diff --git a/ironic_python_agent/netutils.py b/ironic_python_agent/netutils.py index f1078fb8..55c7a3eb 100644 --- a/ironic_python_agent/netutils.py +++ b/ironic_python_agent/netutils.py @@ -30,6 +30,7 @@ LLDP_ETHERTYPE = 0x88cc IFF_PROMISC = 0x100 SIOCGIFFLAGS = 0x8913 SIOCSIFFLAGS = 0x8914 +INFINIBAND_ADDR_LEN = 59 class ifreq(ctypes.Structure): diff --git a/ironic_python_agent/tests/unit/hardware_managers/__init__.py b/ironic_python_agent/tests/unit/hardware_managers/__init__.py new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/ironic_python_agent/tests/unit/hardware_managers/__init__.py diff --git a/ironic_python_agent/tests/unit/hardware_managers/test_mlnx.py b/ironic_python_agent/tests/unit/hardware_managers/test_mlnx.py new file mode 100755 index 00000000..5341b46c --- /dev/null +++ b/ironic_python_agent/tests/unit/hardware_managers/test_mlnx.py @@ -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') |
