summaryrefslogtreecommitdiff
path: root/ironic/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'ironic/drivers')
-rw-r--r--ironic/drivers/base.py65
-rw-r--r--ironic/drivers/modules/boot_mode_utils.py148
-rw-r--r--ironic/drivers/modules/deploy_utils.py56
-rw-r--r--ironic/drivers/modules/pxe.py5
4 files changed, 269 insertions, 5 deletions
diff --git a/ironic/drivers/base.py b/ironic/drivers/base.py
index 4862cabc5..45131ba33 100644
--- a/ironic/drivers/base.py
+++ b/ironic/drivers/base.py
@@ -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
def get_sensors_data(self, task):
"""Get sensors data method.
diff --git a/ironic/drivers/modules/boot_mode_utils.py b/ironic/drivers/modules/boot_mode_utils.py
new file mode 100644
index 000000000..acddf898e
--- /dev/null
+++ b/ironic/drivers/modules/boot_mode_utils.py
@@ -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)
diff --git a/ironic/drivers/modules/deploy_utils.py b/ironic/drivers/modules/deploy_utils.py
index e18209adf..11b74e2c9 100644
--- a/ironic/drivers/modules/deploy_utils.py
+++ b/ironic/drivers/modules/deploy_utils.py
@@ -771,12 +771,17 @@ def get_boot_mode_for_deploy(node):
'trusted_boot' is set to 'true' in 'instance_info/capabilities' of node.
Otherwise it returns value of 'boot_mode' in 'properties/capabilities'
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.
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.
: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):
@@ -789,15 +794,56 @@ def get_boot_mode_for_deploy(node):
LOG.debug('Deploy boot mode is bios for %s.', node.uuid)
return 'bios'
- boot_mode = driver_utils.get_node_capability(node, 'boot_mode')
+ # NOTE(etingof):
+ # 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
+
+ cap_boot_mode = driver_utils.get_node_capability(node, 'boot_mode')
+
+ boot_mode = instance_info.get('deploy_boot_mode')
if boot_mode is None:
- instance_info = node.instance_info
- boot_mode = instance_info.get('deploy_boot_mode')
+ 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.',
{'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):
diff --git a/ironic/drivers/modules/pxe.py b/ironic/drivers/modules/pxe.py
index c2c2ee3da..f2310c64e 100644
--- a/ironic/drivers/modules/pxe.py
+++ b/ironic/drivers/modules/pxe.py
@@ -35,6 +35,7 @@ from ironic.common import states
from ironic.conductor import utils as manager_utils
from ironic.conf import CONF
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 image_cache
from ironic.drivers import utils as driver_utils
@@ -528,6 +529,7 @@ class PXEBoot(base.BootInterface):
# if we are in DEPLOYING state.
if node.provision_state == states.DEPLOYING:
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.update(ramdisk_params)
@@ -590,6 +592,8 @@ class PXEBoot(base.BootInterface):
:param task: a task from TaskManager.
:returns: None
"""
+ boot_mode_utils.sync_boot_mode(task)
+
node = task.node
boot_option = deploy_utils.get_boot_option(node)
boot_device = None
@@ -679,6 +683,7 @@ class PXEBoot(base.BootInterface):
:returns: None
"""
node = task.node
+
try:
images_info = _get_instance_image_info(node, task.context)
except exception.MissingParameterValue as e: