Redfish UefiHttp boot support
Adds a redfish-https boot interface, based upon the redfish-virtual-media boot interface, however substantially copies some base methods because of simplification offered to use by putting "attach/detach" logic into how the sushy library handles the application and reset of a URL as a boot setting. This feature also increases the requirement for the Sushy library to version 4.7.0 which includes support to set the HttpBootUri field in the BMC and automatically unset it as well. Closes-Bug: #2032380 Change-Id: I991611cd67cb91aea21fc30bbae7cd24409dbbfa
This commit is contained in:
parent
76f68582d6
commit
041a7d7064
@ -23,13 +23,14 @@ Enabling the Redfish driver
|
|||||||
#. Add ``redfish`` to the list of ``enabled_hardware_types``,
|
#. Add ``redfish`` to the list of ``enabled_hardware_types``,
|
||||||
``enabled_power_interfaces``, ``enabled_management_interfaces`` and
|
``enabled_power_interfaces``, ``enabled_management_interfaces`` and
|
||||||
``enabled_inspect_interfaces`` as well as ``redfish-virtual-media``
|
``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::
|
For example::
|
||||||
|
|
||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
...
|
...
|
||||||
enabled_hardware_types = ipmi,redfish
|
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_power_interfaces = ipmitool,redfish
|
||||||
enabled_management_interfaces = ipmitool,redfish
|
enabled_management_interfaces = ipmitool,redfish
|
||||||
enabled_inspect_interfaces = inspector,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
|
DHCP-less deploy is supported by the Redfish virtual media boot. See
|
||||||
:doc:`/admin/dhcp-less` for more information.
|
: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
|
Firmware update using manual cleaning
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
@ -52,3 +52,6 @@ FLOPPY = 'floppy'
|
|||||||
|
|
||||||
VMEDIA_DEVICES = [DISK, CDROM, FLOPPY]
|
VMEDIA_DEVICES = [DISK, CDROM, FLOPPY]
|
||||||
"""Devices that make sense for virtual media attachment."""
|
"""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):
|
class InvalidNodeInventory(Invalid):
|
||||||
_msg_fmt = _("Inventory for node %(node)s is invalid: %(reason)s")
|
_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 import exception
|
||||||
from ironic.common.i18n import _
|
from ironic.common.i18n import _
|
||||||
from ironic.common import states
|
from ironic.common import states
|
||||||
|
from ironic.common import utils as common_utils
|
||||||
from ironic.conductor import utils as manager_utils
|
from ironic.conductor import utils as manager_utils
|
||||||
from ironic.conf import CONF
|
from ironic.conf import CONF
|
||||||
from ironic.drivers import base
|
from ironic.drivers import base
|
||||||
@ -712,6 +713,11 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
|||||||
if not configdrive:
|
if not configdrive:
|
||||||
return
|
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)
|
_eject_vmedia(task, managers, sushy.VIRTUAL_MEDIA_USBSTICK)
|
||||||
cd_ref = image_utils.prepare_configdrive_image(task, configdrive)
|
cd_ref = image_utils.prepare_configdrive_image(task, configdrive)
|
||||||
try:
|
try:
|
||||||
@ -775,3 +781,300 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
|||||||
ManagementInterface fails.
|
ManagementInterface fails.
|
||||||
"""
|
"""
|
||||||
manager_utils.node_set_boot_device(task, device, persistent)
|
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_PXE: boot_devices.PXE,
|
||||||
sushy.BOOT_SOURCE_TARGET_HDD: boot_devices.DISK,
|
sushy.BOOT_SOURCE_TARGET_HDD: boot_devices.DISK,
|
||||||
sushy.BOOT_SOURCE_TARGET_CD: boot_devices.CDROM,
|
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()}
|
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.
|
"""An internal routine to set the boot device.
|
||||||
|
|
||||||
:param task: a task from TaskManager.
|
: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
|
:param persistent: Boolean value. True if the boot device will
|
||||||
persist to all future boots, False if not.
|
persist to all future boots, False if not.
|
||||||
Default: False.
|
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
|
: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
|
enabled = (desired_enabled
|
||||||
if desired_enabled != current_enabled else None)
|
if desired_enabled != current_enabled else None)
|
||||||
try:
|
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:
|
except sushy.exceptions.SushyError as e:
|
||||||
if enabled == sushy.BOOT_SOURCE_ENABLED_CONTINUOUS:
|
if enabled == sushy.BOOT_SOURCE_ENABLED_CONTINUOUS:
|
||||||
# NOTE(dtantsur): continuous boot device settings have been
|
# 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',
|
'falling back to one-time boot settings',
|
||||||
{'error': e, 'node': task.node.uuid})
|
{'error': e, 'node': task.node.uuid})
|
||||||
system.set_system_boot_options(
|
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 '
|
LOG.warning('Could not set persistent boot device to '
|
||||||
'%(dev)s for node %(node)s, using one-time '
|
'%(dev)s for node %(node)s, using one-time '
|
||||||
'boot device instead',
|
'boot device instead',
|
||||||
@ -254,6 +262,8 @@ class RedfishManagement(base.ManagementInterface):
|
|||||||
"""
|
"""
|
||||||
utils.pop_node_nested_field(
|
utils.pop_node_nested_field(
|
||||||
task.node, 'driver_internal_info', 'redfish_boot_device')
|
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()
|
task.node.save()
|
||||||
|
|
||||||
system = redfish_utils.get_system(task.node)
|
system = redfish_utils.get_system(task.node)
|
||||||
@ -261,7 +271,7 @@ class RedfishManagement(base.ManagementInterface):
|
|||||||
try:
|
try:
|
||||||
_set_boot_device(
|
_set_boot_device(
|
||||||
task, system, BOOT_DEVICE_MAP_REV[device],
|
task, system, BOOT_DEVICE_MAP_REV[device],
|
||||||
persistent=persistent)
|
persistent=persistent, http_boot_url=http_boot_url)
|
||||||
except sushy.exceptions.SushyError as e:
|
except sushy.exceptions.SushyError as e:
|
||||||
error_msg = (_('Redfish set boot device failed for node '
|
error_msg = (_('Redfish set boot device failed for node '
|
||||||
'%(node)s. Error: %(error)s') %
|
'%(node)s. Error: %(error)s') %
|
||||||
|
@ -59,7 +59,8 @@ class RedfishHardware(generic.GenericHardware):
|
|||||||
# NOTE(dtantsur): virtual media goes last because of limited hardware
|
# NOTE(dtantsur): virtual media goes last because of limited hardware
|
||||||
# vendors support.
|
# vendors support.
|
||||||
return [ipxe.iPXEBoot, pxe.PXEBoot,
|
return [ipxe.iPXEBoot, pxe.PXEBoot,
|
||||||
redfish_boot.RedfishVirtualMediaBoot]
|
redfish_boot.RedfishVirtualMediaBoot,
|
||||||
|
redfish_boot.RedfishHttpsBoot]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_vendor_interfaces(self):
|
def supported_vendor_interfaces(self):
|
||||||
|
@ -1671,3 +1671,853 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
sushy.VIRTUAL_MEDIA_FLOPPY,
|
sushy.VIRTUAL_MEDIA_FLOPPY,
|
||||||
redfish_boot._has_vmedia_device(
|
redfish_boot._has_vmedia_device(
|
||||||
[mock_manager], sushy.VIRTUAL_MEDIA_FLOPPY, inserted=True))
|
[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.PXE, sushy.BOOT_SOURCE_TARGET_PXE),
|
||||||
(boot_devices.DISK, sushy.BOOT_SOURCE_TARGET_HDD),
|
(boot_devices.DISK, sushy.BOOT_SOURCE_TARGET_HDD),
|
||||||
(boot_devices.CDROM, sushy.BOOT_SOURCE_TARGET_CD),
|
(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:
|
for target, expected in expected_values:
|
||||||
@ -130,7 +131,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
|||||||
# Asserts
|
# Asserts
|
||||||
fake_system.set_system_boot_options.assert_has_calls(
|
fake_system.set_system_boot_options.assert_has_calls(
|
||||||
[mock.call(expected,
|
[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)
|
mock_get_system.assert_called_with(task.node)
|
||||||
self.assertNotIn('redfish_boot_device',
|
self.assertNotIn('redfish_boot_device',
|
||||||
task.node.driver_internal_info)
|
task.node.driver_internal_info)
|
||||||
@ -156,7 +158,7 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
fake_system.set_system_boot_options.assert_has_calls(
|
fake_system.set_system_boot_options.assert_has_calls(
|
||||||
[mock.call(sushy.BOOT_SOURCE_TARGET_PXE,
|
[mock.call(sushy.BOOT_SOURCE_TARGET_PXE,
|
||||||
enabled=expected)])
|
enabled=expected, http_boot_uri=None)])
|
||||||
mock_get_system.assert_called_with(task.node)
|
mock_get_system.assert_called_with(task.node)
|
||||||
self.assertNotIn('redfish_boot_device',
|
self.assertNotIn('redfish_boot_device',
|
||||||
task.node.driver_internal_info)
|
task.node.driver_internal_info)
|
||||||
@ -183,7 +185,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
|||||||
task, boot_devices.PXE, persistent=target)
|
task, boot_devices.PXE, persistent=target)
|
||||||
|
|
||||||
fake_system.set_system_boot_options.assert_has_calls(
|
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)
|
mock_get_system.assert_called_with(task.node)
|
||||||
|
|
||||||
# Reset mocks
|
# Reset mocks
|
||||||
@ -205,7 +208,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
|||||||
task.driver.management.set_boot_device, task, boot_devices.PXE)
|
task.driver.management.set_boot_device, task, boot_devices.PXE)
|
||||||
fake_system.set_system_boot_options.assert_called_once_with(
|
fake_system.set_system_boot_options.assert_called_once_with(
|
||||||
sushy.BOOT_SOURCE_TARGET_PXE,
|
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)
|
mock_get_system.assert_called_once_with(task.node)
|
||||||
self.assertNotIn('redfish_boot_device',
|
self.assertNotIn('redfish_boot_device',
|
||||||
task.node.driver_internal_info)
|
task.node.driver_internal_info)
|
||||||
@ -232,7 +236,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
|||||||
task.driver.management.set_boot_device, task,
|
task.driver.management.set_boot_device, task,
|
||||||
boot_devices.PXE, persistent=target)
|
boot_devices.PXE, persistent=target)
|
||||||
fake_system.set_system_boot_options.assert_called_once_with(
|
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)
|
mock_get_system.assert_called_once_with(task.node)
|
||||||
self.assertNotIn('redfish_boot_device',
|
self.assertNotIn('redfish_boot_device',
|
||||||
task.node.driver_internal_info)
|
task.node.driver_internal_info)
|
||||||
@ -258,9 +263,11 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
|||||||
task, boot_devices.PXE, persistent=True)
|
task, boot_devices.PXE, persistent=True)
|
||||||
fake_system.set_system_boot_options.assert_has_calls([
|
fake_system.set_system_boot_options.assert_has_calls([
|
||||||
mock.call(sushy.BOOT_SOURCE_TARGET_PXE,
|
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,
|
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)
|
mock_get_system.assert_called_with(task.node)
|
||||||
|
|
||||||
@ -292,7 +299,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
|||||||
task.driver.management.set_boot_device(
|
task.driver.management.set_boot_device(
|
||||||
task, boot_devices.PXE, persistent=True)
|
task, boot_devices.PXE, persistent=True)
|
||||||
fake_system.set_system_boot_options.assert_called_once_with(
|
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':
|
if vendor == 'SuperMicro':
|
||||||
mock_sync_boot_mode.assert_called_once_with(task)
|
mock_sync_boot_mode.assert_called_once_with(task)
|
||||||
else:
|
else:
|
||||||
@ -303,6 +311,28 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
|||||||
mock_sync_boot_mode.reset_mock()
|
mock_sync_boot_mode.reset_mock()
|
||||||
mock_get_system.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):
|
def test_restore_boot_device(self):
|
||||||
fake_system = mock.Mock()
|
fake_system = mock.Mock()
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
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(
|
fake_system.set_system_boot_options.assert_called_once_with(
|
||||||
sushy.BOOT_SOURCE_TARGET_HDD,
|
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
|
# The stored boot device is kept intact
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
boot_devices.DISK,
|
boot_devices.DISK,
|
||||||
@ -332,7 +363,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
fake_system.set_system_boot_options.assert_called_once_with(
|
fake_system.set_system_boot_options.assert_called_once_with(
|
||||||
sushy.BOOT_SOURCE_TARGET_HDD,
|
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
|
# The stored boot device is kept intact
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
"hdd",
|
"hdd",
|
||||||
@ -362,7 +394,8 @@ class RedfishManagementTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
fake_system.set_system_boot_options.assert_called_once_with(
|
fake_system.set_system_boot_options.assert_called_once_with(
|
||||||
sushy.BOOT_SOURCE_TARGET_HDD,
|
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)
|
self.assertTrue(mock_log.called)
|
||||||
# The stored boot device is kept intact
|
# The stored boot device is kept intact
|
||||||
self.assertEqual(
|
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
|
futurist>=1.2.0 # Apache-2.0
|
||||||
tooz>=2.7.0 # Apache-2.0
|
tooz>=2.7.0 # Apache-2.0
|
||||||
openstacksdk>=0.48.0 # Apache-2.0
|
openstacksdk>=0.48.0 # Apache-2.0
|
||||||
sushy>=4.3.0
|
sushy>=4.7.0
|
||||||
construct>=2.9.39 # MIT
|
construct>=2.9.39 # MIT
|
||||||
netaddr>=0.9.0 # BSD
|
netaddr>=0.9.0 # BSD
|
||||||
|
@ -79,6 +79,7 @@ ironic.hardware.interfaces.boot =
|
|||||||
irmc-virtual-media = ironic.drivers.modules.irmc.boot:IRMCVirtualMediaBoot
|
irmc-virtual-media = ironic.drivers.modules.irmc.boot:IRMCVirtualMediaBoot
|
||||||
pxe = ironic.drivers.modules.pxe:PXEBoot
|
pxe = ironic.drivers.modules.pxe:PXEBoot
|
||||||
redfish-virtual-media = ironic.drivers.modules.redfish.boot:RedfishVirtualMediaBoot
|
redfish-virtual-media = ironic.drivers.modules.redfish.boot:RedfishVirtualMediaBoot
|
||||||
|
redfish-https = ironic.drivers.modules.redfish.boot:RedfishHttpsBoot
|
||||||
|
|
||||||
ironic.hardware.interfaces.console =
|
ironic.hardware.interfaces.console =
|
||||||
fake = ironic.drivers.modules.fake:FakeConsole
|
fake = ironic.drivers.modules.fake:FakeConsole
|
||||||
|
Loading…
Reference in New Issue
Block a user