diff --git a/ironic_inspector/conf/discovery.py b/ironic_inspector/conf/discovery.py index c32a32026..12cfeead3 100644 --- a/ironic_inspector/conf/discovery.py +++ b/ironic_inspector/conf/discovery.py @@ -21,6 +21,16 @@ _OPTS = [ default='fake-hardware', help=_('The name of the Ironic driver used by the enroll ' 'hook when creating a new node in Ironic.')), + cfg.ListOpt('enabled_bmc_address_version', + default=['4', '6'], + help=_('IP version of BMC address that will be ' + 'used when enrolling a new node in Ironic. ' + 'Defaults to "4,6". Could be "4" (use v4 address ' + 'only), "4,6" (v4 address have higher priority and ' + 'if both addresses found v6 version is ignored), ' + '"6,4" (v6 is desired but fall back to v4 address ' + 'for BMCs having v4 address, opposite to "4,6"), ' + '"6" (use v6 address only and ignore v4 version).')), ] diff --git a/ironic_inspector/plugins/discovery.py b/ironic_inspector/plugins/discovery.py index 71a9d4efe..71c72d406 100644 --- a/ironic_inspector/plugins/discovery.py +++ b/ironic_inspector/plugins/discovery.py @@ -28,9 +28,16 @@ LOG = utils.getProcessingLogger(__name__) def _extract_node_driver_info(introspection_data): node_driver_info = {} - ipmi_address = utils.get_ipmi_address_from_data(introspection_data) - if ipmi_address: - node_driver_info['ipmi_address'] = ipmi_address + for ip_version in CONF.discovery.enabled_bmc_address_version: + address = None + if ip_version == '4': + address = utils.get_ipmi_address_from_data(introspection_data) + elif ip_version == '6': + address = utils.get_ipmi_v6address_from_data(introspection_data) + + if address: + node_driver_info['ipmi_address'] = address + break else: LOG.warning('No BMC address provided, discovered node will be ' 'created without ipmi address') diff --git a/ironic_inspector/plugins/standard.py b/ironic_inspector/plugins/standard.py index cfaeb37e5..bbb7dcdd9 100644 --- a/ironic_inspector/plugins/standard.py +++ b/ironic_inspector/plugins/standard.py @@ -243,9 +243,11 @@ class ValidateInterfacesHook(base.ProcessingHook): """Validate information about network interfaces.""" bmc_address = utils.get_ipmi_address_from_data(introspection_data) + bmc_v6address = utils.get_ipmi_v6address_from_data(introspection_data) # Overwrite the old ipmi_address field to avoid inconsistency introspection_data['ipmi_address'] = bmc_address - if not bmc_address: + introspection_data['ipmi_v6address'] = bmc_v6address + if not (bmc_address or bmc_v6address): LOG.debug('No BMC address provided in introspection data, ' 'assuming virtual environment', data=introspection_data) diff --git a/ironic_inspector/process.py b/ironic_inspector/process.py index 6810b80f9..1c33ee5ff 100644 --- a/ironic_inspector/process.py +++ b/ironic_inspector/process.py @@ -79,8 +79,10 @@ def _store_logs(introspection_data, node_info): def _find_node_info(introspection_data, failures): try: + address = utils.get_ipmi_address_from_data(introspection_data) + v6address = utils.get_ipmi_v6address_from_data(introspection_data) return node_cache.find_node( - bmc_address=utils.get_ipmi_address_from_data(introspection_data), + bmc_address=[address, v6address], mac=utils.get_valid_macs(introspection_data)) except utils.NotFoundInCacheError as exc: not_found_hook = plugins_base.node_not_found_hook_manager() diff --git a/ironic_inspector/test/base.py b/ironic_inspector/test/base.py index 2a3b66dd5..4620202d7 100644 --- a/ironic_inspector/test/base.py +++ b/ironic_inspector/test/base.py @@ -92,6 +92,7 @@ class InventoryTest(BaseTest): # Prepare some realistic inventory # https://github.com/openstack/ironic-inspector/blob/master/HTTP-API.rst # noqa self.bmc_address = '1.2.3.4' + self.bmc_v6address = '2001:1234:1234:1234:1234:1234:1234:1234/64' self.macs = ( ['11:22:33:44:55:66', '66:55:44:33:22:11', '7c:fe:90:29:26:52']) self.ips = ['1.2.1.2', '1.2.1.1', '1.2.1.3'] @@ -137,7 +138,8 @@ class InventoryTest(BaseTest): 'memory': { 'physical_mb': 12288 }, - 'bmc_address': self.bmc_address + 'bmc_address': self.bmc_address, + 'bmc_v6address': self.bmc_v6address }, 'root_disk': {'name': '/dev/sda', 'model': 'Big Data Disk', 'size': 1000 * units.Gi, diff --git a/ironic_inspector/test/unit/test_plugins_discovery.py b/ironic_inspector/test/unit/test_plugins_discovery.py index ae9d55c31..4f7c779d6 100644 --- a/ironic_inspector/test/unit/test_plugins_discovery.py +++ b/ironic_inspector/test/unit/test_plugins_discovery.py @@ -60,19 +60,39 @@ class TestEnrollNodeNotFoundHook(test_base.NodeTest): def test_enroll_with_ipmi_address(self, mock_check_existing, mock_client, mock_create_node): mock_client.return_value = self.ironic - introspection_data = {'ipmi_address': '1.2.3.4'} - expected_data = introspection_data.copy() + expected_data = copy.deepcopy(self.data) mock_check_existing = copy_call_args(mock_check_existing) - discovery.enroll_node_not_found_hook(introspection_data) + discovery.enroll_node_not_found_hook(self.data) mock_create_node.assert_called_once_with( 'fake-hardware', ironic=self.ironic, - driver_info={'ipmi_address': '1.2.3.4'}) + driver_info={'ipmi_address': self.bmc_address}) mock_check_existing.assert_called_once_with( - expected_data, {'ipmi_address': '1.2.3.4'}, self.ironic) - self.assertEqual({'ipmi_address': '1.2.3.4', 'auto_discovered': True}, - introspection_data) + expected_data, {'ipmi_address': self.bmc_address}, self.ironic) + self.assertTrue(self.data['auto_discovered']) + + @mock.patch.object(node_cache, 'create_node', autospec=True) + @mock.patch.object(ir_utils, 'get_client', autospec=True) + @mock.patch.object(discovery, '_check_existing_nodes', autospec=True) + def test_enroll_with_ipmi_v6address(self, mock_check_existing, mock_client, + mock_create_node): + mock_client.return_value = self.ironic + # By default enabled_bmc_address_version="4,6". + # Because bmc_address is not set (pop it) _extract_node_driver_info + # method returns bmc_v6address + self.data['inventory'].pop('bmc_address') + expected_data = copy.deepcopy(self.data) + mock_check_existing = copy_call_args(mock_check_existing) + + discovery.enroll_node_not_found_hook(self.data) + + mock_create_node.assert_called_once_with( + 'fake-hardware', ironic=self.ironic, + driver_info={'ipmi_address': self.bmc_v6address}) + mock_check_existing.assert_called_once_with( + expected_data, {'ipmi_address': self.bmc_v6address}, self.ironic) + self.assertTrue(self.data['auto_discovered']) @mock.patch.object(node_cache, 'create_node', autospec=True) @mock.patch.object(ir_utils, 'get_client', autospec=True) diff --git a/ironic_inspector/test/unit/test_process.py b/ironic_inspector/test/unit/test_process.py index 3ba1108fd..c4eaac09a 100644 --- a/ironic_inspector/test/unit/test_process.py +++ b/ironic_inspector/test/unit/test_process.py @@ -89,8 +89,9 @@ class TestProcess(BaseProcessTest): self.assertEqual(self.fake_result_json, res) - self.find_mock.assert_called_once_with(bmc_address=self.bmc_address, - mac=mock.ANY) + self.find_mock.assert_called_once_with( + bmc_address=[self.bmc_address, self.bmc_v6address], + mac=mock.ANY) actual_macs = self.find_mock.call_args[1]['mac'] self.assertEqual(sorted(self.all_macs), sorted(actual_macs)) self.cli.node.get.assert_called_once_with(self.uuid) @@ -99,9 +100,11 @@ class TestProcess(BaseProcessTest): def test_no_ipmi(self): del self.inventory['bmc_address'] + del self.inventory['bmc_v6address'] process.process(self.data) - self.find_mock.assert_called_once_with(bmc_address=None, mac=mock.ANY) + self.find_mock.assert_called_once_with(bmc_address=[None, None], + mac=mock.ANY) actual_macs = self.find_mock.call_args[1]['mac'] self.assertEqual(sorted(self.all_macs), sorted(actual_macs)) self.cli.node.get.assert_called_once_with(self.uuid) @@ -110,9 +113,11 @@ class TestProcess(BaseProcessTest): def test_ipmi_not_detected(self): self.inventory['bmc_address'] = '0.0.0.0' + self.inventory['bmc_v6address'] = '::/0' process.process(self.data) - self.find_mock.assert_called_once_with(bmc_address=None, mac=mock.ANY) + self.find_mock.assert_called_once_with(bmc_address=[None, None], + mac=mock.ANY) actual_macs = self.find_mock.call_args[1]['mac'] self.assertEqual(sorted(self.all_macs), sorted(actual_macs)) self.cli.node.get.assert_called_once_with(self.uuid) @@ -124,7 +129,9 @@ class TestProcess(BaseProcessTest): self.data['ipmi_address'] = '0.0.0.0' process.process(self.data) - self.find_mock.assert_called_once_with(bmc_address=None, mac=mock.ANY) + self.find_mock.assert_called_once_with( + bmc_address=[None, self.bmc_v6address], + mac=mock.ANY) actual_macs = self.find_mock.call_args[1]['mac'] self.assertEqual(sorted(self.all_macs), sorted(actual_macs)) self.cli.node.get.assert_called_once_with(self.uuid) diff --git a/ironic_inspector/utils.py b/ironic_inspector/utils.py index b0cfc2168..43b73a681 100644 --- a/ironic_inspector/utils.py +++ b/ironic_inspector/utils.py @@ -43,6 +43,19 @@ def get_ipmi_address_from_data(introspection_data): return result +def get_ipmi_v6address_from_data(introspection_data): + try: + result = introspection_data['inventory']['bmc_v6address'] + except KeyError: + result = introspection_data.get('ipmi_v6address') + + if result in ('', '::/0'): + # ipmitool can return these values, if it does not know the address + return None + else: + return result + + def get_pxe_mac(introspection_data): pxe_mac = introspection_data.get('boot_interface') if pxe_mac and '-' in pxe_mac: diff --git a/releasenotes/notes/enroll-nodes-with-bmc-v6address-ba224f4a8a151c53.yaml b/releasenotes/notes/enroll-nodes-with-bmc-v6address-ba224f4a8a151c53.yaml new file mode 100644 index 000000000..3e704e1ef --- /dev/null +++ b/releasenotes/notes/enroll-nodes-with-bmc-v6address-ba224f4a8a151c53.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds support to enroll node with IPv6 BMC address. Introduces + a configuration option ``[discovery]enabled_bmc_address_version`` + to specify the order of preferred IP version of the BMC address.