Expose BMC MAC address in inventory data
This exposes the MAC address of the first LAN channel with an assigned IP address in the inventory data. This is useful for inventory processes where the asset number is not discoverable from the software side: the BMC MAC is going to be unique (at least within an organization). Change-Id: I8a4bee0c25743befd7f2033e4e0cba26895c8926
This commit is contained in:
parent
21c24abe61
commit
61af712fe5
@ -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``.
|
Loading…
x
Reference in New Issue
Block a user