Redfish: Adding the ability to set boot mode and devices

This commits adds new function 'set_pending_boot_mode',
'update_persistent_boot' and 'set_one_time_boot' to
the server

Change-Id: I7609eac74143f4b529b011ed1cbab96b578770b8
This commit is contained in:
kesper 2017-07-10 11:48:21 +00:00
parent 7488ae2048
commit 289de798cc
9 changed files with 355 additions and 4 deletions

View File

@ -72,8 +72,10 @@ SUPPORTED_REDFISH_METHODS = [
'insert_virtual_media',
'set_vm_status'
'update_firmware',
'set_vm_status',
'get_persistent_boot_device',
'set_one_time_boot',
'update_persistent_boot',
'set_pending_boot_mode',
]
LOG = log.get_logger(__name__)

View File

@ -485,7 +485,88 @@ class RedfishOperations(operations.IloOperations):
return PERSISTENT_BOOT_MAP.get(boot_device)
except sushy.exceptions.SushyError as e:
msg = (self._("The Redfish controller is unable to get "
"persistent boot device.' Error %(error)s") %
"persistent boot device. Error %(error)s") %
{'error': str(e)})
LOG.debug(msg)
raise exception.IloError(msg)
def set_pending_boot_mode(self, boot_mode):
"""Sets the boot mode of the system for next boot.
:param boot_mode: either 'uefi' or 'legacy'.
:raises: IloInvalidInputError, on an invalid input.
:raises: IloError, on an error from iLO.
"""
sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID)
if boot_mode.upper() not in BOOT_MODE_MAP_REV.keys():
msg = (('Invalid Boot mode: "%(boot_mode)s" specified, valid boot '
'modes are either "uefi" or "legacy"')
% {'boot_mode': boot_mode})
raise exception.IloInvalidInputError(msg)
try:
sushy_system.bios_settings.pending_settings.set_pending_boot_mode(
BOOT_MODE_MAP_REV.get(boot_mode.upper()))
except sushy.exceptions.SushyError as e:
msg = (self._('The Redfish controller failed to set '
'pending boot mode to %(boot_mode)s. '
'Error: %(error)s') %
{'boot_mode': boot_mode, 'error': str(e)})
LOG.debug(msg)
raise exception.IloError(msg)
def update_persistent_boot(self, devices=[], mac=None):
"""Changes the persistent boot device order for the host
:param devices: ordered list of boot devices
:param mac: intiator mac address, mandatory for iSCSI uefi boot
:raises: IloError, on an error from iLO.
:raises: IloInvalidInputError, if the given input is not valid.
"""
sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID)
# Check if the input is valid
for item in devices:
if item.upper() not in DEVICE_COMMON_TO_REDFISH:
msg = (self._('Invalid input "%(device)s". Valid devices: '
'NETWORK, HDD, ISCSI or CDROM.') %
{'device': item})
raise exception.IloInvalidInputError(msg)
try:
sushy_system.update_persistent_boot(
devices, persistent=True, mac=mac)
except sushy.exceptions.SushyError as e:
msg = (self._('The Redfish controller failed to update '
'persistent boot device %(devices)s.'
'Error: %(error)s') %
{'devices': devices, 'error': str(e)})
LOG.debug(msg)
raise exception.IloError(msg)
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: IloInvalidInputError, if the given input is not valid.
"""
sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID)
# Check if the input is valid
if device.upper() not in DEVICE_COMMON_TO_REDFISH:
msg = (self._('Invalid input "%(device)s". Valid devices: '
'NETWORK, HDD, ISCSI or CDROM.') %
{'device': device})
raise exception.IloInvalidInputError(msg)
try:
sushy_system.update_persistent_boot(
[device], persistent=False, mac=mac)
except sushy.exceptions.SushyError as e:
msg = (self._('The Redfish controller failed to set '
'one time boot device %(device)s. '
'Error: %(error)s') %
{'device': device, 'error': str(e)})
LOG.debug(msg)
raise exception.IloError(msg)

View File

@ -17,6 +17,7 @@ from sushy.resources import base
from proliantutils import exception
from proliantutils import log
from proliantutils.redfish.resources.system import constants as sys_cons
from proliantutils.redfish.resources.system import mappings
from proliantutils.redfish import utils
@ -75,6 +76,21 @@ class BIOSPendingSettings(base.ResourceBase):
boot_mode = base.MappedField(["Attributes", "BootMode"],
mappings.GET_BIOS_BOOT_MODE_MAP)
def set_pending_boot_mode(self, boot_mode):
"""Sets the boot mode of the system for next boot.
:param boot_mode: either sys_cons.BIOS_BOOT_MODE_LEGACY_BIOS,
sys_cons.BIOS_BOOT_MODE_UEFI.
"""
bios_properties = {}
bios_properties['BootMode'] = (
mappings.GET_BIOS_BOOT_MODE_MAP_REV.get(boot_mode))
if boot_mode == sys_cons.BIOS_BOOT_MODE_UEFI:
bios_properties['UefiOptimizedBoot'] = 'Enabled'
self._conn.patch(self._path, bios_properties)
class BIOSBootSettings(base.ResourceBase):

View File

@ -23,3 +23,10 @@ PUSH_POWER_BUTTON_PRESS_AND_HOLD = 'press and hold'
BIOS_BOOT_MODE_LEGACY_BIOS = 'legacy bios'
BIOS_BOOT_MODE_UEFI = 'uefi'
# Persistent boot device for set
BOOT_SOURCE_TARGET_CD = 'Cd'
BOOT_SOURCE_TARGET_PXE = 'Pxe'
BOOT_SOURCE_TARGET_UEFI_TARGET = 'UefiTarget'
BOOT_SOURCE_TARGET_HDD = 'Hdd'

View File

@ -14,17 +14,26 @@
__author__ = 'HPE'
import sushy
from sushy.resources import base
from sushy.resources.system import system
from proliantutils import exception
from proliantutils import log
from proliantutils.redfish.resources.system import bios
from proliantutils.redfish.resources.system import constants as sys_cons
from proliantutils.redfish.resources.system import mappings
from proliantutils.redfish import utils
LOG = log.get_logger(__name__)
PERSISTENT_BOOT_DEVICE_MAP = {
'CDROM': sys_cons.BOOT_SOURCE_TARGET_CD,
'NETWORK': sys_cons.BOOT_SOURCE_TARGET_PXE,
'ISCSI': sys_cons.BOOT_SOURCE_TARGET_UEFI_TARGET,
'HDD': sys_cons.BOOT_SOURCE_TARGET_HDD
}
class PowerButtonActionField(base.CompositeField):
allowed_values = base.Field('PushType@Redfish.AllowableValues',
@ -95,3 +104,54 @@ class HPESystem(system.System):
redfish_version=self.redfish_version)
return self._bios_settings
def update_persistent_boot(self, devices=[], persistent=False,
mac=None):
"""Changes the persistent boot device order in BIOS boot mode for host
Note: It uses first boot device from the devices and ignores rest.
:param devices: 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: IloInvalidInputError, if the given input is not valid.
"""
new_device = devices[0]
tenure = 'Continuous' if persistent else 'Once'
try:
boot_sources = self.bios_settings.boot_settings.boot_sources
except sushy.exceptions.SushyError:
msg = ('The BIOS Boot Settings was not found.')
raise exception.IloError(msg)
if devices[0].upper() in PERSISTENT_BOOT_DEVICE_MAP:
new_device = PERSISTENT_BOOT_DEVICE_MAP[devices[0].upper()]
new_boot_settings = {}
if new_device is 'UefiTarget':
if not mac:
msg = ('Mac is needed for iscsi uefi boot')
raise exception.IloInvalidInputError(msg)
boot_string = None
for boot_source in boot_sources:
if(mac.upper() in boot_source['UEFIDevicePath'] and
'iSCSI' in boot_source['UEFIDevicePath']):
boot_string = boot_source['StructuredBootString']
break
if not boot_string:
msg = ('MAC provided "%s" is Invalid' % mac)
raise exception.IloInvalidInputError(msg)
uefi_boot_settings = {}
uefi_boot_settings['Boot'] = (
{'UefiTargetBootSourceOverride': boot_string})
self._conn.patch(self._path, uefi_boot_settings)
new_boot_settings['Boot'] = {'BootSourceOverrideEnabled': tenure,
'BootSourceOverrideTarget': new_device}
self._conn.patch(self._path, new_boot_settings)

View File

@ -105,7 +105,14 @@
"CorrelatableID": "PciRoot(0x0)/Pci(0x1C,0x4)/Pci(0x0,0x4)/USB(0x2,0x0)",
"StructuredBootString": "FD.Virtual.4.1",
"UEFIDevicePath": "PciRoot(0x0)/Pci(0x1C,0x4)/Pci(0x0,0x4)/USB(0x2,0x0)"
}
},
{
"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.org.de:,0x1,0x0,None,None,None,TCP)"
}
],
"DefaultBootOrder":
[
@ -225,7 +232,7 @@
"Id": "boot",
"Name": "Boot Order Current Settings"
},
"BIOS_boot_without_boot_sources": {
"@Redfish.Settings":
{

View File

@ -97,6 +97,23 @@ class BIOSPendingSettingsTestCase(testtools.TestCase):
self.assertEqual(sys_cons.BIOS_BOOT_MODE_UEFI,
self.bios_settings_inst.boot_mode)
def test_set_pending_boot_mode_bios(self):
self.bios_settings_inst.set_pending_boot_mode(
sys_cons.BIOS_BOOT_MODE_LEGACY_BIOS)
data = {}
data['BootMode'] = 'LegacyBios'
self.bios_settings_inst._conn.patch.assert_called_once_with(
'/redfish/v1/Systems/1/bios/settings', data)
def test_set_pending_boot_mode_uefi(self):
self.bios_settings_inst.set_pending_boot_mode(
sys_cons.BIOS_BOOT_MODE_UEFI)
data = {}
data['UefiOptimizedBoot'] = 'Enabled'
data['BootMode'] = 'Uefi'
self.bios_settings_inst._conn.patch.assert_called_once_with(
'/redfish/v1/Systems/1/bios/settings', data)
class BIOSBootSettingsTestCase(testtools.TestCase):

View File

@ -16,6 +16,7 @@
import json
import mock
import sushy
import testtools
from proliantutils import exception
@ -79,3 +80,94 @@ class HPESystemTestCase(testtools.TestCase):
self.assertIs(actual_bios,
self.sys_inst.bios_settings)
self.conn.get.return_value.json.assert_not_called()
def test_update_persistent_boot_persistent(self):
with open('proliantutils/tests/redfish/'
'json_samples/bios.json', 'r') as f:
bios_mock = json.loads(f.read())
with open('proliantutils/tests/redfish/'
'json_samples/bios_boot.json', 'r') as g:
boot_mock = json.loads(g.read())
self.conn.get.return_value.json.side_effect = [bios_mock['Default'],
boot_mock['Default']]
self.sys_inst.update_persistent_boot(['CDROM'], True)
data = {}
data['Boot'] = {'BootSourceOverrideEnabled': 'Continuous',
'BootSourceOverrideTarget': 'Cd'}
self.sys_inst._conn.patch.assert_called_once_with(
'/redfish/v1/Systems/1', data)
def test_update_persistent_boot_not_persistent(self):
with open('proliantutils/tests/redfish/'
'json_samples/bios.json', 'r') as f:
bios_mock = json.loads(f.read())
with open('proliantutils/tests/redfish/'
'json_samples/bios_boot.json', 'r') as f:
boot_mock = json.loads(f.read())
self.conn.get.return_value.json.side_effect = [bios_mock['Default'],
boot_mock['Default']]
self.sys_inst.update_persistent_boot(['CDROM'], False)
data = {}
data['Boot'] = {'BootSourceOverrideEnabled': 'Once',
'BootSourceOverrideTarget': 'Cd'}
self.sys_inst._conn.patch.assert_called_once_with(
'/redfish/v1/Systems/1', data)
def test_update_persistent_boot_uefi_target(self):
with open('proliantutils/tests/redfish/'
'json_samples/bios.json', 'r') as f:
bios_mock = json.loads(f.read())
with open('proliantutils/tests/redfish/'
'json_samples/bios_boot.json', 'r') as f:
boot_mock = json.loads(f.read())
self.conn.get.return_value.json.side_effect = [bios_mock['Default'],
boot_mock['Default']]
self.sys_inst.update_persistent_boot(['ISCSI'], persistent=True,
mac='C4346BB7EF30')
data = {}
data['Boot'] = {'UefiTargetBootSourceOverride': 'NIC.LOM.1.1.iSCSI'}
new_data = {}
new_data['Boot'] = {'BootSourceOverrideEnabled': 'Continuous',
'BootSourceOverrideTarget': 'UefiTarget'}
calls = [mock.call('/redfish/v1/Systems/1', data),
mock.call('/redfish/v1/Systems/1', new_data)]
self.sys_inst._conn.patch.assert_has_calls(calls)
def test_update_persistent_boot_uefi_target_without_mac(self):
with open('proliantutils/tests/redfish/'
'json_samples/bios.json', 'r') as f:
bios_mock = json.loads(f.read())
with open('proliantutils/tests/redfish/'
'json_samples/bios_boot.json', 'r') as f:
boot_mock = json.loads(f.read())
self.conn.get.return_value.json.side_effect = [bios_mock['Default'],
boot_mock['Default']]
self.assertRaisesRegex(
exception.IloInvalidInputError,
'Mac is needed for iscsi uefi boot',
self.sys_inst.update_persistent_boot, ['ISCSI'], True, None)
def test_update_persistent_boot_uefi_target_invalid_mac(self):
with open('proliantutils/tests/redfish/'
'json_samples/bios.json', 'r') as f:
bios_mock = json.loads(f.read())
with open('proliantutils/tests/redfish/'
'json_samples/bios_boot.json', 'r') as f:
boot_mock = json.loads(f.read())
self.conn.get.return_value.json.side_effect = [bios_mock['Default'],
boot_mock['Default']]
self.assertRaisesRegex(
exception.IloInvalidInputError,
'MAC provided "12345678" is Invalid',
self.sys_inst.update_persistent_boot, ['ISCSI'], True, '12345678')
def test_update_persistent_boot_fail(self):
with open('proliantutils/tests/redfish/'
'json_samples/bios.json', 'r') as f:
bios_mock = json.loads(f.read())
self.conn.get.return_value.json.side_effect = (
[bios_mock['Default'], sushy.exceptions.SushyError])
self.assertRaisesRegex(
exception.IloError,
'The BIOS Boot Settings was not found.',
self.sys_inst.update_persistent_boot, ['CDROM'], True, None)

View File

@ -504,3 +504,72 @@ class RedfishOperationsTestCase(testtools.TestCase):
exception.IloError,
'The Redfish controller is unable to get persistent boot device.',
self.rf_client.get_persistent_boot_device)
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_set_pending_boot_mode(self, get_system_mock):
self.rf_client.set_pending_boot_mode('uefi')
(get_system_mock.return_value.
bios_settings.pending_settings.set_pending_boot_mode.
assert_called_once_with('uefi'))
def test_set_pending_boot_mode_invalid_input(self):
self.assertRaisesRegex(
exception.IloInvalidInputError,
'Invalid Boot mode: "test" specified',
self.rf_client.set_pending_boot_mode, 'test')
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_set_pending_boot_mode_fail(self, get_system_mock):
(get_system_mock.return_value.bios_settings.
pending_settings.set_pending_boot_mode.side_effect) = (
sushy.exceptions.SushyError)
self.assertRaisesRegex(
exception.IloError,
'The Redfish controller failed to set pending boot mode.',
self.rf_client.set_pending_boot_mode, 'uefi')
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_update_persistent_boot(self, get_system_mock):
self.rf_client.update_persistent_boot(['NETWORK'])
(get_system_mock.return_value.update_persistent_boot.
assert_called_once_with(['NETWORK'], mac=None, persistent=True))
def test_update_persistent_boot_invalid_input(self):
self.assertRaisesRegex(
exception.IloInvalidInputError,
('Invalid input "test". Valid devices: NETWORK, '
'HDD, ISCSI or CDROM.'),
self.rf_client.update_persistent_boot, ['test'])
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_update_persistent_boot_fail(self, get_system_mock):
get_system_mock.return_value.update_persistent_boot.side_effect = (
sushy.exceptions.SushyError)
self.assertRaisesRegex(
exception.IloError,
'The Redfish controller failed to update persistent boot.',
self.rf_client.update_persistent_boot,
['NETWORK'])
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_set_one_time_boot(self, get_system_mock):
self.rf_client.set_one_time_boot('CDROM')
(get_system_mock.return_value.update_persistent_boot.
assert_called_once_with(['CDROM'], mac=None, persistent=False))
def test_set_one_time_boot_invalid_input(self):
self.assertRaisesRegex(
exception.IloInvalidInputError,
('Invalid input "test". Valid devices: NETWORK, '
'HDD, ISCSI or CDROM.'),
self.rf_client.set_one_time_boot, 'test')
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_set_one_time_boot_fail(self, get_system_mock):
get_system_mock.return_value.update_persistent_boot.side_effect = (
sushy.exceptions.SushyError)
self.assertRaisesRegex(
exception.IloError,
'The Redfish controller failed to set one time boot.',
self.rf_client.set_one_time_boot,
'CDROM')