diff options
Diffstat (limited to 'ironic/drivers')
-rw-r--r-- | ironic/drivers/base.py | 65 | ||||
-rw-r--r-- | ironic/drivers/modules/boot_mode_utils.py | 148 | ||||
-rw-r--r-- | ironic/drivers/modules/deploy_utils.py | 56 | ||||
-rw-r--r-- | ironic/drivers/modules/pxe.py | 5 |
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: |