diff options
Diffstat (limited to 'ironic_python_agent/hardware.py')
-rw-r--r-- | ironic_python_agent/hardware.py | 142 |
1 files changed, 99 insertions, 43 deletions
diff --git a/ironic_python_agent/hardware.py b/ironic_python_agent/hardware.py index badb07db..b08ac98c 100644 --- a/ironic_python_agent/hardware.py +++ b/ironic_python_agent/hardware.py @@ -15,6 +15,7 @@ import abc import binascii import collections +import contextlib import functools import io import ipaddress @@ -58,6 +59,9 @@ WARN_BIOSDEVNAME_NOT_FOUND = False UNIT_CONVERTER = pint.UnitRegistry(filename=None) UNIT_CONVERTER.define('bytes = []') UNIT_CONVERTER.define('MB = 1048576 bytes') +UNIT_CONVERTER.define('bit_s = []') +UNIT_CONVERTER.define('Mbit_s = 1000000 * bit_s') +UNIT_CONVERTER.define('Gbit_s = 1000 * Mbit_s') _MEMORY_ID_RE = re.compile(r'^memory(:\d+)?$') NODE = None API_CLIENT = None @@ -93,28 +97,11 @@ def _get_device_info(dev, devclass, field): 'r') as f: return f.read().strip() except IOError: - LOG.warning("Can't find field %(field)s for" + LOG.warning("Can't find field %(field)s for " "device %(dev)s in device class %(class)s", {'field': field, 'dev': dev, 'class': devclass}) -def _get_system_lshw_dict(): - """Get a dict representation of the system from lshw - - Retrieves a json representation of the system from lshw and converts - it to a python dict - - :return: A python dict from the lshw json output - """ - out, _e = il_utils.execute('lshw', '-quiet', '-json', log_stdout=False) - out = json.loads(out) - # Depending on lshw version, output might be a list, starting with - # https://github.com/lyonel/lshw/commit/135a853c60582b14c5b67e5cd988a8062d9896f4 # noqa - if isinstance(out, list): - return out[0] - return out - - def _udev_settle(): """Wait for the udev event queue to settle. @@ -787,11 +774,11 @@ class NetworkInterface(encoding.SerializableComparable): serializable_fields = ('name', 'mac_address', 'ipv4_address', 'ipv6_address', 'has_carrier', 'lldp', 'vendor', 'product', 'client_id', - 'biosdevname') + 'biosdevname', 'speed_mbps') def __init__(self, name, mac_addr, ipv4_address=None, ipv6_address=None, has_carrier=True, lldp=None, vendor=None, product=None, - client_id=None, biosdevname=None): + client_id=None, biosdevname=None, speed_mbps=None): self.name = name self.mac_address = mac_addr self.ipv4_address = ipv4_address @@ -801,6 +788,7 @@ class NetworkInterface(encoding.SerializableComparable): self.vendor = vendor self.product = product self.biosdevname = biosdevname + self.speed_mbps = speed_mbps # 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 @@ -1202,6 +1190,7 @@ class GenericHardwareManager(HardwareManager): def __init__(self): self.lldp_data = {} + self._lshw_cache = None def evaluate_hardware_support(self): # Do some initialization before we declare ourself ready @@ -1215,6 +1204,48 @@ class GenericHardwareManager(HardwareManager): self.wait_for_disks() return HardwareSupport.GENERIC + def list_hardware_info(self): + """Return full hardware inventory as a serializable dict. + + This inventory is sent to Ironic on lookup and to Inspector on + inspection. + + :return: a dictionary representing inventory + """ + with self._cached_lshw(): + return super().list_hardware_info() + + @contextlib.contextmanager + def _cached_lshw(self): + if self._lshw_cache: + yield # make this context manager reentrant without purging cache + return + + self._lshw_cache = self._get_system_lshw_dict() + try: + yield + finally: + self._lshw_cache = None + + def _get_system_lshw_dict(self): + """Get a dict representation of the system from lshw + + Retrieves a json representation of the system from lshw and converts + it to a python dict + + :return: A python dict from the lshw json output + """ + if self._lshw_cache: + return self._lshw_cache + + out, _e = il_utils.execute('lshw', '-quiet', '-json', log_stdout=False) + out = json.loads(out) + # Depending on lshw version, output might be a list, starting with + # https://github.com/lyonel/lshw/commit/135a853c60582b14c5b67e5cd988a8062d9896f4 # noqa + if isinstance(out, list): + return out[0] + return out + def collect_lldp_data(self, interface_names=None): """Collect and convert LLDP info from the node. @@ -1254,8 +1285,31 @@ class GenericHardwareManager(HardwareManager): if self.lldp_data: return self.lldp_data.get(interface_name) - def get_interface_info(self, interface_name): + def _get_network_speed(self, interface_name): + sys_dict = self._get_system_lshw_dict() + try: + iface_dict = next( + utils.find_in_lshw(sys_dict, by_class='network', + logicalname=interface_name, + recursive=True) + ) + except StopIteration: + LOG.warning('Cannot find detailed information about interface %s', + interface_name) + return None + # speed is the current speed, capacity is the maximum speed + speed = iface_dict.get('capacity') or iface_dict.get('speed') + if not speed: + LOG.debug('No speed information about in %s', iface_dict) + return None + + units = iface_dict.get('units', 'bit_s').replace('/', '_') + return int(UNIT_CONVERTER(f'{speed} {units}') + .to(UNIT_CONVERTER.Mbit_s) + .magnitude) + + def get_interface_info(self, interface_name): mac_addr = netutils.get_mac_addr(interface_name) if mac_addr is None: raise errors.IncompatibleHardwareMethodError() @@ -1267,7 +1321,8 @@ class GenericHardwareManager(HardwareManager): has_carrier=netutils.interface_has_carrier(interface_name), vendor=_get_device_info(interface_name, 'net', 'vendor'), product=_get_device_info(interface_name, 'net', 'device'), - biosdevname=self.get_bios_given_nic_name(interface_name)) + biosdevname=self.get_bios_given_nic_name(interface_name), + speed_mbps=self._get_network_speed(interface_name)) def get_ipv4_addr(self, interface_id): return netutils.get_ipv4_addr(interface_id) @@ -1324,27 +1379,28 @@ class GenericHardwareManager(HardwareManager): interface_names=iface_names) network_interfaces_list = [] - for iface_name in iface_names: - try: - result = dispatch_to_managers( - 'get_interface_info', interface_name=iface_name) - except errors.HardwareManagerMethodNotFound: - LOG.warning('No hardware manager was able to handle ' - 'interface %s', iface_name) - continue - result.lldp = self._get_lldp_data(iface_name) - network_interfaces_list.append(result) - - # If configured, bring up vlan interfaces. If the actual vlans aren't - # defined they are derived from LLDP data - if CONF.enable_vlan_interfaces: - vlan_iface_names = netutils.bring_up_vlan_interfaces( - network_interfaces_list) - for vlan_iface_name in vlan_iface_names: - result = dispatch_to_managers( - 'get_interface_info', interface_name=vlan_iface_name) + with self._cached_lshw(): + for iface_name in iface_names: + try: + result = dispatch_to_managers( + 'get_interface_info', interface_name=iface_name) + except errors.HardwareManagerMethodNotFound: + LOG.warning('No hardware manager was able to handle ' + 'interface %s', iface_name) + continue + result.lldp = self._get_lldp_data(iface_name) network_interfaces_list.append(result) + # If configured, bring up vlan interfaces. If the actual vlans + # aren't defined they are derived from LLDP data + if CONF.enable_vlan_interfaces: + vlan_iface_names = netutils.bring_up_vlan_interfaces( + network_interfaces_list) + for vlan_iface_name in vlan_iface_names: + result = dispatch_to_managers( + 'get_interface_info', interface_name=vlan_iface_name) + network_interfaces_list.append(result) + return network_interfaces_list def get_cpus(self): @@ -1388,7 +1444,7 @@ class GenericHardwareManager(HardwareManager): LOG.exception(("Cannot fetch total memory size using psutil " "version %s"), psutil.version_info[0]) try: - sys_dict = _get_system_lshw_dict() + sys_dict = self._get_system_lshw_dict() except (processutils.ProcessExecutionError, OSError, ValueError) as e: LOG.warning('Could not get real physical RAM from lshw: %s', e) physical = None @@ -1495,7 +1551,7 @@ class GenericHardwareManager(HardwareManager): def get_system_vendor_info(self): try: - sys_dict = _get_system_lshw_dict() + sys_dict = self._get_system_lshw_dict() except (processutils.ProcessExecutionError, OSError, ValueError) as e: LOG.warning('Could not retrieve vendor info from lshw: %s', e) sys_dict = {} |