diff --git a/proliantutils/ilo/ris.py b/proliantutils/ilo/ris.py index a5c111c..e1ca37e 100755 --- a/proliantutils/ilo/ris.py +++ b/proliantutils/ilo/ris.py @@ -39,7 +39,7 @@ TODO : Add rest of the API's that exists in RIBCL. """ DEVICE_COMMON_TO_RIS = {'NETWORK': 'Pxe', 'CDROM': 'Cd', 'HDD': 'Hdd', - } + 'ISCSI': 'UefiTarget'} DEVICE_RIS_TO_COMMON = dict( (v, k) for (k, v) in DEVICE_COMMON_TO_RIS.items()) @@ -1439,7 +1439,8 @@ class RISOperations(operations.IloOperations): else: return None - def _update_persistent_boot(self, device_type=[], persistent=False): + def _update_persistent_boot(self, device_type=[], persistent=False, + mac=None): """Changes the persistent boot device order in BIOS boot mode for host Note: It uses first boot device from the device_type and ignores rest. @@ -1447,13 +1448,13 @@ class RISOperations(operations.IloOperations): :param device_type: ordered list of boot devices :param persistent: Boolean flag to indicate if the device to be set as a persistent boot device + :param mac: intiator mac address, mandotory for iSCSI uefi boot :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedError, if the command is not supported on the server. """ tenure = 'Once' new_device = device_type[0] - # If it is a standard device, we need to convert in RIS convention if device_type[0].upper() in DEVICE_COMMON_TO_RIS: new_device = DEVICE_COMMON_TO_RIS[device_type[0].upper()] @@ -1461,21 +1462,50 @@ class RISOperations(operations.IloOperations): if persistent: tenure = 'Continuous' + systems_uri = "/rest/v1/Systems/1" + # Need to set this option first if device is 'UefiTarget' + if new_device is 'UefiTarget': + if not mac: + msg = ('Mac is needed for iscsi uefi boot') + raise exception.IloInvalidInputError(msg) + + headers, bios_uri, bios_settings = self._check_bios_resource() + # Get the Boot resource and Mappings resource. + boot_settings = self._get_bios_boot_resource(bios_settings) + StructuredBootString = None + + for boot_setting in boot_settings['BootSources']: + if(mac.upper() in boot_setting['UEFIDevicePath'] and + 'iSCSI' in boot_setting['UEFIDevicePath']): + StructuredBootString = boot_setting['StructuredBootString'] + break + if not StructuredBootString: + msg = ('MAC provided is Invalid "%s"' % mac) + raise exception.IloInvalidInputError(msg) + + new_boot_settings = {} + new_boot_settings['Boot'] = {'UefiTargetBootSourceOverride': + StructuredBootString} + status, headers, response = self._rest_patch(systems_uri, None, + new_boot_settings) + if status >= 300: + msg = self._get_extended_error(response) + raise exception.IloError(msg) + new_boot_settings = {} new_boot_settings['Boot'] = {'BootSourceOverrideEnabled': tenure, 'BootSourceOverrideTarget': new_device} - systems_uri = "/rest/v1/Systems/1" - status, headers, response = self._rest_patch(systems_uri, None, new_boot_settings) if status >= 300: msg = self._get_extended_error(response) raise exception.IloError(msg) - def update_persistent_boot(self, device_type=[]): + def update_persistent_boot(self, device_type=[], mac=None): """Changes the persistent boot device order for the host :param device_type: ordered list of boot devices + :param mac: intiator mac address, mandatory for iSCSI uefi boot :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedError, if the command is not supported on the server. @@ -1483,20 +1513,22 @@ class RISOperations(operations.IloOperations): # Check if the input is valid for item in device_type: if item.upper() not in DEVICE_COMMON_TO_RIS: - raise exception.IloInvalidInputError( - "Invalid input. Valid devices: NETWORK, HDD or CDROM.") + raise exception.IloInvalidInputError("Invalid input. Valid " + "devices: NETWORK, HDD," + " ISCSI or CDROM.") - self._update_persistent_boot(device_type, persistent=True) + self._update_persistent_boot(device_type, persistent=True, mac=mac) - def set_one_time_boot(self, device): + def set_one_time_boot(self, device, mac=None): """Configures a single boot from a specific device. :param device: Device to be set as a one time boot device + :param mac: intiator mac address, optional parameter :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedError, if the command is not supported on the server. """ - self._update_persistent_boot([device], persistent=False) + self._update_persistent_boot([device], persistent=False, mac=mac) def get_one_time_boot(self): """Retrieves the current setting for the one time boot. diff --git a/proliantutils/tests/ilo/ris_sample_outputs.py b/proliantutils/tests/ilo/ris_sample_outputs.py index f5c1599..9f04748 100755 --- a/proliantutils/tests/ilo/ris_sample_outputs.py +++ b/proliantutils/tests/ilo/ris_sample_outputs.py @@ -1426,6 +1426,15 @@ GET_BIOS_BOOT = """ "UEFIDevicePath": "PciRoot(0x0)/Pci(0x1C,0x4)/Pci(0x0,0x0)/MAC\ (C4346BB7EF30,0x0)/IPv4(0.0.0.0)" }, + { + "BootString": "Embedded LOM 1 Port 1 : HP Ethernet 1Gb 2-port\ + 361i Adapter - NIC (iSCSI IPv4) ", + "CorrelatableID": "PciRoot(0x0)/Pci(0x2,0x3)/Pci(0x0,0x0)", + "StructuredBootString": "NIC.LOM.1.1.iSCSI", + "UEFIDevicePath": "PciRoot(0x0)/Pci(0x2,0x3)/Pci(0x0,0x0)/MAC\ + (C4346BB7EF30,0x1)/IPv4(0.0.0.0)/iSCSI(iqn.2016-07.org.de\ + :storage,0x1,0x0,None,None,None,TCP)" + }, { "BootString": "Embedded LOM 1 Port 1 : HP Ethernet 1Gb 4-port\ 331i Adapter - NIC (PXE IPv6) ", @@ -1479,6 +1488,7 @@ GET_BIOS_BOOT = """ "PersistentBootConfigOrder": [ "HD.Slot.1.1", "HD.Slot.1.2", + "NIC.LOM.1.1.iSCSI", "NIC.LOM.1.1.IPv4", "NIC.LOM.1.1.IPv6", "Generic.USB.1.1", @@ -3490,6 +3500,7 @@ UEFI_BOOT_SOURCES_ERR = ''' UEFI_PERS_BOOT_DEVICES = ["HD.Slot.1.1", "HD.Slot.1.2", + "NIC.LOM.1.1.iSCSI", "NIC.LOM.1.1.IPv4", "NIC.LOM.1.1.IPv6", "Generic.USB.1.1", @@ -3633,6 +3644,16 @@ UEFI_BootSources = ''' "UEFIDevicePath": "PciRoot(0x0)/Pci(0x1C,0x4)/Pci(0x0,0x0)/MAC\ (C4346BB7EF30,0x0)/IPv4(0.0.0.0)" }, + { + "BootString": "Embedded LOM 1 Port 1 : HP Ethernet 1Gb 2-port\ + 361i Adapter - NIC (iSCSI IPv4) ", + "CorrelatableID": "PciRoot(0x0)/Pci(0x2,0x3)/Pci(0x0,0x0)", + "StructuredBootString": "NIC.LOM.1.1.iSCSI", + "UEFIDevicePath": "PciRoot(0x0)/Pci(0x2,0x3)/Pci(0x0,0x0)/MAC\ + (C4346BB7EF30,0x1)/IPv4(0.0.0.0)/iSCSI(iqn.2016-07.org.de\ + :storage,0x1,0x0,None,None,None,TCP)" + }, + { "BootString": "Embedded LOM 1 Port 1 : HP Ethernet 1Gb 4-port\ 331i Adapter - NIC (PXE IPv6) ", diff --git a/proliantutils/tests/ilo/test_ris.py b/proliantutils/tests/ilo/test_ris.py index e0f09f7..8211f80 100755 --- a/proliantutils/tests/ilo/test_ris.py +++ b/proliantutils/tests/ilo/test_ris.py @@ -747,8 +747,14 @@ class IloRisTestCase(testtools.TestCase): @mock.patch.object(ris.RISOperations, '_update_persistent_boot') def test_set_one_time_boot_cdrom(self, update_persistent_boot_mock): self.client.set_one_time_boot('cdrom') - update_persistent_boot_mock.assert_called_once_with(['cdrom'], - persistent=False) + update_persistent_boot_mock.assert_called_once_with( + ['cdrom'], persistent=False, mac=None) + + @mock.patch.object(ris.RISOperations, '_update_persistent_boot') + def test_set_one_time_boot_iscsi(self, update_persistent_boot_mock): + self.client.set_one_time_boot('ISCSI', '9cb654797870') + update_persistent_boot_mock.assert_called_once_with( + ['ISCSI'], persistent=False, mac='9cb654797870') @mock.patch.object(ris.RISOperations, '_get_host_details') def test_get_persistent_boot_device_cdrom(self, get_host_details_mock): @@ -862,8 +868,14 @@ class IloRisTestCase(testtools.TestCase): @mock.patch.object(ris.RISOperations, '_update_persistent_boot') def test_update_persistent_boot_cdrom(self, update_persistent_boot_mock): self.client.update_persistent_boot(['cdrom']) - update_persistent_boot_mock.assert_called_once_with(['cdrom'], - persistent=True) + update_persistent_boot_mock.assert_called_once_with( + ['cdrom'], mac=None, persistent=True) + + @mock.patch.object(ris.RISOperations, '_update_persistent_boot') + def test_update_persistent_boot_iscsi(self, update_persistent_boot_mock): + self.client.update_persistent_boot(['ISCSI'], '9cb654797870') + update_persistent_boot_mock.assert_called_once_with( + ['ISCSI'], mac='9cb654797870', persistent=True) @mock.patch.object(ris.RISOperations, '_update_persistent_boot') def test_update_persistent_boot_exc(self, update_persistent_boot_mock): @@ -1614,7 +1626,8 @@ class TestRISOperationsPrivateMethods(testtools.TestCase): 'BootSourceOverrideTarget': 'Cd'} rest_patch_mock.return_value = (200, ris_outputs.GET_HEADERS, ris_outputs.REST_POST_RESPONSE) - self.client._update_persistent_boot(['cdrom'], persistent=False) + self.client._update_persistent_boot(['cdrom'], mac=None, + persistent=False) rest_patch_mock.assert_called_once_with(systems_uri, None, new_boot_settings) @@ -1626,7 +1639,8 @@ class TestRISOperationsPrivateMethods(testtools.TestCase): 'BootSourceOverrideTarget': 'Cd'} rest_patch_mock.return_value = (200, ris_outputs.GET_HEADERS, ris_outputs.REST_POST_RESPONSE) - self.client._update_persistent_boot(['cdrom'], persistent=True) + self.client._update_persistent_boot(['cdrom'], mac=None, + persistent=True) rest_patch_mock.assert_called_once_with(systems_uri, None, new_boot_settings) @@ -1638,10 +1652,64 @@ class TestRISOperationsPrivateMethods(testtools.TestCase): 'BootSourceOverrideTarget': 'UefiShell'} rest_patch_mock.return_value = (200, ris_outputs.GET_HEADERS, ris_outputs.REST_POST_RESPONSE) - self.client._update_persistent_boot(['UefiShell'], persistent=True) + self.client._update_persistent_boot(['UefiShell'], mac=None, + persistent=True) rest_patch_mock.assert_called_once_with(systems_uri, None, new_boot_settings) + @mock.patch.object(ris.RISOperations, '_get_bios_boot_resource') + @mock.patch.object(ris.RISOperations, '_check_bios_resource') + @mock.patch.object(ris.RISOperations, '_rest_patch') + def test__update_persistent_boot_for_iscsi_mac_valid(self, rest_patch_mock, + check_bios_mock, + boot_mock): + bios_uri = '/rest/v1/systems/1/bios' + bios_settings = json.loads(ris_outputs.GET_BIOS_SETTINGS) + check_bios_mock.return_value = (ris_outputs.GET_HEADERS, + bios_uri, bios_settings) + boot_settings = json.loads(ris_outputs.GET_BIOS_BOOT) + boot_mock.return_value = boot_settings + systems_uri = '/rest/v1/Systems/1' + new1_boot_settings = {} + new1_boot_settings['Boot'] = {'UefiTargetBootSourceOverride': + u'NIC.LOM.1.1.iSCSI'} + new2_boot_settings = {} + new2_boot_settings['Boot'] = {'BootSourceOverrideEnabled': + 'Continuous', 'BootSourceOverrideTarget': + 'UefiTarget'} + + rest_patch_mock.return_value = (200, ris_outputs.GET_HEADERS, + ris_outputs.REST_POST_RESPONSE) + calls = [mock.call(systems_uri, None, new1_boot_settings), + mock.call(systems_uri, None, new2_boot_settings)] + self.client._update_persistent_boot(['ISCSI'], mac='C4346BB7EF30', + persistent=True) + check_bios_mock.assert_called_once_with() + boot_mock.assert_called_once_with(bios_settings) + rest_patch_mock.assert_has_calls(calls) + + @mock.patch.object(ris.RISOperations, '_get_bios_boot_resource') + @mock.patch.object(ris.RISOperations, '_check_bios_resource') + def test__update_persistent_boot_for_iscsi_mac_invalid(self, + check_bios_mock, + boot_mock): + bios_uri = '/rest/v1/systems/1/bios' + bios_settings = json.loads(ris_outputs.GET_BIOS_SETTINGS) + check_bios_mock.return_value = (ris_outputs.GET_HEADERS, + bios_uri, bios_settings) + boot_settings = json.loads(ris_outputs.GET_BIOS_BOOT) + boot_mock.return_value = boot_settings + self.assertRaises(exception.IloInvalidInputError, + self.client._update_persistent_boot, ['ISCSI'], + mac='234343553', persistent=True) + check_bios_mock.assert_called_once_with() + boot_mock.assert_called_once_with(bios_settings) + + def test__update_persistent_boot_for_iscsi_mac_none(self): + self.assertRaises(exception.IloInvalidInputError, + self.client._update_persistent_boot, ['ISCSI'], + mac=None, persistent=True) + @mock.patch.object(ris.RISOperations, '_rest_patch') def test__update_persistent_boot_fail(self, rest_patch_mock): systems_uri = "/rest/v1/Systems/1" @@ -1652,7 +1720,7 @@ class TestRISOperationsPrivateMethods(testtools.TestCase): ris_outputs.REST_POST_RESPONSE) self.assertRaises(exception.IloError, self.client._update_persistent_boot, - ['FakeDevice'], persistent=True) + ['FakeDevice'], mac=None, persistent=True) rest_patch_mock.assert_called_once_with(systems_uri, None, new_boot_settings)