Merge "Redfish UefiHttp boot support"
This commit is contained in:
commit
88fcf8aa14
@ -23,13 +23,14 @@ Enabling the Redfish driver
|
||||
#. Add ``redfish`` to the list of ``enabled_hardware_types``,
|
||||
``enabled_power_interfaces``, ``enabled_management_interfaces`` and
|
||||
``enabled_inspect_interfaces`` as well as ``redfish-virtual-media``
|
||||
to ``enabled_boot_interfaces`` in ``/etc/ironic/ironic.conf``.
|
||||
and ``redfish-https`` to ``enabled_boot_interfaces`` in
|
||||
``/etc/ironic/ironic.conf``.
|
||||
For example::
|
||||
|
||||
[DEFAULT]
|
||||
...
|
||||
enabled_hardware_types = ipmi,redfish
|
||||
enabled_boot_interfaces = ipxe,redfish-virtual-media
|
||||
enabled_boot_interfaces = ipxe,redfish-virtual-media,redfish-https
|
||||
enabled_power_interfaces = ipmitool,redfish
|
||||
enabled_management_interfaces = ipmitool,redfish
|
||||
enabled_inspect_interfaces = inspector,redfish
|
||||
@ -386,6 +387,41 @@ Layer 3 or DHCP-less ramdisk booting
|
||||
DHCP-less deploy is supported by the Redfish virtual media boot. See
|
||||
:doc:`/admin/dhcp-less` for more information.
|
||||
|
||||
Redfish HTTP(s) Boot
|
||||
====================
|
||||
|
||||
The ``redfish-https`` boot interface is very similar to the
|
||||
``redfish-virtual-media`` boot interface. In this driver, we compose an ISO
|
||||
image, and request the BMC to inform the UEFI firmware to boot the Ironic
|
||||
ramdisk, or a other ramdisk image. This approach is intended to allow a
|
||||
pattern of engagement where we have minimal reliance on addressing and
|
||||
discovery of the Ironic deployment through autoconfiguration like DHCP,
|
||||
and somewhat mirrors vendor examples of booting from an HTTP URL.
|
||||
|
||||
This interface has some basic constraints.
|
||||
|
||||
* There is no configuration drive functionality, while Virtual Media did
|
||||
help provide such functionality.
|
||||
* This interface *is* dependent upon BMC, EFI Firmware, and Bootloader,
|
||||
which means we may not see additional embedded files an contents in
|
||||
an ISO image. This is the same basic constraint over the ``ramdisk``
|
||||
deploy interface when using Network Booting.
|
||||
* This is a UEFI-Only boot interface. No legacy boot is possible with
|
||||
this interface.
|
||||
|
||||
A good starting point for this interface, is to think of it as
|
||||
higher security network boot, as we are explicitly telling the BMC
|
||||
where the node should boot from.
|
||||
|
||||
Like the ``redfish-virtual-media`` boot interface, you will need
|
||||
to create an EFI System Partition image (ESP_), see
|
||||
`Configuring an ESP image`_ for details on how to do this.
|
||||
|
||||
Additionally, if you would like to use the ``ramdisk`` deployment
|
||||
interface, the same basic instructions covered in `Virtual Media Ramdisk`_
|
||||
apply, just use ``redfish-https`` as the boot_interface, and keep in mind,
|
||||
no configuration drives exist with the ``redfish-https`` boot interface.
|
||||
|
||||
Firmware update using manual cleaning
|
||||
=====================================
|
||||
|
||||
|
@ -52,3 +52,6 @@ FLOPPY = 'floppy'
|
||||
|
||||
VMEDIA_DEVICES = [DISK, CDROM, FLOPPY]
|
||||
"""Devices that make sense for virtual media attachment."""
|
||||
|
||||
UEFIHTTP = "uefihttp"
|
||||
"Boot from a UEFI HTTP(s) URL"
|
||||
|
@ -883,3 +883,9 @@ class FirmwareComponentNotFound(NotFound):
|
||||
|
||||
class InvalidNodeInventory(Invalid):
|
||||
_msg_fmt = _("Inventory for node %(node)s is invalid: %(reason)s")
|
||||
|
||||
|
||||
class UnsupportedHardwareFeature(Invalid):
|
||||
_msg_fmt = _("Node %(node)s hardware does not support feature "
|
||||
"%(feature)s, which is required based upon the "
|
||||
"requested configuration.")
|
||||
|
@ -21,6 +21,7 @@ from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import states
|
||||
from ironic.common import utils as common_utils
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.conf import CONF
|
||||
from ironic.drivers import base
|
||||
@ -712,6 +713,11 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
||||
if not configdrive:
|
||||
return
|
||||
|
||||
if 'ramdisk_boot_configdrive' not in self.capabilities:
|
||||
raise exception.InstanceDeployFailure(
|
||||
_('Cannot attach a configdrive to node %s, as it is not '
|
||||
'supported in the driver.') % task.node.uuid)
|
||||
|
||||
_eject_vmedia(task, managers, sushy.VIRTUAL_MEDIA_USBSTICK)
|
||||
cd_ref = image_utils.prepare_configdrive_image(task, configdrive)
|
||||
try:
|
||||
@ -775,3 +781,300 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
||||
ManagementInterface fails.
|
||||
"""
|
||||
manager_utils.node_set_boot_device(task, device, persistent)
|
||||
|
||||
|
||||
class RedfishHttpsBoot(base.BootInterface):
|
||||
"""A driver which utilizes UefiHttp like virtual media.
|
||||
|
||||
Utilizes the virtual media image build to craft a ISO image to
|
||||
signal to remote BMC to boot.
|
||||
|
||||
This interface comes with some constraints. For example, this
|
||||
interface is built under the operating assumption that DHCP is
|
||||
used. The UEFI Firmware needs to load some base configuration,
|
||||
regardless. Also depending on UEFI Firmware, and how it handles
|
||||
UefiHttp Boot, additional ISO contents, such as "configuration drive"
|
||||
materials might be unavailable. A similar constraint exists with
|
||||
``ramdisk`` deployment.
|
||||
"""
|
||||
|
||||
capabilities = ['ramdisk_boot']
|
||||
|
||||
def get_properties(self):
|
||||
"""Return the properties of the interface.
|
||||
|
||||
:returns: dictionary of <property name>:<property description> entries.
|
||||
"""
|
||||
return REQUIRED_PROPERTIES
|
||||
|
||||
def _validate_driver_info(self, task):
|
||||
"""Validate the prerequisites for Redfish HTTPS based boot.
|
||||
|
||||
This method validates whether the 'driver_info' property of the
|
||||
supplied node contains the required information for this driver.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: InvalidParameterValue if any parameters are incorrect
|
||||
:raises: MissingParameterValue if some mandatory information
|
||||
is missing on the node
|
||||
"""
|
||||
node = task.node
|
||||
|
||||
_parse_driver_info(node)
|
||||
# Issue the deprecation warning if needed
|
||||
driver_utils.get_agent_iso(node, deprecated_prefix='redfish')
|
||||
|
||||
def _validate_instance_info(self, task):
|
||||
"""Validate instance image information for the task's node.
|
||||
|
||||
This method validates whether the 'instance_info' property of the
|
||||
supplied node contains the required information for this driver.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: InvalidParameterValue if any parameters are incorrect
|
||||
:raises: MissingParameterValue if some mandatory information
|
||||
is missing on the node
|
||||
"""
|
||||
node = task.node
|
||||
|
||||
# NOTE(dtantsur): if we're are writing an image with local boot
|
||||
# the boot interface does not care about image parameters and
|
||||
# must not validate them.
|
||||
if (not task.driver.storage.should_write_image(task)
|
||||
or deploy_utils.get_boot_option(node) == 'local'):
|
||||
return
|
||||
|
||||
d_info = _parse_deploy_info(node)
|
||||
deploy_utils.validate_image_properties(task, d_info)
|
||||
|
||||
def _validate_hardware(self, task):
|
||||
"""Validates hardware support.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: InvalidParameterValue if vendor not supported
|
||||
"""
|
||||
system = redfish_utils.get_system(task.node)
|
||||
if "UefiHttp" not in system.boot.allowed_values:
|
||||
raise exception.UnsupportedHardwareFeature(
|
||||
node=task.node.uuid,
|
||||
feature="UefiHttp boot")
|
||||
|
||||
def validate(self, task):
|
||||
"""Validate the deployment information for the task's node.
|
||||
|
||||
This method validates whether the 'driver_info' and/or 'instance_info'
|
||||
properties of the task's node contains the required information for
|
||||
this interface to function.
|
||||
|
||||
:param task: A TaskManager instance containing the node to act on.
|
||||
:raises: InvalidParameterValue on malformed parameter(s)
|
||||
:raises: MissingParameterValue on missing parameter(s)
|
||||
"""
|
||||
self._validate_hardware(task)
|
||||
self._validate_driver_info(task)
|
||||
self._validate_instance_info(task)
|
||||
|
||||
def validate_inspection(self, task):
|
||||
"""Validate that the node has required properties for inspection.
|
||||
|
||||
:param task: A TaskManager instance with the node being checked
|
||||
:raises: MissingParameterValue if node is missing one or more required
|
||||
parameters
|
||||
:raises: UnsupportedDriverExtension
|
||||
"""
|
||||
try:
|
||||
self._validate_driver_info(task)
|
||||
except exception.MissingParameterValue:
|
||||
# Fall back to non-managed in-band inspection
|
||||
raise exception.UnsupportedDriverExtension(
|
||||
driver=task.node.driver, extension='inspection')
|
||||
|
||||
def prepare_ramdisk(self, task, ramdisk_params):
|
||||
"""Prepares the boot of the agent ramdisk.
|
||||
|
||||
This method prepares the boot of the deploy or rescue ramdisk after
|
||||
reading relevant information from the node's driver_info and
|
||||
instance_info.
|
||||
|
||||
:param task: A task from TaskManager.
|
||||
:param ramdisk_params: the parameters to be passed to the ramdisk.
|
||||
:returns: None
|
||||
:raises: MissingParameterValue, if some information is missing in
|
||||
node's driver_info or instance_info.
|
||||
:raises: InvalidParameterValue, if some information provided is
|
||||
invalid.
|
||||
:raises: IronicException, if some power or set boot boot device
|
||||
operation failed on the node.
|
||||
"""
|
||||
node = task.node
|
||||
if not driver_utils.need_prepare_ramdisk(node):
|
||||
return
|
||||
|
||||
d_info = _parse_driver_info(node)
|
||||
|
||||
if manager_utils.is_fast_track(task):
|
||||
LOG.debug('Fast track operation for node %s, not setting up '
|
||||
'a HTTP Boot url', node.uuid)
|
||||
return
|
||||
|
||||
can_config = d_info.pop('can_provide_config', True)
|
||||
if can_config:
|
||||
manager_utils.add_secret_token(node, pregenerated=True)
|
||||
node.save()
|
||||
ramdisk_params['ipa-agent-token'] = \
|
||||
node.driver_internal_info['agent_secret_token']
|
||||
|
||||
manager_utils.node_power_action(task, states.POWER_OFF)
|
||||
|
||||
deploy_nic_mac = deploy_utils.get_single_nic_with_vif_port_id(task)
|
||||
if deploy_nic_mac is not None:
|
||||
ramdisk_params['BOOTIF'] = deploy_nic_mac
|
||||
if CONF.debug and 'ipa-debug' not in ramdisk_params:
|
||||
ramdisk_params['ipa-debug'] = '1'
|
||||
|
||||
# NOTE(TheJulia): This is a mandatory setting for virtual media
|
||||
# based deployment operations and boot modes similar where we
|
||||
# want the ramdisk to consider embedded configuration.
|
||||
ramdisk_params['boot_method'] = 'vmedia'
|
||||
|
||||
mode = deploy_utils.rescue_or_deploy_mode(node)
|
||||
|
||||
iso_ref = image_utils.prepare_deploy_iso(task, ramdisk_params,
|
||||
mode, d_info)
|
||||
boot_mode_utils.sync_boot_mode(task)
|
||||
|
||||
self._set_boot_device(task, boot_devices.UEFIHTTP,
|
||||
http_boot_url=iso_ref)
|
||||
|
||||
LOG.debug("Node %(node)s is set to one time boot from "
|
||||
"%(device)s", {'node': task.node.uuid,
|
||||
'device': boot_devices.UEFIHTTP})
|
||||
|
||||
def clean_up_ramdisk(self, task):
|
||||
"""Cleans up the boot of ironic ramdisk.
|
||||
|
||||
This method cleans up the environment that was setup for booting the
|
||||
deploy ramdisk.
|
||||
|
||||
:param task: A task from TaskManager.
|
||||
:returns: None
|
||||
"""
|
||||
if manager_utils.is_fast_track(task):
|
||||
LOG.debug('Fast track operation for node %s, not ejecting '
|
||||
'any devices', task.node.uuid)
|
||||
return
|
||||
|
||||
LOG.debug("Cleaning up deploy boot for "
|
||||
"%(node)s", {'node': task.node.uuid})
|
||||
self._clean_up(task)
|
||||
|
||||
def prepare_instance(self, task):
|
||||
"""Prepares the boot of instance over virtual media.
|
||||
|
||||
This method prepares the boot of the instance after reading
|
||||
relevant information from the node's instance_info.
|
||||
|
||||
The internal logic is as follows:
|
||||
|
||||
- Cleanup any related files
|
||||
- Sync the boot mode with the machine.
|
||||
- Configure Secure boot, if required.
|
||||
- If local boot, or a whole disk image was deployed,
|
||||
set the next boot device as disk.
|
||||
- If "ramdisk" is the desired, then the UefiHttp boot
|
||||
option is set to the BMC with a request for this to
|
||||
be persistent.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:returns: None
|
||||
:raises: InstanceDeployFailure, if its try to boot iSCSI volume in
|
||||
'BIOS' boot mode.
|
||||
"""
|
||||
node = task.node
|
||||
|
||||
self._clean_up(task)
|
||||
|
||||
boot_mode_utils.sync_boot_mode(task)
|
||||
boot_mode_utils.configure_secure_boot_if_needed(task)
|
||||
|
||||
boot_option = deploy_utils.get_boot_option(node)
|
||||
iwdi = node.driver_internal_info.get('is_whole_disk_image')
|
||||
if boot_option == "local" or iwdi:
|
||||
self._set_boot_device(task, boot_devices.DISK, persistent=True)
|
||||
|
||||
LOG.debug("Node %(node)s is set to permanently boot from local "
|
||||
"%(device)s", {'node': task.node.uuid,
|
||||
'device': boot_devices.DISK})
|
||||
return
|
||||
|
||||
params = {}
|
||||
|
||||
if boot_option != 'ramdisk':
|
||||
root_uuid = node.driver_internal_info.get('root_uuid_or_disk_id')
|
||||
if not root_uuid and task.driver.storage.should_write_image(task):
|
||||
LOG.warning(
|
||||
"The UUID of the root partition could not be found for "
|
||||
"node %s. Booting instance from disk anyway.", node.uuid)
|
||||
|
||||
self._set_boot_device(task, boot_devices.DISK, persistent=True)
|
||||
|
||||
return
|
||||
|
||||
params.update(root_uuid=root_uuid)
|
||||
|
||||
deploy_info = _parse_deploy_info(node)
|
||||
|
||||
iso_ref = image_utils.prepare_boot_iso(task, deploy_info, **params)
|
||||
self._set_boot_device(task, boot_devices.UEFIHTTP, persistent=True,
|
||||
http_boot_url=iso_ref)
|
||||
|
||||
LOG.debug("Node %(node)s is set to permanently boot from "
|
||||
"%(device)s", {'node': task.node.uuid,
|
||||
'device': boot_devices.UEFIHTTP})
|
||||
|
||||
def _clean_up(self, task):
|
||||
image_utils.cleanup_iso_image(task)
|
||||
|
||||
def clean_up_instance(self, task):
|
||||
"""Cleans up the boot of instance.
|
||||
|
||||
This method cleans up the environment that was setup for booting
|
||||
the instance.
|
||||
|
||||
:param task: A task from TaskManager.
|
||||
:returns: None
|
||||
"""
|
||||
LOG.debug("Cleaning up instance boot for "
|
||||
"%(node)s", {'node': task.node.uuid})
|
||||
self._clean_up(task)
|
||||
boot_mode_utils.deconfigure_secure_boot_if_needed(task)
|
||||
|
||||
@classmethod
|
||||
def _set_boot_device(cls, task, device, persistent=False,
|
||||
http_boot_url=None):
|
||||
"""Set the boot device for a node.
|
||||
|
||||
This is a hook method which can be used by other drivers based upon
|
||||
this class, in order to facilitate vendor specific logic,
|
||||
if needed.
|
||||
|
||||
Furthermore, we are not considering a *lack* of a URL as fatal.
|
||||
A driver could easily update DHCP and send the message to the BMC.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param device: the boot device, one of
|
||||
:mod:`ironic.common.boot_devices`.
|
||||
:param persistent: Whether to set next-boot, or make the change
|
||||
permanent. Default: False.
|
||||
:param http_boot_url: The URL to send to the BMC in order to boot
|
||||
the node via UEFIHTTP.
|
||||
:raises: InvalidParameterValue if the validation of the
|
||||
ManagementInterface fails.
|
||||
"""
|
||||
|
||||
if http_boot_url:
|
||||
common_utils.set_node_nested_field(
|
||||
task.node, 'driver_internal_info',
|
||||
'redfish_uefi_http_url', http_boot_url)
|
||||
task.node.save()
|
||||
manager_utils.node_set_boot_device(task, device, persistent)
|
||||
|
@ -52,7 +52,8 @@ if sushy:
|
||||
sushy.BOOT_SOURCE_TARGET_PXE: boot_devices.PXE,
|
||||
sushy.BOOT_SOURCE_TARGET_HDD: boot_devices.DISK,
|
||||
sushy.BOOT_SOURCE_TARGET_CD: boot_devices.CDROM,
|
||||
sushy.BOOT_SOURCE_TARGET_BIOS_SETUP: boot_devices.BIOS
|
||||
sushy.BOOT_SOURCE_TARGET_BIOS_SETUP: boot_devices.BIOS,
|
||||
sushy.BOOT_SOURCE_TARGET_UEFI_HTTP: boot_devices.UEFIHTTP
|
||||
}
|
||||
|
||||
BOOT_DEVICE_MAP_REV = {v: k for k, v in BOOT_DEVICE_MAP.items()}
|
||||
@ -101,7 +102,8 @@ _FIRMWARE_UPDATE_ARGS = {
|
||||
}}
|
||||
|
||||
|
||||
def _set_boot_device(task, system, device, persistent=False):
|
||||
def _set_boot_device(task, system, device, persistent=False,
|
||||
http_boot_url=None):
|
||||
"""An internal routine to set the boot device.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
@ -110,6 +112,8 @@ def _set_boot_device(task, system, device, persistent=False):
|
||||
:param persistent: Boolean value. True if the boot device will
|
||||
persist to all future boots, False if not.
|
||||
Default: False.
|
||||
:param http_boot_url: A string value to be sent to the sushy library,
|
||||
which is sent to the BMC as the url to boot from.
|
||||
:raises: SushyError on an error from the Sushy library
|
||||
"""
|
||||
|
||||
@ -133,7 +137,10 @@ def _set_boot_device(task, system, device, persistent=False):
|
||||
enabled = (desired_enabled
|
||||
if desired_enabled != current_enabled else None)
|
||||
try:
|
||||
system.set_system_boot_options(device, enabled=enabled)
|
||||
# NOTE(TheJulia): In sushy, it is uri, due to the convention used
|
||||
# in the standard. URL is used internally in ironic.
|
||||
system.set_system_boot_options(device, enabled=enabled,
|
||||
http_boot_uri=http_boot_url)
|
||||
except sushy.exceptions.SushyError as e:
|
||||
if enabled == sushy.BOOT_SOURCE_ENABLED_CONTINUOUS:
|
||||
# NOTE(dtantsur): continuous boot device settings have been
|
||||
@ -146,7 +153,8 @@ def _set_boot_device(task, system, device, persistent=False):
|
||||
'falling back to one-time boot settings',
|
||||
{'error': e, 'node': task.node.uuid})
|
||||
system.set_system_boot_options(
|
||||
device, enabled=sushy.BOOT_SOURCE_ENABLED_ONCE)
|
||||
device, enabled=sushy.BOOT_SOURCE_ENABLED_ONCE,
|
||||
http_boot_uri=http_boot_url)
|
||||
LOG.warning('Could not set persistent boot device to '
|
||||
'%(dev)s for node %(node)s, using one-time '
|
||||
'boot device instead',
|
||||
@ -254,6 +262,8 @@ class RedfishManagement(base.ManagementInterface):
|
||||
"""
|
||||
utils.pop_node_nested_field(
|
||||
task.node, 'driver_internal_info', 'redfish_boot_device')
|
||||
http_boot_url = utils.pop_node_nested_field(
|
||||
task.node, 'driver_internal_info', 'redfish_uefi_http_url')
|
||||
task.node.save()
|
||||
|
||||
system = redfish_utils.get_system(task.node)
|
||||
@ -261,7 +271,7 @@ class RedfishManagement(base.ManagementInterface):
|
||||
try:
|
||||
_set_boot_device(
|
||||
task, system, BOOT_DEVICE_MAP_REV[device],
|
||||
persistent=persistent)
|
||||
persistent=persistent, http_boot_url=http_boot_url)
|
||||
except sushy.exceptions.SushyError as e:
|
||||
error_msg = (_('Redfish set boot device failed for node '
|
||||
'%(node)s. Error: %(error)s') %
|
||||
|
@ -59,7 +59,8 @@ class RedfishHardware(generic.GenericHardware):
|
||||
# NOTE(dtantsur): virtual media goes last because of limited hardware
|
||||
# vendors support.
|
||||
return [ipxe.iPXEBoot, pxe.PXEBoot,
|
||||
redfish_boot.RedfishVirtualMediaBoot]
|
||||
redfish_boot.RedfishVirtualMediaBoot,
|
||||
redfish_boot.RedfishHttpsBoot]
|
||||
|
||||
@property
|
||||
def supported_vendor_interfaces(self):
|
||||
|
@ -1671,3 +1671,853 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
||||
sushy.VIRTUAL_MEDIA_FLOPPY,
|
||||
redfish_boot._has_vmedia_device(
|
||||
[mock_manager], sushy.VIRTUAL_MEDIA_FLOPPY, inserted=True))
|
||||
|
||||
|
||||
@mock.patch('oslo_utils.eventletutils.EventletEvent.wait',
|
||||
lambda *args, **kwargs: None)
|
||||
class RedfishHTTPBootTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(RedfishHTTPBootTestCase, self).setUp()
|
||||
self.config(enabled_hardware_types=['redfish'],
|
||||
enabled_power_interfaces=['redfish'],
|
||||
enabled_boot_interfaces=['redfish-https'],
|
||||
enabled_management_interfaces=['redfish'],
|
||||
enabled_inspect_interfaces=['redfish'],
|
||||
enabled_bios_interfaces=['redfish'])
|
||||
self.node = obj_utils.create_test_node(
|
||||
self.context, driver='redfish', driver_info=INFO_DICT)
|
||||
|
||||
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
|
||||
autospec=True)
|
||||
@mock.patch.object(image_utils, 'prepare_deploy_iso', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True)
|
||||
@mock.patch.object(redfish_boot.manager_utils, 'node_power_action',
|
||||
autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_prepare_ramdisk_with_params(
|
||||
self, mock_system, mock_boot_mode_utils, mock_node_power_action,
|
||||
mock__parse_driver_info,
|
||||
mock_prepare_deploy_iso, mock_node_set_boot_device):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.node.provision_state = states.DEPLOYING
|
||||
|
||||
mock__parse_driver_info.return_value = {}
|
||||
mock_prepare_deploy_iso.return_value = 'image-url'
|
||||
|
||||
task.driver.boot.prepare_ramdisk(task, {})
|
||||
|
||||
mock_node_power_action.assert_called_once_with(
|
||||
task, states.POWER_OFF)
|
||||
|
||||
token = task.node.driver_internal_info['agent_secret_token']
|
||||
self.assertTrue(token)
|
||||
|
||||
expected_params = {
|
||||
'ipa-agent-token': token,
|
||||
'ipa-debug': '1',
|
||||
'boot_method': 'vmedia',
|
||||
}
|
||||
|
||||
mock_prepare_deploy_iso.assert_called_once_with(
|
||||
task, expected_params, 'deploy', {})
|
||||
|
||||
mock_node_set_boot_device.assert_called_once_with(
|
||||
task, boot_devices.UEFIHTTP, False)
|
||||
self.assertEqual('image-url',
|
||||
task.node.driver_internal_info.get(
|
||||
'redfish_uefi_http_url'))
|
||||
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
||||
self.assertTrue(task.node.driver_internal_info[
|
||||
'agent_secret_token_pregenerated'])
|
||||
|
||||
@mock.patch.object(deploy_utils, 'get_boot_option', lambda node: 'ramdisk')
|
||||
def test_parse_driver_info_ramdisk(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.driver_info = {}
|
||||
task.node.automated_clean = False
|
||||
actual_driver_info = redfish_boot._parse_driver_info(task.node)
|
||||
self.assertEqual({'can_provide_config': False},
|
||||
actual_driver_info)
|
||||
|
||||
def test_parse_driver_info_deploy(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.driver_info.update(
|
||||
{'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'}
|
||||
)
|
||||
|
||||
actual_driver_info = redfish_boot._parse_driver_info(task.node)
|
||||
|
||||
self.assertIn('kernel', actual_driver_info['deploy_kernel'])
|
||||
self.assertIn('ramdisk', actual_driver_info['deploy_ramdisk'])
|
||||
self.assertIn('bootloader', actual_driver_info['bootloader'])
|
||||
self.assertTrue(actual_driver_info['can_provide_config'])
|
||||
|
||||
def test_parse_driver_info_iso(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.driver_info.update(
|
||||
{'deploy_iso': 'http://boot.iso'})
|
||||
|
||||
actual_driver_info = redfish_boot._parse_driver_info(task.node)
|
||||
|
||||
self.assertEqual('http://boot.iso',
|
||||
actual_driver_info['deploy_iso'])
|
||||
self.assertFalse(actual_driver_info['can_provide_config'])
|
||||
|
||||
def test_parse_driver_info_rescue(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.provision_state = states.RESCUING
|
||||
task.node.driver_info.update(
|
||||
{'rescue_kernel': 'kernel',
|
||||
'rescue_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'}
|
||||
)
|
||||
|
||||
actual_driver_info = redfish_boot._parse_driver_info(task.node)
|
||||
|
||||
self.assertIn('kernel', actual_driver_info['rescue_kernel'])
|
||||
self.assertIn('ramdisk', actual_driver_info['rescue_ramdisk'])
|
||||
self.assertIn('bootloader', actual_driver_info['bootloader'])
|
||||
|
||||
def test_parse_driver_info_exc(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
redfish_boot._parse_driver_info,
|
||||
task.node)
|
||||
|
||||
def _test_parse_driver_info_from_conf(self, mode='deploy', by_arch=False):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
if mode == 'rescue':
|
||||
task.node.provision_state = states.RESCUING
|
||||
|
||||
if by_arch:
|
||||
ramdisk = 'glance://%s_ramdisk_uuid' % mode
|
||||
kernel = 'glance://%s_kernel_uuid' % mode
|
||||
|
||||
config = {
|
||||
'%s_ramdisk_by_arch' % mode: {'x86_64': ramdisk},
|
||||
'%s_kernel_by_arch' % mode: {'x86_64': kernel}
|
||||
}
|
||||
expected = {
|
||||
'%s_ramdisk' % mode: ramdisk,
|
||||
'%s_kernel' % mode: kernel
|
||||
}
|
||||
else:
|
||||
expected = {
|
||||
'%s_ramdisk' % mode: 'glance://%s_ramdisk_uuid' % mode,
|
||||
'%s_kernel' % mode: 'glance://%s_kernel_uuid' % mode
|
||||
}
|
||||
config = expected
|
||||
|
||||
self.config(group='conductor', **config)
|
||||
|
||||
image_info = redfish_boot._parse_driver_info(task.node)
|
||||
|
||||
for key, value in expected.items():
|
||||
self.assertEqual(value, image_info[key])
|
||||
|
||||
def test_parse_driver_info_from_conf_deploy(self):
|
||||
self._test_parse_driver_info_from_conf()
|
||||
|
||||
def test_parse_driver_info_from_conf_rescue(self):
|
||||
self._test_parse_driver_info_from_conf(mode='rescue')
|
||||
|
||||
def test_parse_driver_info_from_conf_deploy_by_arch(self):
|
||||
self._test_parse_driver_info_from_conf(by_arch=True)
|
||||
|
||||
def test_parse_driver_info_from_conf_rescue_by_arch(self):
|
||||
self._test_parse_driver_info_from_conf(mode='rescue', by_arch=True)
|
||||
|
||||
def _test_parse_driver_info_mixed_source(self, mode='deploy',
|
||||
by_arch=False):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
if mode == 'rescue':
|
||||
task.node.provision_state = states.RESCUING
|
||||
|
||||
if by_arch:
|
||||
kernel_config = {
|
||||
'%s_kernel_by_arch' % mode: {
|
||||
'x86': 'glance://%s_kernel_uuid' % mode
|
||||
}
|
||||
}
|
||||
else:
|
||||
kernel_config = {
|
||||
'%s_kernel' % mode: 'glance://%s_kernel_uuid' % mode
|
||||
}
|
||||
|
||||
ramdisk_config = {
|
||||
'%s_ramdisk' % mode: 'glance://%s_ramdisk_uuid' % mode,
|
||||
}
|
||||
|
||||
self.config(group='conductor', **kernel_config)
|
||||
|
||||
task.node.driver_info.update(ramdisk_config)
|
||||
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
redfish_boot._parse_driver_info, task.node)
|
||||
|
||||
def test_parse_driver_info_mixed_source_deploy(self):
|
||||
self._test_parse_driver_info_mixed_source()
|
||||
|
||||
def test_parse_driver_info_mixed_source_rescue(self):
|
||||
self._test_parse_driver_info_mixed_source(mode='rescue')
|
||||
|
||||
def test_parse_driver_info_mixed_source_deploy_by_arch(self):
|
||||
self._test_parse_driver_info_mixed_source(by_arch=True)
|
||||
|
||||
def test_parse_driver_info_mixed_source_rescue_by_arch(self):
|
||||
self._test_parse_driver_info_mixed_source(mode='rescue', by_arch=True)
|
||||
|
||||
def _test_parse_driver_info_choose_by_arch(self, mode='deploy'):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
if mode == 'rescue':
|
||||
task.node.provision_state = states.RESCUING
|
||||
task.node.properties['cpu_arch'] = 'aarch64'
|
||||
wrong_ramdisk = 'glance://wrong_%s_ramdisk_uuid' % mode
|
||||
wrong_kernel = 'glance://wrong_%s_kernel_uuid' % mode
|
||||
ramdisk = 'glance://%s_ramdisk_uuid' % mode
|
||||
kernel = 'glance://%s_kernel_uuid' % mode
|
||||
|
||||
config = {
|
||||
'%s_ramdisk_by_arch' % mode: {
|
||||
'x86_64': wrong_ramdisk, 'aarch64': ramdisk},
|
||||
'%s_kernel_by_arch' % mode: {
|
||||
'x86_64': wrong_kernel, 'aarch64': kernel}
|
||||
}
|
||||
expected = {
|
||||
'%s_ramdisk' % mode: ramdisk,
|
||||
'%s_kernel' % mode: kernel
|
||||
}
|
||||
|
||||
self.config(group='conductor', **config)
|
||||
|
||||
image_info = redfish_boot._parse_driver_info(task.node)
|
||||
|
||||
for key, value in expected.items():
|
||||
self.assertEqual(value, image_info[key])
|
||||
|
||||
def test_parse_driver_info_choose_by_arch_deploy(self):
|
||||
self._test_parse_driver_info_choose_by_arch()
|
||||
|
||||
def test_parse_driver_info_choose_by_arch_rescue(self):
|
||||
self._test_parse_driver_info_choose_by_arch(mode='rescue')
|
||||
|
||||
def _test_parse_driver_info_choose_by_hierarchy(self, mode='deploy',
|
||||
ramdisk_missing=False):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
if mode == 'rescue':
|
||||
task.node.provision_state = states.RESCUING
|
||||
|
||||
ramdisk = 'glance://def_%s_ramdisk_uuid' % mode
|
||||
kernel = 'glance://def_%s_kernel_uuid' % mode
|
||||
ramdisk_by_arch = 'glance://%s_ramdisk_by_arch_uuid' % mode
|
||||
kernel_by_arch = 'glance://%s_kernel_by_arch_uuid' % mode
|
||||
|
||||
config = {
|
||||
'%s_kernel_by_arch' % mode: {
|
||||
'x86_64': kernel_by_arch},
|
||||
'%s_ramdisk' % mode: ramdisk,
|
||||
'%s_kernel' % mode: kernel
|
||||
}
|
||||
if not ramdisk_missing:
|
||||
config['%s_ramdisk_by_arch' % mode] = {
|
||||
'x86_64': ramdisk_by_arch}
|
||||
expected = {
|
||||
'%s_ramdisk' % mode: ramdisk_by_arch,
|
||||
'%s_kernel' % mode: kernel_by_arch
|
||||
}
|
||||
else:
|
||||
expected = {
|
||||
'%s_ramdisk' % mode: ramdisk,
|
||||
'%s_kernel' % mode: kernel
|
||||
}
|
||||
|
||||
self.config(group='conductor', **config)
|
||||
|
||||
image_info = redfish_boot._parse_driver_info(task.node)
|
||||
|
||||
for key, value in expected.items():
|
||||
self.assertEqual(value, image_info[key])
|
||||
|
||||
def test_parse_driver_info_choose_by_hierarchy_deploy(self):
|
||||
self._test_parse_driver_info_choose_by_hierarchy()
|
||||
|
||||
def test_parse_driver_info_choose_by_hierarchy_rescue(self):
|
||||
self._test_parse_driver_info_choose_by_hierarchy(mode='rescue')
|
||||
|
||||
def test_parse_driver_info_choose_by_hierarchy_missing_param_deploy(self):
|
||||
self._test_parse_driver_info_choose_by_hierarchy(ramdisk_missing=True)
|
||||
|
||||
def test_parse_driver_info_choose_by_hierarchy_missing_param_rescue(self):
|
||||
self._test_parse_driver_info_choose_by_hierarchy(
|
||||
mode='rescue', ramdisk_missing=True)
|
||||
|
||||
def test_parse_deploy_info(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.driver_info.update(
|
||||
{'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'}
|
||||
)
|
||||
|
||||
task.node.instance_info.update(
|
||||
{'image_source': 'http://boot/iso',
|
||||
'kernel': 'http://kernel/img',
|
||||
'ramdisk': 'http://ramdisk/img'})
|
||||
|
||||
actual_instance_info = redfish_boot._parse_deploy_info(task.node)
|
||||
|
||||
self.assertEqual(
|
||||
'http://boot/iso', actual_instance_info['image_source'])
|
||||
self.assertEqual(
|
||||
'http://kernel/img', actual_instance_info['kernel'])
|
||||
self.assertEqual(
|
||||
'http://ramdisk/img', actual_instance_info['ramdisk'])
|
||||
|
||||
def test_parse_deploy_info_exc(self):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
redfish_boot._parse_deploy_info,
|
||||
task.node)
|
||||
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True)
|
||||
def test_validate_local(self, mock_parse_driver_info, mock_get_system):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.instance_info = {}
|
||||
mock_get_system.return_value.boot.allowed_values = [
|
||||
"UefiHttp", "Hdd"]
|
||||
|
||||
task.node.driver_info.update(
|
||||
{'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'}
|
||||
)
|
||||
task.driver.boot.validate(task)
|
||||
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True)
|
||||
def test_validate_errors_with_lack_of_support(
|
||||
self, mock_parse_driver_info, mock_get_system):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.instance_info = {}
|
||||
mock_get_system.return_value.boot.allowed_values = ["Hdd"]
|
||||
|
||||
task.node.driver_info.update(
|
||||
{'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'}
|
||||
)
|
||||
msg = ("Node %s hardware does not support feature UefiHttp boot, "
|
||||
"which is required based upon the requested configuration."
|
||||
% task.node.uuid)
|
||||
self.assertRaisesRegex(
|
||||
exception.UnsupportedHardwareFeature,
|
||||
msg, task.driver.boot.validate, task)
|
||||
|
||||
@mock.patch.object(redfish_boot.RedfishHttpsBoot, '_validate_hardware',
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'get_boot_option', lambda node: 'ramdisk')
|
||||
@mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'validate_image_properties',
|
||||
autospec=True)
|
||||
def test_validate_kernel_ramdisk(self, mock_validate_image_properties,
|
||||
mock_parse_driver_info,
|
||||
mock_validate_hardware):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.instance_info.update(
|
||||
{'kernel': 'kernel',
|
||||
'ramdisk': 'ramdisk',
|
||||
'image_source': 'http://image/source'}
|
||||
)
|
||||
|
||||
task.node.driver_info.update(
|
||||
{'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'}
|
||||
)
|
||||
|
||||
task.driver.boot.validate(task)
|
||||
|
||||
mock_validate_image_properties.assert_called_once_with(
|
||||
task, mock.ANY)
|
||||
mock_validate_hardware.assert_called_once_with(mock.ANY, task)
|
||||
|
||||
@mock.patch.object(redfish_boot.RedfishHttpsBoot, '_validate_hardware',
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'get_boot_option', lambda node: 'ramdisk')
|
||||
@mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'validate_image_properties',
|
||||
autospec=True)
|
||||
def test_validate_boot_iso(self, mock_validate_image_properties,
|
||||
mock_parse_driver_info,
|
||||
mock_validate_hardware):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.instance_info.update(
|
||||
{'boot_iso': 'http://localhost/file.iso'}
|
||||
)
|
||||
|
||||
task.node.driver_info.update(
|
||||
{'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'}
|
||||
)
|
||||
|
||||
task.driver.boot.validate(task)
|
||||
|
||||
mock_validate_image_properties.assert_called_once_with(
|
||||
task, mock.ANY)
|
||||
mock_validate_hardware.assert_called_once_with(mock.ANY, task)
|
||||
|
||||
@mock.patch.object(redfish_boot.RedfishHttpsBoot, '_validate_hardware',
|
||||
autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'validate_image_properties',
|
||||
autospec=True)
|
||||
def test_validate_correct_vendor(self, mock_validate_image_properties,
|
||||
mock_parse_driver_info,
|
||||
mock_validate_hardware):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.instance_info.update(
|
||||
{'kernel': 'kernel',
|
||||
'ramdisk': 'ramdisk',
|
||||
'image_source': 'http://image/source'}
|
||||
)
|
||||
|
||||
task.node.driver_info.update(
|
||||
{'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'}
|
||||
)
|
||||
|
||||
task.node.properties['vendor'] = "Ironic Co."
|
||||
|
||||
task.driver.boot.validate(task)
|
||||
mock_validate_hardware.assert_called_once_with(mock.ANY, task)
|
||||
|
||||
@mock.patch.object(redfish_boot.RedfishHttpsBoot, '_validate_hardware',
|
||||
autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'validate_image_properties',
|
||||
autospec=True)
|
||||
def test_validate_missing(self, mock_validate_image_properties,
|
||||
mock_parse_driver_info, mock_validate_hardware):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
task.driver.boot.validate, task)
|
||||
mock_validate_hardware.assert_called_once_with(mock.ANY, task)
|
||||
|
||||
@mock.patch.object(redfish_utils, 'parse_driver_info', autospec=True)
|
||||
def test_validate_inspection(self, mock_parse_driver_info):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.driver_info.update(
|
||||
{'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'}
|
||||
)
|
||||
|
||||
task.driver.boot.validate_inspection(task)
|
||||
|
||||
mock_parse_driver_info.assert_called_once_with(task.node)
|
||||
|
||||
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
|
||||
autospec=True)
|
||||
@mock.patch.object(image_utils, 'prepare_deploy_iso', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True)
|
||||
@mock.patch.object(redfish_boot.manager_utils, 'node_power_action',
|
||||
autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_prepare_ramdisk_no_debug(
|
||||
self, mock_system, mock_boot_mode_utils, mock_node_power_action,
|
||||
mock__parse_driver_info,
|
||||
mock_prepare_deploy_iso, mock_node_set_boot_device):
|
||||
self.config(debug=False)
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.provision_state = states.DEPLOYING
|
||||
|
||||
mock__parse_driver_info.return_value = {}
|
||||
mock_prepare_deploy_iso.return_value = 'image-url'
|
||||
|
||||
task.driver.boot.prepare_ramdisk(task, {})
|
||||
|
||||
mock_node_power_action.assert_called_once_with(
|
||||
task, states.POWER_OFF)
|
||||
|
||||
expected_params = {
|
||||
'ipa-agent-token': mock.ANY,
|
||||
'boot_method': 'vmedia',
|
||||
}
|
||||
|
||||
mock_prepare_deploy_iso.assert_called_once_with(
|
||||
task, expected_params, 'deploy', {})
|
||||
|
||||
mock_node_set_boot_device.assert_called_once_with(
|
||||
task, boot_devices.UEFIHTTP, False)
|
||||
|
||||
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(manager_utils, 'is_fast_track', lambda task: True)
|
||||
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
|
||||
autospec=True)
|
||||
@mock.patch.object(image_utils, 'prepare_deploy_iso', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True)
|
||||
@mock.patch.object(redfish_boot.manager_utils, 'node_power_action',
|
||||
autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_prepare_ramdisk_fast_track(
|
||||
self, mock_system, mock_boot_mode_utils, mock_node_power_action,
|
||||
mock__parse_driver_info,
|
||||
mock_prepare_deploy_iso, mock_node_set_boot_device):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.node.provision_state = states.DEPLOYING
|
||||
|
||||
task.driver.boot.prepare_ramdisk(task, {})
|
||||
|
||||
mock_node_power_action.assert_not_called()
|
||||
mock_prepare_deploy_iso.assert_not_called()
|
||||
mock_node_set_boot_device.assert_not_called()
|
||||
mock_boot_mode_utils.sync_boot_mode.assert_not_called()
|
||||
|
||||
@mock.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
|
||||
@mock.patch.object(image_utils, 'cleanup_floppy_image', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_parse_driver_info', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_clean_up_ramdisk(
|
||||
self, mock_system, mock__parse_driver_info,
|
||||
mock_cleanup_floppy_image, mock_cleanup_iso_image):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.provision_state = states.DEPLOYING
|
||||
task.node.driver_info['config_via_removable'] = True
|
||||
|
||||
task.driver.boot.clean_up_ramdisk(task)
|
||||
|
||||
mock_cleanup_iso_image.assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(redfish_boot.RedfishHttpsBoot,
|
||||
'_clean_up', autospec=True)
|
||||
@mock.patch.object(image_utils, 'prepare_boot_iso', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_parse_deploy_info', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'manager_utils', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_prepare_instance_normal_boot(
|
||||
self, mock_system, mock_boot_mode_utils, mock_deploy_utils,
|
||||
mock_manager_utils, mock__parse_deploy_info,
|
||||
mock_prepare_boot_iso, mock_clean_up_instance):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.provision_state = states.DEPLOYING
|
||||
task.node.driver_internal_info[
|
||||
'root_uuid_or_disk_id'] = self.node.uuid
|
||||
|
||||
mock_deploy_utils.get_boot_option.return_value = 'net'
|
||||
|
||||
d_info = {
|
||||
'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'
|
||||
}
|
||||
|
||||
mock__parse_deploy_info.return_value = d_info
|
||||
|
||||
mock_prepare_boot_iso.return_value = 'image-url'
|
||||
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
expected_params = {
|
||||
'root_uuid': self.node.uuid
|
||||
}
|
||||
|
||||
mock_prepare_boot_iso.assert_called_once_with(
|
||||
task, d_info, **expected_params)
|
||||
|
||||
mock_manager_utils.node_set_boot_device.assert_called_once_with(
|
||||
task, boot_devices.UEFIHTTP, persistent=True)
|
||||
|
||||
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
||||
csb = mock_boot_mode_utils.configure_secure_boot_if_needed
|
||||
csb.assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(redfish_boot.RedfishHttpsBoot,
|
||||
'_clean_up', autospec=True)
|
||||
@mock.patch.object(image_utils, 'prepare_boot_iso', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_parse_deploy_info', autospec=True)
|
||||
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
|
||||
autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_prepare_instance_ramdisk_boot(
|
||||
self, mock_system, mock_boot_mode_utils, mock_deploy_utils,
|
||||
mock_node_set_boot_device, mock__parse_deploy_info,
|
||||
mock_prepare_boot_iso, mock_clean_up_instance):
|
||||
|
||||
configdrive = 'Y29udGVudA=='
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.provision_state = states.DEPLOYING
|
||||
task.node.driver_internal_info[
|
||||
'root_uuid_or_disk_id'] = self.node.uuid
|
||||
task.node.instance_info['configdrive'] = configdrive
|
||||
|
||||
mock_deploy_utils.get_boot_option.return_value = 'ramdisk'
|
||||
|
||||
d_info = {
|
||||
'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'
|
||||
}
|
||||
mock__parse_deploy_info.return_value = d_info
|
||||
|
||||
mock_prepare_boot_iso.return_value = 'image-url'
|
||||
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
mock_clean_up_instance.assert_called_once_with(mock.ANY, task)
|
||||
|
||||
mock_prepare_boot_iso.assert_called_once_with(task, d_info)
|
||||
|
||||
mock_node_set_boot_device.assert_called_once_with(
|
||||
task, boot_devices.UEFIHTTP, persistent=True)
|
||||
|
||||
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(redfish_boot.RedfishHttpsBoot,
|
||||
'_clean_up', autospec=True)
|
||||
@mock.patch.object(image_utils, 'prepare_boot_iso', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_parse_deploy_info', autospec=True)
|
||||
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
|
||||
autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_prepare_instance_ramdisk_boot_iso(
|
||||
self, mock_system, mock_boot_mode_utils, mock_deploy_utils,
|
||||
mock_node_set_boot_device, mock__parse_deploy_info,
|
||||
mock_prepare_boot_iso,
|
||||
mock_clean_up_instance):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.provision_state = states.DEPLOYING
|
||||
task.node.driver_internal_info[
|
||||
'root_uuid_or_disk_id'] = self.node.uuid
|
||||
task.node.instance_info['configdrive'] = None
|
||||
|
||||
mock_deploy_utils.get_boot_option.return_value = 'ramdisk'
|
||||
|
||||
d_info = {
|
||||
'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'
|
||||
}
|
||||
|
||||
mock__parse_deploy_info.return_value = d_info
|
||||
mock_prepare_boot_iso.return_value = 'image-url'
|
||||
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
mock_prepare_boot_iso.assert_called_once_with(task, d_info)
|
||||
|
||||
mock_node_set_boot_device.assert_called_once_with(
|
||||
task, boot_devices.UEFIHTTP, persistent=True)
|
||||
|
||||
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
||||
|
||||
@mock.patch.object(image_utils, 'cleanup_disk_image', autospec=True)
|
||||
@mock.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
|
||||
@mock.patch.object(image_utils, 'prepare_boot_iso', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_parse_deploy_info', autospec=True)
|
||||
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
|
||||
autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_prepare_instance_ramdisk_boot_iso_boot(
|
||||
self, mock_system, mock_boot_mode_utils, mock_deploy_utils,
|
||||
mock_node_set_boot_device, mock__parse_deploy_info,
|
||||
mock_prepare_boot_iso,
|
||||
mock_image_cleanup, mock_disk_cleanup):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.provision_state = states.DEPLOYING
|
||||
i_info = task.node.instance_info
|
||||
i_info['boot_iso'] = "super-magic"
|
||||
del i_info['configdrive']
|
||||
task.node.instance_info = i_info
|
||||
mock_deploy_utils.get_boot_option.return_value = 'ramdisk'
|
||||
mock__parse_deploy_info.return_value = {}
|
||||
|
||||
mock_prepare_boot_iso.return_value = 'image-url'
|
||||
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
mock_prepare_boot_iso.assert_called_once_with(task, {})
|
||||
|
||||
mock_node_set_boot_device.assert_called_once_with(
|
||||
task, boot_devices.UEFIHTTP, persistent=True)
|
||||
|
||||
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
||||
mock_image_cleanup.assert_called_once_with(task)
|
||||
mock_disk_cleanup.assert_not_called()
|
||||
|
||||
@mock.patch.object(image_utils, 'cleanup_disk_image', autospec=True)
|
||||
@mock.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
|
||||
@mock.patch.object(image_utils, 'prepare_boot_iso', autospec=True)
|
||||
@mock.patch.object(redfish_boot, '_parse_deploy_info', autospec=True)
|
||||
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
|
||||
autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_prepare_instance_ramdisk_boot_render_configdrive(
|
||||
self, mock_system, mock_boot_mode_utils, mock_deploy_utils,
|
||||
mock_node_set_boot_device, mock__parse_deploy_info,
|
||||
mock_prepare_boot_iso,
|
||||
mock_image_cleanup, mock_disk_cleanup):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.provision_state = states.DEPLOYING
|
||||
task.node.driver_internal_info[
|
||||
'root_uuid_or_disk_id'] = self.node.uuid
|
||||
task.node.instance_info['configdrive'] = {'meta_data': {}}
|
||||
|
||||
mock_deploy_utils.get_boot_option.return_value = 'ramdisk'
|
||||
|
||||
d_info = {
|
||||
'deploy_kernel': 'kernel',
|
||||
'deploy_ramdisk': 'ramdisk',
|
||||
'bootloader': 'bootloader'
|
||||
}
|
||||
mock__parse_deploy_info.return_value = d_info
|
||||
|
||||
mock_prepare_boot_iso.return_value = 'image-url'
|
||||
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
mock_prepare_boot_iso.assert_called_once_with(task, d_info)
|
||||
|
||||
mock_node_set_boot_device.assert_called_once_with(
|
||||
task, boot_devices.UEFIHTTP, persistent=True)
|
||||
|
||||
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
||||
mock_image_cleanup.assert_called_once_with(task)
|
||||
mock_disk_cleanup.assert_not_called()
|
||||
|
||||
@mock.patch.object(boot_mode_utils, 'configure_secure_boot_if_needed',
|
||||
autospec=True)
|
||||
@mock.patch.object(boot_mode_utils, 'sync_boot_mode', autospec=True)
|
||||
@mock.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
|
||||
@mock.patch.object(redfish_boot, 'manager_utils', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def _test_prepare_instance_local_boot(
|
||||
self, mock_system, mock_manager_utils,
|
||||
mock_cleanup_iso_image, mock_sync_boot_mode,
|
||||
mock_secure_boot):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
task.node.provision_state = states.DEPLOYING
|
||||
task.node.driver_internal_info[
|
||||
'root_uuid_or_disk_id'] = self.node.uuid
|
||||
|
||||
task.driver.boot.prepare_instance(task)
|
||||
|
||||
mock_manager_utils.node_set_boot_device.assert_called_once_with(
|
||||
task, boot_devices.DISK, persistent=True)
|
||||
mock_cleanup_iso_image.assert_called_once_with(task)
|
||||
mock_sync_boot_mode.assert_called_once_with(task)
|
||||
mock_secure_boot.assert_called_once_with(task)
|
||||
|
||||
def test_prepare_instance_local_whole_disk_image(self):
|
||||
self.node.driver_internal_info = {'is_whole_disk_image': True}
|
||||
self.node.save()
|
||||
self._test_prepare_instance_local_boot()
|
||||
|
||||
def test_prepare_instance_local_boot_option(self):
|
||||
instance_info = self.node.instance_info
|
||||
instance_info['capabilities'] = '{"boot_option": "local"}'
|
||||
self.node.instance_info = instance_info
|
||||
self.node.save()
|
||||
self._test_prepare_instance_local_boot()
|
||||
|
||||
@mock.patch.object(boot_mode_utils, 'deconfigure_secure_boot_if_needed',
|
||||
autospec=True)
|
||||
@mock.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def _test_clean_up_instance(self, mock_system, mock_cleanup_iso_image,
|
||||
mock_secure_boot):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
|
||||
task.driver.boot.clean_up_instance(task)
|
||||
|
||||
mock_cleanup_iso_image.assert_called_once_with(task)
|
||||
mock_secure_boot.assert_called_once_with(task)
|
||||
|
||||
def test_clean_up_instance_only_cdrom(self):
|
||||
self._test_clean_up_instance()
|
||||
|
||||
def test_clean_up_instance_cdrom_and_floppy(self):
|
||||
driver_info = self.node.driver_info
|
||||
driver_info['config_via_removable'] = True
|
||||
self.node.driver_info = driver_info
|
||||
self.node.save()
|
||||
self._test_clean_up_instance()
|
||||
|
||||
@mock.patch.object(boot_mode_utils, 'deconfigure_secure_boot_if_needed',
|
||||
autospec=True)
|
||||
@mock.patch.object(deploy_utils, 'get_boot_option', autospec=True)
|
||||
@mock.patch.object(image_utils, 'cleanup_disk_image', autospec=True)
|
||||
@mock.patch.object(image_utils, 'cleanup_iso_image', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_clean_up_instance_ramdisk(self, mock_system,
|
||||
mock_cleanup_iso_image,
|
||||
mock_cleanup_disk_image,
|
||||
mock_get_boot_option,
|
||||
mock_secure_boot):
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=True) as task:
|
||||
mock_get_boot_option.return_value = 'ramdisk'
|
||||
|
||||
task.driver.boot.clean_up_instance(task)
|
||||
|
||||
mock_cleanup_iso_image.assert_called_once_with(task)
|
||||
|
||||
mock_secure_boot.assert_called_once_with(task)
|
||||
mock_cleanup_disk_image.assert_not_called()
|
||||
|
@ -121,7 +121,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
||||
(boot_devices.PXE, sushy.BOOT_SOURCE_TARGET_PXE),
|
||||
(boot_devices.DISK, sushy.BOOT_SOURCE_TARGET_HDD),
|
||||
(boot_devices.CDROM, sushy.BOOT_SOURCE_TARGET_CD),
|
||||
(boot_devices.BIOS, sushy.BOOT_SOURCE_TARGET_BIOS_SETUP)
|
||||
(boot_devices.BIOS, sushy.BOOT_SOURCE_TARGET_BIOS_SETUP),
|
||||
(boot_devices.UEFIHTTP, sushy.BOOT_SOURCE_TARGET_UEFI_HTTP)
|
||||
]
|
||||
|
||||
for target, expected in expected_values:
|
||||
@ -130,7 +131,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
||||
# Asserts
|
||||
fake_system.set_system_boot_options.assert_has_calls(
|
||||
[mock.call(expected,
|
||||
enabled=sushy.BOOT_SOURCE_ENABLED_ONCE)])
|
||||
enabled=sushy.BOOT_SOURCE_ENABLED_ONCE,
|
||||
http_boot_uri=None)])
|
||||
mock_get_system.assert_called_with(task.node)
|
||||
self.assertNotIn('redfish_boot_device',
|
||||
task.node.driver_internal_info)
|
||||
@ -156,7 +158,7 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
||||
|
||||
fake_system.set_system_boot_options.assert_has_calls(
|
||||
[mock.call(sushy.BOOT_SOURCE_TARGET_PXE,
|
||||
enabled=expected)])
|
||||
enabled=expected, http_boot_uri=None)])
|
||||
mock_get_system.assert_called_with(task.node)
|
||||
self.assertNotIn('redfish_boot_device',
|
||||
task.node.driver_internal_info)
|
||||
@ -183,7 +185,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
||||
task, boot_devices.PXE, persistent=target)
|
||||
|
||||
fake_system.set_system_boot_options.assert_has_calls(
|
||||
[mock.call(sushy.BOOT_SOURCE_TARGET_PXE, enabled=None)])
|
||||
[mock.call(sushy.BOOT_SOURCE_TARGET_PXE, enabled=None,
|
||||
http_boot_uri=None)])
|
||||
mock_get_system.assert_called_with(task.node)
|
||||
|
||||
# Reset mocks
|
||||
@ -205,7 +208,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
||||
task.driver.management.set_boot_device, task, boot_devices.PXE)
|
||||
fake_system.set_system_boot_options.assert_called_once_with(
|
||||
sushy.BOOT_SOURCE_TARGET_PXE,
|
||||
enabled=sushy.BOOT_SOURCE_ENABLED_ONCE)
|
||||
enabled=sushy.BOOT_SOURCE_ENABLED_ONCE,
|
||||
http_boot_uri=None)
|
||||
mock_get_system.assert_called_once_with(task.node)
|
||||
self.assertNotIn('redfish_boot_device',
|
||||
task.node.driver_internal_info)
|
||||
@ -232,7 +236,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
||||
task.driver.management.set_boot_device, task,
|
||||
boot_devices.PXE, persistent=target)
|
||||
fake_system.set_system_boot_options.assert_called_once_with(
|
||||
sushy.BOOT_SOURCE_TARGET_PXE, enabled=None)
|
||||
sushy.BOOT_SOURCE_TARGET_PXE, enabled=None,
|
||||
http_boot_uri=None)
|
||||
mock_get_system.assert_called_once_with(task.node)
|
||||
self.assertNotIn('redfish_boot_device',
|
||||
task.node.driver_internal_info)
|
||||
@ -258,9 +263,11 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
||||
task, boot_devices.PXE, persistent=True)
|
||||
fake_system.set_system_boot_options.assert_has_calls([
|
||||
mock.call(sushy.BOOT_SOURCE_TARGET_PXE,
|
||||
enabled=sushy.BOOT_SOURCE_ENABLED_CONTINUOUS),
|
||||
enabled=sushy.BOOT_SOURCE_ENABLED_CONTINUOUS,
|
||||
http_boot_uri=None),
|
||||
mock.call(sushy.BOOT_SOURCE_TARGET_PXE,
|
||||
enabled=sushy.BOOT_SOURCE_ENABLED_ONCE)
|
||||
enabled=sushy.BOOT_SOURCE_ENABLED_ONCE,
|
||||
http_boot_uri=None)
|
||||
])
|
||||
mock_get_system.assert_called_with(task.node)
|
||||
|
||||
@ -292,7 +299,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
||||
task.driver.management.set_boot_device(
|
||||
task, boot_devices.PXE, persistent=True)
|
||||
fake_system.set_system_boot_options.assert_called_once_with(
|
||||
sushy.BOOT_SOURCE_TARGET_PXE, enabled=expected)
|
||||
sushy.BOOT_SOURCE_TARGET_PXE, enabled=expected,
|
||||
http_boot_uri=None)
|
||||
if vendor == 'SuperMicro':
|
||||
mock_sync_boot_mode.assert_called_once_with(task)
|
||||
else:
|
||||
@ -303,6 +311,28 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
||||
mock_sync_boot_mode.reset_mock()
|
||||
mock_get_system.reset_mock()
|
||||
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_set_boot_device_http_boot(self, mock_get_system):
|
||||
fake_system = mock.Mock()
|
||||
mock_get_system.return_value = fake_system
|
||||
self.node.driver_internal_info = {
|
||||
'redfish_uefi_http_url': 'http://foo.url'}
|
||||
self.node.save()
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.management.set_boot_device(task,
|
||||
boot_devices.UEFIHTTP)
|
||||
fake_system.set_system_boot_options.assert_has_calls(
|
||||
[mock.call(sushy.BOOT_SOURCE_TARGET_UEFI_HTTP,
|
||||
enabled=sushy.BOOT_SOURCE_ENABLED_ONCE,
|
||||
http_boot_uri='http://foo.url')])
|
||||
mock_get_system.assert_called_with(task.node)
|
||||
self.assertNotIn('redfish_boot_device',
|
||||
task.node.driver_internal_info)
|
||||
task.node.refresh()
|
||||
self.assertNotIn('redfish_uefi_http_url',
|
||||
task.node.driver_internal_info)
|
||||
|
||||
def test_restore_boot_device(self):
|
||||
fake_system = mock.Mock()
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
@ -315,7 +345,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
||||
|
||||
fake_system.set_system_boot_options.assert_called_once_with(
|
||||
sushy.BOOT_SOURCE_TARGET_HDD,
|
||||
enabled=sushy.BOOT_SOURCE_ENABLED_ONCE)
|
||||
enabled=sushy.BOOT_SOURCE_ENABLED_ONCE,
|
||||
http_boot_uri=None)
|
||||
# The stored boot device is kept intact
|
||||
self.assertEqual(
|
||||
boot_devices.DISK,
|
||||
@ -332,7 +363,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
||||
|
||||
fake_system.set_system_boot_options.assert_called_once_with(
|
||||
sushy.BOOT_SOURCE_TARGET_HDD,
|
||||
enabled=sushy.BOOT_SOURCE_ENABLED_ONCE)
|
||||
enabled=sushy.BOOT_SOURCE_ENABLED_ONCE,
|
||||
http_boot_uri=None)
|
||||
# The stored boot device is kept intact
|
||||
self.assertEqual(
|
||||
"hdd",
|
||||
@ -362,7 +394,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
||||
|
||||
fake_system.set_system_boot_options.assert_called_once_with(
|
||||
sushy.BOOT_SOURCE_TARGET_HDD,
|
||||
enabled=sushy.BOOT_SOURCE_ENABLED_ONCE)
|
||||
enabled=sushy.BOOT_SOURCE_ENABLED_ONCE,
|
||||
http_boot_uri=None)
|
||||
self.assertTrue(mock_log.called)
|
||||
# The stored boot device is kept intact
|
||||
self.assertEqual(
|
||||
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds support for Redfish based HTTPBoot, which leveragings the DMTF Redfish
|
||||
``HttpBootUri`` ``ComputerSystem`` resource in a BMC, to assert the URL
|
||||
for the next boot operation. This requires Sushy 4.7.0 as the minimum
|
||||
version.
|
@ -46,6 +46,6 @@ psutil>=3.2.2 # BSD
|
||||
futurist>=1.2.0 # Apache-2.0
|
||||
tooz>=2.7.0 # Apache-2.0
|
||||
openstacksdk>=0.48.0 # Apache-2.0
|
||||
sushy>=4.3.0
|
||||
sushy>=4.7.0
|
||||
construct>=2.9.39 # MIT
|
||||
netaddr>=0.9.0 # BSD
|
||||
|
@ -79,6 +79,7 @@ ironic.hardware.interfaces.boot =
|
||||
irmc-virtual-media = ironic.drivers.modules.irmc.boot:IRMCVirtualMediaBoot
|
||||
pxe = ironic.drivers.modules.pxe:PXEBoot
|
||||
redfish-virtual-media = ironic.drivers.modules.redfish.boot:RedfishVirtualMediaBoot
|
||||
redfish-https = ironic.drivers.modules.redfish.boot:RedfishHttpsBoot
|
||||
|
||||
ironic.hardware.interfaces.console =
|
||||
fake = ironic.drivers.modules.fake:FakeConsole
|
||||
|
Loading…
Reference in New Issue
Block a user