diff --git a/ironic_python_agent/hardware.py b/ironic_python_agent/hardware.py index d028b2cdd..ef43ad1f2 100644 --- a/ironic_python_agent/hardware.py +++ b/ironic_python_agent/hardware.py @@ -2464,8 +2464,34 @@ class GenericHardwareManager(HardwareManager): continue if ip == "0.0.0.0": - # disabled, ignore - continue + # Check if we have IPv6 address configured + out, e = il_utils.execute( + "ipmitool lan6 print {} | awk '/^IPv6" + " (Dynamic|Static) Address [0-9]+:/" + " {{in_section=1; next}} /^IPv6 / {{in_section=0}}" + " in_section && /Address:/ {{print $2}}'". + format(channel), shell=True) + if e.startswith("Invalid channel"): + continue + + valid_ipv6_found = False + try: + ipv6_list = out.strip().split("\n") + # Skip auto-configured link-local addresses + # and ignore "::/255", which indicates unconfigured + # addresses returned by ipmitool. + valid_ipv6_found = any( + not ipv6.startswith("::") + and not ipv6.startswith("fe80") + for ipv6 in ipv6_list + ) + except ValueError: + LOG.warning('Invalid ipmitool output %(output)s', + {'output': out}) + continue + + if not valid_ipv6_found: + continue if not re.match("^[0-9a-f]{2}(:[0-9a-f]{2}){5}$", mac, re.I): LOG.warning('Invalid MAC address %(output)s', diff --git a/ironic_python_agent/tests/unit/test_hardware.py b/ironic_python_agent/tests/unit/test_hardware.py index 299123f70..457e36c37 100644 --- a/ironic_python_agent/tests/unit/test_hardware.py +++ b/ironic_python_agent/tests/unit/test_hardware.py @@ -3185,16 +3185,125 @@ class TestGenericHardwareManager(base.IronicAgentTest): 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 lan6 print"): + return '::/255', '' 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(hardware.GenericHardwareManager, + 'any_ipmi_device_exists', autospec=True) + @mock.patch.object(il_utils, 'execute', autospec=True) + def test_get_bmc_mac_for_ipv6(self, mocked_execute, + mock_ipmi_device_exists): + mock_ipmi_device_exists.return_value = True + + def side_effect(*args, **kwargs): + if args[0].startswith("ipmitool lan print"): + return '0.0.0.0\n01:02:03:04:05:06', '' + elif args[0].startswith("ipmitool lan6 print"): + return '2001:db8::/32', '' + else: + raise AssertionError + + mocked_execute.side_effect = side_effect + self.assertEqual('01:02:03:04:05:06', self.hardware.get_bmc_mac()) + + @mock.patch.object(hardware.GenericHardwareManager, + 'any_ipmi_device_exists', autospec=True) + @mock.patch.object(il_utils, 'execute', autospec=True) + def test_get_bmc_mac_with_invalid_ipv6(self, mocked_execute, + mock_ipmi_device_exists): + mock_ipmi_device_exists.return_value = True + + def side_effect(*args, **kwargs): + if args[0].startswith("ipmitool lan print"): + return '0.0.0.0\n01:02:03:04:05:06', '' + elif args[0].startswith("ipmitool lan6 print"): + return '::/255', '' + else: + raise AssertionError + + mocked_execute.side_effect = side_effect + self.assertRaises(errors.IncompatibleHardwareMethodError, + self.hardware.get_bmc_mac) + + @mock.patch.object(hardware.GenericHardwareManager, + 'any_ipmi_device_exists', autospec=True) + @mock.patch.object(il_utils, 'execute', autospec=True) + def test_get_bmc_mac_with_valid_ipv6_and_invalid_mac( + self, mocked_execute, mock_ipmi_device_exists): + mock_ipmi_device_exists.return_value = True + + def side_effect(*args, **kwargs): + if args[0].startswith("ipmitool lan print"): + return '0.0.0.0\n00:00:00:00:00:00', '' + elif args[0].startswith("ipmitool lan6 print"): + return '2001:db8::/32', '' + else: + raise AssertionError + + mocked_execute.side_effect = side_effect + self.assertRaises(errors.IncompatibleHardwareMethodError, + self.hardware.get_bmc_mac) + + @mock.patch.object(hardware.GenericHardwareManager, + 'any_ipmi_device_exists', autospec=True) + @mock.patch.object(il_utils, 'execute', autospec=True) + def test_get_bmc_mac_no_valid_ip_or_ipv6(self, + mocked_execute, + mock_ipmi_device_exists): + mock_ipmi_device_exists.return_value = True + + def side_effect(*args, **kwargs): + if args[0].startswith("ipmitool lan print"): + return '0.0.0.0\n00:00:00:00:00:00', '' + elif args[0].startswith("ipmitool lan6 print"): + return '::/255', '' + else: + raise AssertionError + + mocked_execute.side_effect = side_effect + self.assertRaises(errors.IncompatibleHardwareMethodError, + self.hardware.get_bmc_mac) + + @mock.patch.object(hardware.GenericHardwareManager, + 'any_ipmi_device_exists', autospec=True) + @mock.patch.object(il_utils, 'execute', autospec=True) + def test_get_bmc_mac_iterate_channels_ipv6(self, + mocked_execute, + mock_ipmi_device_exists): + mock_ipmi_device_exists.return_value = True + # For channel 4 we simulate configured IPv6 and MAC + + 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 'meow', '' + elif args[0].startswith("ipmitool lan6 print 1"): + return '', 'Invalid channel 1\n' + elif args[0].startswith("ipmitool lan6 print 2"): + return 'meow', '' + elif args[0].startswith("ipmitool lan print 3"): + return '0.0.0.0\n00:00:00:00:01:02', '' + elif args[0].startswith("ipmitool lan6 print 3"): + return 'fe80::/64', '' + elif args[0].startswith("ipmitool lan print 4"): + return '0.0.0.0\n01:02:03:04:05:06', '' + elif args[0].startswith("ipmitool lan6 print 4"): + return '2001:db8::/32', '' + else: + raise AssertionError + + mocked_execute.side_effect = side_effect + self.assertEqual('01:02:03:04:05:06', self.hardware.get_bmc_mac()) + @mock.patch.object(hardware.GenericHardwareManager, 'any_ipmi_device_exists', autospec=True) @mock.patch.object(il_utils, 'execute', autospec=True) diff --git a/releasenotes/notes/ipv6-bmc-mac-6133fb30c0d4cc5e.yaml b/releasenotes/notes/ipv6-bmc-mac-6133fb30c0d4cc5e.yaml new file mode 100644 index 000000000..2939f1b08 --- /dev/null +++ b/releasenotes/notes/ipv6-bmc-mac-6133fb30c0d4cc5e.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds support for detecting MAC addresses for interfaces with + only a IPv6 address.