def prepare_node_for_deploy(task): """Common preparatory steps for all iLO drivers. This method performs common preparatory steps required for all drivers. 1. Power off node 2. Disables secure boot, if it is in enabled state. 3. Updates boot_mode capability to 'uefi' if secure boot is requested. 4. Changes boot mode of the node if secure boot is disabled currently. :param task: a TaskManager instance containing the node to act on. :raises: IloOperationError, if some operation on iLO failed. """ manager_utils.node_power_action(task, states.POWER_OFF) # Boot mode can be changed only if secure boot is in disabled state. # secure boot and boot mode cannot be changed together. change_boot_mode = True # Disable secure boot on the node if it is in enabled state. if _disable_secure_boot(task): change_boot_mode = False if change_boot_mode: ilo_common.update_boot_mode(task) else: # Need to update boot mode that will be used during deploy, if one is # not provided. # Since secure boot was disabled, we are in 'uefi' boot mode. if boot_mode_utils.get_boot_mode_for_deploy(task.node) is None: instance_info = task.node.instance_info instance_info['deploy_boot_mode'] = 'uefi' task.node.instance_info = instance_info task.node.save()
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_for_deploy(node), iwdi, deploy_utils.is_trusted_boot_requested(node), deploy_utils.is_iscsi_boot(task), ramdisk_boot, ipxe_enabled=ipxe_enabled)
def _prepare_instance_pxe_config(self, 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 = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) pxe_config_path = pxe_utils.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)) pxe_utils.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)
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 service_utils.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_filename = _get_iso_name(task.node, label='deploy') deploy_iso = ('file://' + os.path.join( CONF.irmc.remote_image_share_root, deploy_iso_filename)) boot_mode = boot_mode_utils.get_boot_mode_for_deploy(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, 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 _get_boot_mode(node): """Gets the boot mode. :param node: A single Node. :returns: A string representing the boot mode type. Defaults to 'bios'. """ boot_mode = boot_mode_utils.get_boot_mode_for_deploy(node) if boot_mode: return boot_mode return "bios"
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, } 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) boot_mode = boot_mode_utils.get_boot_mode_for_deploy(node) if boot_mode: image_info['deploy_boot_mode'] = boot_mode else: image_info['deploy_boot_mode'] = 'bios' 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 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_for_deploy( 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 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(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_for_deploy(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 _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_for_deploy(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
def _prepare_iso_image(cls, task, kernel_href, ramdisk_href, bootloader_href=None, root_uuid=None, params=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. :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: raise exception.InvalidParameterValue( _("Unable to find kernel or ramdisk for " "building ISO for %(node)s") % {'node': task.node.uuid}) i_info = task.node.instance_info if deploy_utils.get_boot_option(task.node) == "ramdisk": kernel_params = "root=/dev/ram0 text " kernel_params += i_info.get("ramdisk_kernel_arguments", "") else: kernel_params = i_info.get('kernel_append_params', CONF.redfish.kernel_append_params) if params: kernel_params = ' '.join( (kernel_params, ' '.join('%s=%s' % kv for kv in params.items()))) boot_mode = boot_mode_utils.get_boot_mode_for_deploy(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 fileobj: boot_iso_tmp_file = 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) iso_object_name = cls._get_iso_image_name(task.node) image_url = cls._publish_image(boot_iso_tmp_file, iso_object_name) LOG.debug( "Created ISO %(name)s in Swift 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 prepare_iso_image(task, kernel_href, ramdisk_href, deploy_iso_href=None, bootloader_href=None, root_uuid=None, kernel_params=None, timeout=None, use_web_server=False, container=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 kernel_params: a dictionary containing 'parameter name'->'value' mapping to be passed to kernel command line. :param timeout: swift object expiry timeout :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: raise exception.InvalidParameterValue( _("Unable to find kernel or ramdisk for " "building ISO for %(node)s") % {'node': task.node.uuid}) boot_mode = boot_mode_utils.get_boot_mode_for_deploy(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 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_href, esp_image_href=bootloader_href, root_uuid=root_uuid, kernel_params=kernel_params, boot_mode=boot_mode) iso_object_name = get_iso_image_name(task.node) if use_web_server: boot_iso_url = (deploy_utils.copy_image_to_web_server( boot_iso_tmp_file, 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: swift_api = swift.SwiftAPI() object_headers = None if task.node.driver == 'redfish': object_headers = {'X-Delete-After': str(timeout)} swift_api.create_object(container, iso_object_name, boot_iso_tmp_file, object_headers=object_headers) LOG.debug("Created ISO %(name)s in Swift for node %(node)s", { 'node': task.node.uuid, 'name': iso_object_name }) if task.node.driver == 'redfish': boot_iso_url = swift_api.get_temp_url(container, iso_object_name, timeout) return boot_iso_url else: return 'swift:%s' % iso_object_name
def update_boot_mode(task): """Update instance_info with boot mode to be used for deploy. This method updates instance_info with boot mode to be used for deploy if node properties['capabilities'] do not have boot_mode. It sets the boot mode on the node. :param task: Task object. :raises: IloOperationError if setting boot mode failed. """ node = task.node boot_mode = boot_mode_utils.get_boot_mode_for_deploy(node) # No boot mode found. Check if default_boot_mode is defined if not boot_mode and (CONF.ilo.default_boot_mode in ['bios', 'uefi']): boot_mode = CONF.ilo.default_boot_mode instance_info = node.instance_info instance_info['deploy_boot_mode'] = boot_mode node.instance_info = instance_info node.save() # Boot mode is computed, setting it for the deploy if boot_mode: LOG.debug("Node %(uuid)s boot mode is being set to %(boot_mode)s", { 'uuid': node.uuid, 'boot_mode': boot_mode }) set_boot_mode(node, boot_mode) return # Computing boot mode based on boot mode settings on bare metal LOG.debug("Check pending boot mode for node %s.", node.uuid) ilo_object = get_ilo_object(node) try: boot_mode = ilo_object.get_pending_boot_mode() except ilo_error.IloCommandNotSupportedError: boot_mode = 'legacy' if boot_mode != 'UNKNOWN': boot_mode = BOOT_MODE_ILO_TO_GENERIC[boot_mode.lower()] if boot_mode == 'UNKNOWN': # NOTE(faizan) ILO will return this in remote cases and mostly on # the nodes which supports UEFI. Such nodes mostly comes with UEFI # as default boot mode. So we will try setting bootmode to UEFI # and if it fails then we fall back to BIOS boot mode. try: boot_mode = 'uefi' ilo_object.set_pending_boot_mode( BOOT_MODE_GENERIC_TO_ILO[boot_mode].upper()) except ilo_error.IloError as ilo_exception: operation = _("Setting %s as boot mode") % boot_mode raise exception.IloOperationError(operation=operation, error=ilo_exception) LOG.debug( "Node %(uuid)s boot mode is being set to %(boot_mode)s " "as pending boot mode is unknown.", { 'uuid': node.uuid, 'boot_mode': boot_mode }) instance_info = node.instance_info instance_info['deploy_boot_mode'] = boot_mode node.instance_info = instance_info node.save()
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_for_deploy( 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. In case of netboot, it updates the dhcp entries and switches the PXE config. In case of localboot, it cleans up the PXE config. :param task: a task from TaskManager. :returns: None """ boot_mode_utils.sync_boot_mode(task) node = task.node boot_option = deploy_utils.get_boot_option(node) boot_device = None if deploy_utils.is_iscsi_boot(task): dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) # configure iPXE for iscsi boot pxe_config_path = pxe_utils.get_pxe_config_file_path( task.node.uuid) if not os.path.isfile(pxe_config_path): pxe_options = _build_pxe_config_options(task, {}) pxe_config_template = ( deploy_utils.get_pxe_config_template(node)) pxe_utils.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=True) boot_device = boot_devices.PXE elif boot_option == "ramdisk": instance_image_info = _get_instance_image_info( task.node, task.context) _cache_ramdisk_kernel(task.context, task.node, instance_image_info) dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) pxe_config_path = pxe_utils.get_pxe_config_file_path( task.node.uuid) deploy_utils.switch_pxe_config( pxe_config_path, None, boot_mode_utils.get_boot_mode_for_deploy(node), False, iscsi_boot=False, ramdisk_boot=True) boot_device = boot_devices.PXE elif boot_option != "local": if task.driver.storage.should_write_image(task): # Make sure that the instance kernel/ramdisk is cached. # This is for the takeover scenario for active nodes. instance_image_info = _get_instance_image_info( task.node, task.context) _cache_ramdisk_kernel(task.context, task.node, instance_image_info) # If it's going to PXE boot we need to update the DHCP server dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) iwdi = task.node.driver_internal_info.get('is_whole_disk_image') try: root_uuid_or_disk_id = task.node.driver_internal_info[ 'root_uuid_or_disk_id'] except KeyError: if not task.driver.storage.should_write_image(task): pass elif not iwdi: LOG.warning( "The UUID for the root partition can't be " "found, unable to switch the pxe config from " "deployment mode to service (boot) mode for " "node %(node)s", {"node": task.node.uuid}) else: LOG.warning( "The disk id for the whole disk image can't " "be found, unable to switch the pxe config " "from deployment mode to service (boot) mode " "for node %(node)s. Booting the instance " "from disk.", {"node": task.node.uuid}) pxe_utils.clean_up_pxe_config(task) boot_device = boot_devices.DISK else: _build_service_pxe_config(task, instance_image_info, root_uuid_or_disk_id) boot_device = boot_devices.PXE else: # If it's going to boot from the local disk, we don't need # PXE config files. They still need to be generated as part # of the prepare() because the deployment does PXE boot the # deploy ramdisk pxe_utils.clean_up_pxe_config(task) boot_device = boot_devices.DISK # NOTE(pas-ha) do not re-set boot device on ACTIVE nodes # during takeover if boot_device and task.node.provision_state != states.ACTIVE: manager_utils.node_set_boot_device(task, boot_device, persistent=True)
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_for_deploy(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 reboot_to_instance(self, task): task.process_event('resume') node = task.node iwdi = task.node.driver_internal_info.get('is_whole_disk_image') 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. 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_for_deploy(node) == 'uefi': efi_sys_uuid = ( self._get_uuid_from_result(task, 'efi_system_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) else: manager_utils.node_set_boot_device(task, 'disk', persistent=True) LOG.debug('Rebooting node %s to instance', node.uuid) self.reboot_and_finish_deploy(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_for_deploy(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_uuid, root_uuid, kernel_params, 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 create_pxe_config(task, pxe_options, template=None): """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.node.uuid) pxe_config_file_path = get_pxe_config_file_path(task.node.uuid) is_uefi_boot_mode = (boot_mode_utils.get_boot_mode_for_deploy( 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) if is_uefi_boot_mode and not CONF.pxe.ipxe_enabled: _link_ip_address_pxe_configs(task, hex_form) else: _link_mac_pxe_configs(task)