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 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 _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 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 = (deploy_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 ))' 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)
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 prepare_ramdisk(self, task, ramdisk_params): """Prepares the boot of Ironic ramdisk using PXE. This method prepares the boot of the deploy kernel/ramdisk after reading relevant information from the node's driver_info and instance_info. :param task: a task from TaskManager. :param ramdisk_params: the parameters to be passed to the ramdisk. pxe driver passes these parameters as kernel command-line arguments. :returns: None :raises: MissingParameterValue, if some information is missing in node's driver_info or instance_info. :raises: InvalidParameterValue, if some information provided is invalid. :raises: IronicException, if some power or set boot boot device operation failed on the node. """ node = task.node if CONF.pxe.ipxe_enabled: # NOTE(mjturek): At this point, the ipxe boot script should # already exist as it is created at startup time. However, we # call the boot script create method here to assert its # existence and handle the unlikely case that it wasn't created # or was deleted. pxe_utils.create_ipxe_boot_script() dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) pxe_info = _get_deploy_image_info(node) # NODE: Try to validate and fetch instance images only # if we are in DEPLOYING state. if node.provision_state == states.DEPLOYING: pxe_info.update(_get_instance_image_info(node, task.context)) pxe_options = _build_pxe_config_options(task, pxe_info) pxe_options.update(ramdisk_params) pxe_config_template = deploy_utils.get_pxe_config_template(node) pxe_utils.create_pxe_config(task, pxe_options, pxe_config_template) persistent = strutils.bool_from_string( node.driver_info.get('force_persistent_boot_device', False)) manager_utils.node_set_boot_device(task, boot_devices.PXE, persistent=persistent) if CONF.pxe.ipxe_enabled and CONF.pxe.ipxe_use_swift: pxe_info.pop('deploy_kernel', None) pxe_info.pop('deploy_ramdisk', None) if pxe_info: _cache_ramdisk_kernel(task.context, node, pxe_info)
def prepare_ramdisk(self, task, ramdisk_params): """Prepares the boot of Ironic ramdisk using PXE. This method prepares the boot of the deploy kernel/ramdisk after reading relevant information from the node's driver_info and instance_info. :param task: a task from TaskManager. :param ramdisk_params: the parameters to be passed to the ramdisk. pxe driver passes these parameters as kernel command-line arguments. :returns: None :raises: MissingParameterValue, if some information is missing in node's driver_info or instance_info. :raises: InvalidParameterValue, if some information provided is invalid. :raises: IronicException, if some power or set boot boot device operation failed on the node. """ node = task.node if CONF.pxe.ipxe_enabled: # NOTE(mjturek): At this point, the ipxe boot script should # already exist as it is created at startup time. However, we # call the boot script create method here to assert its # existence and handle the unlikely case that it wasn't created # or was deleted. pxe_utils.create_ipxe_boot_script() dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) pxe_info = _get_deploy_image_info(node) # NODE: Try to validate and fetch instance images only # if we are in DEPLOYING state. if node.provision_state == states.DEPLOYING: pxe_info.update(_get_instance_image_info(node, task.context)) pxe_options = _build_pxe_config_options(task, pxe_info) pxe_options.update(ramdisk_params) pxe_config_template = deploy_utils.get_pxe_config_template(node) pxe_utils.create_pxe_config(task, pxe_options, pxe_config_template) persistent = strutils.bool_from_string( node.driver_info.get('force_persistent_boot_device', False)) manager_utils.node_set_boot_device(task, boot_devices.PXE, persistent=persistent) if CONF.pxe.ipxe_enabled and CONF.pxe.ipxe_use_swift: pxe_info.pop('deploy_kernel', None) pxe_info.pop('deploy_ramdisk', None) if pxe_info: _cache_ramdisk_kernel(task.context, node, pxe_info)
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 = deploy_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 ))" 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)
def prepare_ramdisk(self, task, ramdisk_params): """Prepares the boot of Ironic ramdisk using PXE. This method prepares the boot of the deploy kernel/ramdisk after reading relevant information from the node's driver_info and instance_info. :param task: a task from TaskManager. :param ramdisk_params: the parameters to be passed to the ramdisk. pxe driver passes these parameters as kernel command-line arguments. :returns: None :raises: MissingParameterValue, if some information is missing in node's driver_info or instance_info. :raises: InvalidParameterValue, if some information provided is invalid. :raises: IronicException, if some power or set boot boot device operation failed on the node. """ node = task.node if CONF.pxe.ipxe_enabled: # Copy the iPXE boot script to HTTP root directory bootfile_path = os.path.join( CONF.deploy.http_root, os.path.basename(CONF.pxe.ipxe_boot_script)) if (not os.path.isfile(bootfile_path) or not filecmp.cmp(CONF.pxe.ipxe_boot_script, bootfile_path)): shutil.copyfile(CONF.pxe.ipxe_boot_script, bootfile_path) dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) pxe_info = _get_deploy_image_info(node) # NODE: Try to validate and fetch instance images only # if we are in DEPLOYING state. if node.provision_state == states.DEPLOYING: pxe_info.update(_get_instance_image_info(node, task.context)) pxe_options = _build_pxe_config_options(task, pxe_info) pxe_options.update(ramdisk_params) pxe_config_template = deploy_utils.get_pxe_config_template(node) pxe_utils.create_pxe_config(task, pxe_options, pxe_config_template) deploy_utils.try_set_boot_device(task, boot_devices.PXE) if CONF.pxe.ipxe_enabled and CONF.pxe.ipxe_use_swift: pxe_info.pop('deploy_kernel', None) pxe_info.pop('deploy_ramdisk', None) if pxe_info: _cache_ramdisk_kernel(task.context, node, pxe_info)
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 _build_service_pxe_config(task, instance_image_info, root_uuid_or_disk_id): node = task.node pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid) # NOTE(pas-ha) if it is takeover of ACTIVE node, # first ensure that basic PXE configs and links # are in place before switching pxe config if (node.provision_state == states.ACTIVE and not os.path.isfile(pxe_config_path)): pxe_options = _build_pxe_config_options(task, instance_image_info, service=True) pxe_config_template = deploy_utils.get_pxe_config_template(node) pxe_utils.create_pxe_config(task, pxe_options, pxe_config_template) iwdi = node.driver_internal_info.get('is_whole_disk_image') deploy_utils.switch_pxe_config( pxe_config_path, root_uuid_or_disk_id, deploy_utils.get_boot_mode_for_deploy(node), iwdi, deploy_utils.is_trusted_boot_requested(node))
def _build_service_pxe_config(task, instance_image_info, root_uuid_or_disk_id): node = task.node pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid) # NOTE(pas-ha) if it is takeover of ACTIVE node, # first ensure that basic PXE configs and links # are in place before switching pxe config if (node.provision_state == states.ACTIVE and not os.path.isfile(pxe_config_path)): pxe_options = _build_pxe_config_options(task, instance_image_info, service=True) pxe_config_template = deploy_utils.get_pxe_config_template(node) pxe_utils.create_pxe_config(task, pxe_options, pxe_config_template) iwdi = node.driver_internal_info.get('is_whole_disk_image') deploy_utils.switch_pxe_config( pxe_config_path, root_uuid_or_disk_id, deploy_utils.get_boot_mode_for_deploy(node), iwdi, deploy_utils.is_trusted_boot_requested(node), deploy_utils.is_iscsi_boot(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, 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 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. :param task: a task from TaskManager. :returns: None """ 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, deploy_utils.get_boot_mode_for_deploy(node), False, iscsi_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", {"node": task.node.uuid}) 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 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 prepare_ramdisk(self, task, ramdisk_params): """Prepares the boot of Ironic ramdisk using PXE. This method prepares the boot of the deploy kernel/ramdisk after reading relevant information from the node's driver_info and instance_info. :param task: a task from TaskManager. :param ramdisk_params: the parameters to be passed to the ramdisk. pxe driver passes these parameters as kernel command-line arguments. :returns: None :raises: MissingParameterValue, if some information is missing in node's driver_info or instance_info. :raises: InvalidParameterValue, if some information provided is invalid. :raises: IronicException, if some power or set boot boot device operation failed on the node. """ node = task.node if CONF.pxe.ipxe_enabled: # Render the iPXE boot script template and save it # to HTTP root directory boot_script = utils.render_template( CONF.pxe.ipxe_boot_script, {'ipxe_for_mac_uri': pxe_utils.PXE_CFG_DIR_NAME + '/'}) bootfile_path = os.path.join( CONF.deploy.http_root, os.path.basename(CONF.pxe.ipxe_boot_script)) # NOTE(pas-ha) to prevent unneeded writes, # only write to file if its content is different from required, # which should be rather rare if (not os.path.isfile(bootfile_path) or not utils.file_has_content(bootfile_path, boot_script)): utils.write_to_file(bootfile_path, boot_script) dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) pxe_info = _get_deploy_image_info(node) # NODE: Try to validate and fetch instance images only # if we are in DEPLOYING state. if node.provision_state == states.DEPLOYING: pxe_info.update(_get_instance_image_info(node, task.context)) pxe_options = _build_pxe_config_options(task, pxe_info) pxe_options.update(ramdisk_params) pxe_config_template = deploy_utils.get_pxe_config_template(node) pxe_utils.create_pxe_config(task, pxe_options, pxe_config_template) deploy_utils.try_set_boot_device(task, boot_devices.PXE) if CONF.pxe.ipxe_enabled and CONF.pxe.ipxe_use_swift: pxe_info.pop('deploy_kernel', None) pxe_info.pop('deploy_ramdisk', None) if pxe_info: _cache_ramdisk_kernel(task.context, node, pxe_info)
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 """ 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, deploy_utils.get_boot_mode_for_deploy(node), False, iscsi_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_ramdisk(self, task, ramdisk_params): """Prepares the boot of Ironic ramdisk using PXE. This method prepares the boot of the deploy or rescue kernel/ramdisk after reading relevant information from the node's driver_info and instance_info. :param task: a task from TaskManager. :param ramdisk_params: the parameters to be passed to the ramdisk. pxe driver passes these parameters as kernel command-line arguments. :returns: None :raises: MissingParameterValue, if some information is missing in node's driver_info or instance_info. :raises: InvalidParameterValue, if some information provided is invalid. :raises: IronicException, if some power or set boot boot device operation failed on the node. """ node = task.node # Label indicating a deploy or rescue operation being carried out on # the node, 'deploy' or 'rescue'. Unless the node is in a rescue like # state, the mode is set to 'deploy', indicating deploy operation is # being carried out. mode = deploy_utils.rescue_or_deploy_mode(node) # NOTE(mjturek): At this point, the ipxe boot script should # already exist as it is created at startup time. However, we # call the boot script create method here to assert its # existence and handle the unlikely case that it wasn't created # or was deleted. pxe_utils.create_ipxe_boot_script() dhcp_opts = pxe_utils.dhcp_options_for_instance(task, ipxe_enabled=True) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) pxe_info = pxe_utils.get_image_info(node, mode=mode, ipxe_enabled=True) # NODE: Try to validate and fetch instance images only # if we are in DEPLOYING state. if node.provision_state == states.DEPLOYING: pxe_info.update( pxe_utils.get_instance_image_info(task, ipxe_enabled=True)) boot_mode_utils.sync_boot_mode(task) pxe_options = pxe_utils.build_pxe_config_options(task, pxe_info, ipxe_enabled=True) pxe_options.update(ramdisk_params) pxe_config_template = deploy_utils.get_pxe_config_template(node) pxe_utils.create_pxe_config(task, pxe_options, pxe_config_template, ipxe_enabled=True) persistent = False value = node.driver_info.get('force_persistent_boot_device', 'Default') if value in {'Always', 'Default', 'Never'}: if value == 'Always': persistent = True else: persistent = strutils.bool_from_string(value, False) manager_utils.node_set_boot_device(task, boot_devices.PXE, persistent=persistent) if CONF.pxe.ipxe_use_swift: kernel_label = '%s_kernel' % mode ramdisk_label = '%s_ramdisk' % mode pxe_info.pop(kernel_label, None) pxe_info.pop(ramdisk_label, None) if pxe_info: pxe_utils.cache_ramdisk_kernel(task, pxe_info, ipxe_enabled=True)
def prepare_ramdisk(self, task, ramdisk_params): """Prepares the boot of Ironic ramdisk using PXE. This method prepares the boot of the deploy or rescue kernel/ramdisk after reading relevant information from the node's driver_info and instance_info. :param task: a task from TaskManager. :param ramdisk_params: the parameters to be passed to the ramdisk. pxe driver passes these parameters as kernel command-line arguments. :returns: None :raises: MissingParameterValue, if some information is missing in node's driver_info or instance_info. :raises: InvalidParameterValue, if some information provided is invalid. :raises: IronicException, if some power or set boot boot device operation failed on the node. """ node = task.node # Label indicating a deploy or rescue operation being carried out on # the node, 'deploy' or 'rescue'. Unless the node is in a rescue like # state, the mode is set to 'deploy', indicating deploy operation is # being carried out. mode = deploy_utils.rescue_or_deploy_mode(node) # NOTE(mjturek): At this point, the ipxe boot script should # already exist as it is created at startup time. However, we # call the boot script create method here to assert its # existence and handle the unlikely case that it wasn't created # or was deleted. pxe_utils.create_ipxe_boot_script() dhcp_opts = pxe_utils.dhcp_options_for_instance( task, ipxe_enabled=True) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) pxe_info = pxe_utils.get_image_info(node, mode=mode, ipxe_enabled=True) # NODE: Try to validate and fetch instance images only # if we are in DEPLOYING state. if node.provision_state == states.DEPLOYING: pxe_info.update( pxe_utils.get_instance_image_info(task, ipxe_enabled=True)) boot_mode_utils.sync_boot_mode(task) pxe_options = pxe_utils.build_pxe_config_options(task, pxe_info, ipxe_enabled=True) pxe_options.update(ramdisk_params) pxe_config_template = deploy_utils.get_pxe_config_template(node) pxe_utils.create_pxe_config(task, pxe_options, pxe_config_template, ipxe_enabled=True) persistent = False value = node.driver_info.get('force_persistent_boot_device', 'Default') if value in {'Always', 'Default', 'Never'}: if value == 'Always': persistent = True else: persistent = strutils.bool_from_string(value, False) manager_utils.node_set_boot_device(task, boot_devices.PXE, persistent=persistent) if CONF.pxe.ipxe_use_swift: kernel_label = '%s_kernel' % mode ramdisk_label = '%s_ramdisk' % mode pxe_info.pop(kernel_label, None) pxe_info.pop(ramdisk_label, None) if pxe_info: pxe_utils.cache_ramdisk_kernel(task, pxe_info, ipxe_enabled=True)
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_ramdisk(self, task, ramdisk_params): """Prepares the boot of Ironic ramdisk using PXE. This method prepares the boot of the deploy or rescue kernel/ramdisk after reading relevant information from the node's driver_info and instance_info. :param task: a task from TaskManager. :param ramdisk_params: the parameters to be passed to the ramdisk. pxe driver passes these parameters as kernel command-line arguments. :returns: None :raises: MissingParameterValue, if some information is missing in node's driver_info or instance_info. :raises: InvalidParameterValue, if some information provided is invalid. :raises: IronicException, if some power or set boot boot device operation failed on the node. """ node = task.node # Label indicating a deploy or rescue operation being carried out on # the node, 'deploy' or 'rescue'. Unless the node is in a rescue like # state, the mode is set to 'deploy', indicating deploy operation is # being carried out. mode = deploy_utils.rescue_or_deploy_mode(node) ipxe_enabled = CONF.pxe.ipxe_enabled if ipxe_enabled: # NOTE(mjturek): At this point, the ipxe boot script should # already exist as it is created at startup time. However, we # call the boot script create method here to assert its # existence and handle the unlikely case that it wasn't created # or was deleted. pxe_utils.create_ipxe_boot_script() dhcp_opts = pxe_utils.dhcp_options_for_instance( task, ipxe_enabled=ipxe_enabled) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts) pxe_info = pxe_utils.get_image_info(node, mode=mode) # NODE: Try to validate and fetch instance images only # if we are in DEPLOYING state. if node.provision_state == states.DEPLOYING: pxe_info.update(pxe_utils.get_instance_image_info(task)) boot_mode_utils.sync_boot_mode(task) pxe_options = pxe_utils.build_pxe_config_options( task, pxe_info, ipxe_enabled=ipxe_enabled, ramdisk_params=ramdisk_params) # TODO(dtantsur): backwards compability hack, remove in the V release if ramdisk_params.get("ipa-api-url"): pxe_options["ipa-api-url"] = ramdisk_params["ipa-api-url"] pxe_config_template = deploy_utils.get_pxe_config_template(node) pxe_utils.create_pxe_config(task, pxe_options, pxe_config_template, ipxe_enabled=CONF.pxe.ipxe_enabled) persistent = self._persistent_ramdisk_boot(node) manager_utils.node_set_boot_device(task, boot_devices.PXE, persistent=persistent) if CONF.pxe.ipxe_enabled and CONF.pxe.ipxe_use_swift: kernel_label = '%s_kernel' % mode ramdisk_label = '%s_ramdisk' % mode pxe_info.pop(kernel_label, None) pxe_info.pop(ramdisk_label, None) if pxe_info: pxe_utils.cache_ramdisk_kernel(task, pxe_info, ipxe_enabled=CONF.pxe.ipxe_enabled) LOG.debug( 'Ramdisk PXE boot for node %(node)s has been prepared ' 'with kernel params %(params)s', { 'node': node.uuid, 'params': pxe_options })