summaryrefslogtreecommitdiff
path: root/ironic_python_agent
diff options
context:
space:
mode:
authorMoshe Levi <moshele@mellanox.com>2016-11-17 02:11:04 +0200
committerSzymon Borkowski <szymon.borkowski@intel.com>2016-11-22 15:46:12 +0100
commit1bdcd4449f6dc3c39cffcefa8efba243b7c00456 (patch)
tree7da005a297795141feb1859d0bbba4deba2cf8ac /ironic_python_agent
parentf9236682f7bb7048e607bd9235cf14d2088806ff (diff)
downloadironic-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.py9
-rw-r--r--ironic_python_agent/hardware_managers/__init__.py0
-rw-r--r--ironic_python_agent/hardware_managers/mlnx.py111
-rw-r--r--ironic_python_agent/netutils.py1
-rwxr-xr-xironic_python_agent/tests/unit/hardware_managers/__init__.py0
-rwxr-xr-xironic_python_agent/tests/unit/hardware_managers/test_mlnx.py130
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')