Merge "Expose BMC MAC address in inventory data"
This commit is contained in:
		@@ -704,6 +704,9 @@ class HardwareManager(object, metaclass=abc.ABCMeta):
 | 
			
		||||
    def get_bmc_address(self):
 | 
			
		||||
        raise errors.IncompatibleHardwareMethodError()
 | 
			
		||||
 | 
			
		||||
    def get_bmc_mac(self):
 | 
			
		||||
        raise errors.IncompatibleHardwareMethodError()
 | 
			
		||||
 | 
			
		||||
    def get_bmc_v6address(self):
 | 
			
		||||
        raise errors.IncompatibleHardwareMethodError()
 | 
			
		||||
 | 
			
		||||
@@ -829,6 +832,14 @@ class HardwareManager(object, metaclass=abc.ABCMeta):
 | 
			
		||||
        hardware_info['system_vendor'] = self.get_system_vendor_info()
 | 
			
		||||
        hardware_info['boot'] = self.get_boot_info()
 | 
			
		||||
        hardware_info['hostname'] = netutils.get_hostname()
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            hardware_info['bmc_mac'] = self.get_bmc_mac()
 | 
			
		||||
        except errors.IncompatibleHardwareMethodError:
 | 
			
		||||
            # if the hardware manager does not support obtaining the BMC MAC,
 | 
			
		||||
            # we simply don't expose it.
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        LOG.info('Inventory collected in %.2f second(s)', time.time() - start)
 | 
			
		||||
        return hardware_info
 | 
			
		||||
 | 
			
		||||
