From 6ccd3965ff1fa672420dd0617c822f3d97d66c23 Mon Sep 17 00:00:00 2001 From: Maximilian Brandt Date: Thu, 7 Nov 2024 15:20:25 +0100 Subject: [PATCH] Inventoried MAC address for only ipv6 addresses Extended the function that expose BMC MAC address in inventory data for an IPv6 only interface. Previously, if no IPv4 address was configured, no mac address was exposed. Change-Id: I93e49d308cfd63be1c09749ced4428a87a3daff9 --- ironic_python_agent/hardware.py | 30 ++++- .../tests/unit/test_hardware.py | 111 +++++++++++++++++- .../notes/ipv6-bmc-mac-6133fb30c0d4cc5e.yaml | 5 + 3 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/ipv6-bmc-mac-6133fb30c0d4cc5e.yaml 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.