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