summaryrefslogtreecommitdiff
path: root/ironic/drivers
diff options
context:
space:
mode:
authorIlya Etingof <etingof@gmail.com>2017-12-07 17:13:59 +0100
committerJulia Kreger <juliaashleykreger@gmail.com>2018-06-15 15:19:41 +0000
commit5e8f2e39d4f3508b2189dc09d6eecc9492c42f0c (patch)
tree840c898c5dbe2788f9fdcc4a3a8828374a05168a /ironic/drivers
parent643422b4d6d7db9dd062bd443a0b272d24c05f61 (diff)
downloadironic-5e8f2e39d4f3508b2189dc09d6eecc9492c42f0c.tar.gz
Adds boot mode support to ManagementInterface
This change introduces optional boot mode get/set methods to driver management interface [1] [2] alongside existing get/set boot device calls. The management interface is called at deploy time to synchronize BM machine boot mode setting with Ironic node configuration whenever needed. Also, this change introduces common exception class to be eventually used by the drivers to communicate their runtime errors to Ironic code in a uniformed way. 1. http://eavesdrop.openstack.org/irclogs/%23openstack-ironic/%23openstack-ironic.2018-01-09.log.html#t2018-01-09T15:54:16 2. http://eavesdrop.openstack.org/irclogs/%23openstack-ironic/%23openstack-ironic.2018-05-11.log.html#t2018-05-11T12:47:12 Story: 1734131 Task: 10640 Change-Id: If3db6ab8d3fae35d17808a231b7eecf11cf58327
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: