Julia Kreger eb95273ffb Add get_service_steps logic to the agent
Initial code patches for service steps have merged in
ironic, and it is now time to add support into the
agent which allows service steps to be raised to
the service.

Updates the default hardware manager version to 1.2,
which has *rarely* been incremented due to oversight.

Change-Id: Iabd2c6c551389ec3c24e94b71245b1250345f7a7
2023-08-31 06:22:22 -07:00

174 lines
6.7 KiB
Python

# 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 oslo_log import log
from ironic_python_agent import errors
from ironic_python_agent import hardware
from ironic_python_agent.hardware_managers.nvidia import nvidia_fw_update
from ironic_python_agent import netutils
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
:returns: Boolean value. True if the machine contain one
or more Mellanox NIC(s), False otherwise.
"""
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
"""
address = netutils.get_mac_addr(interface_name)
if address is None:
raise errors.IncompatibleHardwareMethodError()
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)
def get_clean_steps(self, node, ports):
"""Get a list of clean steps with priority.
:param node: The node object as provided by Ironic.
:param ports: Port objects as provided by Ironic.
:returns: A list of cleaning steps, as a list of dicts.
"""
return [{'step': 'update_nvidia_nic_firmware_image',
'priority': 0,
'interface': 'deploy',
'reboot_requested': True,
'abortable': False,
'argsinfo': {
'images': {
'description': 'Json blob contains a list of images,'
' where each image contains a map of '
'url: to firmware image (file://, '
'http://), '
'checksum: of the provided image, '
'checksumType: md5/sha512/sha256, '
'componentProfile: PSID of the nic, '
'version: of the FW',
'required': True,
}, }
},
{'step': 'update_nvidia_nic_firmware_settings',
'priority': 0,
'interface': 'deploy',
'reboot_requested': True,
'abortable': False,
'argsinfo': {
'settings': {
'description': 'Json blob contains a list of '
'settings per device ID, where each '
'settings contains a map of '
'deviceID: device ID '
'globalConfig: global config '
'function0Config: function 0 config '
'function1Config: function 1 config',
'required': True,
}, }
}
]
def get_service_steps(self, node, ports):
"""Alias wrapper for method get_clean_steps."""
# NOTE(TheJulia): Since these steps can be run upon service, why not
return self.get_clean_steps(node, ports)
# TODO(TheJulia): Should there be a get_deploy_steps handler here? Since
# flashing firmware on deploy is a valid case.
def update_nvidia_nic_firmware_image(self, node, ports, images):
nvidia_fw_update.update_nvidia_nic_firmware_image(images)
def update_nvidia_nic_firmware_settings(self, node, ports, settings):
nvidia_fw_update.update_nvidia_nic_firmware_settings(settings)