Merge "Adds boot mode support to ManagementInterface"
This commit is contained in:
commit
ce9bdbffb1
29
ironic/common/boot_modes.py
Normal file
29
ironic/common/boot_modes.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Copyright 2018 Red Hat, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Mapping of boot modes used when requesting the system to boot
|
||||||
|
using alternative firmware interfaces.
|
||||||
|
|
||||||
|
The options presented were based on the Redfish protocol capabilities,
|
||||||
|
specifically on the BootSourceOverrideMode property.
|
||||||
|
"""
|
||||||
|
|
||||||
|
LEGACY_BIOS = 'bios'
|
||||||
|
"Boot over legacy PC BIOS firmware interface"
|
||||||
|
|
||||||
|
UEFI = 'uefi'
|
||||||
|
"Boot over Unified Extensible Firmware Interface (UEFI) firmware interface"
|
@ -586,6 +586,10 @@ class DriverLoadError(IronicException):
|
|||||||
"loaded. Reason: %(reason)s.")
|
"loaded. Reason: %(reason)s.")
|
||||||
|
|
||||||
|
|
||||||
|
class DriverOperationError(IronicException):
|
||||||
|
_msg_fmt = _("Runtime driver %(driver)s failure. Reason: %(reason)s.")
|
||||||
|
|
||||||
|
|
||||||
class ConsoleError(IronicException):
|
class ConsoleError(IronicException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -602,15 +606,15 @@ class PasswordFileFailedToCreate(IronicException):
|
|||||||
_msg_fmt = _("Failed to create the password file. %(error)s")
|
_msg_fmt = _("Failed to create the password file. %(error)s")
|
||||||
|
|
||||||
|
|
||||||
class IloOperationError(IronicException):
|
class IloOperationError(DriverOperationError):
|
||||||
_msg_fmt = _("%(operation)s failed, error: %(error)s")
|
_msg_fmt = _("%(operation)s failed, error: %(error)s")
|
||||||
|
|
||||||
|
|
||||||
class IloOperationNotSupported(IronicException):
|
class IloOperationNotSupported(DriverOperationError):
|
||||||
_msg_fmt = _("%(operation)s not supported. error: %(error)s")
|
_msg_fmt = _("%(operation)s not supported. error: %(error)s")
|
||||||
|
|
||||||
|
|
||||||
class DracOperationError(IronicException):
|
class DracOperationError(DriverOperationError):
|
||||||
_msg_fmt = _('DRAC operation failed. Reason: %(error)s')
|
_msg_fmt = _('DRAC operation failed. Reason: %(error)s')
|
||||||
|
|
||||||
|
|
||||||
@ -643,7 +647,7 @@ class SwiftObjectNotFoundError(SwiftOperationError):
|
|||||||
"not found. Operation '%(operation)s' failed.")
|
"not found. Operation '%(operation)s' failed.")
|
||||||
|
|
||||||
|
|
||||||
class SNMPFailure(IronicException):
|
class SNMPFailure(DriverOperationError):
|
||||||
_msg_fmt = _("SNMP operation '%(operation)s' failed: %(error)s")
|
_msg_fmt = _("SNMP operation '%(operation)s' failed: %(error)s")
|
||||||
|
|
||||||
|
|
||||||
@ -652,11 +656,11 @@ class FileSystemNotSupported(IronicException):
|
|||||||
"File system %(fs)s is not supported.")
|
"File system %(fs)s is not supported.")
|
||||||
|
|
||||||
|
|
||||||
class IRMCOperationError(IronicException):
|
class IRMCOperationError(DriverOperationError):
|
||||||
_msg_fmt = _('iRMC %(operation)s failed. Reason: %(error)s')
|
_msg_fmt = _('iRMC %(operation)s failed. Reason: %(error)s')
|
||||||
|
|
||||||
|
|
||||||
class IRMCSharedFileSystemNotMounted(IronicException):
|
class IRMCSharedFileSystemNotMounted(DriverOperationError):
|
||||||
_msg_fmt = _("iRMC shared file system '%(share)s' is not mounted.")
|
_msg_fmt = _("iRMC shared file system '%(share)s' is not mounted.")
|
||||||
|
|
||||||
|
|
||||||
@ -676,7 +680,7 @@ class DirectoryNotWritable(IronicException):
|
|||||||
_msg_fmt = _("Directory %(dir)s is not writable.")
|
_msg_fmt = _("Directory %(dir)s is not writable.")
|
||||||
|
|
||||||
|
|
||||||
class UcsOperationError(IronicException):
|
class UcsOperationError(DriverOperationError):
|
||||||
_msg_fmt = _("Cisco UCS client: operation %(operation)s failed for node"
|
_msg_fmt = _("Cisco UCS client: operation %(operation)s failed for node"
|
||||||
" %(node)s. Reason: %(error)s")
|
" %(node)s. Reason: %(error)s")
|
||||||
|
|
||||||
@ -691,11 +695,11 @@ class ImageUploadFailed(IronicException):
|
|||||||
"%(web_server)s, reason: %(reason)s")
|
"%(web_server)s, reason: %(reason)s")
|
||||||
|
|
||||||
|
|
||||||
class CIMCException(IronicException):
|
class CIMCException(DriverOperationError):
|
||||||
_msg_fmt = _("Cisco IMC exception occurred for node %(node)s: %(error)s")
|
_msg_fmt = _("Cisco IMC exception occurred for node %(node)s: %(error)s")
|
||||||
|
|
||||||
|
|
||||||
class OneViewError(IronicException):
|
class OneViewError(DriverOperationError):
|
||||||
_msg_fmt = _("OneView exception occurred. Error: %(error)s")
|
_msg_fmt = _("OneView exception occurred. Error: %(error)s")
|
||||||
|
|
||||||
|
|
||||||
@ -737,7 +741,7 @@ class StorageError(IronicException):
|
|||||||
_msg_fmt = _("Storage operation failure.")
|
_msg_fmt = _("Storage operation failure.")
|
||||||
|
|
||||||
|
|
||||||
class RedfishError(IronicException):
|
class RedfishError(DriverOperationError):
|
||||||
_msg_fmt = _("Redfish exception occurred. Error: %(error)s")
|
_msg_fmt = _("Redfish exception occurred. Error: %(error)s")
|
||||||
|
|
||||||
|
|
||||||
|
@ -944,6 +944,7 @@ class ConductorManager(base_manager.BaseConductorManager):
|
|||||||
driver_internal_info.pop('clean_steps', None)
|
driver_internal_info.pop('clean_steps', None)
|
||||||
driver_internal_info.pop('root_uuid_or_disk_id', None)
|
driver_internal_info.pop('root_uuid_or_disk_id', None)
|
||||||
driver_internal_info.pop('is_whole_disk_image', None)
|
driver_internal_info.pop('is_whole_disk_image', None)
|
||||||
|
driver_internal_info.pop('deploy_boot_mode', None)
|
||||||
node.driver_internal_info = driver_internal_info
|
node.driver_internal_info = driver_internal_info
|
||||||
network.remove_vifs_from_node(task)
|
network.remove_vifs_from_node(task)
|
||||||
node.save()
|
node.save()
|
||||||
|
@ -47,9 +47,6 @@ CLEANING_INTERFACE_PRIORITY = {
|
|||||||
def node_set_boot_device(task, device, persistent=False):
|
def node_set_boot_device(task, device, persistent=False):
|
||||||
"""Set the boot device for a node.
|
"""Set the boot device for a node.
|
||||||
|
|
||||||
Sets the boot device for a node if the node's driver interface
|
|
||||||
contains a 'management' interface.
|
|
||||||
|
|
||||||
If the node that the boot device change is being requested for
|
If the node that the boot device change is being requested for
|
||||||
is in ADOPTING state, the boot device will not be set as that
|
is in ADOPTING state, the boot device will not be set as that
|
||||||
change could potentially result in the future running state of
|
change could potentially result in the future running state of
|
||||||
@ -63,7 +60,10 @@ def node_set_boot_device(task, device, persistent=False):
|
|||||||
ManagementInterface fails.
|
ManagementInterface fails.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if getattr(task.driver, 'management', None):
|
# TODO(etingof): remove `if` once classic drivers are gone
|
||||||
|
if not getattr(task.driver, 'management', None):
|
||||||
|
return
|
||||||
|
|
||||||
task.driver.management.validate(task)
|
task.driver.management.validate(task)
|
||||||
if task.node.provision_state != states.ADOPTING:
|
if task.node.provision_state != states.ADOPTING:
|
||||||
task.driver.management.set_boot_device(task,
|
task.driver.management.set_boot_device(task,
|
||||||
@ -71,6 +71,75 @@ def node_set_boot_device(task, device, persistent=False):
|
|||||||
persistent=persistent)
|
persistent=persistent)
|
||||||
|
|
||||||
|
|
||||||
|
def node_get_boot_mode(task):
|
||||||
|
"""Read currently set boot mode from a node.
|
||||||
|
|
||||||
|
Reads the boot mode for a node. If boot mode can't be discovered,
|
||||||
|
`None` is returned.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance.
|
||||||
|
:raises: DriverOperationError or its derivative in case
|
||||||
|
of driver runtime error.
|
||||||
|
:raises: UnsupportedDriverExtension if current driver does not have
|
||||||
|
management interface or `get_boot_mode()` method is
|
||||||
|
not supported.
|
||||||
|
:returns: Boot mode. One of :mod:`ironic.common.boot_mode` or `None`
|
||||||
|
if boot mode can't be discovered
|
||||||
|
"""
|
||||||
|
# TODO(etingof): remove `if` once classic drivers are gone
|
||||||
|
if not getattr(task.driver, 'management', None):
|
||||||
|
return
|
||||||
|
|
||||||
|
task.driver.management.validate(task)
|
||||||
|
return task.driver.management.get_boot_mode(task)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO(ietingof): remove `Sets the boot mode...` from the docstring
|
||||||
|
# once classic drivers are gone
|
||||||
|
@task_manager.require_exclusive_lock
|
||||||
|
def node_set_boot_mode(task, mode):
|
||||||
|
"""Set the boot mode for a node.
|
||||||
|
|
||||||
|
Sets the boot mode for a node if the node's driver interface
|
||||||
|
contains a 'management' interface.
|
||||||
|
|
||||||
|
If the node that the boot mode change is being requested for
|
||||||
|
is in ADOPTING state, the boot mode will not be set as that
|
||||||
|
change could potentially result in the future running state of
|
||||||
|
an adopted node being modified erroneously.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance.
|
||||||
|
:param mode: Boot mode. Values are one of
|
||||||
|
:mod:`ironic.common.boot_modes`
|
||||||
|
:raises: InvalidParameterValue if the validation of the
|
||||||
|
ManagementInterface fails.
|
||||||
|
:raises: DriverOperationError or its derivative in case
|
||||||
|
of driver runtime error.
|
||||||
|
:raises: UnsupportedDriverExtension if current driver does not have
|
||||||
|
vendor interface or method is unsupported.
|
||||||
|
"""
|
||||||
|
if task.node.provision_state == states.ADOPTING:
|
||||||
|
return
|
||||||
|
|
||||||
|
# TODO(etingof): remove `if` once classic drivers are gone
|
||||||
|
if not getattr(task.driver, 'management', None):
|
||||||
|
return
|
||||||
|
|
||||||
|
task.driver.management.validate(task)
|
||||||
|
|
||||||
|
boot_modes = task.driver.management.get_supported_boot_modes(task)
|
||||||
|
|
||||||
|
if mode not in boot_modes:
|
||||||
|
msg = _("Unsupported boot mode %(mode)s specified for "
|
||||||
|
"node %(node_id)s. Supported boot modes are: "
|
||||||
|
"%(modes)s") % {'mode': mode,
|
||||||
|
'modes': ', '.join(boot_modes),
|
||||||
|
'node_id': task.node.uuid}
|
||||||
|
raise exception.InvalidParameterValue(msg)
|
||||||
|
|
||||||
|
task.driver.management.set_boot_mode(task, mode=mode)
|
||||||
|
|
||||||
|
|
||||||
def node_wait_for_power_state(task, new_state, timeout=None):
|
def node_wait_for_power_state(task, new_state, timeout=None):
|
||||||
"""Wait for node to be in new power state.
|
"""Wait for node to be in new power state.
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from ironic.common import boot_modes
|
||||||
from ironic.common.i18n import _
|
from ironic.common.i18n import _
|
||||||
|
|
||||||
|
|
||||||
@ -73,6 +74,17 @@ opts = [
|
|||||||
'default is "netboot", but it will be changed to '
|
'default is "netboot", but it will be changed to '
|
||||||
'"local" in the future. It is recommended to set '
|
'"local" in the future. It is recommended to set '
|
||||||
'an explicit value for this option.')),
|
'an explicit value for this option.')),
|
||||||
|
cfg.StrOpt('default_boot_mode',
|
||||||
|
choices=[(boot_modes.UEFI, _('UEFI boot mode')),
|
||||||
|
(boot_modes.LEGACY_BIOS, _('Legacy BIOS boot mode'))],
|
||||||
|
default=boot_modes.LEGACY_BIOS,
|
||||||
|
help=_('Default boot mode to use when no boot mode is '
|
||||||
|
'requested in node\'s driver_info, capabilities or '
|
||||||
|
'in the `instance_info` configuration. Currently the '
|
||||||
|
'default boot mode is "%(bios)s". This option only '
|
||||||
|
'has effect when management interface supports boot '
|
||||||
|
'mode management') % {
|
||||||
|
'bios': boot_modes.LEGACY_BIOS}),
|
||||||
cfg.BoolOpt('configdrive_use_object_store',
|
cfg.BoolOpt('configdrive_use_object_store',
|
||||||
default=False,
|
default=False,
|
||||||
deprecated_group='conductor',
|
deprecated_group='conductor',
|
||||||
|
@ -849,6 +849,71 @@ class ManagementInterface(BaseInterface):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def get_supported_boot_modes(self, task):
|
||||||
|
"""Get a list of the supported boot modes.
|
||||||
|
|
||||||
|
NOTE: Not all drivers support this method. Older hardware
|
||||||
|
may not implement that.
|
||||||
|
|
||||||
|
:param task: A task from TaskManager.
|
||||||
|
:raises: UnsupportedDriverExtension if requested operation is
|
||||||
|
not supported by the driver
|
||||||
|
:raises: DriverOperationError or its derivative in case
|
||||||
|
of driver runtime error.
|
||||||
|
:raises: MissingParameterValue if a required parameter is missing
|
||||||
|
:returns: A list with the supported boot modes defined
|
||||||
|
in :mod:`ironic.common.boot_modes`. If boot
|
||||||
|
mode support can't be determined, empty list
|
||||||
|
is returned.
|
||||||
|
"""
|
||||||
|
raise exception.UnsupportedDriverExtension(
|
||||||
|
driver=task.node.driver, extension='get_supported_boot_modes')
|
||||||
|
|
||||||
|
def set_boot_mode(self, task, mode):
|
||||||
|
"""Set the boot mode for a node.
|
||||||
|
|
||||||
|
Set the boot mode to use on next reboot of the node.
|
||||||
|
|
||||||
|
Drivers implementing this method are required to implement
|
||||||
|
the `get_supported_boot_modes` method as well.
|
||||||
|
|
||||||
|
NOTE: Not all drivers support this method. Hardware supporting only
|
||||||
|
one boot mode may not implement that.
|
||||||
|
|
||||||
|
:param task: A task from TaskManager.
|
||||||
|
:param mode: The boot mode, one of
|
||||||
|
:mod:`ironic.common.boot_modes`.
|
||||||
|
:raises: InvalidParameterValue if an invalid boot mode is
|
||||||
|
specified.
|
||||||
|
:raises: MissingParameterValue if a required parameter is missing
|
||||||
|
:raises: UnsupportedDriverExtension if requested operation is
|
||||||
|
not supported by the driver
|
||||||
|
:raises: DriverOperationError or its derivative in case
|
||||||
|
of driver runtime error.
|
||||||
|
"""
|
||||||
|
raise exception.UnsupportedDriverExtension(
|
||||||
|
driver=task.node.driver, extension='set_boot_mode')
|
||||||
|
|
||||||
|
def get_boot_mode(self, task):
|
||||||
|
"""Get the current boot mode for a node.
|
||||||
|
|
||||||
|
Provides the current boot mode of the node.
|
||||||
|
|
||||||
|
NOTE: Not all drivers support this method. Older hardware
|
||||||
|
may not implement that.
|
||||||
|
|
||||||
|
:param task: A task from TaskManager.
|
||||||
|
:raises: MissingParameterValue if a required parameter is missing
|
||||||
|
:raises: DriverOperationError or its derivative in case
|
||||||
|
of driver runtime error.
|
||||||
|
:raises: UnsupportedDriverExtension if requested operation is
|
||||||
|
not supported by the driver
|
||||||
|
:returns: The boot mode, one of :mod:`ironic.common.boot_mode` or
|
||||||
|
None if it is unknown.
|
||||||
|
"""
|
||||||
|
raise exception.UnsupportedDriverExtension(
|
||||||
|
driver=task.node.driver, extension='get_boot_mode')
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_sensors_data(self, task):
|
def get_sensors_data(self, task):
|
||||||
"""Get sensors data method.
|
"""Get sensors data method.
|
||||||
|
148
ironic/drivers/modules/boot_mode_utils.py
Normal file
148
ironic/drivers/modules/boot_mode_utils.py
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
# Copyright 2018 Red Hat, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from ironic.common import exception
|
||||||
|
from ironic.common.i18n import _
|
||||||
|
from ironic.conductor import utils as manager_utils
|
||||||
|
from ironic.conf import CONF
|
||||||
|
from ironic.drivers.modules import deploy_utils
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _set_boot_mode_on_bm(task, ironic_boot_mode, fail_if_unsupported=False):
|
||||||
|
try:
|
||||||
|
manager_utils.node_set_boot_mode(task, ironic_boot_mode)
|
||||||
|
|
||||||
|
except exception.UnsupportedDriverExtension as ex:
|
||||||
|
if fail_if_unsupported:
|
||||||
|
msg = (_("Baremetal node %(uuid)s boot mode is not set "
|
||||||
|
"to boot mode %(boot_mode)s: %(error)s") %
|
||||||
|
{'uuid': task.node.uuid,
|
||||||
|
'boot_mode': ironic_boot_mode,
|
||||||
|
'error': ex})
|
||||||
|
LOG.error(msg)
|
||||||
|
raise exception.UnsupportedDriverExtension(msg)
|
||||||
|
|
||||||
|
msg_tmpl = _("Baremetal node %(uuid)s boot mode is not set "
|
||||||
|
"to boot mode %(boot_mode)s. Assuming "
|
||||||
|
"baremetal node is already in %(boot_mode)s or "
|
||||||
|
"driver set boot mode via some other "
|
||||||
|
"mechanism: %(error)s")
|
||||||
|
|
||||||
|
LOG.debug(msg_tmpl, {'uuid': task.node.uuid,
|
||||||
|
'boot_mode': ironic_boot_mode,
|
||||||
|
'error': ex})
|
||||||
|
|
||||||
|
except exception.InvalidParameterValue as ex:
|
||||||
|
msg = (_("Node %(uuid)s boot mode is not set. "
|
||||||
|
"Attempt to set %(ironic_boot_mode)s boot mode "
|
||||||
|
"on the baremetal node failed with error %(error)s") %
|
||||||
|
{'uuid': task.node.uuid,
|
||||||
|
'ironic_boot_mode': ironic_boot_mode,
|
||||||
|
'error': ex})
|
||||||
|
LOG.error(msg)
|
||||||
|
raise exception.InvalidParameterValue(msg)
|
||||||
|
|
||||||
|
else:
|
||||||
|
LOG.info("Baremetal node boot mode is set to boot "
|
||||||
|
"mode %(boot_mode)s",
|
||||||
|
{'uuid': task.node.uuid, 'boot_mode': ironic_boot_mode})
|
||||||
|
|
||||||
|
|
||||||
|
def sync_boot_mode(task):
|
||||||
|
"""Set node's boot mode from bare metal configuration
|
||||||
|
|
||||||
|
Attempt to read currently set boot mode off the bare metal machine.
|
||||||
|
Also read node's boot mode configuration:
|
||||||
|
|
||||||
|
* If BM driver does not implement getting boot mode, assume
|
||||||
|
BM boot mode is not set and apply the logic that follows
|
||||||
|
* If Ironic node boot mode is not set and BM node boot mode is
|
||||||
|
not set - set Ironic boot mode to `[deploy]/default_boot_mode`
|
||||||
|
* If Ironic node boot mode is not set and BM node boot mode
|
||||||
|
is set - set BM node boot mode on the Ironic node
|
||||||
|
* If Ironic node boot mode is set and BM node boot mode is
|
||||||
|
not set - set Ironic boot mode to BM boot mode
|
||||||
|
* If both Ironic and BM node boot modes are set but they
|
||||||
|
differ - try to set Ironic boot mode to BM boot mode and fail hard
|
||||||
|
if underlying hardware type does not support setting boot mode
|
||||||
|
|
||||||
|
In the end, the new boot mode may be set in
|
||||||
|
'driver_internal_info/deploy_boot_mode'.
|
||||||
|
|
||||||
|
:param task: a task object
|
||||||
|
"""
|
||||||
|
node = task.node
|
||||||
|
|
||||||
|
try:
|
||||||
|
bm_boot_mode = manager_utils.node_get_boot_mode(task)
|
||||||
|
|
||||||
|
except exception.UnsupportedDriverExtension as ex:
|
||||||
|
bm_boot_mode = None
|
||||||
|
|
||||||
|
LOG.debug("Cannot determine node %(uuid)s boot mode: %(error)s",
|
||||||
|
{'uuid': node.uuid, 'error': ex})
|
||||||
|
|
||||||
|
ironic_boot_mode = deploy_utils.get_boot_mode_for_deploy(node)
|
||||||
|
|
||||||
|
# NOTE(etingof): the outcome of the branching that follows is that
|
||||||
|
# the new boot mode may be set in 'driver_internal_info/deploy_boot_mode'
|
||||||
|
|
||||||
|
if not ironic_boot_mode and not bm_boot_mode:
|
||||||
|
driver_internal_info = node.driver_internal_info
|
||||||
|
default_boot_mode = CONF.deploy.default_boot_mode
|
||||||
|
driver_internal_info['deploy_boot_mode'] = default_boot_mode
|
||||||
|
node.driver_internal_info = driver_internal_info
|
||||||
|
node.save()
|
||||||
|
|
||||||
|
LOG.debug("Ironic node %(uuid)s boot mode will be set to default "
|
||||||
|
"boot mode %(boot_mode)s",
|
||||||
|
{'uuid': node.uuid, 'boot_mode': default_boot_mode})
|
||||||
|
|
||||||
|
_set_boot_mode_on_bm(task, default_boot_mode)
|
||||||
|
|
||||||
|
elif not ironic_boot_mode and bm_boot_mode:
|
||||||
|
driver_internal_info = node.driver_internal_info
|
||||||
|
driver_internal_info['deploy_boot_mode'] = bm_boot_mode
|
||||||
|
node.driver_internal_info = driver_internal_info
|
||||||
|
node.save()
|
||||||
|
|
||||||
|
LOG.debug("Ironic node %(uuid)s boot mode is set to boot mode "
|
||||||
|
"%(boot_mode)s reported by the driver",
|
||||||
|
{'uuid': node.uuid, 'boot_mode': bm_boot_mode})
|
||||||
|
|
||||||
|
elif ironic_boot_mode and not bm_boot_mode:
|
||||||
|
# NOTE(etingof): if only ironic boot mode is known, try to synchronize
|
||||||
|
# (e.g. ironic -> bm) and do not fail if setting boot mode is not
|
||||||
|
# supported by the underlying hardware type
|
||||||
|
_set_boot_mode_on_bm(task, ironic_boot_mode)
|
||||||
|
|
||||||
|
elif ironic_boot_mode != bm_boot_mode:
|
||||||
|
msg = (_("Boot mode %(node_boot_mode)s currently configured "
|
||||||
|
"on node %(uuid)s does not match the boot mode "
|
||||||
|
"%(ironic_boot_mode)s requested for provisioning."
|
||||||
|
"Attempting to set node boot mode to %(ironic_boot_mode)s.") %
|
||||||
|
{'uuid': node.uuid, 'node_boot_mode': bm_boot_mode,
|
||||||
|
'ironic_boot_mode': ironic_boot_mode})
|
||||||
|
LOG.info(msg)
|
||||||
|
|
||||||
|
# NOTE(etingof): if boot modes are known and different, try
|
||||||
|
# to synchronize them (e.g. ironic -> bm) and fail hard if
|
||||||
|
# underlying hardware type does not support setting boot mode as
|
||||||
|
# it seems to be a hopeless misconfiguration
|
||||||
|
_set_boot_mode_on_bm(task, ironic_boot_mode, fail_if_unsupported=True)
|
@ -769,12 +769,17 @@ def get_boot_mode_for_deploy(node):
|
|||||||
'trusted_boot' is set to 'true' in 'instance_info/capabilities' of node.
|
'trusted_boot' is set to 'true' in 'instance_info/capabilities' of node.
|
||||||
Otherwise it returns value of 'boot_mode' in 'properties/capabilities'
|
Otherwise it returns value of 'boot_mode' in 'properties/capabilities'
|
||||||
of node if set. If that is not set, it returns boot mode in
|
of node if set. If that is not set, it returns boot mode in
|
||||||
|
'driver_internal_info/deploy_boot_mode' for the node.
|
||||||
|
If that is not set, it returns boot mode in
|
||||||
'instance_info/deploy_boot_mode' for the node.
|
'instance_info/deploy_boot_mode' for the node.
|
||||||
It would return None if boot mode is present neither in 'capabilities' of
|
It would return None if boot mode is present neither in 'capabilities' of
|
||||||
node 'properties' nor in node's 'instance_info' (which could also be None).
|
node 'properties' nor in node's 'driver_internal_info' nor in node's
|
||||||
|
'instance_info' (which could also be None).
|
||||||
|
|
||||||
:param node: an ironic node object.
|
:param node: an ironic node object.
|
||||||
:returns: 'bios', 'uefi' or None
|
:returns: 'bios', 'uefi' or None
|
||||||
|
:raises: InvalidParameterValue, if the node boot mode disagrees with
|
||||||
|
the boot mode set to node properties/capabilities
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if is_secure_boot_requested(node):
|
if is_secure_boot_requested(node):
|
||||||
@ -787,15 +792,56 @@ def get_boot_mode_for_deploy(node):
|
|||||||
LOG.debug('Deploy boot mode is bios for %s.', node.uuid)
|
LOG.debug('Deploy boot mode is bios for %s.', node.uuid)
|
||||||
return 'bios'
|
return 'bios'
|
||||||
|
|
||||||
boot_mode = driver_utils.get_node_capability(node, 'boot_mode')
|
# NOTE(etingof):
|
||||||
if boot_mode is None:
|
# The search for a boot mode should be in the priority order:
|
||||||
|
#
|
||||||
|
# 1) instance_info
|
||||||
|
# 2) properties.capabilities
|
||||||
|
# 3) driver_internal_info
|
||||||
|
#
|
||||||
|
# Because:
|
||||||
|
#
|
||||||
|
# (1) can be deleted before teardown
|
||||||
|
# (3) will never be touched if node properties/capabilities
|
||||||
|
# are still present.
|
||||||
|
# (2) becomes operational default as the last resort
|
||||||
|
|
||||||
instance_info = node.instance_info
|
instance_info = node.instance_info
|
||||||
|
|
||||||
|
cap_boot_mode = driver_utils.get_node_capability(node, 'boot_mode')
|
||||||
|
|
||||||
boot_mode = instance_info.get('deploy_boot_mode')
|
boot_mode = instance_info.get('deploy_boot_mode')
|
||||||
|
if boot_mode is None:
|
||||||
|
boot_mode = cap_boot_mode
|
||||||
|
if cap_boot_mode is None:
|
||||||
|
driver_internal_info = node.driver_internal_info
|
||||||
|
boot_mode = driver_internal_info.get('deploy_boot_mode')
|
||||||
|
|
||||||
|
if not boot_mode:
|
||||||
|
return
|
||||||
|
|
||||||
|
boot_mode = boot_mode.lower()
|
||||||
|
|
||||||
|
# NOTE(etingof):
|
||||||
|
# Make sure that the ultimate boot_mode agrees with the one set to
|
||||||
|
# node properties/capabilities. This locks down node to use only
|
||||||
|
# boot mode specified in properties/capabilities.
|
||||||
|
# TODO(etingof): this logic will have to go away when we switch to traits
|
||||||
|
if cap_boot_mode:
|
||||||
|
cap_boot_mode = cap_boot_mode.lower()
|
||||||
|
if cap_boot_mode != boot_mode:
|
||||||
|
msg = (_("Node %(uuid)s boot mode %(boot_mode)s violates "
|
||||||
|
"node properties/capabilities %(caps)s") %
|
||||||
|
{'uuid': node.uuid,
|
||||||
|
'boot_mode': boot_mode,
|
||||||
|
'caps': cap_boot_mode})
|
||||||
|
LOG.error(msg)
|
||||||
|
raise exception.InvalidParameterValue(msg)
|
||||||
|
|
||||||
LOG.debug('Deploy boot mode is %(boot_mode)s for %(node)s.',
|
LOG.debug('Deploy boot mode is %(boot_mode)s for %(node)s.',
|
||||||
{'boot_mode': boot_mode, 'node': node.uuid})
|
{'boot_mode': boot_mode, 'node': node.uuid})
|
||||||
|
|
||||||
return boot_mode.lower() if boot_mode else boot_mode
|
return boot_mode
|
||||||
|
|
||||||
|
|
||||||
def get_pxe_boot_file(node):
|
def get_pxe_boot_file(node):
|
||||||
|
@ -35,6 +35,7 @@ from ironic.common import states
|
|||||||
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
|
||||||
|
from ironic.drivers.modules import boot_mode_utils
|
||||||
from ironic.drivers.modules import deploy_utils
|
from ironic.drivers.modules import deploy_utils
|
||||||
from ironic.drivers.modules import image_cache
|
from ironic.drivers.modules import image_cache
|
||||||
from ironic.drivers import utils as driver_utils
|
from ironic.drivers import utils as driver_utils
|
||||||
@ -528,6 +529,7 @@ class PXEBoot(base.BootInterface):
|
|||||||
# if we are in DEPLOYING state.
|
# if we are in DEPLOYING state.
|
||||||
if node.provision_state == states.DEPLOYING:
|
if node.provision_state == states.DEPLOYING:
|
||||||
pxe_info.update(_get_instance_image_info(node, task.context))
|
pxe_info.update(_get_instance_image_info(node, task.context))
|
||||||
|
boot_mode_utils.sync_boot_mode(task)
|
||||||
|
|
||||||
pxe_options = _build_pxe_config_options(task, pxe_info)
|
pxe_options = _build_pxe_config_options(task, pxe_info)
|
||||||
pxe_options.update(ramdisk_params)
|
pxe_options.update(ramdisk_params)
|
||||||
@ -590,6 +592,8 @@ class PXEBoot(base.BootInterface):
|
|||||||
:param task: a task from TaskManager.
|
:param task: a task from TaskManager.
|
||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
|
boot_mode_utils.sync_boot_mode(task)
|
||||||
|
|
||||||
node = task.node
|
node = task.node
|
||||||
boot_option = deploy_utils.get_boot_option(node)
|
boot_option = deploy_utils.get_boot_option(node)
|
||||||
boot_device = None
|
boot_device = None
|
||||||
@ -679,6 +683,7 @@ class PXEBoot(base.BootInterface):
|
|||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
node = task.node
|
node = task.node
|
||||||
|
|
||||||
try:
|
try:
|
||||||
images_info = _get_instance_image_info(node, task.context)
|
images_info = _get_instance_image_info(node, task.context)
|
||||||
except exception.MissingParameterValue as e:
|
except exception.MissingParameterValue as e:
|
||||||
|
@ -14,6 +14,7 @@ import mock
|
|||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
|
from ironic.common import boot_modes
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.common import network
|
from ironic.common import network
|
||||||
from ironic.common import states
|
from ironic.common import states
|
||||||
@ -77,6 +78,77 @@ class NodeSetBootDeviceTestCase(db_base.DbTestCase):
|
|||||||
self.assertFalse(mock_sbd.called)
|
self.assertFalse(mock_sbd.called)
|
||||||
|
|
||||||
|
|
||||||
|
class NodeGetBootModeTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(NodeGetBootModeTestCase, self).setUp()
|
||||||
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
|
uuid=uuidutils.generate_uuid())
|
||||||
|
self.task = task_manager.TaskManager(self.context, self.node.uuid)
|
||||||
|
|
||||||
|
@mock.patch.object(fake.FakeManagement, 'get_boot_mode', autospec=True)
|
||||||
|
def test_node_get_boot_mode_valid(self, mock_gbm):
|
||||||
|
mock_gbm.return_value = 'bios'
|
||||||
|
boot_mode = conductor_utils.node_get_boot_mode(self.task)
|
||||||
|
self.assertEqual(boot_mode, 'bios')
|
||||||
|
mock_gbm.assert_called_once_with(mock.ANY, self.task)
|
||||||
|
|
||||||
|
@mock.patch.object(fake.FakeManagement, 'get_boot_mode', autospec=True)
|
||||||
|
def test_node_get_boot_mode_unsupported(self, mock_gbm):
|
||||||
|
mock_gbm.side_effect = exception.UnsupportedDriverExtension(
|
||||||
|
driver=self.task.node.driver, extension='get_boot_mode')
|
||||||
|
self.assertRaises(exception.UnsupportedDriverExtension,
|
||||||
|
conductor_utils.node_get_boot_mode, self.task)
|
||||||
|
|
||||||
|
|
||||||
|
class NodeSetBootModeTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(NodeSetBootModeTestCase, self).setUp()
|
||||||
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
|
uuid=uuidutils.generate_uuid())
|
||||||
|
self.task = task_manager.TaskManager(self.context, self.node.uuid)
|
||||||
|
|
||||||
|
@mock.patch.object(fake.FakeManagement, 'get_supported_boot_modes',
|
||||||
|
autospec=True)
|
||||||
|
def test_node_set_boot_mode_non_existent_mode(self, mock_gsbm):
|
||||||
|
|
||||||
|
mock_gsbm.return_value = [boot_modes.LEGACY_BIOS]
|
||||||
|
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
conductor_utils.node_set_boot_mode,
|
||||||
|
self.task,
|
||||||
|
mode='non-existing')
|
||||||
|
|
||||||
|
@mock.patch.object(fake.FakeManagement, 'set_boot_mode', autospec=True)
|
||||||
|
@mock.patch.object(fake.FakeManagement, 'get_supported_boot_modes',
|
||||||
|
autospec=True)
|
||||||
|
def test_node_set_boot_mode_valid(self, mock_gsbm, mock_sbm):
|
||||||
|
mock_gsbm.return_value = [boot_modes.LEGACY_BIOS]
|
||||||
|
|
||||||
|
conductor_utils.node_set_boot_mode(self.task,
|
||||||
|
mode=boot_modes.LEGACY_BIOS)
|
||||||
|
mock_sbm.assert_called_once_with(mock.ANY, self.task,
|
||||||
|
mode=boot_modes.LEGACY_BIOS)
|
||||||
|
|
||||||
|
@mock.patch.object(fake.FakeManagement, 'set_boot_mode', autospec=True)
|
||||||
|
@mock.patch.object(fake.FakeManagement, 'get_supported_boot_modes',
|
||||||
|
autospec=True)
|
||||||
|
def test_node_set_boot_mode_adopting(self, mock_gsbm, mock_sbm):
|
||||||
|
mock_gsbm.return_value = [boot_modes.LEGACY_BIOS]
|
||||||
|
|
||||||
|
old_provision_state = self.task.node.provision_state
|
||||||
|
self.task.node.provision_state = states.ADOPTING
|
||||||
|
try:
|
||||||
|
conductor_utils.node_set_boot_mode(self.task,
|
||||||
|
mode=boot_modes.LEGACY_BIOS)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
self.task.node.provision_state = old_provision_state
|
||||||
|
|
||||||
|
self.assertFalse(mock_sbm.called)
|
||||||
|
|
||||||
|
|
||||||
class NodePowerActionTestCase(db_base.DbTestCase):
|
class NodePowerActionTestCase(db_base.DbTestCase):
|
||||||
@mock.patch.object(fake.FakePower, 'get_power_state', autospec=True)
|
@mock.patch.object(fake.FakePower, 'get_power_state', autospec=True)
|
||||||
def test_node_power_action_power_on(self, get_power_mock):
|
def test_node_power_action_power_on(self, get_power_mock):
|
||||||
|
@ -1905,6 +1905,9 @@ class IRMCPXEBootBasicTestCase(test_pxe.PXEBootTestCase):
|
|||||||
|
|
||||||
driver = 'pxe_irmc'
|
driver = 'pxe_irmc'
|
||||||
boot_interface = None
|
boot_interface = None
|
||||||
|
# NOTE(etingof): add driver-specific configuration
|
||||||
|
driver_info = dict(test_pxe.PXEBootTestCase.driver_info)
|
||||||
|
driver_info.update(PARSED_IFNO)
|
||||||
|
|
||||||
def test_get_properties(self):
|
def test_get_properties(self):
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
@ -26,6 +26,7 @@ from oslo_utils import fileutils
|
|||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
from ironic.common import boot_devices
|
from ironic.common import boot_devices
|
||||||
|
from ironic.common import boot_modes
|
||||||
from ironic.common import dhcp_factory
|
from ironic.common import dhcp_factory
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.common.glance_service import base_image_service
|
from ironic.common.glance_service import base_image_service
|
||||||
@ -752,6 +753,8 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
driver = 'fake-hardware'
|
driver = 'fake-hardware'
|
||||||
boot_interface = 'pxe'
|
boot_interface = 'pxe'
|
||||||
|
driver_info = DRV_INFO_DICT
|
||||||
|
driver_internal_info = DRV_INTERNAL_INFO_DICT
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(PXEBootTestCase, self).setUp()
|
super(PXEBootTestCase, self).setUp()
|
||||||
@ -776,8 +779,8 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
vendor_interface=('no-vendor' if self.driver == 'fake-hardware'
|
vendor_interface=('no-vendor' if self.driver == 'fake-hardware'
|
||||||
else None),
|
else None),
|
||||||
instance_info=instance_info,
|
instance_info=instance_info,
|
||||||
driver_info=DRV_INFO_DICT,
|
driver_info=self.driver_info,
|
||||||
driver_internal_info=DRV_INTERNAL_INFO_DICT)
|
driver_internal_info=self.driver_internal_info)
|
||||||
self.port = obj_utils.create_test_port(self.context,
|
self.port = obj_utils.create_test_port(self.context,
|
||||||
node_id=self.node.id)
|
node_id=self.node.id)
|
||||||
self.config(group='conductor', api_url='http://127.0.0.1:1234/')
|
self.config(group='conductor', api_url='http://127.0.0.1:1234/')
|
||||||
@ -908,6 +911,7 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
self.assertRaises(exception.InvalidParameterValue,
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
task.driver.boot.validate, task)
|
task.driver.boot.validate, task)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_get_boot_mode', autospec=True)
|
||||||
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
@mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True)
|
||||||
@mock.patch.object(dhcp_factory, 'DHCPFactory')
|
@mock.patch.object(dhcp_factory, 'DHCPFactory')
|
||||||
@mock.patch.object(pxe, '_get_instance_image_info', autospec=True)
|
@mock.patch.object(pxe, '_get_instance_image_info', autospec=True)
|
||||||
@ -921,11 +925,13 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
mock_instance_img_info,
|
mock_instance_img_info,
|
||||||
dhcp_factory_mock,
|
dhcp_factory_mock,
|
||||||
set_boot_device_mock,
|
set_boot_device_mock,
|
||||||
|
get_boot_mode_mock,
|
||||||
uefi=False,
|
uefi=False,
|
||||||
cleaning=False,
|
cleaning=False,
|
||||||
ipxe_use_swift=False,
|
ipxe_use_swift=False,
|
||||||
whole_disk_image=False,
|
whole_disk_image=False,
|
||||||
mode='deploy'):
|
mode='deploy',
|
||||||
|
node_boot_mode=None):
|
||||||
mock_build_pxe.return_value = {}
|
mock_build_pxe.return_value = {}
|
||||||
kernel_label = '%s_kernel' % mode
|
kernel_label = '%s_kernel' % mode
|
||||||
ramdisk_label = '%s_ramdisk' % mode
|
ramdisk_label = '%s_ramdisk' % mode
|
||||||
@ -939,6 +945,7 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
mock_cache_r_k.return_value = None
|
mock_cache_r_k.return_value = None
|
||||||
provider_mock = mock.MagicMock()
|
provider_mock = mock.MagicMock()
|
||||||
dhcp_factory_mock.return_value = provider_mock
|
dhcp_factory_mock.return_value = provider_mock
|
||||||
|
get_boot_mode_mock.return_value = node_boot_mode
|
||||||
driver_internal_info = self.node.driver_internal_info
|
driver_internal_info = self.node.driver_internal_info
|
||||||
driver_internal_info['is_whole_disk_image'] = whole_disk_image
|
driver_internal_info['is_whole_disk_image'] = whole_disk_image
|
||||||
self.node.driver_internal_info = driver_internal_info
|
self.node.driver_internal_info = driver_internal_info
|
||||||
@ -952,6 +959,8 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
task.driver.boot.prepare_ramdisk(task, {'foo': 'bar'})
|
task.driver.boot.prepare_ramdisk(task, {'foo': 'bar'})
|
||||||
mock_deploy_img_info.assert_called_once_with(task.node, mode=mode)
|
mock_deploy_img_info.assert_called_once_with(task.node, mode=mode)
|
||||||
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts)
|
||||||
|
if self.node.provision_state == states.DEPLOYING:
|
||||||
|
get_boot_mode_mock.assert_called_once_with(task)
|
||||||
set_boot_device_mock.assert_called_once_with(task,
|
set_boot_device_mock.assert_called_once_with(task,
|
||||||
boot_devices.PXE,
|
boot_devices.PXE,
|
||||||
persistent=False)
|
persistent=False)
|
||||||
@ -1097,6 +1106,104 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
self.node.save()
|
self.node.save()
|
||||||
self._test_prepare_ramdisk(cleaning=True)
|
self._test_prepare_ramdisk(cleaning=True)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_mode', autospec=True)
|
||||||
|
def test_prepare_ramdisk_set_boot_mode_on_bm(
|
||||||
|
self, set_boot_mode_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
properties = self.node.properties
|
||||||
|
properties['capabilities'] = 'boot_mode:uefi'
|
||||||
|
self.node.properties = properties
|
||||||
|
self.node.save()
|
||||||
|
self._test_prepare_ramdisk(uefi=True)
|
||||||
|
set_boot_mode_mock.assert_called_once_with(mock.ANY, boot_modes.UEFI)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_mode', autospec=True)
|
||||||
|
def test_prepare_ramdisk_set_boot_mode_on_ironic(
|
||||||
|
self, set_boot_mode_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.save()
|
||||||
|
self._test_prepare_ramdisk(node_boot_mode=boot_modes.LEGACY_BIOS)
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
driver_internal_info = task.node.driver_internal_info
|
||||||
|
self.assertIn('deploy_boot_mode', driver_internal_info)
|
||||||
|
self.assertEqual(boot_modes.LEGACY_BIOS,
|
||||||
|
driver_internal_info['deploy_boot_mode'])
|
||||||
|
self.assertEqual(set_boot_mode_mock.call_count, 0)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_mode', autospec=True)
|
||||||
|
def test_prepare_ramdisk_set_default_boot_mode_on_ironic_bios(
|
||||||
|
self, set_boot_mode_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.save()
|
||||||
|
|
||||||
|
self.config(default_boot_mode=boot_modes.LEGACY_BIOS, group='deploy')
|
||||||
|
|
||||||
|
self._test_prepare_ramdisk()
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
driver_internal_info = task.node.driver_internal_info
|
||||||
|
self.assertIn('deploy_boot_mode', driver_internal_info)
|
||||||
|
self.assertEqual(boot_modes.LEGACY_BIOS,
|
||||||
|
driver_internal_info['deploy_boot_mode'])
|
||||||
|
self.assertEqual(set_boot_mode_mock.call_count, 1)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_mode', autospec=True)
|
||||||
|
def test_prepare_ramdisk_set_default_boot_mode_on_ironic_uefi(
|
||||||
|
self, set_boot_mode_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.save()
|
||||||
|
|
||||||
|
self.config(default_boot_mode=boot_modes.UEFI, group='deploy')
|
||||||
|
|
||||||
|
self._test_prepare_ramdisk(uefi=True)
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
driver_internal_info = task.node.driver_internal_info
|
||||||
|
self.assertIn('deploy_boot_mode', driver_internal_info)
|
||||||
|
self.assertEqual(boot_modes.UEFI,
|
||||||
|
driver_internal_info['deploy_boot_mode'])
|
||||||
|
self.assertEqual(set_boot_mode_mock.call_count, 1)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_mode', autospec=True)
|
||||||
|
def test_prepare_ramdisk_conflicting_boot_modes(
|
||||||
|
self, set_boot_mode_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
properties = self.node.properties
|
||||||
|
properties['capabilities'] = 'boot_mode:uefi'
|
||||||
|
self.node.properties = properties
|
||||||
|
self.node.save()
|
||||||
|
self._test_prepare_ramdisk(uefi=True,
|
||||||
|
node_boot_mode=boot_modes.LEGACY_BIOS)
|
||||||
|
set_boot_mode_mock.assert_called_once_with(mock.ANY, boot_modes.UEFI)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_mode', autospec=True)
|
||||||
|
def test_prepare_ramdisk_conflicting_boot_modes_set_unsupported(
|
||||||
|
self, set_boot_mode_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
properties = self.node.properties
|
||||||
|
properties['capabilities'] = 'boot_mode:uefi'
|
||||||
|
self.node.properties = properties
|
||||||
|
self.node.save()
|
||||||
|
set_boot_mode_mock.side_effect = exception.UnsupportedDriverExtension(
|
||||||
|
extension='management', driver='test-driver'
|
||||||
|
)
|
||||||
|
self.assertRaises(exception.UnsupportedDriverExtension,
|
||||||
|
self._test_prepare_ramdisk,
|
||||||
|
uefi=True, node_boot_mode=boot_modes.LEGACY_BIOS)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'node_set_boot_mode', autospec=True)
|
||||||
|
def test_prepare_ramdisk_set_boot_mode_not_called(
|
||||||
|
self, set_boot_mode_mock):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.save()
|
||||||
|
properties = self.node.properties
|
||||||
|
properties['capabilities'] = 'boot_mode:uefi'
|
||||||
|
self.node.properties = properties
|
||||||
|
self.node.save()
|
||||||
|
self._test_prepare_ramdisk(uefi=True, node_boot_mode=boot_modes.UEFI)
|
||||||
|
self.assertEqual(set_boot_mode_mock.call_count, 0)
|
||||||
|
|
||||||
@mock.patch.object(pxe, '_clean_up_pxe_env', autospec=True)
|
@mock.patch.object(pxe, '_clean_up_pxe_env', autospec=True)
|
||||||
@mock.patch.object(pxe, '_get_image_info', autospec=True)
|
@mock.patch.object(pxe, '_get_image_info', autospec=True)
|
||||||
def _test_clean_up_ramdisk(self, get_image_info_mock,
|
def _test_clean_up_ramdisk(self, get_image_info_mock,
|
||||||
@ -1290,6 +1397,7 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
|
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
|
||||||
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
pxe_config_path = pxe_utils.get_pxe_config_file_path(
|
||||||
task.node.uuid)
|
task.node.uuid)
|
||||||
|
task.node.properties['capabilities'] = 'boot_mode:bios'
|
||||||
task.driver.boot.prepare_instance(task)
|
task.driver.boot.prepare_instance(task)
|
||||||
self.assertFalse(get_image_info_mock.called)
|
self.assertFalse(get_image_info_mock.called)
|
||||||
self.assertFalse(cache_mock.called)
|
self.assertFalse(cache_mock.called)
|
||||||
@ -1297,7 +1405,8 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
create_pxe_config_mock.assert_called_once_with(
|
create_pxe_config_mock.assert_called_once_with(
|
||||||
task, mock.ANY, CONF.pxe.pxe_config_template)
|
task, mock.ANY, CONF.pxe.pxe_config_template)
|
||||||
switch_pxe_config_mock.assert_called_once_with(
|
switch_pxe_config_mock.assert_called_once_with(
|
||||||
pxe_config_path, None, None, False, iscsi_boot=True)
|
pxe_config_path, None, boot_modes.LEGACY_BIOS, False,
|
||||||
|
iscsi_boot=True)
|
||||||
set_boot_device_mock.assert_called_once_with(task,
|
set_boot_device_mock.assert_called_once_with(task,
|
||||||
boot_devices.PXE,
|
boot_devices.PXE,
|
||||||
persistent=True)
|
persistent=True)
|
||||||
@ -1307,7 +1416,10 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
def test_prepare_instance_localboot(self, clean_up_pxe_config_mock,
|
def test_prepare_instance_localboot(self, clean_up_pxe_config_mock,
|
||||||
set_boot_device_mock):
|
set_boot_device_mock):
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
task.node.instance_info['capabilities'] = {'boot_option': 'local'}
|
instance_info = task.node.instance_info
|
||||||
|
instance_info['capabilities'] = {'boot_option': 'local'}
|
||||||
|
task.node.instance_info = instance_info
|
||||||
|
task.node.save()
|
||||||
task.driver.boot.prepare_instance(task)
|
task.driver.boot.prepare_instance(task)
|
||||||
clean_up_pxe_config_mock.assert_called_once_with(task)
|
clean_up_pxe_config_mock.assert_called_once_with(task)
|
||||||
set_boot_device_mock.assert_called_once_with(task,
|
set_boot_device_mock.assert_called_once_with(task,
|
||||||
@ -1319,7 +1431,10 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
def test_is_force_persistent_boot_device_enabled(
|
def test_is_force_persistent_boot_device_enabled(
|
||||||
self, clean_up_pxe_config_mock, set_boot_device_mock):
|
self, clean_up_pxe_config_mock, set_boot_device_mock):
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
task.node.instance_info['capabilities'] = {'boot_option': 'local'}
|
instance_info = task.node.instance_info
|
||||||
|
instance_info['capabilities'] = {'boot_option': 'local'}
|
||||||
|
task.node.instance_info = instance_info
|
||||||
|
task.node.save()
|
||||||
task.driver.boot.prepare_instance(task)
|
task.driver.boot.prepare_instance(task)
|
||||||
clean_up_pxe_config_mock.assert_called_once_with(task)
|
clean_up_pxe_config_mock.assert_called_once_with(task)
|
||||||
driver_info = task.node.driver_info
|
driver_info = task.node.driver_info
|
||||||
@ -1336,7 +1451,10 @@ class PXEBootTestCase(db_base.DbTestCase):
|
|||||||
self.node.provision_state = states.ACTIVE
|
self.node.provision_state = states.ACTIVE
|
||||||
self.node.save()
|
self.node.save()
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
task.node.instance_info['capabilities'] = {'boot_option': 'local'}
|
instance_info = task.node.instance_info
|
||||||
|
instance_info['capabilities'] = {'boot_option': 'local'}
|
||||||
|
task.node.instance_info = instance_info
|
||||||
|
task.node.save()
|
||||||
task.driver.boot.prepare_instance(task)
|
task.driver.boot.prepare_instance(task)
|
||||||
clean_up_pxe_config_mock.assert_called_once_with(task)
|
clean_up_pxe_config_mock.assert_called_once_with(task)
|
||||||
self.assertFalse(set_boot_device_mock.called)
|
self.assertFalse(set_boot_device_mock.called)
|
||||||
|
@ -478,6 +478,27 @@ class TestManagementInterface(base.TestCase):
|
|||||||
self.assertRaises(exception.UnsupportedDriverExtension,
|
self.assertRaises(exception.UnsupportedDriverExtension,
|
||||||
management.inject_nmi, task_mock)
|
management.inject_nmi, task_mock)
|
||||||
|
|
||||||
|
def test_get_supported_boot_modes_default_impl(self):
|
||||||
|
management = fake.FakeManagement()
|
||||||
|
task_mock = mock.MagicMock(spec_set=['node'])
|
||||||
|
|
||||||
|
self.assertRaises(exception.UnsupportedDriverExtension,
|
||||||
|
management.get_supported_boot_modes, task_mock)
|
||||||
|
|
||||||
|
def test_set_boot_mode_default_impl(self):
|
||||||
|
management = fake.FakeManagement()
|
||||||
|
task_mock = mock.MagicMock(spec_set=['node'])
|
||||||
|
|
||||||
|
self.assertRaises(exception.UnsupportedDriverExtension,
|
||||||
|
management.set_boot_mode, task_mock, 'whatever')
|
||||||
|
|
||||||
|
def test_get_boot_mode_default_impl(self):
|
||||||
|
management = fake.FakeManagement()
|
||||||
|
task_mock = mock.MagicMock(spec_set=['node'])
|
||||||
|
|
||||||
|
self.assertRaises(exception.UnsupportedDriverExtension,
|
||||||
|
management.get_boot_mode, task_mock)
|
||||||
|
|
||||||
|
|
||||||
class TestBaseDriver(base.TestCase):
|
class TestBaseDriver(base.TestCase):
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
|
|
||||||
from ironic.common import boot_devices
|
from ironic.common import boot_devices
|
||||||
|
from ironic.common import boot_modes
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.common import states
|
from ironic.common import states
|
||||||
from ironic.conductor import task_manager
|
from ironic.conductor import task_manager
|
||||||
@ -111,6 +112,13 @@ class FakeHardwareTestCase(db_base.DbTestCase):
|
|||||||
self.assertEqual(expected,
|
self.assertEqual(expected,
|
||||||
self.driver.management.get_boot_device(self.task))
|
self.driver.management.get_boot_device(self.task))
|
||||||
|
|
||||||
|
def test_management_interface_set_boot_mode_good(self):
|
||||||
|
self.assertRaises(
|
||||||
|
exception.UnsupportedDriverExtension,
|
||||||
|
self.driver.management.set_boot_mode,
|
||||||
|
self.task, boot_modes.LEGACY_BIOS
|
||||||
|
)
|
||||||
|
|
||||||
def test_inspect_interface(self):
|
def test_inspect_interface(self):
|
||||||
self.assertEqual({}, self.driver.inspect.get_properties())
|
self.assertEqual({}, self.driver.inspect.get_properties())
|
||||||
self.driver.inspect.validate(self.task)
|
self.driver.inspect.validate(self.task)
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
other:
|
||||||
|
- Adds ``get_boot_mode``, ``set_boot_mode`` and
|
||||||
|
``get_supported_boot_modes`` methods to driver management interface.
|
||||||
|
Drivers can override these methods implementing boot mode management
|
||||||
|
calls to the BMC of the baremetal nodes being managed.
|
||||||
|
features:
|
||||||
|
- The new ironic configuration setting ``[deploy]/default_boot_mode``
|
||||||
|
allows to set the default boot mode when ironic can't pick boot mode
|
||||||
|
automatically based on node configuration, hardware capabilities,
|
||||||
|
bare-metal machine configuration.
|
||||||
|
fixes:
|
||||||
|
- If the bare metal machine's boot mode differs from the requested one,
|
||||||
|
ironic will attempt to set requested boot mode on the bare metal
|
||||||
|
machine and fail explicitly if the driver does not support setting
|
||||||
|
boot mode on the node.
|
Loading…
Reference in New Issue
Block a user