diff --git a/ironic/drivers/modules/drac/inspect.py b/ironic/drivers/modules/drac/inspect.py index bdccfd0188..b452da6b03 100644 --- a/ironic/drivers/modules/drac/inspect.py +++ b/ironic/drivers/modules/drac/inspect.py @@ -119,11 +119,18 @@ class DracInspect(base.InspectInterface): {'node_uuid': node.uuid, 'error': exc}) raise exception.HardwareInspectionFailure(error=exc) + pxe_dev_nics = self._get_pxe_dev_nics(client, nics, node) + if pxe_dev_nics is None: + LOG.warning('No PXE enabled NIC was found for node ' + '%(node_uuid)s.', {'node_uuid': node.uuid}) + for nic in nics: try: port = objects.Port(task.context, address=nic.mac, - node_id=node.id) + node_id=node.id, + pxe_enabled=(nic.id in pxe_dev_nics)) port.create() + LOG.info('Port created with MAC address %(mac)s ' 'for node %(node_uuid)s during inspection', {'mac': nic.mac, 'node_uuid': node.uuid}) @@ -161,3 +168,47 @@ class DracInspect(base.InspectInterface): return cpu.cores * 2 else: return cpu.cores + + def _get_pxe_dev_nics(self, client, nics, node): + """Get a list of pxe device interfaces. + + :param client: Dracclient to list the bios settings and nics + :param nics: list of nics + + :returns: Returns list of pxe device interfaces. + """ + pxe_dev_nics = [] + pxe_params = ["PxeDev1EnDis", "PxeDev2EnDis", + "PxeDev3EnDis", "PxeDev4EnDis"] + pxe_nics = ["PxeDev1Interface", "PxeDev2Interface", + "PxeDev3Interface", "PxeDev4Interface"] + + try: + bios_settings = client.list_bios_settings() + except drac_exceptions.BaseClientException as exc: + LOG.error('DRAC driver failed to list bios settings ' + 'for %(node_uuid)s. Reason: %(error)s.', + {'node_uuid': node.uuid, 'error': exc}) + raise exception.HardwareInspectionFailure(error=exc) + + if bios_settings["BootMode"].current_value == "Uefi": + for param, nic in zip(pxe_params, pxe_nics): + if param in bios_settings and bios_settings[ + param].current_value == "Enabled": + pxe_dev_nics.append( + bios_settings[nic].current_value) + elif bios_settings["BootMode"].current_value == "Bios": + for nic in nics: + try: + nic_cap = client.list_nic_settings(nic_id=nic.id) + except drac_exceptions.BaseClientException as exc: + LOG.error('DRAC driver failed to list nic settings ' + 'for %(node_uuid)s. Reason: %(error)s.', + {'node_uuid': node.uuid, 'error': exc}) + raise exception.HardwareInspectionFailure(error=exc) + + if ("LegacyBootProto" in nic_cap and nic_cap[ + 'LegacyBootProto'].current_value == "PXE"): + pxe_dev_nics.append(nic.id) + + return pxe_dev_nics diff --git a/ironic/tests/unit/drivers/modules/drac/test_inspect.py b/ironic/tests/unit/drivers/modules/drac/test_inspect.py index 49f9ea36d1..764bc1021b 100644 --- a/ironic/tests/unit/drivers/modules/drac/test_inspect.py +++ b/ironic/tests/unit/drivers/modules/drac/test_inspect.py @@ -125,6 +125,20 @@ class DracInspectionTestCase(db_base.DbTestCase): 'speed': '1000 Mbps', 'duplex': 'full duplex', 'media_type': 'Base T'}] + bios_boot_settings = {'BootMode': {'current_value': 'Bios'}} + uefi_boot_settings = {'BootMode': {'current_value': 'Uefi'}, + 'PxeDev1EnDis': {'current_value': 'Enabled'}, + 'PxeDev2EnDis': {'current_value': 'Disabled'}, + 'PxeDev3EnDis': {'current_value': 'Disabled'}, + 'PxeDev4EnDis': {'current_value': 'Disabled'}, + 'PxeDev1Interface': { + 'current_value': 'NIC.Embedded.1-1-1'}, + 'PxeDev2Interface': None, + 'PxeDev3Interface': None, + 'PxeDev4Interface': None} + nic_settings = {'LegacyBootProto': {'current_value': 'PXE'}, + 'FQDD': 'NIC.Embedded.1-1-1'} + self.memory = [test_utils.dict_to_namedtuple(values=m) for m in memory] self.cpus = [test_utils.dict_to_namedtuple(values=c) for c in cpus] self.virtual_disks = [test_utils.dict_to_namedtuple(values=vd) @@ -132,6 +146,9 @@ class DracInspectionTestCase(db_base.DbTestCase): self.physical_disks = [test_utils.dict_to_namedtuple(values=pd) for pd in physical_disks] self.nics = [test_utils.dict_to_namedtuple(values=n) for n in nics] + self.bios_boot_settings = test_utils.dict_of_object(bios_boot_settings) + self.uefi_boot_settings = test_utils.dict_of_object(uefi_boot_settings) + self.nic_settings = test_utils.dict_of_object(nic_settings) def test_get_properties(self): expected = drac_common.COMMON_PROPERTIES @@ -153,6 +170,7 @@ class DracInspectionTestCase(db_base.DbTestCase): mock_client.list_cpus.return_value = self.cpus mock_client.list_virtual_disks.return_value = self.virtual_disks mock_client.list_nics.return_value = self.nics + mock_client.list_bios_settings.return_value = self.uefi_boot_settings with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: @@ -197,6 +215,7 @@ class DracInspectionTestCase(db_base.DbTestCase): mock_client.list_virtual_disks.return_value = [] mock_client.list_physical_disks.return_value = self.physical_disks mock_client.list_nics.return_value = self.nics + mock_client.list_bios_settings.return_value = self.uefi_boot_settings with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: @@ -241,6 +260,8 @@ class DracInspectionTestCase(db_base.DbTestCase): mock_client.list_cpus.return_value = self.cpus mock_client.list_virtual_disks.return_value = self.virtual_disks mock_client.list_nics.return_value = self.nics + mock_client.list_bios_settings.return_value = self.uefi_boot_settings + mock_port_create.side_effect = exception.MACAlreadyExists("boom") with task_manager.acquire(self.context, self.node.uuid, @@ -275,3 +296,84 @@ class DracInspectionTestCase(db_base.DbTestCase): self.cpus[1]) self.assertEqual(6, cpu) + + @mock.patch.object(drac_common, 'get_drac_client', spec_set=True, + autospec=True) + def test__get_pxe_dev_nics_with_UEFI_boot_mode(self, mock_get_drac_client): + expected_pxe_nic = self.uefi_boot_settings[ + 'PxeDev1Interface'].current_value + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + mock_client.list_bios_settings.return_value = self.uefi_boot_settings + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + pxe_dev_nics = task.driver.inspect._get_pxe_dev_nics( + mock_client, self.nics, self.node) + + self.assertEqual(expected_pxe_nic, pxe_dev_nics[0]) + + @mock.patch.object(drac_common, 'get_drac_client', spec_set=True, + autospec=True) + def test__get_pxe_dev_nics_with_BIOS_boot_mode(self, mock_get_drac_client): + expected_pxe_nic = self.nic_settings['FQDD'] + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + mock_client.list_bios_settings.return_value = self.bios_boot_settings + mock_client.list_nic_settings.return_value = self.nic_settings + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + pxe_dev_nics = task.driver.inspect._get_pxe_dev_nics( + mock_client, self.nics, self.node) + + self.assertEqual(expected_pxe_nic, pxe_dev_nics[0]) + + @mock.patch.object(drac_common, 'get_drac_client', spec_set=True, + autospec=True) + def test__get_pxe_dev_nics_list_boot_setting_failure(self, + mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + mock_client.list_bios_settings.side_effect = ( + drac_exceptions.BaseClientException('foo')) + + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + self.assertRaises(exception.HardwareInspectionFailure, + task.driver.inspect._get_pxe_dev_nics, + mock_client, + self.nics, + self.node) + + @mock.patch.object(drac_common, 'get_drac_client', spec_set=True, + autospec=True) + def test__get_pxe_dev_nics_list_nic_setting_failure(self, + mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + mock_client.list_bios_settings.return_value = self.bios_boot_settings + mock_client.list_nic_settings.side_effect = ( + drac_exceptions.BaseClientException('bar')) + + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + self.assertRaises(exception.HardwareInspectionFailure, + task.driver.inspect._get_pxe_dev_nics, + mock_client, + self.nics, + self.node) + + @mock.patch.object(drac_common, 'get_drac_client', spec_set=True, + autospec=True) + def test__get_pxe_dev_nics_with_empty_list(self, mock_get_drac_client): + expected_pxe_nic = [] + nic_setting = [] + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + mock_client.list_bios_settings.return_value = self.bios_boot_settings + mock_client.list_nic_settings.return_value = nic_setting + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + pxe_dev_nics = task.driver.inspect._get_pxe_dev_nics( + mock_client, self.nics, self.node) + + self.assertEqual(expected_pxe_nic, pxe_dev_nics) diff --git a/ironic/tests/unit/drivers/modules/drac/utils.py b/ironic/tests/unit/drivers/modules/drac/utils.py index ea0958ef20..cf93d48a07 100644 --- a/ironic/tests/unit/drivers/modules/drac/utils.py +++ b/ironic/tests/unit/drivers/modules/drac/utils.py @@ -14,6 +14,13 @@ import collections +class DictToObj(object): + """Returns a dictionary into a class""" + def __init__(self, dictionary): + for key in dictionary: + setattr(self, key, dictionary[key]) + + def dict_to_namedtuple(name='GenericNamedTuple', values=None): """Converts a dict to a collections.namedtuple""" @@ -21,3 +28,13 @@ def dict_to_namedtuple(name='GenericNamedTuple', values=None): values = {} return collections.namedtuple(name, list(values))(**values) + + +def dict_of_object(data): + """Create a dictionary object""" + + for k, v in data.items(): + if isinstance(v, dict): + dict_obj = DictToObj(v) + data[k] = dict_obj + return data diff --git a/releasenotes/notes/update-port-pxe-enabled-f954f934209cbf5b.yaml b/releasenotes/notes/update-port-pxe-enabled-f954f934209cbf5b.yaml new file mode 100644 index 0000000000..1e0bfa1d1c --- /dev/null +++ b/releasenotes/notes/update-port-pxe-enabled-f954f934209cbf5b.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixes a bug where ironic port is not updated in node introspection as per + PXE enabled setting for ``idrac`` hardware type. + See bug `2004340 + `_ for details.