From feb3c5faea344eb149f2bc06accf012480336eec Mon Sep 17 00:00:00 2001 From: digambar Date: Tue, 27 Nov 2018 04:17:02 -0500 Subject: [PATCH] Fix OOB introspection to use pxe_enabled flag in idrac driver Baremetal nodes fail to pxe boot during OpenStack deployment. Reason of this failure is because we are not checking the actual pxe device interface and updating the port pxe_enabled field. This patch make sure that everytime when port is created, it checks the the BIOS and UEFI boot modes of node and fetch the pxe device interfaces values matching to nic id and then update the port's pxe_enabled field. Change-Id: I2890bf16110b713e269d4a4fe410f57273dc8e83 Story: 2004340 (cherry picked from commit 2b74d940c932c1bb4498f47372c0545e4480799f) --- ironic/drivers/modules/drac/inspect.py | 53 ++++++++- .../unit/drivers/modules/drac/test_inspect.py | 102 ++++++++++++++++++ .../tests/unit/drivers/modules/drac/utils.py | 17 +++ ...ate-port-pxe-enabled-f954f934209cbf5b.yaml | 7 ++ 4 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/update-port-pxe-enabled-f954f934209cbf5b.yaml 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.