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:
Moshe Levi 2016-11-17 02:11:04 +02:00 committed by Szymon Borkowski
parent f9236682f7
commit 1bdcd4449f
8 changed files with 256 additions and 2 deletions

View File

@ -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

View 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)

View File

@ -30,6 +30,7 @@ LLDP_ETHERTYPE = 0x88cc
IFF_PROMISC = 0x100
SIOCGIFFLAGS = 0x8913
SIOCSIFFLAGS = 0x8914
INFINIBAND_ADDR_LEN = 59
class ifreq(ctypes.Structure):

View 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')

View File

@ -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.

View File

@ -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