@@ -1790,6 +1801,57 @@ class GenericHardwareManager(HardwareManager):
 | 
			
		||||
 | 
			
		||||
        return '0.0.0.0'
 | 
			
		||||
 | 
			
		||||
    def get_bmc_mac(self):
 | 
			
		||||
        """Attempt to detect BMC MAC address
 | 
			
		||||
 | 
			
		||||
        :return: MAC address of the first LAN channel or 00:00:00:00:00:00 in
 | 
			
		||||
                 case none of them has one or is configured properly
 | 
			
		||||
        """
 | 
			
		||||
        # These modules are rarely loaded automatically
 | 
			
		||||
        il_utils.try_execute('modprobe', 'ipmi_msghandler')
 | 
			
		||||
        il_utils.try_execute('modprobe', 'ipmi_devintf')
 | 
			
		||||
        il_utils.try_execute('modprobe', 'ipmi_si')
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            # From all the channels 0-15, only 1-11 can be assigned to
 | 
			
		||||
            # different types of communication media and protocols and
 | 
			
		||||
            # effectively used
 | 
			
		||||
            for channel in range(1, 12):
 | 
			
		||||
                out, e = utils.execute(
 | 
			
		||||
                    "ipmitool lan print {} | awk '/(IP|MAC) Address[ \\t]*:/"
 | 
			
		||||
                    " {{print $4}}'".format(channel), shell=True)
 | 
			
		||||
                if e.startswith("Invalid channel"):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                try:
 | 
			
		||||
                    ip, mac = out.strip().split("\n")
 | 
			
		||||
                except ValueError:
 | 
			
		||||
                    LOG.warning('Invalid ipmitool output %(output)s',
 | 
			
		||||
                                {'output': out})
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                if ip == "0.0.0.0":
 | 
			
		||||
                    # disabled, ignore
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                if not re.match("^[0-9a-f]{2}(:[0-9a-f]{2}){5}$", mac, re.I):
 | 
			
		||||
                    LOG.warning('Invalid MAC address %(output)s',
 | 
			
		||||
                                {'output': mac})
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                # In case we get 00:00:00:00:00:00 on a valid channel, we need
 | 
			
		||||
                # to keep querying
 | 
			
		||||
                if mac != '00:00:00:00:00:00':
 | 
			
		||||
                    return mac
 | 
			
		||||
 | 
			
		||||
        except (processutils.ProcessExecutionError, OSError) as e:
 | 
			
		||||
            # Not error, because it's normal in virtual environment
 | 
			
		||||
            LOG.warning("Cannot get BMC MAC address: %s", e)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # no valid mac found, signal this clearly
 | 
			
		||||
        raise errors.IncompatibleHardwareMethodError()
 | 
			
		||||
 | 
			
		||||
    def get_bmc_v6address(self):
 | 
			
		||||
        """Attempt to detect BMC v6 address
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1165,6 +1165,7 @@ class TestGenericHardwareManager(base.IronicAgentTest):
 | 
			
		||||
            current_boot_mode='bios', pxe_interface='boot:if')
 | 
			
		||||
 | 
			
		||||
        self.hardware.get_bmc_address = mock.Mock()
 | 
			
		||||
        self.hardware.get_bmc_mac = mock.Mock()
 | 
			
		||||
        self.hardware.get_bmc_v6address = mock.Mock()
 | 
			
		||||
        self.hardware.get_system_vendor_info = mock.Mock()
 | 
			
		||||
 | 
			
		||||
@@ -2300,6 +2301,68 @@ class TestGenericHardwareManager(base.IronicAgentTest):
 | 
			
		||||
        mocked_execute.return_value = '', ''
 | 
			
		||||
        self.assertEqual('0.0.0.0', self.hardware.get_bmc_address())
 | 
			
		||||
 | 
			
		||||
    @mock.patch.object(il_utils, 'try_execute', autospec=True)
 | 
			
		||||
    @mock.patch.object(utils, 'execute', autospec=True)
 | 
			
		||||
    def test_get_bmc_mac_not_available(self, mocked_execute, mte):
 | 
			
		||||
        mocked_execute.return_value = '', ''
 | 
			
		||||
        self.assertRaises(errors.IncompatibleHardwareMethodError,
 | 
			
		||||
                          self.hardware.get_bmc_mac)
 | 
			
		||||
 | 
			
		||||
    @mock.patch.object(il_utils, 'try_execute', autospec=True)
 | 
			
		||||
    @mock.patch.object(utils, 'execute', autospec=True)
 | 
			
		||||
    def test_get_bmc_mac(self, mocked_execute, mte):
 | 
			
		||||
        mocked_execute.return_value = '192.1.2.3\n01:02:03:04:05:06', ''
 | 
			
		||||
        self.assertEqual('01:02:03:04:05:06', self.hardware.get_bmc_mac())
 | 
			
		||||
 | 
			
		||||
    @mock.patch.object(il_utils, 'try_execute', autospec=True)
 | 
			
		||||
    @mock.patch.object(utils, 'execute', autospec=True)
 | 
			
		||||
    def test_get_bmc_mac_virt(self, mocked_execute, mte):
 | 
			
		||||
        mocked_execute.side_effect = processutils.ProcessExecutionError()
 | 
			
		||||
        self.assertIsNone(self.hardware.get_bmc_mac())
 | 
			
		||||
 | 
			
		||||
    @mock.patch.object(il_utils, 'try_execute', autospec=True)
 | 
			
		||||
    @mock.patch.object(utils, 'execute', autospec=True)
 | 
			
		||||
    def test_get_bmc_mac_zeroed(self, mocked_execute, mte):
 | 
			
		||||
        mocked_execute.return_value = '0.0.0.0\n00:00:00:00:00:00', ''
 | 
			
		||||
        self.assertRaises(errors.IncompatibleHardwareMethodError,
 | 
			
		||||
                          self.hardware.get_bmc_mac)
 | 
			
		||||
 | 
			
		||||
    @mock.patch.object(il_utils, 'try_execute', autospec=True)
 | 
			
		||||
    @mock.patch.object(utils, 'execute', autospec=True)
 | 
			
		||||
    def test_get_bmc_mac_invalid(self, mocked_execute, mte):
 | 
			
		||||
        # In case of invalid lan channel, stdout is empty and the error
 | 
			
		||||
        # on stderr is "Invalid channel"
 | 
			
		||||
        mocked_execute.return_value = '\n', 'Invalid channel: 55'
 | 
			
		||||
        self.assertRaises(errors.IncompatibleHardwareMethodError,
 | 
			
		||||
                          self.hardware.get_bmc_mac)
 | 
			
		||||
 | 
			
		||||
    @mock.patch.object(il_utils, 'try_execute', autospec=True)
 | 
			
		||||
    @mock.patch.object(utils, 'execute', autospec=True)
 | 
			
		||||
    def test_get_bmc_mac_random_error(self, mocked_execute, mte):
 | 
			
		||||
        mocked_execute.return_value = ('192.1.2.3\n00:00:00:00:00:02',
 | 
			
		||||
                                       'Random error message')
 | 
			
		||||
        self.assertEqual('00:00:00:00:00:02', self.hardware.get_bmc_mac())
 | 
			
		||||
 | 
			
		||||
    @mock.patch.object(il_utils, 'try_execute', autospec=True)
 | 
			
		||||
    @mock.patch.object(utils, 'execute', autospec=True)
 | 
			
		||||
    def test_get_bmc_mac_iterate_channels(self, mocked_execute, mte):
 | 
			
		||||
        # For channel 1 we simulate unconfigured IP
 | 
			
		||||
        # and for any other we return a correct IP address
 | 
			
		||||
        def side_effect(*args, **kwargs):
 | 
			
		||||
            if args[0].startswith("ipmitool lan print 1"):
 | 
			
		||||
                return '', 'Invalid channel 1\n'
 | 
			
		||||
            elif args[0].startswith("ipmitool lan print 2"):
 | 
			
		||||
                return '0.0.0.0\n00:00:00:00:23:42', ''
 | 
			
		||||
            elif args[0].startswith("ipmitool lan print 3"):
 | 
			
		||||
                return 'meow', ''
 | 
			
		||||
            elif args[0].startswith("ipmitool lan print 4"):
 | 
			
		||||
                return '192.1.2.3\n01:02:03:04:05:06', ''
 | 
			
		||||
            else:
 | 
			
		||||
                # this should never happen because the previous one was good
 | 
			
		||||
                raise AssertionError
 | 
			
		||||
        mocked_execute.side_effect = side_effect
 | 
			
		||||
        self.assertEqual('01:02:03:04:05:06', self.hardware.get_bmc_mac())
 | 
			
		||||
 | 
			
		||||
    @mock.patch.object(il_utils, 'try_execute', autospec=True)
 | 
			
		||||
    @mock.patch.object(utils, 'execute', autospec=True)
 | 
			
		||||
    def test_get_bmc_v6address_not_enabled(self, mocked_execute, mte):
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
features:
 | 
			
		||||
  - |
 | 
			
		||||
    The introspection now includes the MAC address of the IPMI LAN channel
 | 
			
		||||
    which has a valid IP address and MAC address assigned in the hardware
 | 
			
		||||
    inventory data as ``bmc_mac``.
 | 
			
		||||
		Reference in New Issue
	
	Block a user