From 642dedf784b98904ffb72f8905762b95b60f22c6 Mon Sep 17 00:00:00 2001 From: Julia Kreger Date: Wed, 5 Sep 2018 16:07:01 -0700 Subject: [PATCH] pxe/ipxe: Move common calls out pxe.py Moves common method calls out of the body of pxe.py into pxe_utils, and accordingly removes some of the imports and re-maps the methods for compatability, as unit tests and methods have not yet been updated. Tests and methods will be updated in the next patch. Story: 1628069 Task: 26724 Change-Id: I717c49b99837b1785fc0080106414c6094dbf8eb --- ironic/common/pxe_utils.py | 379 +++++++++++++++++- ironic/drivers/modules/pxe.py | 337 +--------------- ironic/tests/unit/drivers/modules/test_pxe.py | 8 +- 3 files changed, 401 insertions(+), 323 deletions(-) diff --git a/ironic/common/pxe_utils.py b/ironic/common/pxe_utils.py index ede0bd81b5..292c228307 100644 --- a/ironic/common/pxe_utils.py +++ b/ironic/common/pxe_utils.py @@ -17,19 +17,22 @@ import os from ironic_lib import utils as ironic_utils -from oslo_config import cfg from oslo_log import log as logging from oslo_utils import excutils from oslo_utils import fileutils from ironic.common import dhcp_factory from ironic.common import exception +from ironic.common.glance_service import service_utils from ironic.common.i18n import _ +from ironic.common import image_service as service +from ironic.common import images +from ironic.common import states from ironic.common import utils +from ironic.conf import CONF from ironic.drivers.modules import boot_mode_utils from ironic.drivers.modules import deploy_utils - -CONF = cfg.CONF +from ironic import objects LOG = logging.getLogger(__name__) @@ -429,3 +432,373 @@ def get_path_relative_to_tftp_root(file_path): :returns: The path relative to CONF.pxe.tftp_root """ return os.path.relpath(file_path, get_tftp_path_prefix()) + + +def is_ipxe_enabled(task): + """Return true if ipxe is set. + + :param task: A TaskManager object + :returns: boolean true if ``[pxe]ipxe_enabled`` is configured + or if the task driver instance is the iPXE driver. + """ + # TODO(TheJulia): Due to the order being shuffled of the patches, + # I'm mostly leaving this in place. + # NOTE(TheJulia): importutils used here as we seem to get in circular + # import weirdness otherwise, specifically when the classes that use + # the pxe interface as their parent. + # iPXEBoot = importutils.import_class( + # 'ironic.drivers.modules.ipxe.iPXEBoot') + # return CONF.pxe.ipxe_enabled or isinstance(task.driver.boot, + # iPXEBoot) + return CONF.pxe.ipxe_enabled + + +def parse_driver_info(node, mode='deploy'): + """Gets the driver specific Node deployment info. + + This method validates whether the 'driver_info' property of the + supplied node contains the required information for this driver to + deploy images to, or rescue, the node. + + :param node: a single Node. + :param mode: Label indicating a deploy or rescue operation being + carried out on the node. Supported values are + 'deploy' and 'rescue'. Defaults to 'deploy', indicating + deploy operation is being carried out. + :returns: A dict with the driver_info values. + :raises: MissingParameterValue + """ + info = node.driver_info + + params_to_check = KERNEL_RAMDISK_LABELS[mode] + + d_info = {k: info.get(k) for k in params_to_check} + error_msg = _("Cannot validate PXE bootloader. Some parameters were" + " missing in node's driver_info") + deploy_utils.check_for_missing_params(d_info, error_msg) + return d_info + + +def get_instance_image_info(node, ctx): + """Generate the paths for TFTP files for instance related images. + + This method generates the paths for instance kernel and + instance ramdisk. This method also updates the node, so caller should + already have a non-shared lock on the node. + + :param node: a node object + :param ctx: context + :returns: a dictionary whose keys are the names of the images (kernel, + ramdisk) and values are the absolute paths of them. If it's a whole + disk image or node is configured for localboot, + it returns an empty dictionary. + """ + image_info = {} + # NOTE(pas-ha) do not report image kernel and ramdisk for + # local boot or whole disk images so that they are not cached + if (node.driver_internal_info.get('is_whole_disk_image') + or deploy_utils.get_boot_option(node) == 'local'): + return image_info + + root_dir = get_root_dir() + i_info = node.instance_info + labels = ('kernel', 'ramdisk') + d_info = deploy_utils.get_image_instance_info(node) + if not (i_info.get('kernel') and i_info.get('ramdisk')): + glance_service = service.GlanceImageService( + version=CONF.glance.glance_api_version, context=ctx) + iproperties = glance_service.show(d_info['image_source'])['properties'] + for label in labels: + i_info[label] = str(iproperties[label + '_id']) + node.instance_info = i_info + node.save() + + for label in labels: + image_info[label] = ( + i_info[label], + os.path.join(root_dir, node.uuid, label) + ) + + return image_info + + +def get_image_info(node, mode='deploy'): + """Generate the paths for TFTP files for deploy or rescue images. + + This method generates the paths for the deploy (or rescue) kernel and + deploy (or rescue) ramdisk. + + :param node: a node object + :param mode: Label indicating a deploy or rescue operation being + carried out on the node. Supported values are 'deploy' and 'rescue'. + Defaults to 'deploy', indicating deploy operation is being carried out. + :returns: a dictionary whose keys are the names of the images + (deploy_kernel, deploy_ramdisk, or rescue_kernel, rescue_ramdisk) and + values are the absolute paths of them. + :raises: MissingParameterValue, if deploy_kernel/deploy_ramdisk or + rescue_kernel/rescue_ramdisk is missing in node's driver_info. + """ + d_info = parse_driver_info(node, mode=mode) + + return get_kernel_ramdisk_info( + node.uuid, d_info, mode=mode) + + +def build_deploy_pxe_options(task, pxe_info, mode='deploy'): + pxe_opts = {} + node = task.node + + kernel_label = '%s_kernel' % mode + ramdisk_label = '%s_ramdisk' % mode + ipxe_enabled = is_ipxe_enabled(task) + for label, option in ((kernel_label, 'deployment_aki_path'), + (ramdisk_label, 'deployment_ari_path')): + if ipxe_enabled: + image_href = pxe_info[label][0] + if (CONF.pxe.ipxe_use_swift + and service_utils.is_glance_image(image_href)): + pxe_opts[option] = images.get_temp_url_for_glance_image( + task.context, image_href) + else: + pxe_opts[option] = '/'.join([CONF.deploy.http_url, node.uuid, + label]) + else: + pxe_opts[option] = get_path_relative_to_tftp_root( + pxe_info[label][1]) + if ipxe_enabled: + pxe_opts['initrd_filename'] = ramdisk_label + return pxe_opts + + +def build_instance_pxe_options(task, pxe_info): + pxe_opts = {} + node = task.node + + for label, option in (('kernel', 'aki_path'), + ('ramdisk', 'ari_path')): + if label in pxe_info: + if is_ipxe_enabled(task): + # NOTE(pas-ha) do not use Swift TempURLs for kernel and + # ramdisk of user image when boot_option is not local, + # as this breaks instance reboot later when temp urls + # have timed out. + pxe_opts[option] = '/'.join( + [CONF.deploy.http_url, node.uuid, label]) + else: + # It is possible that we don't have kernel/ramdisk or even + # image_source to determine if it's a whole disk image or not. + # For example, when transitioning to 'available' state + # for first time from 'manage' state. + pxe_opts[option] = get_path_relative_to_tftp_root( + pxe_info[label][1]) + + # These are dummy values to satisfy elilo. + # image and initrd fields in elilo config cannot be blank. + pxe_opts.setdefault('aki_path', 'no_kernel') + pxe_opts.setdefault('ari_path', 'no_ramdisk') + + i_info = task.node.instance_info + try: + pxe_opts['ramdisk_opts'] = i_info['ramdisk_kernel_arguments'] + except KeyError: + pass + + return pxe_opts + + +def build_extra_pxe_options(): + # Enable debug in IPA according to CONF.debug if it was not + # specified yet + pxe_append_params = CONF.pxe.pxe_append_params + if CONF.debug and 'ipa-debug' not in pxe_append_params: + pxe_append_params += ' ipa-debug=1' + + return {'pxe_append_params': pxe_append_params, + 'tftp_server': CONF.pxe.tftp_server, + 'ipxe_timeout': CONF.pxe.ipxe_timeout * 1000} + + +def build_pxe_config_options(task, pxe_info, service=False): + """Build the PXE config options for a node + + This method builds the PXE boot options for a node, + given all the required parameters. + + The options should then be passed to pxe_utils.create_pxe_config to + create the actual config files. + + :param task: A TaskManager object + :param pxe_info: a dict of values to set on the configuration file + :param service: if True, build "service mode" pxe config for netboot-ed + user image and skip adding deployment image kernel and ramdisk info + to PXE options. + :returns: A dictionary of pxe options to be used in the pxe bootfile + template. + """ + node = task.node + mode = deploy_utils.rescue_or_deploy_mode(node) + if service: + pxe_options = {} + elif (node.driver_internal_info.get('boot_from_volume') + and is_ipxe_enabled(task)): + pxe_options = get_volume_pxe_options(task) + else: + pxe_options = build_deploy_pxe_options(task, pxe_info, mode=mode) + + # NOTE(pas-ha) we still must always add user image kernel and ramdisk + # info as later during switching PXE config to service mode the + # template will not be regenerated anew, but instead edited as-is. + # This can be changed later if/when switching PXE config will also use + # proper templating instead of editing existing files on disk. + pxe_options.update(build_instance_pxe_options(task, pxe_info)) + + pxe_options.update(build_extra_pxe_options()) + + return pxe_options + + +def build_service_pxe_config(task, instance_image_info, + root_uuid_or_disk_id, + ramdisk_boot=False): + node = task.node + pxe_config_path = get_pxe_config_file_path(node.uuid) + # NOTE(pas-ha) if it is takeover of ACTIVE node or node performing + # unrescue operation, first ensure that basic PXE configs and links + # are in place before switching pxe config + if (node.provision_state in [states.ACTIVE, states.UNRESCUING] + and not os.path.isfile(pxe_config_path)): + pxe_options = build_pxe_config_options(task, instance_image_info, + service=True) + pxe_config_template = deploy_utils.get_pxe_config_template(node) + create_pxe_config(task, pxe_options, pxe_config_template) + iwdi = node.driver_internal_info.get('is_whole_disk_image') + deploy_utils.switch_pxe_config( + pxe_config_path, root_uuid_or_disk_id, + boot_mode_utils.get_boot_mode_for_deploy(node), + iwdi, deploy_utils.is_trusted_boot_requested(node), + deploy_utils.is_iscsi_boot(task), ramdisk_boot) + # TODO(TheJulia): Add with ipxe interface + # ipxe_enabled=is_ipxe_enabled(task)) + + +def get_volume_pxe_options(task): + """Identify volume information for iPXE template generation.""" + def __return_item_or_first_if_list(item): + if isinstance(item, list): + return item[0] + else: + return item + + def __get_property(properties, key): + prop = __return_item_or_first_if_list(properties.get(key, '')) + if prop is not '': + return prop + return __return_item_or_first_if_list(properties.get(key + 's', '')) + + def __generate_iscsi_url(properties): + """Returns iscsi url.""" + portal = __get_property(properties, 'target_portal') + iqn = __get_property(properties, 'target_iqn') + lun = __get_property(properties, 'target_lun') + + if ':' in portal: + host, port = portal.split(':') + else: + host = portal + port = '' + return ("iscsi:%(host)s::%(port)s:%(lun)s:%(iqn)s" % + {'host': host, 'port': port, 'lun': lun, 'iqn': iqn}) + + pxe_options = {} + node = task.node + boot_volume = node.driver_internal_info.get('boot_from_volume') + volume = objects.VolumeTarget.get_by_uuid(task.context, + boot_volume) + + properties = volume.properties + if 'iscsi' in volume['volume_type']: + if 'auth_username' in properties: + pxe_options['username'] = properties['auth_username'] + if 'auth_password' in properties: + pxe_options['password'] = properties['auth_password'] + iscsi_initiator_iqn = None + for vc in task.volume_connectors: + if vc.type == 'iqn': + iscsi_initiator_iqn = vc.connector_id + + pxe_options.update( + {'iscsi_boot_url': __generate_iscsi_url(volume.properties), + 'iscsi_initiator_iqn': iscsi_initiator_iqn}) + # NOTE(TheJulia): This may be the route to multi-path, define + # volumes via sanhook in the ipxe template and let the OS sort it out. + extra_targets = [] + + for target in task.volume_targets: + if target.boot_index != 0 and 'iscsi' in target.volume_type: + iscsi_url = __generate_iscsi_url(target.properties) + username = target.properties['auth_username'] + password = target.properties['auth_password'] + extra_targets.append({'url': iscsi_url, + 'username': username, + 'password': password}) + pxe_options.update({'iscsi_volumes': extra_targets, + 'boot_from_volume': True}) + # TODO(TheJulia): FibreChannel boot, i.e. wwpn in volume_type + # for FCoE, should go here. + return pxe_options + + +def validate_boot_parameters_for_trusted_boot(node): + """Check if boot parameters are valid for trusted boot.""" + boot_mode = boot_mode_utils.get_boot_mode_for_deploy(node) + boot_option = deploy_utils.get_boot_option(node) + is_whole_disk_image = node.driver_internal_info.get('is_whole_disk_image') + # 'is_whole_disk_image' is not supported by trusted boot, because there is + # no Kernel/Ramdisk to measure at all. + if (boot_mode != 'bios' + or is_whole_disk_image + or boot_option != 'netboot'): + msg = (_("Trusted boot is only supported in BIOS boot mode with " + "netboot and without whole_disk_image, but Node " + "%(node_uuid)s was configured with boot_mode: %(boot_mode)s, " + "boot_option: %(boot_option)s, is_whole_disk_image: " + "%(is_whole_disk_image)s: at least one of them is wrong, and " + "this can be caused by enable secure boot.") % + {'node_uuid': node.uuid, 'boot_mode': boot_mode, + 'boot_option': boot_option, + 'is_whole_disk_image': is_whole_disk_image}) + LOG.error(msg) + raise exception.InvalidParameterValue(msg) + + +def prepare_instance_pxe_config(task, image_info, + iscsi_boot=False, + ramdisk_boot=False): + """Prepares the config file for PXE boot + + :param task: a task from TaskManager. + :param image_info: a dict of values of instance image + metadata to set on the configuration file. + :param iscsi_boot: if boot is from an iSCSI volume or not. + :param ramdisk_boot: if the boot is to a ramdisk configuration. + :returns: None + """ + + node = task.node + dhcp_opts = dhcp_options_for_instance(task) + provider = dhcp_factory.DHCPFactory() + provider.update_dhcp(task, dhcp_opts) + pxe_config_path = get_pxe_config_file_path( + node.uuid) + if not os.path.isfile(pxe_config_path): + pxe_options = build_pxe_config_options( + task, image_info, service=ramdisk_boot) + pxe_config_template = ( + deploy_utils.get_pxe_config_template(node)) + create_pxe_config( + task, pxe_options, pxe_config_template) + deploy_utils.switch_pxe_config( + pxe_config_path, None, + boot_mode_utils.get_boot_mode_for_deploy(node), False, + iscsi_boot=iscsi_boot, ramdisk_boot=ramdisk_boot, + ipxe_enabled=is_ipxe_enabled(task)) diff --git a/ironic/drivers/modules/pxe.py b/ironic/drivers/modules/pxe.py index e10c492fa2..bbe88f9aa7 100644 --- a/ironic/drivers/modules/pxe.py +++ b/ironic/drivers/modules/pxe.py @@ -28,9 +28,7 @@ from ironic.common import dhcp_factory from ironic.common import exception from ironic.common.glance_service import service_utils from ironic.common.i18n import _ -from ironic.common import image_service as service -from ironic.common import images -from ironic.common import pxe_utils +from ironic.common import pxe_utils as pxe_utils from ironic.common import states from ironic.conductor import task_manager from ironic.conductor import utils as manager_utils @@ -41,7 +39,6 @@ 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 -from ironic import objects LOG = logging.getLogger(__name__) METRICS = metrics_utils.get_metrics_logger(__name__) @@ -68,320 +65,28 @@ RESCUE_PROPERTIES = { COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy() COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES) +# TODO(TheJulia): The lines below are for compatability purposes, +# to enable a phased migration of related code over a series of +# patches instead of attempting to refactor large portions of the +# boot interface code in a single patch. These private method +# mappings should be removed as soon as reasonably possible. -def _parse_driver_info(node, mode='deploy'): - """Gets the driver specific Node deployment info. +_parse_driver_info = pxe_utils.parse_driver_info +_get_instance_image_info = pxe_utils.parse_driver_info +_get_instance_image_info = pxe_utils.get_instance_image_info +_get_image_info = pxe_utils.get_image_info +_build_deploy_pxe_options = pxe_utils.build_instance_pxe_options +_build_instance_pxe_options = pxe_utils.build_instance_pxe_options +_build_extra_pxe_options = pxe_utils.build_extra_pxe_options +_build_pxe_config_options = pxe_utils.build_pxe_config_options +_build_service_pxe_config = pxe_utils.build_service_pxe_config +_get_volume_pxe_options = pxe_utils.get_volume_pxe_options +_prepare_instance_pxe_config = pxe_utils.prepare_instance_pxe_config - This method validates whether the 'driver_info' property of the - supplied node contains the required information for this driver to - deploy images to, or rescue, the node. - - :param node: a single Node. - :param mode: Label indicating a deploy or rescue operation being - carried out on the node. Supported values are - 'deploy' and 'rescue'. Defaults to 'deploy', indicating - deploy operation is being carried out. - :returns: A dict with the driver_info values. - :raises: MissingParameterValue - """ - info = node.driver_info - - params_to_check = pxe_utils.KERNEL_RAMDISK_LABELS[mode] - - d_info = {k: info.get(k) for k in params_to_check} - error_msg = _("Cannot validate PXE bootloader. Some parameters were" - " missing in node's driver_info") - deploy_utils.check_for_missing_params(d_info, error_msg) - return d_info - - -def _get_instance_image_info(node, ctx): - """Generate the paths for TFTP files for instance related images. - - This method generates the paths for instance kernel and - instance ramdisk. This method also updates the node, so caller should - already have a non-shared lock on the node. - - :param node: a node object - :param ctx: context - :returns: a dictionary whose keys are the names of the images (kernel, - ramdisk) and values are the absolute paths of them. If it's a whole - disk image or node is configured for localboot, - it returns an empty dictionary. - """ - image_info = {} - # NOTE(pas-ha) do not report image kernel and ramdisk for - # local boot or whole disk images so that they are not cached - if (node.driver_internal_info.get('is_whole_disk_image') - or deploy_utils.get_boot_option(node) == 'local'): - return image_info - - root_dir = pxe_utils.get_root_dir() - i_info = node.instance_info - labels = ('kernel', 'ramdisk') - d_info = deploy_utils.get_image_instance_info(node) - if not (i_info.get('kernel') and i_info.get('ramdisk')): - glance_service = service.GlanceImageService( - version=CONF.glance.glance_api_version, context=ctx) - iproperties = glance_service.show(d_info['image_source'])['properties'] - for label in labels: - i_info[label] = str(iproperties[label + '_id']) - node.instance_info = i_info - node.save() - - for label in labels: - image_info[label] = ( - i_info[label], - os.path.join(root_dir, node.uuid, label) - ) - - return image_info - - -def _get_image_info(node, mode='deploy'): - """Generate the paths for TFTP files for deploy or rescue images. - - This method generates the paths for the deploy (or rescue) kernel and - deploy (or rescue) ramdisk. - - :param node: a node object - :param mode: Label indicating a deploy or rescue operation being - carried out on the node. Supported values are 'deploy' and 'rescue'. - Defaults to 'deploy', indicating deploy operation is being carried out. - :returns: a dictionary whose keys are the names of the images - (deploy_kernel, deploy_ramdisk, or rescue_kernel, rescue_ramdisk) and - values are the absolute paths of them. - :raises: MissingParameterValue, if deploy_kernel/deploy_ramdisk or - rescue_kernel/rescue_ramdisk is missing in node's driver_info. - """ - d_info = _parse_driver_info(node, mode=mode) - - return pxe_utils.get_kernel_ramdisk_info( - node.uuid, d_info, mode=mode) - - -def _build_deploy_pxe_options(task, pxe_info, mode='deploy'): - pxe_opts = {} - node = task.node - - kernel_label = '%s_kernel' % mode - ramdisk_label = '%s_ramdisk' % mode - - for label, option in ((kernel_label, 'deployment_aki_path'), - (ramdisk_label, 'deployment_ari_path')): - if CONF.pxe.ipxe_enabled: - image_href = pxe_info[label][0] - if (CONF.pxe.ipxe_use_swift - and service_utils.is_glance_image(image_href)): - pxe_opts[option] = images.get_temp_url_for_glance_image( - task.context, image_href) - else: - pxe_opts[option] = '/'.join([CONF.deploy.http_url, node.uuid, - label]) - else: - pxe_opts[option] = pxe_utils.get_path_relative_to_tftp_root( - pxe_info[label][1]) - if CONF.pxe.ipxe_enabled: - pxe_opts['initrd_filename'] = ramdisk_label - return pxe_opts - - -def _build_instance_pxe_options(task, pxe_info): - pxe_opts = {} - node = task.node - - for label, option in (('kernel', 'aki_path'), - ('ramdisk', 'ari_path')): - if label in pxe_info: - if CONF.pxe.ipxe_enabled: - # NOTE(pas-ha) do not use Swift TempURLs for kernel and - # ramdisk of user image when boot_option is not local, - # as this breaks instance reboot later when temp urls - # have timed out. - pxe_opts[option] = '/'.join( - [CONF.deploy.http_url, node.uuid, label]) - else: - # It is possible that we don't have kernel/ramdisk or even - # image_source to determine if it's a whole disk image or not. - # For example, when transitioning to 'available' state - # for first time from 'manage' state. - pxe_opts[option] = pxe_utils.get_path_relative_to_tftp_root( - pxe_info[label][1]) - - # These are dummy values to satisfy elilo. - # image and initrd fields in elilo config cannot be blank. - pxe_opts.setdefault('aki_path', 'no_kernel') - pxe_opts.setdefault('ari_path', 'no_ramdisk') - - i_info = task.node.instance_info - try: - pxe_opts['ramdisk_opts'] = i_info['ramdisk_kernel_arguments'] - except KeyError: - pass - - return pxe_opts - - -def _build_extra_pxe_options(): - # Enable debug in IPA according to CONF.debug if it was not - # specified yet - pxe_append_params = CONF.pxe.pxe_append_params - if CONF.debug and 'ipa-debug' not in pxe_append_params: - pxe_append_params += ' ipa-debug=1' - - return {'pxe_append_params': pxe_append_params, - 'tftp_server': CONF.pxe.tftp_server, - 'ipxe_timeout': CONF.pxe.ipxe_timeout * 1000} - - -def _build_pxe_config_options(task, pxe_info, service=False): - """Build the PXE config options for a node - - This method builds the PXE boot options for a node, - given all the required parameters. - - The options should then be passed to pxe_utils.create_pxe_config to - create the actual config files. - - :param task: A TaskManager object - :param pxe_info: a dict of values to set on the configuration file - :param service: if True, build "service mode" pxe config for netboot-ed - user image and skip adding deployment image kernel and ramdisk info - to PXE options. - :returns: A dictionary of pxe options to be used in the pxe bootfile - template. - """ - node = task.node - mode = deploy_utils.rescue_or_deploy_mode(node) - if service: - pxe_options = {} - elif (node.driver_internal_info.get('boot_from_volume') - and CONF.pxe.ipxe_enabled): - pxe_options = _get_volume_pxe_options(task) - else: - pxe_options = _build_deploy_pxe_options(task, pxe_info, mode=mode) - - # NOTE(pas-ha) we still must always add user image kernel and ramdisk - # info as later during switching PXE config to service mode the - # template will not be regenerated anew, but instead edited as-is. - # This can be changed later if/when switching PXE config will also use - # proper templating instead of editing existing files on disk. - pxe_options.update(_build_instance_pxe_options(task, pxe_info)) - - pxe_options.update(_build_extra_pxe_options()) - - return pxe_options - - -def _build_service_pxe_config(task, instance_image_info, - root_uuid_or_disk_id, - ramdisk_boot=False): - node = task.node - pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid) - # NOTE(pas-ha) if it is takeover of ACTIVE node or node performing - # unrescue operation, first ensure that basic PXE configs and links - # are in place before switching pxe config - if (node.provision_state in [states.ACTIVE, states.UNRESCUING] - and not os.path.isfile(pxe_config_path)): - pxe_options = _build_pxe_config_options(task, instance_image_info, - service=True) - pxe_config_template = deploy_utils.get_pxe_config_template(node) - pxe_utils.create_pxe_config(task, pxe_options, pxe_config_template) - iwdi = node.driver_internal_info.get('is_whole_disk_image') - deploy_utils.switch_pxe_config( - pxe_config_path, root_uuid_or_disk_id, - boot_mode_utils.get_boot_mode_for_deploy(node), - iwdi, deploy_utils.is_trusted_boot_requested(node), - deploy_utils.is_iscsi_boot(task), ramdisk_boot) - - -def _get_volume_pxe_options(task): - """Identify volume information for iPXE template generation.""" - def __return_item_or_first_if_list(item): - if isinstance(item, list): - return item[0] - else: - return item - - def __get_property(properties, key): - prop = __return_item_or_first_if_list(properties.get(key, '')) - if prop is not '': - return prop - return __return_item_or_first_if_list(properties.get(key + 's', '')) - - def __generate_iscsi_url(properties): - """Returns iscsi url.""" - portal = __get_property(properties, 'target_portal') - iqn = __get_property(properties, 'target_iqn') - lun = __get_property(properties, 'target_lun') - - if ':' in portal: - host, port = portal.split(':') - else: - host = portal - port = '' - return ("iscsi:%(host)s::%(port)s:%(lun)s:%(iqn)s" % - {'host': host, 'port': port, 'lun': lun, 'iqn': iqn}) - - pxe_options = {} - node = task.node - boot_volume = node.driver_internal_info.get('boot_from_volume') - volume = objects.VolumeTarget.get_by_uuid(task.context, - boot_volume) - properties = volume.properties - if 'iscsi' in volume['volume_type']: - if 'auth_username' in properties: - pxe_options['username'] = properties['auth_username'] - if 'auth_password' in properties: - pxe_options['password'] = properties['auth_password'] - iscsi_initiator_iqn = None - for vc in task.volume_connectors: - if vc.type == 'iqn': - iscsi_initiator_iqn = vc.connector_id - - pxe_options.update( - {'iscsi_boot_url': __generate_iscsi_url(volume.properties), - 'iscsi_initiator_iqn': iscsi_initiator_iqn}) - # NOTE(TheJulia): This may be the route to multi-path, define - # volumes via sanhook in the ipxe template and let the OS sort it out. - extra_targets = [] - - for target in task.volume_targets: - if target.boot_index != 0 and 'iscsi' in target.volume_type: - iscsi_url = __generate_iscsi_url(target.properties) - username = target.properties['auth_username'] - password = target.properties['auth_password'] - extra_targets.append({'url': iscsi_url, - 'username': username, - 'password': password}) - pxe_options.update({'iscsi_volumes': extra_targets, - 'boot_from_volume': True}) - # TODO(TheJulia): FibreChannel boot, i.e. wwpn in volume_type - # for FCoE, should go here. - return pxe_options - - -def validate_boot_parameters_for_trusted_boot(node): - """Check if boot parameters are valid for trusted boot.""" - boot_mode = boot_mode_utils.get_boot_mode_for_deploy(node) - boot_option = deploy_utils.get_boot_option(node) - is_whole_disk_image = node.driver_internal_info.get('is_whole_disk_image') - # 'is_whole_disk_image' is not supported by trusted boot, because there is - # no Kernel/Ramdisk to measure at all. - if (boot_mode != 'bios' - or is_whole_disk_image - or boot_option != 'netboot'): - msg = (_("Trusted boot is only supported in BIOS boot mode with " - "netboot and without whole_disk_image, but Node " - "%(node_uuid)s was configured with boot_mode: %(boot_mode)s, " - "boot_option: %(boot_option)s, is_whole_disk_image: " - "%(is_whole_disk_image)s: at least one of them is wrong, and " - "this can be caused by enable secure boot.") % - {'node_uuid': node.uuid, 'boot_mode': boot_mode, - 'boot_option': boot_option, - 'is_whole_disk_image': is_whole_disk_image}) - LOG.error(msg) - raise exception.InvalidParameterValue(msg) +# NOTE(TheJulia): This was previously a public method to the code being +# moved. This mapping should be removed in the T* cycle. +validate_boot_parameters_for_trusted_boot = pxe_utils.validate_boot_parameters_for_trusted_boot # noqa +# NOTE(TheJulia): End section of mappings for migrated common pxe code. @image_cache.cleanup(priority=25) diff --git a/ironic/tests/unit/drivers/modules/test_pxe.py b/ironic/tests/unit/drivers/modules/test_pxe.py index d85ac33ba5..765d5781f5 100644 --- a/ironic/tests/unit/drivers/modules/test_pxe.py +++ b/ironic/tests/unit/drivers/modules/test_pxe.py @@ -675,7 +675,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase): list(fake_pxe_info.values()), True) - @mock.patch.object(pxe.LOG, 'error', autospec=True) + @mock.patch.object(pxe_utils.LOG, 'error', autospec=True) def test_validate_boot_parameters_for_trusted_boot_one(self, mock_log): properties = {'capabilities': 'boot_mode:uefi'} instance_info = {"boot_option": "netboot"} @@ -687,7 +687,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase): self.node) self.assertTrue(mock_log.called) - @mock.patch.object(pxe.LOG, 'error', autospec=True) + @mock.patch.object(pxe_utils.LOG, 'error', autospec=True) def test_validate_boot_parameters_for_trusted_boot_two(self, mock_log): properties = {'capabilities': 'boot_mode:bios'} instance_info = {"boot_option": "local"} @@ -699,7 +699,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase): self.node) self.assertTrue(mock_log.called) - @mock.patch.object(pxe.LOG, 'error', autospec=True) + @mock.patch.object(pxe_utils.LOG, 'error', autospec=True) def test_validate_boot_parameters_for_trusted_boot_three(self, mock_log): properties = {'capabilities': 'boot_mode:bios'} instance_info = {"boot_option": "netboot"} @@ -711,7 +711,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase): self.node) self.assertTrue(mock_log.called) - @mock.patch.object(pxe.LOG, 'error', autospec=True) + @mock.patch.object(pxe_utils.LOG, 'error', autospec=True) def test_validate_boot_parameters_for_trusted_boot_pass(self, mock_log): properties = {'capabilities': 'boot_mode:bios'} instance_info = {"boot_option": "netboot"}