def test_get_boot_mode_default(self, mock_for_deploy, mock_log): boot_mode_utils.warn_about_default_boot_mode = False mock_for_deploy.return_value = None boot_mode = boot_mode_utils.get_boot_mode(self.node) self.assertEqual(boot_modes.LEGACY_BIOS, boot_mode) boot_mode = boot_mode_utils.get_boot_mode(self.node) self.assertEqual(boot_modes.LEGACY_BIOS, boot_mode) self.assertEqual(1, mock_log.warning.call_count)
def test_get_boot_mode_default(self, mock_for_deploy, mock_log): boot_mode_utils.warn_about_default_boot_mode = False mock_for_deploy.return_value = None boot_mode = boot_mode_utils.get_boot_mode(self.node) self.assertEqual(boot_modes.LEGACY_BIOS, boot_mode) boot_mode = boot_mode_utils.get_boot_mode(self.node) self.assertEqual(boot_modes.LEGACY_BIOS, boot_mode) self.assertEqual(1, mock_log.warning.call_count)
def build_service_pxe_config(task, instance_image_info, root_uuid_or_disk_id, ramdisk_boot=False, ipxe_enabled=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 # NOTE(TheJulia): Also consider deploying a valid state to go ahead # and check things before continuing, as otherwise deployments can # fail if the agent was booted outside the direct actions of the # boot interface. if (node.provision_state in [states.ACTIVE, states.UNRESCUING, states.DEPLOYING] and not os.path.isfile(pxe_config_path)): pxe_options = build_pxe_config_options(task, instance_image_info, service=True, ipxe_enabled=ipxe_enabled) pxe_config_template = deploy_utils.get_pxe_config_template(node) create_pxe_config(task, pxe_options, pxe_config_template, ipxe_enabled=ipxe_enabled) 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(node), iwdi, deploy_utils.is_trusted_boot_requested(node), deploy_utils.is_iscsi_boot(task), ramdisk_boot, ipxe_enabled=ipxe_enabled)
def try_set_boot_device(task, device, persistent=True): """Tries to set the boot device on the node. This method tries to set the boot device on the node to the given boot device. Under uefi boot mode, setting of boot device may differ between different machines. IPMI does not work for setting boot devices in uefi mode for certain machines. This method ignores the expected IPMI failure for uefi boot mode and just logs a message. In error cases, it is expected the operator has to manually set the node to boot from the correct device. :param task: a TaskManager object containing the node :param device: the boot device :param persistent: Whether to set the boot device persistently :raises: Any exception from set_boot_device except IPMIFailure (setting of boot device using ipmi is expected to fail). """ try: manager_utils.node_set_boot_device(task, device, persistent=persistent) except exception.IPMIFailure: with excutils.save_and_reraise_exception() as ctxt: if boot_mode_utils.get_boot_mode(task.node) == 'uefi': ctxt.reraise = False LOG.warning( "ipmitool is unable to set boot device while " "the node %s is in UEFI boot mode. Please set " "the boot device manually.", task.node.uuid)
def build_service_pxe_config(task, instance_image_info, root_uuid_or_disk_id, ramdisk_boot=False, ipxe_enabled=False): node = task.node pxe_config_path = get_pxe_config_file_path(node.uuid, ipxe_enabled=ipxe_enabled) # 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 # NOTE(TheJulia): Also consider deploying a valid state to go ahead # and check things before continuing, as otherwise deployments can # fail if the agent was booted outside the direct actions of the # boot interface. if (node.provision_state in [states.ACTIVE, states.UNRESCUING, states.DEPLOYING] and not os.path.isfile(pxe_config_path)): pxe_options = build_pxe_config_options(task, instance_image_info, service=True, ipxe_enabled=ipxe_enabled) pxe_config_template = deploy_utils.get_pxe_config_template(node) create_pxe_config(task, pxe_options, pxe_config_template, ipxe_enabled=ipxe_enabled) 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(node), iwdi, deploy_utils.is_trusted_boot_requested(node), deploy_utils.is_iscsi_boot(task), ramdisk_boot, ipxe_enabled=ipxe_enabled)
def build_service_pxe_config(task, instance_image_info, root_uuid_or_disk_id, ramdisk_boot=False, ipxe_enabled=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, ipxe_enabled=ipxe_enabled) pxe_config_template = deploy_utils.get_pxe_config_template(node) create_pxe_config(task, pxe_options, pxe_config_template, ipxe_enabled=ipxe_enabled) 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(node), iwdi, deploy_utils.is_trusted_boot_requested(node), deploy_utils.is_iscsi_boot(task), ramdisk_boot, ipxe_enabled=ipxe_enabled)
def test_get_boot_mode_default_set(self, mock_for_deploy, mock_log): self.config(default_boot_mode='uefi', group='deploy') boot_mode_utils.warn_about_default_boot_mode = False mock_for_deploy.return_value = None boot_mode = boot_mode_utils.get_boot_mode(self.node) self.assertEqual(boot_modes.UEFI, boot_mode) self.assertEqual(0, mock_log.warning.call_count)
def continue_deploy(self, task): task.process_event('resume') node = task.node image_source = node.instance_info.get('image_source') LOG.debug('Continuing deploy for node %(node)s with image %(img)s', { 'node': node.uuid, 'img': image_source }) image_info = { 'id': image_source.split('/')[-1], 'urls': [node.instance_info['image_url']], 'checksum': node.instance_info['image_checksum'], # NOTE(comstud): Older versions of ironic do not set # 'disk_format' nor 'container_format', so we use .get() # to maintain backwards compatibility in case code was # upgraded in the middle of a build request. 'disk_format': node.instance_info.get('image_disk_format'), 'container_format': node.instance_info.get('image_container_format'), 'stream_raw_images': CONF.agent.stream_raw_images, } if (node.instance_info.get('image_os_hash_algo') and node.instance_info.get('image_os_hash_value')): image_info['os_hash_algo'] = node.instance_info[ 'image_os_hash_algo'] image_info['os_hash_value'] = node.instance_info[ 'image_os_hash_value'] proxies = {} for scheme in ('http', 'https'): proxy_param = 'image_%s_proxy' % scheme proxy = node.driver_info.get(proxy_param) if proxy: proxies[scheme] = proxy if proxies: image_info['proxies'] = proxies no_proxy = node.driver_info.get('image_no_proxy') if no_proxy is not None: image_info['no_proxy'] = no_proxy image_info['node_uuid'] = node.uuid iwdi = node.driver_internal_info.get('is_whole_disk_image') if not iwdi: for label in PARTITION_IMAGE_LABELS: image_info[label] = node.instance_info.get(label) boot_option = deploy_utils.get_boot_option(node) image_info['deploy_boot_mode'] = ( boot_mode_utils.get_boot_mode(node)) image_info['boot_option'] = boot_option disk_label = deploy_utils.get_disk_label(node) if disk_label is not None: image_info['disk_label'] = disk_label # Tell the client to download and write the image with the given args self._client.prepare_image(node, image_info) task.process_event('wait')
def _prepare_boot_iso(task, root_uuid): """Prepare a boot ISO to boot the node. :param task: a TaskManager instance containing the node to act on. :param root_uuid: the uuid of the root partition. :raises: MissingParameterValue, if any of the required parameters are missing. :raises: InvalidParameterValue, if any of the parameters have invalid value. :raises: ImageCreationFailed, if creating boot ISO for BIOS boot_mode failed. """ deploy_info = _parse_deploy_info(task.node) driver_internal_info = task.node.driver_internal_info # fetch boot iso if deploy_info.get('irmc_boot_iso'): boot_iso_href = deploy_info['irmc_boot_iso'] if _is_image_href_ordinary_file_name(boot_iso_href): driver_internal_info['irmc_boot_iso'] = boot_iso_href else: boot_iso_filename = _get_iso_name(task.node, label='boot') boot_iso_fullpathname = os.path.join( CONF.irmc.remote_image_share_root, boot_iso_filename) images.fetch(task.context, boot_iso_href, boot_iso_fullpathname) driver_internal_info['irmc_boot_iso'] = boot_iso_filename # create boot iso else: image_href = deploy_info['image_source'] image_props = ['kernel_id', 'ramdisk_id'] image_properties = images.get_image_properties( task.context, image_href, image_props) kernel_href = (task.node.instance_info.get('kernel') or image_properties['kernel_id']) ramdisk_href = (task.node.instance_info.get('ramdisk') or image_properties['ramdisk_id']) deploy_iso_href = deploy_info['irmc_deploy_iso'] boot_mode = boot_mode_utils.get_boot_mode(task.node) kernel_params = CONF.pxe.pxe_append_params boot_iso_filename = _get_iso_name(task.node, label='boot') boot_iso_fullpathname = os.path.join( CONF.irmc.remote_image_share_root, boot_iso_filename) images.create_boot_iso(task.context, boot_iso_fullpathname, kernel_href, ramdisk_href, deploy_iso_href=deploy_iso_href, root_uuid=root_uuid, kernel_params=kernel_params, boot_mode=boot_mode) driver_internal_info['irmc_boot_iso'] = boot_iso_filename # save driver_internal_info['irmc_boot_iso'] task.node.driver_internal_info = driver_internal_info task.node.save()
def _prepare_boot_iso(task, root_uuid): """Prepare a boot ISO to boot the node. :param task: a TaskManager instance containing the node to act on. :param root_uuid: the uuid of the root partition. :raises: MissingParameterValue, if any of the required parameters are missing. :raises: InvalidParameterValue, if any of the parameters have invalid value. :raises: ImageCreationFailed, if creating boot ISO for BIOS boot_mode failed. """ deploy_info = _parse_deploy_info(task.node) driver_internal_info = task.node.driver_internal_info # fetch boot iso if deploy_info.get('irmc_boot_iso'): boot_iso_href = deploy_info['irmc_boot_iso'] if _is_image_href_ordinary_file_name(boot_iso_href): driver_internal_info['irmc_boot_iso'] = boot_iso_href else: boot_iso_filename = _get_iso_name(task.node, label='boot') boot_iso_fullpathname = os.path.join( CONF.irmc.remote_image_share_root, boot_iso_filename) images.fetch(task.context, boot_iso_href, boot_iso_fullpathname) driver_internal_info['irmc_boot_iso'] = boot_iso_filename # create boot iso else: image_href = deploy_info['image_source'] image_props = ['kernel_id', 'ramdisk_id'] image_properties = images.get_image_properties(task.context, image_href, image_props) kernel_href = (task.node.instance_info.get('kernel') or image_properties['kernel_id']) ramdisk_href = (task.node.instance_info.get('ramdisk') or image_properties['ramdisk_id']) deploy_iso_href = deploy_info['irmc_deploy_iso'] boot_mode = boot_mode_utils.get_boot_mode(task.node) kernel_params = CONF.pxe.pxe_append_params boot_iso_filename = _get_iso_name(task.node, label='boot') boot_iso_fullpathname = os.path.join(CONF.irmc.remote_image_share_root, boot_iso_filename) images.create_boot_iso(task.context, boot_iso_fullpathname, kernel_href, ramdisk_href, deploy_iso_href, root_uuid, kernel_params, boot_mode) driver_internal_info['irmc_boot_iso'] = boot_iso_filename # save driver_internal_info['irmc_boot_iso'] task.node.driver_internal_info = driver_internal_info task.node.save()
def clean_up_pxe_config(task, ipxe_enabled=False): """Clean up the TFTP environment for the task's node. :param task: A TaskManager instance. """ LOG.debug("Cleaning up PXE config for node %s", task.node.uuid) is_uefi_boot_mode = (boot_mode_utils.get_boot_mode(task.node) == 'uefi') if is_uefi_boot_mode and not ipxe_enabled: api = dhcp_factory.DHCPFactory().provider ip_addresses = api.get_ip_addresses(task) if not ip_addresses: return for port_ip_address in ip_addresses: try: # Get xx.xx.xx.xx based grub config file ip_address_path = _get_pxe_ip_address_path(port_ip_address, False) # NOTE(TheJulia): Remove elilo support after the deprecation # period, in the Queens release. # Get 0AOAOAOA based elilo config file hex_ip_path = _get_pxe_ip_address_path(port_ip_address, True) except exception.InvalidIPv4Address: continue except exception.FailedToGetIPAddressOnPort: continue # Cleaning up config files created for grub2. ironic_utils.unlink_without_raise(ip_address_path) # Cleaning up config files created for elilo. ironic_utils.unlink_without_raise(hex_ip_path) for port in task.ports: client_id = port.extra.get('client-id') # syslinux, ipxe, etc. ironic_utils.unlink_without_raise( _get_pxe_mac_path(port.address, client_id=client_id, ipxe_enabled=ipxe_enabled)) # Grub2 MAC address based confiuration ironic_utils.unlink_without_raise( _get_pxe_grub_mac_path(port.address)) if ipxe_enabled: utils.rmtree_without_raise(os.path.join(get_ipxe_root_dir(), task.node.uuid)) else: utils.rmtree_without_raise(os.path.join(get_root_dir(), task.node.uuid))
def clean_up_pxe_config(task, ipxe_enabled=False): """Clean up the TFTP environment for the task's node. :param task: A TaskManager instance. """ LOG.debug("Cleaning up PXE config for node %s", task.node.uuid) is_uefi_boot_mode = (boot_mode_utils.get_boot_mode(task.node) == 'uefi') if is_uefi_boot_mode and not ipxe_enabled: api = dhcp_factory.DHCPFactory().provider ip_addresses = api.get_ip_addresses(task) if not ip_addresses: return for port_ip_address in ip_addresses: try: # Get xx.xx.xx.xx based grub config file ip_address_path = _get_pxe_ip_address_path(port_ip_address, False) # NOTE(TheJulia): Remove elilo support after the deprecation # period, in the Queens release. # Get 0AOAOAOA based elilo config file hex_ip_path = _get_pxe_ip_address_path(port_ip_address, True) except exception.InvalidIPv4Address: continue except exception.FailedToGetIPAddressOnPort: continue # Cleaning up config files created for grub2. ironic_utils.unlink_without_raise(ip_address_path) # Cleaning up config files created for elilo. ironic_utils.unlink_without_raise(hex_ip_path) for port in task.ports: client_id = port.extra.get('client-id') # syslinux, ipxe, etc. ironic_utils.unlink_without_raise( _get_pxe_mac_path(port.address, client_id=client_id, ipxe_enabled=ipxe_enabled)) # Grub2 MAC address based confiuration ironic_utils.unlink_without_raise( _get_pxe_grub_mac_path(port.address)) if ipxe_enabled: utils.rmtree_without_raise(os.path.join(get_ipxe_root_dir(), task.node.uuid)) else: utils.rmtree_without_raise(os.path.join(get_root_dir(), task.node.uuid))
def prepare_instance_pxe_config(task, image_info, iscsi_boot=False, ramdisk_boot=False, ipxe_enabled=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. :param ipxe_enabled: Default false boolean to indicate if ipxe is in use by the caller. :returns: None """ node = task.node # Generate options for both IPv4 and IPv6, and they can be # filtered down later based upon the port options. # TODO(TheJulia): This should be re-tooled during the Victoria # development cycle so that we call a single method and return # combined options. The method we currently call is relied upon # by two eternal projects, to changing the behavior is not ideal. dhcp_opts = dhcp_options_for_instance(task, ipxe_enabled, ip_version=4) dhcp_opts += dhcp_options_for_instance(task, ipxe_enabled, ip_version=6) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) pxe_config_path = get_pxe_config_file_path(node.uuid, ipxe_enabled=ipxe_enabled) if not os.path.isfile(pxe_config_path): pxe_options = build_pxe_config_options(task, image_info, service=ramdisk_boot, ipxe_enabled=ipxe_enabled) if ipxe_enabled: pxe_config_template = (deploy_utils.get_ipxe_config_template(node)) else: pxe_config_template = (deploy_utils.get_pxe_config_template(node)) create_pxe_config(task, pxe_options, pxe_config_template, ipxe_enabled=ipxe_enabled) deploy_utils.switch_pxe_config(pxe_config_path, None, boot_mode_utils.get_boot_mode(node), False, iscsi_boot=iscsi_boot, ramdisk_boot=ramdisk_boot, ipxe_enabled=ipxe_enabled)
def get_pxe_boot_file(node): """Return the PXE boot file name requested for deploy. This method returns PXE boot file name to be used for deploy. Architecture specific boot file is searched first. BIOS/UEFI boot file is used if no valid architecture specific file found. :param node: A single Node. :returns: The PXE boot file name. """ cpu_arch = node.properties.get('cpu_arch') boot_file = CONF.pxe.pxe_bootfile_name_by_arch.get(cpu_arch) if boot_file is None: if boot_mode_utils.get_boot_mode(node) == 'uefi': boot_file = CONF.pxe.uefi_pxe_bootfile_name else: boot_file = CONF.pxe.pxe_bootfile_name return boot_file
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(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 validate_boot_parameters_for_trusted_boot(node): """Check if boot parameters are valid for trusted boot.""" boot_mode = boot_mode_utils.get_boot_mode(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, ipxe_enabled=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. :param ipxe_enabled: Default false boolean to indicate if ipxe is in use by the caller. :returns: None """ node = task.node dhcp_opts = dhcp_options_for_instance(task, ipxe_enabled) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) pxe_config_path = get_pxe_config_file_path(node.uuid, ipxe_enabled=ipxe_enabled) if not os.path.isfile(pxe_config_path): pxe_options = build_pxe_config_options(task, image_info, service=ramdisk_boot, ipxe_enabled=ipxe_enabled) pxe_config_template = (deploy_utils.get_pxe_config_template(node)) create_pxe_config(task, pxe_options, pxe_config_template, ipxe_enabled=ipxe_enabled) deploy_utils.switch_pxe_config(pxe_config_path, None, boot_mode_utils.get_boot_mode(node), False, iscsi_boot=iscsi_boot, ramdisk_boot=ramdisk_boot, ipxe_enabled=ipxe_enabled)
def get_pxe_config_template(node): """Return the PXE config template file name requested for deploy. This method returns PXE config template file to be used for deploy. First specific pxe template is searched in the node. After that architecture specific template file is searched. BIOS/UEFI template file is used if no valid architecture specific file found. :param node: A single Node. :returns: The PXE config template file name. """ config_template = node.driver_info.get("pxe_template", None) if config_template is None: cpu_arch = node.properties.get('cpu_arch') config_template = CONF.pxe.pxe_config_template_by_arch.get(cpu_arch) if config_template is None: if boot_mode_utils.get_boot_mode(node) == 'uefi': config_template = CONF.pxe.uefi_pxe_config_template else: config_template = CONF.pxe.pxe_config_template return config_template
def prepare_instance(self, task): """Prepares the boot of instance. This method prepares the boot of the instance after reading relevant information from the node's instance_info. In case of netboot, it updates the dhcp entries and switches the PXE config. In case of localboot, it cleans up the PXE config. In case of 'boot from volume', it updates the iSCSI info onto iLO and sets the node to boot from 'UefiTarget' boot device. :param task: a task from TaskManager. :returns: None :raises: IloOperationError, if some operation on iLO failed. """ # Set boot mode ilo_common.update_boot_mode(task) # Need to enable secure boot, if being requested ilo_common.update_secure_boot_mode(task, True) boot_mode = boot_mode_utils.get_boot_mode(task.node) if deploy_utils.is_iscsi_boot(task) and boot_mode == 'uefi': # Need to set 'ilo_uefi_iscsi_boot' param for clean up driver_internal_info = task.node.driver_internal_info driver_internal_info['ilo_uefi_iscsi_boot'] = True task.node.driver_internal_info = driver_internal_info task.node.save() # It will set iSCSI info onto iLO task.driver.management.set_iscsi_boot_target(task) manager_utils.node_set_boot_device(task, boot_devices.ISCSIBOOT, persistent=True) else: # Volume boot in BIOS boot mode is handled using # PXE boot interface super(IloiPXEBoot, self).prepare_instance(task)
def prepare_instance_pxe_config(task, image_info, iscsi_boot=False, ramdisk_boot=False, ipxe_enabled=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. :param ipxe_enabled: Default false boolean to indicate if ipxe is in use by the caller. :returns: None """ node = task.node dhcp_opts = dhcp_options_for_instance(task, ipxe_enabled) 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, ipxe_enabled=ipxe_enabled) pxe_config_template = ( deploy_utils.get_pxe_config_template(node)) create_pxe_config( task, pxe_options, pxe_config_template, ipxe_enabled=ipxe_enabled) deploy_utils.switch_pxe_config( pxe_config_path, None, boot_mode_utils.get_boot_mode(node), False, iscsi_boot=iscsi_boot, ramdisk_boot=ramdisk_boot, ipxe_enabled=ipxe_enabled)
def prepare_instance(self, task): """Prepares the boot of instance. This method prepares the boot of the instance after reading relevant information from the node's instance_info. In case of netboot, it updates the dhcp entries and switches the PXE config. In case of localboot, it cleans up the PXE config. In case of 'boot from volume', it updates the iSCSI info onto iLO and sets the node to boot from 'UefiTarget' boot device. :param task: a task from TaskManager. :returns: None :raises: IloOperationError, if some operation on iLO failed. """ # Set boot mode ilo_common.update_boot_mode(task) # Need to enable secure boot, if being requested ilo_common.update_secure_boot_mode(task, True) boot_mode = boot_mode_utils.get_boot_mode(task.node) if deploy_utils.is_iscsi_boot(task) and boot_mode == 'uefi': # Need to set 'ilo_uefi_iscsi_boot' param for clean up driver_internal_info = task.node.driver_internal_info driver_internal_info['ilo_uefi_iscsi_boot'] = True task.node.driver_internal_info = driver_internal_info task.node.save() # It will set iSCSI info onto iLO task.driver.management.set_iscsi_boot_target(task) manager_utils.node_set_boot_device(task, boot_devices.ISCSIBOOT, persistent=True) else: # Volume boot in BIOS boot mode is handled using # PXE boot interface super(IloPXEBoot, self).prepare_instance(task)
def _get_boot_iso(task, root_uuid): """This method returns a boot ISO to boot the node. It chooses one of the three options in the order as below: 1. Does nothing if 'ilo_boot_iso' is present in node's instance_info and 'boot_iso_created_in_web_server' is not set in 'driver_internal_info'. 2. Image deployed has a meta-property 'boot_iso' in Glance. This should refer to the UUID of the boot_iso which exists in Glance. 3. Generates a boot ISO on the fly using kernel and ramdisk mentioned in the image deployed. It uploads the generated boot ISO to Swift. :param task: a TaskManager instance containing the node to act on. :param root_uuid: the uuid of the root partition. :returns: boot ISO URL. Should be either of below: * A Swift object - It should be of format 'swift:<object-name>'. It is assumed that the image object is present in CONF.ilo.swift_ilo_container; * A Glance image - It should be format 'glance://<glance-image-uuid>' or just <glance-image-uuid>; * An HTTP URL. On error finding the boot iso, it returns None. :raises: MissingParameterValue, if any of the required parameters are missing in the node's driver_info or instance_info. :raises: InvalidParameterValue, if any of the parameters have invalid value in the node's driver_info or instance_info. :raises: SwiftOperationError, if operation with Swift fails. :raises: ImageCreationFailed, if creation of boot ISO failed. :raises: exception.ImageRefValidationFailed if ilo_boot_iso is not HTTP(S) URL. """ LOG.debug("Trying to get a boot ISO to boot the baremetal node") # Option 1 - Check if user has provided ilo_boot_iso in node's # instance_info driver_internal_info = task.node.driver_internal_info boot_iso_created_in_web_server = ( driver_internal_info.get('boot_iso_created_in_web_server')) if (task.node.instance_info.get('ilo_boot_iso') and not boot_iso_created_in_web_server): LOG.debug("Using ilo_boot_iso provided in node's instance_info") boot_iso = task.node.instance_info['ilo_boot_iso'] if not service_utils.is_glance_image(boot_iso): try: image_service.HttpImageService().validate_href(boot_iso) except exception.ImageRefValidationFailed: with excutils.save_and_reraise_exception(): LOG.error("Virtual media deploy accepts only Glance " "images or HTTP(S) URLs as " "instance_info['ilo_boot_iso']. Either %s " "is not a valid HTTP(S) URL or is " "not reachable.", boot_iso) return task.node.instance_info['ilo_boot_iso'] # Option 2 - Check if user has provided a boot_iso in Glance. If boot_iso # is a supported non-glance href execution will proceed to option 3. deploy_info = _parse_deploy_info(task.node) image_href = deploy_info['image_source'] image_properties = ( images.get_image_properties( task.context, image_href, ['boot_iso', 'kernel_id', 'ramdisk_id'])) boot_iso_uuid = image_properties.get('boot_iso') kernel_href = (task.node.instance_info.get('kernel') or image_properties.get('kernel_id')) ramdisk_href = (task.node.instance_info.get('ramdisk') or image_properties.get('ramdisk_id')) if boot_iso_uuid: LOG.debug("Found boot_iso %s in Glance", boot_iso_uuid) return boot_iso_uuid if not kernel_href or not ramdisk_href: LOG.error("Unable to find kernel or ramdisk for " "image %(image)s to generate boot ISO for %(node)s", {'image': image_href, 'node': task.node.uuid}) return # NOTE(rameshg87): Functionality to share the boot ISOs created for # similar instances (instances with same deployed image) is # not implemented as of now. Creation/Deletion of such a shared boot ISO # will require synchronisation across conductor nodes for the shared boot # ISO. Such a synchronisation mechanism doesn't exist in ironic as of now. # Option 3 - Create boot_iso from kernel/ramdisk, upload to Swift # or web server and provide its name. deploy_iso_uuid = deploy_info['ilo_deploy_iso'] boot_mode = boot_mode_utils.get_boot_mode(task.node) boot_iso_object_name = _get_boot_iso_object_name(task.node) kernel_params = "" if deploy_utils.get_boot_option(task.node) == "ramdisk": i_info = task.node.instance_info kernel_params = "root=/dev/ram0 text " kernel_params += i_info.get("ramdisk_kernel_arguments", "") else: kernel_params = CONF.pxe.pxe_append_params with tempfile.NamedTemporaryFile(dir=CONF.tempdir) as fileobj: boot_iso_tmp_file = fileobj.name images.create_boot_iso(task.context, boot_iso_tmp_file, kernel_href, ramdisk_href, deploy_iso_href=deploy_iso_uuid, root_uuid=root_uuid, kernel_params=kernel_params, boot_mode=boot_mode) if CONF.ilo.use_web_server_for_images: boot_iso_url = ( ilo_common.copy_image_to_web_server(boot_iso_tmp_file, boot_iso_object_name)) driver_internal_info = task.node.driver_internal_info driver_internal_info['boot_iso_created_in_web_server'] = True task.node.driver_internal_info = driver_internal_info task.node.save() LOG.debug("Created boot_iso %(boot_iso)s for node %(node)s", {'boot_iso': boot_iso_url, 'node': task.node.uuid}) return boot_iso_url else: container = CONF.ilo.swift_ilo_container swift_api = swift.SwiftAPI() swift_api.create_object(container, boot_iso_object_name, boot_iso_tmp_file) LOG.debug("Created boot_iso %s in Swift", boot_iso_object_name) return 'swift:%s' % boot_iso_object_name
def prepare_instance(self, task): """Prepares the boot of instance. This method prepares the boot of the instance after reading relevant information from the node's instance_info. It does the following depending on boot_option for deploy: - If the boot mode is 'uefi' and its booting from volume, then it sets the iSCSI target info and node to boot from 'UefiTarget' boot device. - If not 'boot from volume' and the boot_option requested for this deploy is 'local' or image is a whole disk image, then it sets the node to boot from disk. - Otherwise it finds/creates the boot ISO to boot the instance image, attaches the boot ISO to the bare metal and then sets the node to boot from CDROM. :param task: a task from TaskManager. :returns: None :raises: IloOperationError, if some operation on iLO failed. :raises: InstanceDeployFailure, if its try to boot iSCSI volume in 'BIOS' boot mode. """ ilo_common.cleanup_vmedia_boot(task) boot_mode = boot_mode_utils.get_boot_mode(task.node) boot_option = deploy_utils.get_boot_option(task.node) if deploy_utils.is_iscsi_boot(task): # It will set iSCSI info onto iLO if boot_mode == 'uefi': # Need to set 'ilo_uefi_iscsi_boot' param for clean up driver_internal_info = task.node.driver_internal_info driver_internal_info['ilo_uefi_iscsi_boot'] = True task.node.driver_internal_info = driver_internal_info task.node.save() task.driver.management.set_iscsi_boot_target(task) manager_utils.node_set_boot_device( task, boot_devices.ISCSIBOOT, persistent=True) else: msg = 'Virtual media can not boot volume in BIOS boot mode.' raise exception.InstanceDeployFailure(msg) elif boot_option == "ramdisk": boot_iso = _get_boot_iso(task, None) ilo_common.setup_vmedia_for_boot(task, boot_iso) manager_utils.node_set_boot_device(task, boot_devices.CDROM, persistent=True) else: # Boot from disk every time if the image deployed is # a whole disk image. node = task.node iwdi = node.driver_internal_info.get('is_whole_disk_image') if deploy_utils.get_boot_option(node) == "local" or iwdi: manager_utils.node_set_boot_device(task, boot_devices.DISK, persistent=True) else: drv_int_info = node.driver_internal_info root_uuid_or_disk_id = drv_int_info.get('root_uuid_or_disk_id') if root_uuid_or_disk_id: self._configure_vmedia_boot(task, root_uuid_or_disk_id) else: LOG.warning("The UUID for the root partition could not " "be found for node %s", node.uuid) # Set boot mode ilo_common.update_boot_mode(task) # Need to enable secure boot, if being requested ilo_common.update_secure_boot_mode(task, True)
def test_get_boot_mode_uefi(self, mock_for_deploy): mock_for_deploy.return_value = boot_modes.UEFI boot_mode = boot_mode_utils.get_boot_mode(self.node) self.assertEqual(boot_modes.UEFI, boot_mode)
def test_get_boot_mode_uefi(self, mock_for_deploy): mock_for_deploy.return_value = boot_modes.UEFI boot_mode = boot_mode_utils.get_boot_mode(self.node) self.assertEqual(boot_modes.UEFI, boot_mode)
def prepare_instance_boot(self, task): if not task.driver.storage.should_write_image(task): task.driver.boot.prepare_instance(task) # Move straight to the final steps return node = task.node iwdi = task.node.driver_internal_info.get('is_whole_disk_image') cpu_arch = task.node.properties.get('cpu_arch') # If `boot_option` is set to `netboot`, PXEBoot.prepare_instance() # would need root_uuid of the whole disk image to add it into the # pxe config to perform chain boot. # IPA would have returned us the 'root_uuid_or_disk_id' if image # being provisioned is a whole disk image. IPA would also provide us # 'efi_system_partition_uuid' if the image being provisioned is a # partition image. # In case of local boot using partition image, we need both # 'root_uuid_or_disk_id' and 'efi_system_partition_uuid' to configure # bootloader for local boot. # NOTE(mjturek): In the case of local boot using a partition image on # ppc64* hardware we need to provide the 'PReP_Boot_partition_uuid' to # direct where the bootloader should be installed. driver_internal_info = task.node.driver_internal_info try: partition_uuids = self._client.get_partition_uuids(node).get( 'command_result') or {} root_uuid = partition_uuids.get('root uuid') except exception.AgentAPIError: # TODO(dtantsur): remove in W LOG.warning('Old ironic-python-agent detected, please update ' 'to Victoria or newer') partition_uuids = None root_uuid = self._get_uuid_from_result(task, 'root_uuid') if root_uuid: driver_internal_info['root_uuid_or_disk_id'] = root_uuid task.node.driver_internal_info = driver_internal_info task.node.save() elif not iwdi: LOG.error( 'No root UUID returned from the ramdisk for node ' '%(node)s, the deploy will likely fail. Partition ' 'UUIDs are %(uuids)s', { 'node': node.uuid, 'uuid': partition_uuids }) efi_sys_uuid = None if not iwdi: if boot_mode_utils.get_boot_mode(node) == 'uefi': # TODO(dtantsur): remove in W if partition_uuids is None: efi_sys_uuid = (self._get_uuid_from_result( task, 'efi_system_partition_uuid')) else: efi_sys_uuid = partition_uuids.get( 'efi system partition uuid') prep_boot_part_uuid = None if cpu_arch is not None and cpu_arch.startswith('ppc64'): # TODO(dtantsur): remove in W if partition_uuids is None: prep_boot_part_uuid = (self._get_uuid_from_result( task, 'PReP_Boot_partition_uuid')) else: prep_boot_part_uuid = partition_uuids.get( 'PReP Boot partition uuid') LOG.info('Image successfully written to node %s', node.uuid) if CONF.agent.manage_agent_boot: # It is necessary to invoke prepare_instance() of the node's # boot interface, so that the any necessary configurations like # setting of the boot mode (e.g. UEFI secure boot) which cannot # be done on node during deploy stage can be performed. LOG.debug( 'Executing driver specific tasks before booting up the ' 'instance for node %s', node.uuid) self.prepare_instance_to_boot(task, root_uuid, efi_sys_uuid, prep_boot_part_uuid) else: manager_utils.node_set_boot_device(task, 'disk', persistent=True) # Remove symbolic link when deploy is done. if CONF.agent.image_download_source == 'http': deploy_utils.remove_http_instance_symlink(task.node.uuid)
def test_get_boot_mode_bios(self, mock_for_deploy): mock_for_deploy.return_value = boot_modes.LEGACY_BIOS boot_mode = boot_mode_utils.get_boot_mode(self.node) self.assertEqual(boot_modes.LEGACY_BIOS, boot_mode)
def create_pxe_config(task, pxe_options, template=None, ipxe_enabled=False): """Generate PXE configuration file and MAC address links for it. This method will generate the PXE configuration file for the task's node under a directory named with the UUID of that node. For each MAC address or DHCP IP address (port) of that node, a symlink for the configuration file will be created under the PXE configuration directory, so regardless of which port boots first they'll get the same PXE configuration. If grub2 bootloader is in use, then its configuration will be created based on DHCP IP address in the form nn.nn.nn.nn. :param task: A TaskManager instance. :param pxe_options: A dictionary with the PXE configuration parameters. :param template: The PXE configuration template. If no template is given the node specific template will be used. """ LOG.debug("Building PXE config for node %s", task.node.uuid) if template is None: template = deploy_utils.get_pxe_config_template(task.node) _ensure_config_dirs_exist(task, ipxe_enabled) pxe_config_file_path = get_pxe_config_file_path(task.node.uuid, ipxe_enabled=ipxe_enabled) is_uefi_boot_mode = (boot_mode_utils.get_boot_mode(task.node) == 'uefi') uefi_with_grub = is_uefi_boot_mode and not ipxe_enabled # grub bootloader panics with '{}' around any of its tags in its # config file. To overcome that 'ROOT' and 'DISK_IDENTIFIER' are enclosed # with '(' and ')' in uefi boot mode. if uefi_with_grub: pxe_config_root_tag = '(( ROOT ))' pxe_config_disk_ident = '(( DISK_IDENTIFIER ))' else: # TODO(stendulker): We should use '(' ')' as the delimiters for all our # config files so that we do not need special handling for each of the # bootloaders. Should be removed once the Mitaka release starts. pxe_config_root_tag = '{{ ROOT }}' pxe_config_disk_ident = '{{ DISK_IDENTIFIER }}' params = { 'pxe_options': pxe_options, 'ROOT': pxe_config_root_tag, 'DISK_IDENTIFIER': pxe_config_disk_ident } pxe_config = utils.render_template(template, params) utils.write_to_file(pxe_config_file_path, pxe_config) # Always write the mac addresses _link_mac_pxe_configs(task, ipxe_enabled=ipxe_enabled) if uefi_with_grub: try: _link_ip_address_pxe_configs(task, ipxe_enabled) # NOTE(TheJulia): The IP address support will fail if the # dhcp_provider interface is set to none. This will result # in the MAC addresses and DHCP files being written, and # we can remove IP address creation for the grub use. except exception.FailedToGetIPAddressOnPort as e: if CONF.dhcp.dhcp_provider != 'none': with excutils.save_and_reraise_exception(): LOG.error( 'Unable to create boot config, IP address ' 'was unable to be retrieved. %(error)s', {'error': e})
def _get_boot_iso(task, root_uuid): """This method returns a boot ISO to boot the node. It chooses one of the three options in the order as below: 1. Does nothing if 'ilo_boot_iso' is present in node's instance_info and 'boot_iso_created_in_web_server' is not set in 'driver_internal_info'. 2. Image deployed has a meta-property 'boot_iso' in Glance. This should refer to the UUID of the boot_iso which exists in Glance. 3. Generates a boot ISO on the fly using kernel and ramdisk mentioned in the image deployed. It uploads the generated boot ISO to Swift. :param task: a TaskManager instance containing the node to act on. :param root_uuid: the uuid of the root partition. :returns: boot ISO URL. Should be either of below: * A Swift object - It should be of format 'swift:<object-name>'. It is assumed that the image object is present in CONF.ilo.swift_ilo_container; * A Glance image - It should be format 'glance://<glance-image-uuid>' or just <glance-image-uuid>; * An HTTP URL. On error finding the boot iso, it returns None. :raises: MissingParameterValue, if any of the required parameters are missing in the node's driver_info or instance_info. :raises: InvalidParameterValue, if any of the parameters have invalid value in the node's driver_info or instance_info. :raises: SwiftOperationError, if operation with Swift fails. :raises: ImageCreationFailed, if creation of boot ISO failed. :raises: exception.ImageRefValidationFailed if ilo_boot_iso is not HTTP(S) URL. """ LOG.debug("Trying to get a boot ISO to boot the baremetal node") # Option 1 - Check if user has provided ilo_boot_iso in node's # instance_info driver_internal_info = task.node.driver_internal_info boot_iso_created_in_web_server = ( driver_internal_info.get('boot_iso_created_in_web_server')) if (task.node.instance_info.get('ilo_boot_iso') and not boot_iso_created_in_web_server): LOG.debug("Using ilo_boot_iso provided in node's instance_info") boot_iso = task.node.instance_info['ilo_boot_iso'] if not service_utils.is_glance_image(boot_iso): try: image_service.HttpImageService().validate_href(boot_iso) except exception.ImageRefValidationFailed: with excutils.save_and_reraise_exception(): LOG.error( "Virtual media deploy accepts only Glance " "images or HTTP(S) URLs as " "instance_info['ilo_boot_iso']. Either %s " "is not a valid HTTP(S) URL or is " "not reachable.", boot_iso) return task.node.instance_info['ilo_boot_iso'] # Option 2 - Check if user has provided a boot_iso in Glance. If boot_iso # is a supported non-glance href execution will proceed to option 3. deploy_info = _parse_deploy_info(task.node) image_href = deploy_info['image_source'] image_properties = (images.get_image_properties( task.context, image_href, ['boot_iso', 'kernel_id', 'ramdisk_id'])) boot_iso_uuid = image_properties.get('boot_iso') kernel_href = (task.node.instance_info.get('kernel') or image_properties.get('kernel_id')) ramdisk_href = (task.node.instance_info.get('ramdisk') or image_properties.get('ramdisk_id')) if boot_iso_uuid: LOG.debug("Found boot_iso %s in Glance", boot_iso_uuid) return boot_iso_uuid if not kernel_href or not ramdisk_href: LOG.error( "Unable to find kernel or ramdisk for " "image %(image)s to generate boot ISO for %(node)s", { 'image': image_href, 'node': task.node.uuid }) return # NOTE(rameshg87): Functionality to share the boot ISOs created for # similar instances (instances with same deployed image) is # not implemented as of now. Creation/Deletion of such a shared boot ISO # will require synchronisation across conductor nodes for the shared boot # ISO. Such a synchronisation mechanism doesn't exist in ironic as of now. # Option 3 - Create boot_iso from kernel/ramdisk, upload to Swift # or web server and provide its name. deploy_iso_uuid = deploy_info['ilo_deploy_iso'] boot_mode = boot_mode_utils.get_boot_mode(task.node) boot_iso_object_name = _get_boot_iso_object_name(task.node) kernel_params = "" if deploy_utils.get_boot_option(task.node) == "ramdisk": i_info = task.node.instance_info kernel_params = "root=/dev/ram0 text " kernel_params += i_info.get("ramdisk_kernel_arguments", "") else: kernel_params = CONF.pxe.pxe_append_params with tempfile.NamedTemporaryFile(dir=CONF.tempdir) as fileobj: boot_iso_tmp_file = fileobj.name images.create_boot_iso(task.context, boot_iso_tmp_file, kernel_href, ramdisk_href, deploy_iso_href=deploy_iso_uuid, root_uuid=root_uuid, kernel_params=kernel_params, boot_mode=boot_mode) if CONF.ilo.use_web_server_for_images: boot_iso_url = (ilo_common.copy_image_to_web_server( boot_iso_tmp_file, boot_iso_object_name)) driver_internal_info = task.node.driver_internal_info driver_internal_info['boot_iso_created_in_web_server'] = True task.node.driver_internal_info = driver_internal_info task.node.save() LOG.debug("Created boot_iso %(boot_iso)s for node %(node)s", { 'boot_iso': boot_iso_url, 'node': task.node.uuid }) return boot_iso_url else: container = CONF.ilo.swift_ilo_container swift_api = swift.SwiftAPI() swift_api.create_object(container, boot_iso_object_name, boot_iso_tmp_file) LOG.debug("Created boot_iso %s in Swift", boot_iso_object_name) return 'swift:%s' % boot_iso_object_name
def get_deploy_info(node, address, iqn, port=None, lun='1', conv_flags=None): """Returns the information required for doing iSCSI deploy in a dictionary. :param node: ironic node object :param address: iSCSI address :param iqn: iSCSI iqn for the target disk :param port: iSCSI port, defaults to one specified in the configuration :param lun: iSCSI lun, defaults to '1' :param conv_flags: flag that will modify the behaviour of the image copy to disk. :raises: MissingParameterValue, if some required parameters were not passed. :raises: InvalidParameterValue, if any of the parameters have invalid value. """ i_info = deploy_utils.parse_instance_info(node) params = { 'address': address, 'port': port or CONF.iscsi.portal_port, 'iqn': iqn, 'lun': lun, 'image_path': deploy_utils._get_image_file_path(node.uuid), 'node_uuid': node.uuid } is_whole_disk_image = node.driver_internal_info['is_whole_disk_image'] if not is_whole_disk_image: params.update({ 'root_mb': i_info['root_mb'], 'swap_mb': i_info['swap_mb'], 'ephemeral_mb': i_info['ephemeral_mb'], 'preserve_ephemeral': i_info['preserve_ephemeral'], 'boot_option': deploy_utils.get_boot_option(node), 'boot_mode': boot_mode_utils.get_boot_mode(node) }) cpu_arch = node.properties.get('cpu_arch') if cpu_arch is not None: params['cpu_arch'] = cpu_arch # Append disk label if specified disk_label = deploy_utils.get_disk_label(node) if disk_label is not None: params['disk_label'] = disk_label missing = [key for key in params if params[key] is None] if missing: raise exception.MissingParameterValue( _("Parameters %s were not passed to ironic" " for deploy.") % missing) # configdrive is nullable params['configdrive'] = i_info.get('configdrive') if is_whole_disk_image: return params if conv_flags: params['conv_flags'] = conv_flags # ephemeral_format is nullable params['ephemeral_format'] = i_info.get('ephemeral_format') return params
def get_deploy_info(node, address, iqn, port=None, lun='1', conv_flags=None): """Returns the information required for doing iSCSI deploy in a dictionary. :param node: ironic node object :param address: iSCSI address :param iqn: iSCSI iqn for the target disk :param port: iSCSI port, defaults to one specified in the configuration :param lun: iSCSI lun, defaults to '1' :param conv_flags: flag that will modify the behaviour of the image copy to disk. :raises: MissingParameterValue, if some required parameters were not passed. :raises: InvalidParameterValue, if any of the parameters have invalid value. """ i_info = deploy_utils.parse_instance_info(node) params = { 'address': address, 'port': port or CONF.iscsi.portal_port, 'iqn': iqn, 'lun': lun, 'image_path': deploy_utils._get_image_file_path(node.uuid), 'node_uuid': node.uuid} is_whole_disk_image = node.driver_internal_info['is_whole_disk_image'] if not is_whole_disk_image: params.update({'root_mb': i_info['root_mb'], 'swap_mb': i_info['swap_mb'], 'ephemeral_mb': i_info['ephemeral_mb'], 'preserve_ephemeral': i_info['preserve_ephemeral'], 'boot_option': deploy_utils.get_boot_option(node), 'boot_mode': boot_mode_utils.get_boot_mode(node)}) cpu_arch = node.properties.get('cpu_arch') if cpu_arch is not None: params['cpu_arch'] = cpu_arch # Append disk label if specified disk_label = deploy_utils.get_disk_label(node) if disk_label is not None: params['disk_label'] = disk_label missing = [key for key in params if params[key] is None] if missing: raise exception.MissingParameterValue( _("Parameters %s were not passed to ironic" " for deploy.") % missing) # configdrive is nullable params['configdrive'] = i_info.get('configdrive') if is_whole_disk_image: return params if conv_flags: params['conv_flags'] = conv_flags # ephemeral_format is nullable params['ephemeral_format'] = i_info.get('ephemeral_format') return params
def _prepare_iso_image(task, kernel_href, ramdisk_href, bootloader_href=None, root_uuid=None, params=None, base_iso=None, inject_files=None): """Prepare an ISO to boot the node. Build bootable ISO out of `kernel_href` and `ramdisk_href` (and `bootloader` if it's UEFI boot), then push built image up to Swift and return a temporary URL. :param task: a TaskManager instance containing the node to act on. :param kernel_href: URL or Glance UUID of the kernel to use :param ramdisk_href: URL or Glance UUID of the ramdisk to use :param bootloader_href: URL or Glance UUID of the EFI bootloader image to use when creating UEFI bootbable ISO :param root_uuid: optional uuid of the root partition. :param params: a dictionary containing 'parameter name'->'value' mapping to be passed to kernel command line. :param inject_files: Mapping of local source file paths to their location on the final ISO image. :returns: bootable ISO HTTP URL. :raises: MissingParameterValue, if any of the required parameters are missing. :raises: InvalidParameterValue, if any of the parameters have invalid value. :raises: ImageCreationFailed, if creating ISO image failed. """ if (not kernel_href or not ramdisk_href) and not base_iso: raise exception.InvalidParameterValue( _("Unable to find kernel, ramdisk for " "building ISO, or explicit ISO for %(node)s") % {'node': task.node.uuid}) img_handler = ImageHandler(task.node.driver) k_param = img_handler.kernel_params i_info = task.node.instance_info # NOTE(TheJulia): Until we support modifying a base iso, most of # this logic actually does nothing in the end. But it should! if deploy_utils.get_boot_option(task.node) == "ramdisk": if not base_iso: kernel_params = "root=/dev/ram0 text " kernel_params += i_info.get("ramdisk_kernel_arguments", "") else: kernel_params = None else: kernel_params = i_info.get('kernel_append_params', k_param) if params and not base_iso: kernel_params = ' '.join( (kernel_params, ' '.join('%s=%s' % kv for kv in params.items()))) boot_mode = boot_mode_utils.get_boot_mode(task.node) LOG.debug( "Trying to create %(boot_mode)s ISO image for node %(node)s " "with kernel %(kernel_href)s, ramdisk %(ramdisk_href)s, " "bootloader %(bootloader_href)s and kernel params %(params)s" "", { 'node': task.node.uuid, 'boot_mode': boot_mode, 'kernel_href': kernel_href, 'ramdisk_href': ramdisk_href, 'bootloader_href': bootloader_href, 'params': kernel_params }) with tempfile.NamedTemporaryFile(dir=CONF.tempdir, suffix='.iso') as boot_fileobj: boot_iso_tmp_file = boot_fileobj.name images.create_boot_iso(task.context, boot_iso_tmp_file, kernel_href, ramdisk_href, esp_image_href=bootloader_href, root_uuid=root_uuid, kernel_params=kernel_params, boot_mode=boot_mode, base_iso=base_iso, inject_files=inject_files) iso_object_name = _get_name(task.node, prefix='boot', suffix='.iso') image_url = img_handler.publish_image(boot_iso_tmp_file, iso_object_name) LOG.debug( "Created ISO %(name)s in object store for node %(node)s, " "exposed as temporary URL " "%(url)s", { 'node': task.node.uuid, 'name': iso_object_name, 'url': image_url }) return image_url
def reboot_to_instance(self, task): task.process_event('resume') node = task.node iwdi = task.node.driver_internal_info.get('is_whole_disk_image') cpu_arch = task.node.properties.get('cpu_arch') error = self.check_deploy_success(node) if error is not None: # TODO(jimrollenhagen) power off if using neutron dhcp to # align with pxe driver? msg = (_('node %(node)s command status errored: %(error)s') % { 'node': node.uuid, 'error': error }) LOG.error(msg) deploy_utils.set_failed_state(task, msg) return # If `boot_option` is set to `netboot`, PXEBoot.prepare_instance() # would need root_uuid of the whole disk image to add it into the # pxe config to perform chain boot. # IPA would have returned us the 'root_uuid_or_disk_id' if image # being provisioned is a whole disk image. IPA would also provide us # 'efi_system_partition_uuid' if the image being provisioned is a # partition image. # In case of local boot using partition image, we need both # 'root_uuid_or_disk_id' and 'efi_system_partition_uuid' to configure # bootloader for local boot. # NOTE(mjturek): In the case of local boot using a partition image on # ppc64* hardware we need to provide the 'PReP_Boot_partition_uuid' to # direct where the bootloader should be installed. driver_internal_info = task.node.driver_internal_info root_uuid = self._get_uuid_from_result(task, 'root_uuid') if root_uuid: driver_internal_info['root_uuid_or_disk_id'] = root_uuid task.node.driver_internal_info = driver_internal_info task.node.save() elif iwdi and CONF.agent.manage_agent_boot: # IPA version less than 3.1.0 will not return root_uuid for # whole disk image. Also IPA version introduced a requirement # for hexdump utility that may not be always available. Need to # fall back to older behavior for the same. LOG.warning( "With the deploy ramdisk based on Ironic Python Agent " "version 3.1.0 and beyond, the drivers using " "`direct` deploy interface performs `netboot` or " "`local` boot for whole disk image based on value " "of boot option setting. When you upgrade Ironic " "Python Agent in your deploy ramdisk, ensure that " "boot option is set appropriately for the node %s. " "The boot option can be set using configuration " "`[deploy]/default_boot_option` or as a `boot_option` " "capability in node's `properties['capabilities']`. " "Also please note that this functionality requires " "`hexdump` command in the ramdisk.", node.uuid) efi_sys_uuid = None if not iwdi: if boot_mode_utils.get_boot_mode(node) == 'uefi': efi_sys_uuid = (self._get_uuid_from_result( task, 'efi_system_partition_uuid')) prep_boot_part_uuid = None if cpu_arch is not None and cpu_arch.startswith('ppc64'): prep_boot_part_uuid = (self._get_uuid_from_result( task, 'PReP_Boot_partition_uuid')) LOG.info('Image successfully written to node %s', node.uuid) if CONF.agent.manage_agent_boot: # It is necessary to invoke prepare_instance() of the node's # boot interface, so that the any necessary configurations like # setting of the boot mode (e.g. UEFI secure boot) which cannot # be done on node during deploy stage can be performed. LOG.debug( 'Executing driver specific tasks before booting up the ' 'instance for node %s', node.uuid) self.prepare_instance_to_boot(task, root_uuid, efi_sys_uuid, prep_boot_part_uuid) else: manager_utils.node_set_boot_device(task, 'disk', persistent=True) # Remove symbolic link when deploy is done. if CONF.agent.image_download_source == 'http': deploy_utils.remove_http_instance_symlink(task.node.uuid) LOG.debug('Rebooting node %s to instance', node.uuid) self.reboot_and_finish_deploy(task)
def create_pxe_config(task, pxe_options, template=None, ipxe_enabled=False): """Generate PXE configuration file and MAC address links for it. This method will generate the PXE configuration file for the task's node under a directory named with the UUID of that node. For each MAC address or DHCP IP address (port) of that node, a symlink for the configuration file will be created under the PXE configuration directory, so regardless of which port boots first they'll get the same PXE configuration. If elilo is the bootloader in use, then its configuration file will be created based on hex form of DHCP IP address. If grub2 bootloader is in use, then its configuration will be created based on DHCP IP address in the form nn.nn.nn.nn. :param task: A TaskManager instance. :param pxe_options: A dictionary with the PXE configuration parameters. :param template: The PXE configuration template. If no template is given the node specific template will be used. """ LOG.debug("Building PXE config for node %s", task.node.uuid) if template is None: template = deploy_utils.get_pxe_config_template(task.node) _ensure_config_dirs_exist(task, ipxe_enabled) pxe_config_file_path = get_pxe_config_file_path( task.node.uuid, ipxe_enabled=ipxe_enabled) is_uefi_boot_mode = (boot_mode_utils.get_boot_mode(task.node) == 'uefi') # grub bootloader panics with '{}' around any of its tags in its # config file. To overcome that 'ROOT' and 'DISK_IDENTIFIER' are enclosed # with '(' and ')' in uefi boot mode. # These changes do not have any impact on elilo bootloader. hex_form = True if is_uefi_boot_mode and utils.is_regex_string_in_file(template, '^menuentry'): hex_form = False pxe_config_root_tag = '(( ROOT ))' pxe_config_disk_ident = '(( DISK_IDENTIFIER ))' LOG.warning("The requested config appears to support elilo. " "Support for elilo has been deprecated and will be " "removed in the Queens release of OpenStack.") else: # TODO(stendulker): We should use '(' ')' as the delimiters for all our # config files so that we do not need special handling for each of the # bootloaders. Should be removed once the Mitaka release starts. pxe_config_root_tag = '{{ ROOT }}' pxe_config_disk_ident = '{{ DISK_IDENTIFIER }}' params = {'pxe_options': pxe_options, 'ROOT': pxe_config_root_tag, 'DISK_IDENTIFIER': pxe_config_disk_ident} pxe_config = utils.render_template(template, params) utils.write_to_file(pxe_config_file_path, pxe_config) # Always write the mac addresses _link_mac_pxe_configs(task, ipxe_enabled=ipxe_enabled) if is_uefi_boot_mode and not ipxe_enabled: try: _link_ip_address_pxe_configs(task, hex_form, ipxe_enabled) # NOTE(TheJulia): The IP address support will fail if the # dhcp_provider interface is set to none. This will result # in the MAC addresses and DHCP files being written, and # we can remove IP address creation for the grub use # case, considering that will ease removal of elilo support. except exception.FailedToGetIPaddressesOnPort as e: if CONF.dhcp.dhcp_provider != 'none': with excutils.save_and_reraise_exception(): LOG.error('Unable to create boot config, IP address ' 'was unable to be retrieved. %(error)s', {'error': e})
def prepare_instance(self, task): """Prepares the boot of instance. This method prepares the boot of the instance after reading relevant information from the node's instance_info. It does the following depending on boot_option for deploy: - If the boot mode is 'uefi' and its booting from volume, then it sets the iSCSI target info and node to boot from 'UefiTarget' boot device. - If not 'boot from volume' and the boot_option requested for this deploy is 'local' or image is a whole disk image, then it sets the node to boot from disk. - Otherwise it finds/creates the boot ISO to boot the instance image, attaches the boot ISO to the bare metal and then sets the node to boot from CDROM. :param task: a task from TaskManager. :returns: None :raises: IloOperationError, if some operation on iLO failed. :raises: InstanceDeployFailure, if its try to boot iSCSI volume in 'BIOS' boot mode. """ ilo_common.cleanup_vmedia_boot(task) boot_mode = boot_mode_utils.get_boot_mode(task.node) boot_option = deploy_utils.get_boot_option(task.node) if deploy_utils.is_iscsi_boot(task): # It will set iSCSI info onto iLO if boot_mode == 'uefi': # Need to set 'ilo_uefi_iscsi_boot' param for clean up driver_internal_info = task.node.driver_internal_info driver_internal_info['ilo_uefi_iscsi_boot'] = True task.node.driver_internal_info = driver_internal_info task.node.save() task.driver.management.set_iscsi_boot_target(task) manager_utils.node_set_boot_device(task, boot_devices.ISCSIBOOT, persistent=True) else: msg = 'Virtual media can not boot volume in BIOS boot mode.' raise exception.InstanceDeployFailure(msg) elif boot_option == "ramdisk": boot_iso = _get_boot_iso(task, None) ilo_common.setup_vmedia_for_boot(task, boot_iso) manager_utils.node_set_boot_device(task, boot_devices.CDROM, persistent=True) else: # Boot from disk every time if the image deployed is # a whole disk image. node = task.node iwdi = node.driver_internal_info.get('is_whole_disk_image') if deploy_utils.get_boot_option(node) == "local" or iwdi: manager_utils.node_set_boot_device(task, boot_devices.DISK, persistent=True) else: drv_int_info = node.driver_internal_info root_uuid_or_disk_id = drv_int_info.get('root_uuid_or_disk_id') if root_uuid_or_disk_id: self._configure_vmedia_boot(task, root_uuid_or_disk_id) else: LOG.warning( "The UUID for the root partition could not " "be found for node %s", node.uuid) # Set boot mode ilo_common.update_boot_mode(task) # Need to enable secure boot, if being requested ilo_common.update_secure_boot_mode(task, True)
def write_image(self, task): if not task.driver.storage.should_write_image(task): return node = task.node image_source = node.instance_info.get('image_source') LOG.debug('Continuing deploy for node %(node)s with image %(img)s', { 'node': node.uuid, 'img': image_source }) image_info = { 'id': image_source.split('/')[-1], 'urls': [node.instance_info['image_url']], # NOTE(comstud): Older versions of ironic do not set # 'disk_format' nor 'container_format', so we use .get() # to maintain backwards compatibility in case code was # upgraded in the middle of a build request. 'disk_format': node.instance_info.get('image_disk_format'), 'container_format': node.instance_info.get('image_container_format'), 'stream_raw_images': CONF.agent.stream_raw_images, } if node.instance_info.get('image_checksum'): image_info['checksum'] = node.instance_info['image_checksum'] if (node.instance_info.get('image_os_hash_algo') and node.instance_info.get('image_os_hash_value')): image_info['os_hash_algo'] = node.instance_info[ 'image_os_hash_algo'] image_info['os_hash_value'] = node.instance_info[ 'image_os_hash_value'] proxies = {} for scheme in ('http', 'https'): proxy_param = 'image_%s_proxy' % scheme proxy = node.driver_info.get(proxy_param) if proxy: proxies[scheme] = proxy if proxies: image_info['proxies'] = proxies no_proxy = node.driver_info.get('image_no_proxy') if no_proxy is not None: image_info['no_proxy'] = no_proxy image_info['node_uuid'] = node.uuid iwdi = node.driver_internal_info.get('is_whole_disk_image') if not iwdi: for label in PARTITION_IMAGE_LABELS: image_info[label] = node.instance_info.get(label) boot_option = deploy_utils.get_boot_option(node) image_info['deploy_boot_mode'] = ( boot_mode_utils.get_boot_mode(node)) image_info['boot_option'] = boot_option disk_label = deploy_utils.get_disk_label(node) if disk_label is not None: image_info['disk_label'] = disk_label configdrive = manager_utils.get_configdrive_image(node) if configdrive: # FIXME(dtantsur): remove this duplication once IPA is ready: # https://review.opendev.org/c/openstack/ironic-python-agent/+/790471 image_info['configdrive'] = configdrive # Now switch into the corresponding in-band deploy step and let the # result be polled normally. new_step = { 'interface': 'deploy', 'step': 'write_image', 'args': { 'image_info': image_info, 'configdrive': configdrive } } return agent_base.execute_step(task, new_step, 'deploy', client=self._client)
def configure_local_boot(self, task, root_uuid=None, efi_system_part_uuid=None, prep_boot_part_uuid=None): """Helper method to configure local boot on the node. This method triggers bootloader installation on the node. On successful installation of bootloader, this method sets the node to boot from disk. :param task: a TaskManager object containing the node :param root_uuid: The UUID of the root partition. This is used for identifying the partition which contains the image deployed or None in case of whole disk images which we expect to already have a bootloader installed. :param efi_system_part_uuid: The UUID of the efi system partition. This is used only in uefi boot mode. :param prep_boot_part_uuid: The UUID of the PReP Boot partition. This is used only for booting ppc64* hardware. :raises: InstanceDeployFailure if bootloader installation failed or on encountering error while setting the boot device on the node. """ node = task.node LOG.debug('Configuring local boot for node %s', node.uuid) # If the target RAID configuration is set to 'software' for the # 'controller', we need to trigger the installation of grub on # the holder disks of the desired Software RAID. internal_info = node.driver_internal_info raid_config = node.target_raid_config logical_disks = raid_config.get('logical_disks', []) software_raid = False for logical_disk in logical_disks: if logical_disk.get('controller') == 'software': LOG.debug('Node %s has a Software RAID configuration', node.uuid) software_raid = True break # For software RAID try to get the UUID of the root fs from the # image's metadata (via Glance). Fall back to the driver internal # info in case it is not available (e.g. not set or there's no Glance). if software_raid: image_source = node.instance_info.get('image_source') try: context = task.context context.is_admin = True glance = image_service.GlanceImageService( context=context) image_info = glance.show(image_source) image_properties = image_info.get('properties') root_uuid = image_properties['rootfs_uuid'] LOG.debug('Got rootfs_uuid from Glance: %s ' '(node %s)', root_uuid, node.uuid) except Exception as e: LOG.warning('Could not get \'rootfs_uuid\' property for ' 'image %(image)s from Glance for node %(node)s. ' '%(cls)s: %(error)s.', {'image': image_source, 'node': node.uuid, 'cls': e.__class__.__name__, 'error': e}) root_uuid = internal_info.get('root_uuid_or_disk_id') LOG.debug('Got rootfs_uuid from driver internal info: ' '%s (node %s)', root_uuid, node.uuid) # For whole disk images it is not necessary that the root_uuid # be provided since the bootloaders on the disk will be used whole_disk_image = internal_info.get('is_whole_disk_image') if (software_raid or (root_uuid and not whole_disk_image) or (whole_disk_image and boot_mode_utils.get_boot_mode(node) == 'uefi')): LOG.debug('Installing the bootloader for node %(node)s on ' 'partition %(part)s, EFI system partition %(efi)s', {'node': node.uuid, 'part': root_uuid, 'efi': efi_system_part_uuid}) result = self._client.install_bootloader( node, root_uuid=root_uuid, efi_system_part_uuid=efi_system_part_uuid, prep_boot_part_uuid=prep_boot_part_uuid) if result['command_status'] == 'FAILED': if not whole_disk_image: msg = (_("Failed to install a bootloader when " "deploying node %(node)s. Error: %(error)s") % {'node': node.uuid, 'error': result['command_error']}) log_and_raise_deployment_error(task, msg) else: # Its possible the install will fail if the IPA image # has not been updated, log this and continue LOG.info('Could not install bootloader for whole disk ' 'image for node %(node)s, Error: %(error)s"', {'node': node.uuid, 'error': result['command_error']}) return try: persistent = True if node.driver_info.get('force_persistent_boot_device', 'Default') == 'Never': persistent = False deploy_utils.try_set_boot_device(task, boot_devices.DISK, persistent=persistent) except Exception as e: msg = (_("Failed to change the boot device to %(boot_dev)s " "when deploying node %(node)s. Error: %(error)s") % {'boot_dev': boot_devices.DISK, 'node': node.uuid, 'error': e}) log_and_raise_deployment_error(task, msg, exc=e) LOG.info('Local boot successfully configured for node %s', node.uuid)
def test_get_boot_mode_bios(self, mock_for_deploy): mock_for_deploy.return_value = boot_modes.LEGACY_BIOS boot_mode = boot_mode_utils.get_boot_mode(self.node) self.assertEqual(boot_modes.LEGACY_BIOS, boot_mode)
def write_image(self, task): if not task.driver.storage.should_write_image(task): return node = task.node image_source = node.instance_info.get('image_source') LOG.debug('Continuing deploy for node %(node)s with image %(img)s', { 'node': node.uuid, 'img': image_source }) image_info = { 'id': image_source.split('/')[-1], 'urls': [node.instance_info['image_url']], # NOTE(comstud): Older versions of ironic do not set # 'disk_format' nor 'container_format', so we use .get() # to maintain backwards compatibility in case code was # upgraded in the middle of a build request. 'disk_format': node.instance_info.get('image_disk_format'), 'container_format': node.instance_info.get('image_container_format'), 'stream_raw_images': CONF.agent.stream_raw_images, } if node.instance_info.get('image_checksum'): image_info['checksum'] = node.instance_info['image_checksum'] if (node.instance_info.get('image_os_hash_algo') and node.instance_info.get('image_os_hash_value')): image_info['os_hash_algo'] = node.instance_info[ 'image_os_hash_algo'] image_info['os_hash_value'] = node.instance_info[ 'image_os_hash_value'] proxies = {} for scheme in ('http', 'https'): proxy_param = 'image_%s_proxy' % scheme proxy = node.driver_info.get(proxy_param) if proxy: proxies[scheme] = proxy if proxies: image_info['proxies'] = proxies no_proxy = node.driver_info.get('image_no_proxy') if no_proxy is not None: image_info['no_proxy'] = no_proxy image_info['node_uuid'] = node.uuid iwdi = node.driver_internal_info.get('is_whole_disk_image') if not iwdi: for label in PARTITION_IMAGE_LABELS: image_info[label] = node.instance_info.get(label) boot_option = deploy_utils.get_boot_option(node) image_info['deploy_boot_mode'] = ( boot_mode_utils.get_boot_mode(node)) image_info['boot_option'] = boot_option disk_label = deploy_utils.get_disk_label(node) if disk_label is not None: image_info['disk_label'] = disk_label has_write_image = agent_base.find_step(task, 'deploy', 'deploy', 'write_image') is not None if not has_write_image: LOG.warning( 'The agent on node %s does not have the deploy ' 'step deploy.write_image, using the deprecated ' 'synchronous fall-back', task.node.uuid) if self.has_decomposed_deploy_steps and has_write_image: configdrive = node.instance_info.get('configdrive') # Now switch into the corresponding in-band deploy step and let the # result be polled normally. new_step = { 'interface': 'deploy', 'step': 'write_image', 'args': { 'image_info': image_info, 'configdrive': configdrive } } return agent_base.execute_step(task, new_step, 'deploy', client=self._client) else: # TODO(dtantsur): remove in W command = self._client.prepare_image(node, image_info, wait=True) if command['command_status'] == 'FAILED': # TODO(jimrollenhagen) power off if using neutron dhcp to # align with pxe driver? msg = (_('node %(node)s command status errored: %(error)s') % { 'node': node.uuid, 'error': command['command_error'] }) LOG.error(msg) deploy_utils.set_failed_state(task, msg)
def _prepare_iso_image(task, kernel_href, ramdisk_href, bootloader_href=None, configdrive=None, root_uuid=None, params=None, base_iso=None): """Prepare an ISO to boot the node. Build bootable ISO out of `kernel_href` and `ramdisk_href` (and `bootloader` if it's UEFI boot), then push built image up to Swift and return a temporary URL. If `configdrive` is specified it will be eventually written onto the boot ISO image. :param task: a TaskManager instance containing the node to act on. :param kernel_href: URL or Glance UUID of the kernel to use :param ramdisk_href: URL or Glance UUID of the ramdisk to use :param bootloader_href: URL or Glance UUID of the EFI bootloader image to use when creating UEFI bootbable ISO :param configdrive: URL to or a compressed blob of a ISO9660 or FAT-formatted OpenStack config drive image. This image will be written onto the built ISO image. Optional. :param root_uuid: optional uuid of the root partition. :param params: a dictionary containing 'parameter name'->'value' mapping to be passed to kernel command line. :returns: bootable ISO HTTP URL. :raises: MissingParameterValue, if any of the required parameters are missing. :raises: InvalidParameterValue, if any of the parameters have invalid value. :raises: ImageCreationFailed, if creating ISO image failed. """ if (not kernel_href or not ramdisk_href) and not base_iso: raise exception.InvalidParameterValue(_( "Unable to find kernel, ramdisk for " "building ISO, or explicit ISO for %(node)s") % {'node': task.node.uuid}) img_handler = ImageHandler(task.node.driver) k_param = img_handler.kernel_params i_info = task.node.instance_info # NOTE(TheJulia): Until we support modifying a base iso, most of # this logic actually does nothing in the end. But it should! if deploy_utils.get_boot_option(task.node) == "ramdisk": if not base_iso: kernel_params = "root=/dev/ram0 text " kernel_params += i_info.get("ramdisk_kernel_arguments", "") else: kernel_params = None else: kernel_params = i_info.get('kernel_append_params', k_param) if params and not base_iso: kernel_params = ' '.join( (kernel_params, ' '.join( '%s=%s' % kv for kv in params.items()))) boot_mode = boot_mode_utils.get_boot_mode(task.node) LOG.debug("Trying to create %(boot_mode)s ISO image for node %(node)s " "with kernel %(kernel_href)s, ramdisk %(ramdisk_href)s, " "bootloader %(bootloader_href)s and kernel params %(params)s" "", {'node': task.node.uuid, 'boot_mode': boot_mode, 'kernel_href': kernel_href, 'ramdisk_href': ramdisk_href, 'bootloader_href': bootloader_href, 'params': kernel_params}) with tempfile.NamedTemporaryFile( dir=CONF.tempdir, suffix='.iso') as boot_fileobj: with tempfile.NamedTemporaryFile( dir=CONF.tempdir, suffix='.img') as cfgdrv_fileobj: configdrive_href = configdrive # FIXME(TheJulia): This is treated as conditional with # a base_iso as the intent, eventually, is to support # injection into the supplied image. if configdrive and not base_iso: parsed_url = urlparse.urlparse(configdrive) if not parsed_url.scheme: cfgdrv_blob = base64.decode_as_bytes(configdrive) with open(cfgdrv_fileobj.name, 'wb') as f: f.write(cfgdrv_blob) configdrive_href = urlparse.urlunparse( ('file', '', cfgdrv_fileobj.name, '', '', '')) LOG.debug("Built configdrive out of configdrive blob " "for node %(node)s", {'node': task.node.uuid}) boot_iso_tmp_file = boot_fileobj.name images.create_boot_iso( task.context, boot_iso_tmp_file, kernel_href, ramdisk_href, esp_image_href=bootloader_href, configdrive_href=configdrive_href, root_uuid=root_uuid, kernel_params=kernel_params, boot_mode=boot_mode, base_iso=base_iso) iso_object_name = _get_iso_image_name(task.node) image_url = img_handler.publish_image( boot_iso_tmp_file, iso_object_name) LOG.debug("Created ISO %(name)s in object store for node %(node)s, " "exposed as temporary URL " "%(url)s", {'node': task.node.uuid, 'name': iso_object_name, 'url': image_url}) return image_url