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):
|
||||
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
|
||||
|
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
|
||||
SIOCGIFFLAGS = 0x8913
|
||||
SIOCSIFFLAGS = 0x8914
|
||||
INFINIBAND_ADDR_LEN = 59
|
||||
|
||||
|
||||
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 =
|
||||
generic = ironic_python_agent.hardware:GenericHardwareManager
|
||||
mlnx = ironic_python_agent.hardware_managers.mlnx:MellanoxDeviceHardwareManager
|
||||
|
||||
ironic_python_agent.inspector.collectors =
|
||||
default = ironic_python_agent.inspector:collect_default
|
||||
|
Loading…
Reference in New Issue
Block a user