def _setup_deploy_iso(task, ramdisk_options): """Attaches virtual media and sets it as boot device. This method attaches the given deploy ISO as virtual media, prepares the arguments for ramdisk in virtual media floppy. :param task: a TaskManager instance containing the node to act on. :param ramdisk_options: the options to be passed to the ramdisk in virtual media floppy. :raises: ImageRefValidationFailed if no image service can handle specified href. :raises: ImageCreationFailed, if it failed while creating the floppy image. :raises: IRMCOperationError, if some operation on iRMC failed. :raises: InvalidParameterValue if the validation of the PowerInterface or ManagementInterface fails. """ d_info = task.node.driver_info deploy_iso_href = d_info["irmc_deploy_iso"] if service_utils.is_image_href_ordinary_file_name(deploy_iso_href): deploy_iso_file = deploy_iso_href else: deploy_iso_file = _get_deploy_iso_name(task.node) deploy_iso_fullpathname = os.path.join(CONF.irmc.remote_image_share_root, deploy_iso_file) images.fetch(task.context, deploy_iso_href, deploy_iso_fullpathname) _setup_vmedia_for_boot(task, deploy_iso_file, ramdisk_options) manager_utils.node_set_boot_device(task, boot_devices.CDROM)
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_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. """ ilo_common.cleanup_vmedia_boot(task) # For iscsi_ilo driver, we 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(_LW("The UUID for the root partition could not " "be found for node %s"), node.uuid)
def deploy(self, task): """Start deployment of the task's node'. Fetches instance image, creates a temporary keystone token file, updates the Neutron DHCP port options for next boot, and issues a reboot request to the power driver. This causes the node to boot into the deployment ramdisk and triggers the next phase of PXE-based deployment via VendorPassthru._continue_deploy(). :param task: a TaskManager instance containing the node to act on. :returns: deploy state DEPLOYING. """ _cache_instance_image(task.context, task.node) _check_image_size(task) # TODO(yuriyz): more secure way needed for pass auth token # to deploy ramdisk _create_token_file(task) dhcp_opts = pxe_utils.dhcp_options_for_instance() neutron.update_neutron(task, dhcp_opts) manager_utils.node_set_boot_device(task, 'pxe', persistent=True) manager_utils.node_power_action(task, states.REBOOT) return states.DEPLOYWAIT
def _setup_vmedia(task, mode, ramdisk_options): """Attaches virtual media and sets it as boot device. This method attaches the deploy or rescue ISO as virtual media, prepares the arguments for ramdisk in virtual media floppy. :param task: a TaskManager instance containing the node to act on. :param mode: Label indicating a deploy or rescue operation being carried out on the node. Supported values are 'deploy' and 'rescue'. :param ramdisk_options: the options to be passed to the ramdisk in virtual media floppy. :raises: ImageRefValidationFailed if no image service can handle specified href. :raises: ImageCreationFailed, if it failed while creating the floppy image. :raises: IRMCOperationError, if some operation on iRMC failed. :raises: InvalidParameterValue if the validation of the PowerInterface or ManagementInterface fails. """ if mode == 'rescue': iso = task.node.driver_info['irmc_rescue_iso'] else: iso = task.node.driver_info['irmc_deploy_iso'] if _is_image_href_ordinary_file_name(iso): iso_file = iso else: iso_file = _get_iso_name(task.node, label=mode) iso_fullpathname = os.path.join( CONF.irmc.remote_image_share_root, iso_file) images.fetch(task.context, iso, iso_fullpathname) _setup_vmedia_for_boot(task, iso_file, ramdisk_options) manager_utils.node_set_boot_device(task, boot_devices.CDROM)
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 database. :param task: a task from TaskManager. :returns: None """ if task.node.driver_internal_info.get('boot_from_volume'): LOG.debug('Node %(node)s is configured for booting from a remote ' 'volume.', {'node': task.node.uuid}) self._configure_boot_from_volume(task) return _cleanup_vmedia_boot(task) 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: driver_internal_info = node.driver_internal_info root_uuid_or_disk_id = driver_internal_info['root_uuid_or_disk_id'] self._configure_vmedia_boot(task, root_uuid_or_disk_id) # Enable secure boot, if being requested if deploy_utils.is_secure_boot_requested(node): irmc_common.set_secure_boot_mode(node, enable=True)
def _configure_vmedia_boot(self, task, root_uuid): """Configure vmedia boot for the node. :param task: a task from TaskManager. :param root_uuid: uuid of the root partition :returns: None :raises: IloOperationError, if some operation on iLO failed. """ node = task.node boot_iso = _get_boot_iso(task, root_uuid) if not boot_iso: LOG.error("Cannot get boot ISO for node %s", node.uuid) return # Upon deploy complete, some distros cloud images reboot the system as # part of its configuration. Hence boot device should be persistent and # not one-time. ilo_common.setup_vmedia_for_boot(task, boot_iso) manager_utils.node_set_boot_device(task, boot_devices.CDROM, persistent=True) i_info = node.instance_info i_info['ilo_boot_iso'] = boot_iso node.instance_info = i_info node.save()
def setup_vmedia(task, iso, ramdisk_options): """Attaches virtual media and sets it as boot device. This method attaches the given bootable ISO as virtual media, prepares the arguments for ramdisk in virtual media floppy. :param task: a TaskManager instance containing the node to act on. :param iso: a bootable ISO image href to attach to. 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. :param ramdisk_options: the options to be passed to the ramdisk in virtual media floppy. :raises: ImageCreationFailed, if it failed while creating the floppy image. :raises: IloOperationError, if some operation on iLO failed. """ setup_vmedia_for_boot(task, iso, ramdisk_options) # In UEFI boot mode, upon inserting virtual CDROM, one has to reset the # system to see it as a valid boot device in persistent boot devices. # But virtual CDROM device is always available for one-time boot. # During enable/disable of secure boot settings, iLO internally resets # the server twice. But it retains one time boot settings across internal # resets. Hence no impact of this change for secure boot deploy. manager_utils.node_set_boot_device(task, boot_devices.CDROM)
def reboot_to_instance(self, task, **kwargs): task.process_event('resume') node = task.node 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 LOG.info(_LI('Image successfully written to node %s'), node.uuid) LOG.debug('Rebooting node %s to instance', node.uuid) manager_utils.node_set_boot_device(task, 'disk', persistent=True) self.reboot_and_finish_deploy(task) # NOTE(TheJulia): If we deployed a whole disk image, we # should expect a whole disk image and clean-up the tftp files # on-disk incase the node is disregarding the boot preference. # TODO(rameshg87): Not all in-tree drivers using reboot_to_instance # have a boot interface. So include a check for now. Remove this # check once all in-tree drivers have a boot interface. if task.driver.boot: task.driver.boot.clean_up_ramdisk(task)
def reboot_to_instance(self, task, **kwargs): task.process_event('resume') node = task.node 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 LOG.info(_LI('Image successfully written to node %s'), node.uuid) LOG.debug('Rebooting node %s to instance', node.uuid) manager_utils.node_set_boot_device(task, 'disk', persistent=True) self.reboot_and_finish_deploy(task) # NOTE(TheJulia): If we we deployed a whole disk image, we # should expect a whole disk image and clean-up the tftp files # on-disk incase the node is disregarding the boot preference. # TODO(rameshg87): This shouldn't get called for virtual media deploy # drivers (iLO and iRMC). This is just a hack, but it will be taken # care in boot/deploy interface separation. if (_driver_uses_pxe(task.driver) and node.driver_internal_info.get('is_whole_disk_image')): _clean_up_pxe(task)
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: if get_boot_mode_for_deploy(task.node) == 'uefi': LOG.warning(_LW("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) else: raise
def deploy(self, task): """Start deployment of the task's node'. Config host file and xcat dhcp, generate image info for xcat and issues a reboot request to the power driver. This causes the node to boot into the deployment ramdisk and triggers the next phase of PXE-based deployment via VendorPassthru._continue_deploy(). :param task: a TaskManager instance containing the node to act on. :returns: deploy state DEPLOYDONE. """ d_info = _parse_deploy_info(task.node) if not task.node.instance_info.get('fixed_ip_address') or not task.node.instance_info.get('image_name'): raise exception.InvalidParameterValue self._config_host_file(d_info,task.node.instance_info.get('fixed_ip_address')) self._make_dhcp() self._nodeset_osimage(d_info,task.node.instance_info.get('image_name')) manager_utils.node_set_boot_device(task, 'pxe', persistent=True) manager_utils.node_power_action(task, states.REBOOT) try: self._wait_for_node_deploy(task) except xcat_exception.xCATDeploymentFailure: LOG.info(_("xcat deployment failed")) return states.ERROR return states.DEPLOYDONE
def _deploy(task, node_address): """Internal function for deployment to a node.""" notags = ['wait'] if CONF.ansible.use_ramdisk_callback else [] node = task.node LOG.debug('IP of node %(node)s is %(ip)s', {'node': node.uuid, 'ip': node_address}) iwdi = node.driver_internal_info.get('is_whole_disk_image') variables = _prepare_variables(task) if iwdi: notags.append('parted') else: variables.update(_parse_partitioning_info(task.node)) playbook, user, key = _parse_ansible_driver_info(task.node) node_list = [(node.uuid, node_address, user, node.extra)] extra_vars = _prepare_extra_vars(node_list, variables=variables) LOG.debug('Starting deploy on node %s', node.uuid) # any caller should manage exceptions raised from here _run_playbook(playbook, extra_vars, key, notags=notags) LOG.info(_LI('Ansible complete deploy on node %s'), node.uuid) LOG.debug('Rebooting node %s to instance', node.uuid) manager_utils.node_set_boot_device(task, 'disk', persistent=True) _reboot_and_finish_deploy(task) task.driver.boot.clean_up_ramdisk(task)
def pass_deploy_info(self, task, **kwargs): """Continues the iSCSI deployment from where ramdisk left off. This method continues the iSCSI deployment from the conductor node and writes the deploy image to the bare metal's disk. After that, it does the following depending on boot_option for deploy: - If the boot_option requested for this deploy is 'local', then it sets the node to boot from disk (ramdisk installs the boot loader present within the image to the bare metal's disk). - If the boot_option requested is 'netboot' or no boot_option is requested, 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 TaskManager instance containing the node to act on. :param kwargs: kwargs containing parameters for iSCSI deployment. :raises: InvalidState """ node = task.node task.process_event('resume') iwdi = node.driver_internal_info.get('is_whole_disk_image') ilo_common.cleanup_vmedia_boot(task) uuid_dict = iscsi_deploy.continue_deploy(task, **kwargs) root_uuid_or_disk_id = uuid_dict.get( 'root uuid', uuid_dict.get('disk identifier')) try: # Set boot mode ilo_common.update_boot_mode(task) # Need to enable secure boot, if being requested _update_secure_boot_mode(task, True) # For iscsi_ilo driver, we boot from disk every time if the image # deployed is a whole disk image. if iscsi_deploy.get_boot_option(node) == "local" or iwdi: manager_utils.node_set_boot_device(task, boot_devices.DISK, persistent=True) # Ask the ramdisk to install bootloader and # wait for the call-back through the vendor passthru # 'pass_bootloader_install_info', if it's not a whole # disk image. if not iwdi: deploy_utils.notify_ramdisk_to_proceed(kwargs['address']) task.process_event('wait') return else: self._configure_vmedia_boot(task, root_uuid_or_disk_id) except Exception as e: LOG.error(_LE('Deploy failed for instance %(instance)s. ' 'Error: %(error)s'), {'instance': node.instance_uuid, 'error': e}) msg = _('Failed to continue iSCSI deployment.') deploy_utils.set_failed_state(task, msg) else: iscsi_deploy.finish_deploy(task, kwargs.get('address'))
def _configure_vmedia_boot(self, task, root_uuid_or_disk_id): """Configure vmedia boot for the node.""" node = task.node _prepare_boot_iso(task, root_uuid_or_disk_id) _setup_vmedia_for_boot( task, node.driver_internal_info['irmc_boot_iso']) manager_utils.node_set_boot_device(task, boot_devices.CDROM, persistent=True)
def reboot_to_instance(self, task): node = task.node LOG.info('Ansible complete deploy on node %s', node.uuid) LOG.debug('Rebooting node %s to instance', node.uuid) manager_utils.node_set_boot_device(task, 'disk', persistent=True) self.reboot_and_finish_deploy(task) task.driver.boot.clean_up_ramdisk(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: # 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 pass_deploy_info(self, task, **kwargs): LOG.debug("Pass deploy info for the deployment on node %s", task.node.uuid) manager_utils.node_set_boot_device(task, boot_devices.PXE, persistent=True) # Set boot mode ilo_common.update_boot_mode(task) # Need to enable secure boot, if being requested _update_secure_boot_mode(task, True) super(IloPXEVendorPassthru, self).pass_deploy_info(task, **kwargs)
def inspect_hardware(self, task): """Inspect hardware. Inspect hardware to obtain the essential hardware properties and mac addresses. :param task: a task from TaskManager. :raises: HardwareInspectionFailure, if hardware inspection failed. :returns: states.MANAGEABLE, if hardware inspection succeeded. """ node = task.node kwargs = {} # Inspect additional capabilities task requires node with power on # status old_power_state = task.driver.power.get_power_state(task) if old_power_state == states.POWER_OFF: manager_utils.node_set_boot_device(task, boot_devices.BIOS, False) manager_utils.node_power_action(task, states.POWER_ON) LOG.info("The Node %(node_uuid)s being powered on for inspection", {'node_uuid': task.node.uuid}) kwargs['sleep_flag'] = True traits_obj = objects.TraitList.get_by_node_id(task.context, node.id) existing_traits = traits_obj.get_trait_names() props, macs, new_traits = _inspect_hardware(node, existing_traits, **kwargs) node.properties = dict(node.properties, **props) if existing_traits != new_traits: objects.TraitList.create(task.context, node.id, new_traits) node.save() for mac in macs: try: new_port = objects.Port(task.context, address=mac, node_id=node.id) new_port.create() LOG.info("Port created for MAC address %(address)s " "for node %(node_uuid)s during inspection", {'address': mac, 'node_uuid': node.uuid}) except exception.MACAlreadyExists: LOG.warning("Port already existed for MAC address " "%(address)s for node %(node_uuid)s " "during inspection", {'address': mac, 'node_uuid': node.uuid}) LOG.info("Node %s inspected", node.uuid) # restore old power state if old_power_state == states.POWER_OFF: manager_utils.node_power_action(task, states.POWER_OFF) LOG.info("The Node %(node_uuid)s being powered off after " "inspection", {'node_uuid': task.node.uuid}) return states.MANAGEABLE
def pass_deploy_info(self, task, **kwargs): """Continues the deployment of baremetal node.""" node = task.node task.process_event('resume') err_msg = _('Failed to continue deployment with Fuel Agent.') agent_status = kwargs.get('status') if agent_status != 'ready': LOG.error(_LE('Deploy failed for node %(node)s. Fuel Agent is not ' 'in ready state, error: %(error)s'), {'node': node.uuid, 'error': kwargs.get('error_message')}) deploy_utils.set_failed_state(task, err_msg) return params = _parse_driver_info(node) params['host'] = kwargs.get('address') cmd = ('%s --data_driver ironic --config-file ' '/etc/fuel-agent/fuel-agent.conf' % params.pop('script')) if CONF.debug: cmd += ' --debug' instance_info = node.instance_info try: deploy_data = _get_deploy_data(task.context, instance_info['image_source']) image_data = {"/": {"uri": instance_info['image_url'], "format": "raw", "container": "raw"}} deploy_data['ks_meta']['image_data'] = image_data ssh = utils.ssh_connect(params) sftp = ssh.open_sftp() _sftp_upload(sftp, json.dumps(deploy_data), '/tmp/provision.json') # swift configdrive store should be disabled configdrive = instance_info.get('configdrive') if configdrive is not None: _sftp_upload(sftp, configdrive, '/tmp/config-drive.img') _ssh_execute(ssh, cmd, params) LOG.info(_LI('Fuel Agent pass on node %s'), node.uuid) manager_utils.node_set_boot_device(task, boot_devices.DISK, persistent=True) manager_utils.node_power_action(task, states.REBOOT) except Exception as e: msg = (_('Deploy failed for node %(node)s. Error: %(error)s') % {'node': node.uuid, 'error': e}) LOG.error(msg) deploy_utils.set_failed_state(task, msg) else: task.process_event('done') LOG.info(_LI('Deployment to node %s done'), task.node.uuid)
def deploy(self, task): """Start deployment of the task's node. This method sets the boot device to 'NETWORK' and then calls PXEDeploy's deploy method to deploy on the given node. :param task: a TaskManager instance containing the node to act on. :returns: deploy state DEPLOYWAIT. """ manager_utils.node_set_boot_device(task, boot_devices.PXE) return super(IloPXEDeploy, self).deploy(task)
def _do_pxe_boot(task, ports=None): """Reboot the node into the PXE ramdisk. :param task: a TaskManager instance :param ports: a list of Neutron port dicts to update DHCP options on. If None, will get the list of ports from the Ironic port objects. """ dhcp_opts = pxe_utils.dhcp_options_for_instance(task) provider = dhcp_factory.DHCPFactory() provider.update_dhcp(task, dhcp_opts, ports) manager_utils.node_set_boot_device(task, boot_devices.PXE, persistent=True) manager_utils.node_power_action(task, states.REBOOT)
def test_node_set_boot_device_valid(self): mgr_utils.mock_the_extension_manager(driver="fake_ipmitool") self.driver = driver_factory.get_driver("fake_ipmitool") ipmi_info = utils.get_test_ipmi_info() node = obj_utils.create_test_node( self.context, uuid=uuidutils.generate_uuid(), driver="fake_ipmitool", driver_info=ipmi_info ) task = task_manager.TaskManager(self.context, node.uuid) with mock.patch.object(self.driver.management, "set_boot_device") as mock_sbd: conductor_utils.node_set_boot_device(task, device="pxe") mock_sbd.assert_called_once_with(task, device="pxe", persistent=False)
def _attach_boot_iso(task): """Attaches boot ISO for a deployed node. This method checks the instance info of the baremetal node for a boot iso. It attaches the boot ISO on the baremetal node, and then sets the node to boot from virtual media cdrom. :param task: a TaskManager instance containing the node to act on. """ i_info = task.node.instance_info if 'ilo_boot_iso' in i_info: ilo_common.setup_vmedia_for_boot(task, i_info['ilo_boot_iso']) manager_utils.node_set_boot_device(task, boot_devices.CDROM)
def test_node_set_boot_device_adopting(self): mgr_utils.mock_the_extension_manager(driver="fake_ipmitool") self.driver = driver_factory.get_driver("fake_ipmitool") ipmi_info = utils.get_test_ipmi_info() node = obj_utils.create_test_node(self.context, uuid=uuidutils.generate_uuid(), driver='fake_ipmitool', driver_info=ipmi_info, provision_state=states.ADOPTING) task = task_manager.TaskManager(self.context, node.uuid) with mock.patch.object(self.driver.management, 'set_boot_device') as mock_sbd: conductor_utils.node_set_boot_device(task, device='pxe') self.assertFalse(mock_sbd.called)
def try_set_boot_device(task, device, persistent=True): # NOTE(faizan): 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. # Expected IPMI failure for uefi boot mode. Logging a message to # set the boot device manually and continue with deploy. try: manager_utils.node_set_boot_device(task, device, persistent=persistent) except exception.IPMIFailure: if driver_utils.get_node_capability(task.node, 'boot_mode') == 'uefi': LOG.warning(_LW("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) else: raise
def _continue_deploy(self, task, **kwargs): """Continues the iSCSI deployment from where ramdisk left off. Continues the iSCSI deployment from the conductor node, finds the boot ISO to boot the node, and sets the node to boot from boot ISO. :param task: a TaskManager instance containing the node to act on. :param kwargs: kwargs containing parameters for iSCSI deployment. """ node = task.node if node.provision_state != states.DEPLOYWAIT: LOG.error(_LE('Node %s is not waiting to be deployed.'), node.uuid) return ilo_common.cleanup_vmedia_boot(task) root_uuid = iscsi_deploy.continue_deploy(task, **kwargs) if not root_uuid: return try: boot_iso = _get_boot_iso(task, root_uuid) if not boot_iso: LOG.error(_LE("Cannot get boot ISO for node %s"), node.uuid) return ilo_common.setup_vmedia_for_boot(task, boot_iso) manager_utils.node_set_boot_device(task, boot_devices.CDROM) address = kwargs.get('address') deploy_utils.notify_deploy_complete(address) node.provision_state = states.ACTIVE node.target_provision_state = states.NOSTATE i_info = node.instance_info i_info['ilo_boot_iso'] = boot_iso node.instance_info = i_info node.save() LOG.info(_LI('Deployment to node %s done'), node.uuid) except Exception as e: LOG.error(_LE('Deploy failed for instance %(instance)s. ' 'Error: %(error)s'), {'instance': node.instance_uuid, 'error': e}) msg = _('Failed to continue iSCSI deployment.') iscsi_deploy.set_failed_state(task, msg)
def reboot_and_finish_deploy(self, task): """Helper method to trigger reboot on the node and finish deploy. This method initiates a reboot on the node. On success, it marks the deploy as complete. On failure, it logs the error and marks deploy as failure. :param task: a TaskManager object containing the node :raises: InstanceDeployFailure, if node reboot failed. """ wait = CONF.agent.post_deploy_get_power_state_retry_interval * 1000 attempts = CONF.agent.post_deploy_get_power_state_retries + 1 @retrying.retry( stop_max_attempt_number=attempts, retry_on_result=lambda state: state != states.POWER_OFF, wait_fixed=wait ) def _wait_until_powered_off(task): return task.driver.power.get_power_state(task) node = task.node try: try: self._client.power_off(node) _wait_until_powered_off(task) except Exception as e: LOG.warning( 'Failed to soft power off node %(node_uuid)s ' 'in at least %(timeout)d seconds. Error: %(error)s', {'node_uuid': node.uuid, 'timeout': (wait * (attempts - 1)) / 1000, 'error': e}) manager_utils.node_power_action(task, states.POWER_OFF) manager_utils.node_set_boot_device(task, 'disk', persistent=True) manager_utils.node_power_action(task, states.POWER_ON) except Exception as e: msg = (_('Error rebooting node %(node)s after deploy. ' 'Error: %(error)s') % {'node': node.uuid, 'error': e}) agent_base_vendor.log_and_raise_deployment_error(task, msg) task.process_event('done') LOG.info('Deployment to node %s done', task.node.uuid)
def reboot_to_instance(self, task): node = task.node LOG.info('Ansible complete deploy on node %s', node.uuid) LOG.debug('Rebooting node %s to instance', node.uuid) manager_utils.node_set_boot_device(task, 'disk', persistent=True) self.reboot_and_finish_deploy(task) task.driver.boot.clean_up_ramdisk(task) if not node.deploy_step: # TODO(rloo): delete this 'if' part after deprecation period, when # we expect all (out-of-tree) drivers to support deploy steps. # After which we will always notify_conductor_resume_deploy(). task.process_event('done') LOG.info('Deployment to node %s done', task.node.uuid) else: manager_utils.notify_conductor_resume_deploy(task)
def deploy(self, task): """Perform a deployment to a node. Perform the necessary work to deploy an image onto the specified node. This method will be called after prepare(), which may have already performed any preparatory steps, such as pre-caching some data for the node. :param task: a TaskManager instance. :returns: status of the deploy. One of ironic.common.states. """ dhcp_opts = pxe_utils.dhcp_options_for_instance() neutron.update_neutron(task, dhcp_opts) manager_utils.node_set_boot_device(task, 'pxe', persistent=True) manager_utils.node_power_action(task, states.REBOOT) return states.DEPLOYWAIT
def reboot_to_instance(self, task, **kwargs): node = task.node LOG.debug('Preparing to reboot to instance for node %s', node.uuid) 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 LOG.debug('Rebooting node %s to disk', node.uuid) manager_utils.node_set_boot_device(task, 'disk', persistent=True) self.reboot_and_finish_deploy(task)
def deploy(self, task, node): """Perform start deployment a node. Creates a temporary keystone token file, updates the Neutron DHCP port options for next boot, and issues a reboot request to the power driver. This causes the node to boot into the deployment ramdisk and triggers the next phase of PXE-based deployment via VendorPassthru._continue_deploy(). :param task: a TaskManager instance. :param node: the Node to act upon. :returns: deploy state DEPLOYING. """ # TODO(yuriyz): more secure way needed for pass auth token # to deploy ramdisk _create_token_file(task, node) _update_neutron(task, node) manager_utils.node_set_boot_device(task, node, 'pxe', persistent=True) manager_utils.node_power_action(task, node, states.REBOOT) return states.DEPLOYWAIT
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 database. :param task: a task from TaskManager. :returns: None """ _cleanup_vmedia_boot(task) 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: driver_internal_info = node.driver_internal_info root_uuid_or_disk_id = driver_internal_info['root_uuid_or_disk_id'] self._configure_vmedia_boot(task, root_uuid_or_disk_id)
def reboot_to_instance(self, task, **kwargs): 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 not iwdi: root_uuid = self._get_uuid_from_result(task, 'root_uuid') if deploy_utils.get_boot_mode_for_deploy(node) == 'uefi': efi_sys_uuid = (self._get_uuid_from_result( task, 'efi_system_partition_uuid')) else: efi_sys_uuid = None task.node.driver_internal_info['root_uuid_or_disk_id'] = root_uuid task.node.save() self.prepare_instance_to_boot(task, root_uuid, efi_sys_uuid) LOG.info(_LI('Image successfully written to node %s'), node.uuid) LOG.debug('Rebooting node %s to instance', node.uuid) if iwdi: manager_utils.node_set_boot_device(task, 'disk', persistent=True) self.reboot_and_finish_deploy(task) # NOTE(TheJulia): If we deployed a whole disk image, we # should expect a whole disk image and clean-up the tftp files # on-disk incase the node is disregarding the boot preference. # TODO(rameshg87): Not all in-tree drivers using reboot_to_instance # have a boot interface. So include a check for now. Remove this # check once all in-tree drivers have a boot interface. if task.driver.boot and iwdi: task.driver.boot.clean_up_ramdisk(task)
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_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. """ ilo_common.cleanup_vmedia_boot(task) # For iscsi_ilo driver, we 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, **kwargs): node = task.node LOG.debug('Preparing to reboot to instance for node %s', node.uuid) 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 LOG.debug('Rebooting node %s to disk', node.uuid) manager_utils.node_set_boot_device(task, 'disk', persistent=True) manager_utils.node_power_action(task, states.REBOOT) task.process_event('done')
def _attach_boot_iso_if_needed(task): """Attaches boot ISO for a deployed node. This method checks the instance info of the baremetal node for a boot iso. If the instance info has a value of key 'ilo_boot_iso', it indicates that 'boot_option' is 'netboot'. Therefore it attaches the boot ISO on the baremetal node and then sets the node to boot from virtual media cdrom. :param task: a TaskManager instance containing the node to act on. """ i_info = task.node.instance_info node_state = task.node.provision_state # NOTE: On instance rebuild, ilo_boot_iso will be present in # instance_info but the node will be in DEPLOYING state. # In such a scenario, the ilo_boot_iso shouldn't be # attached to the node while powering on the node (the node # should boot from deploy ramdisk instead, which will already # be attached by the deploy driver). if 'ilo_boot_iso' in i_info and node_state == states.ACTIVE: ilo_common.setup_vmedia_for_boot(task, i_info['ilo_boot_iso']) manager_utils.node_set_boot_device(task, boot_devices.CDROM)
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 _check_boot_status(self, task): if not isinstance(task.driver.boot, PXEBaseMixin): return if not _should_retry_boot(task.node): return task.upgrade_lock(purpose='retrying PXE boot') # Retry critical checks after acquiring the exclusive lock. if (task.node.maintenance or task.node.provision_state not in self._RETRY_ALLOWED_STATES or not _should_retry_boot(task.node)): return LOG.info('Booting the ramdisk on node %(node)s is taking more than ' '%(timeout)d seconds, retrying boot', {'node': task.node.uuid, 'timeout': CONF.pxe.boot_retry_timeout}) manager_utils.node_power_action(task, states.POWER_OFF) manager_utils.node_set_boot_device(task, boot_devices.PXE, persistent=False) manager_utils.node_power_action(task, states.POWER_ON)
def deploy(self, task): """Start deployment of the task's node'. Fetches instance image, creates a temporary keystone token file, updates the Neutron DHCP port options for next boot, and issues a reboot request to the power driver. This causes the node to boot into the deployment ramdisk and triggers the next phase of PXE-based deployment via VendorPassthru._continue_deploy(). :param task: a TaskManager instance containing the node to act on. :returns: deploy state DEPLOYING. """ _cache_instance_image(task.context, task.node) _check_image_size(task) # TODO(yuriyz): more secure way needed for pass auth token # to deploy ramdisk _create_token_file(task) _update_neutron(task) manager_utils.node_set_boot_device(task, 'pxe', persistent=True) manager_utils.node_power_action(task, states.REBOOT) return states.DEPLOYWAIT
def pass_deploy_info(self, task, **kwargs): """Continues the iSCSI deployment from where ramdisk left off. This method continues the iSCSI deployment from the conductor node and writes the deploy image to the bare metal's disk. After that, it does the following depending on boot_option for deploy: - If the boot_option requested for this deploy is 'local', then it sets the node to boot from disk (ramdisk installs the boot loader present within the image to the bare metal's disk). - If the boot_option requested is 'netboot' or no boot_option is requested, 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 TaskManager instance containing the node to act on. :param kwargs: kwargs containing parameters for iSCSI deployment. :raises: InvalidState """ node = task.node LOG.warning(_LW("The node %s is using the bash deploy ramdisk for " "its deployment. This deploy ramdisk has been " "deprecated. Please use the ironic-python-agent " "(IPA) ramdisk instead."), node.uuid) task.process_event('resume') LOG.debug('Continuing iSCSI virtual media deployment on node %s', node.uuid) is_whole_disk_image = node.driver_internal_info.get( 'is_whole_disk_image') uuid_dict = iscsi_deploy.continue_deploy(task, **kwargs) root_uuid_or_disk_id = uuid_dict.get( 'root uuid', uuid_dict.get('disk identifier')) try: _cleanup_vmedia_boot(task) if (deploy_utils.get_boot_option(node) == "local" or is_whole_disk_image): manager_utils.node_set_boot_device(task, boot_devices.DISK, persistent=True) # Ask the ramdisk to install bootloader and # wait for the call-back through the vendor passthru # 'pass_bootloader_install_info', if it's not a whole # disk image. if not is_whole_disk_image: deploy_utils.notify_ramdisk_to_proceed(kwargs['address']) task.process_event('wait') return else: self._configure_vmedia_boot(task, root_uuid_or_disk_id) except Exception as e: LOG.exception(_LE('Deploy failed for instance %(instance)s. ' 'Error: %(error)s'), {'instance': node.instance_uuid, 'error': e}) msg = _('Failed to continue iSCSI deployment.') deploy_utils.set_failed_state(task, msg) else: iscsi_deploy.finish_deploy(task, kwargs.get('address'))
def heartbeat(self, task, callback_url): """Continues the deployment of baremetal node.""" node = task.node # NOTE(pas-ha) this driver does not support cleaning, # so the only valid state to continue on heartbeat is DEPLOYWAIT if node.provision_state != states.DEPLOYWAIT: LOG.warning( _LW('Call back from %(node)s in invalid provision ' 'state %(state)s'), { 'node': node.uuid, 'state': node.provision_state }) return task.upgrade_lock(purpose='deploy') task.process_event('resume') params = _parse_driver_info(node) params['host'] = urlparse.urlparse(callback_url).netloc.split(':')[0] cmd = ('%s --data_driver ironic --config-file ' '/etc/fuel-agent/fuel-agent.conf' % params.pop('script')) if CONF.debug: cmd += ' --debug' instance_info = node.instance_info try: deploy_data = _get_deploy_data(task.context, instance_info['image_source']) image_data = { "/": { "uri": instance_info['image_url'], "format": "raw", "container": "raw" } } deploy_data['ks_meta']['image_data'] = image_data ssh = utils.ssh_connect(params) sftp = ssh.open_sftp() _sftp_upload(sftp, json.dumps(deploy_data), '/tmp/provision.json') # swift configdrive store should be disabled configdrive = instance_info.get('configdrive') if configdrive is not None: _sftp_upload(sftp, configdrive, '/tmp/config-drive.img') _ssh_execute(ssh, cmd, params) LOG.info(_LI('Fuel Agent pass on node %s'), node.uuid) manager_utils.node_set_boot_device(task, boot_devices.DISK, persistent=True) manager_utils.node_power_action(task, states.POWER_OFF) task.driver.network.remove_provisioning_network(task) task.driver.network.configure_tenant_networks(task) manager_utils.node_power_action(task, states.POWER_ON) except Exception as e: msg = (_('Deploy failed for node %(node)s. Error: %(error)s') % { 'node': node.uuid, 'error': e }) LOG.error(msg) deploy_utils.set_failed_state(task, msg, collect_logs=False) else: task.process_event('done') LOG.info(_LI('Deployment to node %s done'), task.node.uuid)
def _continue_deploy(self, task, **kwargs): manager_utils.node_set_boot_device(task, boot_devices.PXE, True) super(IloPXEVendorPassthru, self)._continue_deploy(task, **kwargs)
def _set_deploy_boot_device(self, task): """Set the boot device for deployment""" manager_utils.node_set_boot_device(task, boot_devices.CDROM)
def set_boot_device(self, task): manager_utils.node_set_boot_device(task, boot_devices.CDROM, persistent=True)
def inspect_hardware(self, task): """Inspect hardware. Inspect hardware to obtain the essential hardware properties and mac addresses. :param task: a task from TaskManager. :raises: HardwareInspectionFailure, if hardware inspection failed. :returns: states.MANAGEABLE, if hardware inspection succeeded. """ node = task.node kwargs = {} # Inspect additional capabilities task requires node with power on # status old_power_state = task.driver.power.get_power_state(task) if old_power_state == states.POWER_OFF: manager_utils.node_set_boot_device(task, boot_devices.BIOS, False) manager_utils.node_power_action(task, states.POWER_ON) LOG.info("The Node %(node_uuid)s being powered on for inspection", {'node_uuid': task.node.uuid}) kwargs['sleep_flag'] = True traits_obj = objects.TraitList.get_by_node_id(task.context, node.id) existing_traits = traits_obj.get_trait_names() props, macs, new_traits = _inspect_hardware(node, existing_traits, **kwargs) node.properties = dict(node.properties, **props) if existing_traits != new_traits: objects.TraitList.create(task.context, node.id, new_traits) node.save() for mac in macs: try: new_port = objects.Port(task.context, address=mac, node_id=node.id) new_port.create() LOG.info( "Port created for MAC address %(address)s " "for node %(node_uuid)s during inspection", { 'address': mac, 'node_uuid': node.uuid }) except exception.MACAlreadyExists: LOG.warning( "Port already existed for MAC address " "%(address)s for node %(node_uuid)s " "during inspection", { 'address': mac, 'node_uuid': node.uuid }) LOG.info("Node %s inspected", node.uuid) # restore old power state if old_power_state == states.POWER_OFF: manager_utils.node_power_action(task, states.POWER_OFF) LOG.info( "The Node %(node_uuid)s being powered off after " "inspection", {'node_uuid': task.node.uuid}) return states.MANAGEABLE
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 pass_deploy_info(self, task, **kwargs): """Continues the deployment of baremetal node.""" node = task.node task.process_event('resume') err_msg = _('Failed to continue deployment with Fuel Agent.') agent_status = kwargs.get('status') if agent_status != 'ready': LOG.error( _LE('Deploy failed for node %(node)s. Fuel Agent is not ' 'in ready state, error: %(error)s'), { 'node': node.uuid, 'error': kwargs.get('error_message') }) deploy_utils.set_failed_state(task, err_msg) return params = _parse_driver_info(node) params['host'] = kwargs.get('address') cmd = ('%s --data_driver ironic --config-file ' '/etc/fuel-agent/fuel-agent.conf' % params.pop('script')) if CONF.debug: cmd += ' --debug' instance_info = node.instance_info try: deploy_data = _get_deploy_data(task.context, instance_info['image_source']) image_data = { "/": { "uri": instance_info['image_url'], "format": "raw", "container": "raw" } } deploy_data['ks_meta']['image_data'] = image_data ssh = utils.ssh_connect(params) sftp = ssh.open_sftp() _sftp_upload(sftp, json.dumps(deploy_data), '/tmp/provision.json') # swift configdrive store should be disabled configdrive = instance_info.get('configdrive') if configdrive is not None: _sftp_upload(sftp, configdrive, '/tmp/config-drive.img') _ssh_execute(ssh, cmd, params) LOG.info(_LI('Fuel Agent pass on node %s'), node.uuid) manager_utils.node_set_boot_device(task, boot_devices.DISK, persistent=True) manager_utils.node_power_action(task, states.REBOOT) except Exception as e: msg = (_('Deploy failed for node %(node)s. Error: %(error)s') % { 'node': node.uuid, 'error': e }) LOG.error(msg) deploy_utils.set_failed_state(task, msg) else: task.process_event('done') LOG.info(_LI('Deployment to node %s done'), task.node.uuid)
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 instance_image_info = {} if boot_option == "ramdisk": instance_image_info = pxe_utils.get_instance_image_info( task, ipxe_enabled=self.ipxe_enabled) pxe_utils.cache_ramdisk_kernel(task, instance_image_info, ipxe_enabled=self.ipxe_enabled) if deploy_utils.is_iscsi_boot(task) or boot_option == "ramdisk": pxe_utils.prepare_instance_pxe_config( task, instance_image_info, iscsi_boot=deploy_utils.is_iscsi_boot(task), ramdisk_boot=(boot_option == "ramdisk"), ipxe_enabled=self.ipxe_enabled) 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 = pxe_utils.get_instance_image_info( task, ipxe_enabled=self.ipxe_enabled) pxe_utils.cache_ramdisk_kernel(task, instance_image_info, ipxe_enabled=self.ipxe_enabled) # If it's going to PXE boot we need to update the DHCP server dhcp_opts = pxe_utils.dhcp_options_for_instance( task, ipxe_enabled=self.ipxe_enabled, ip_version=4) dhcp_opts += pxe_utils.dhcp_options_for_instance( task, ipxe_enabled=self.ipxe_enabled, ip_version=6) 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, ipxe_enabled=self.ipxe_enabled) boot_device = boot_devices.DISK else: pxe_utils.build_service_pxe_config( task, instance_image_info, root_uuid_or_disk_id, ipxe_enabled=self.ipxe_enabled) boot_device = boot_devices.PXE else: # NOTE(dtantsur): create a PXE configuration as a safety net for # hardware uncapable of persistent boot. If on a reboot it will try # to boot from PXE, this configuration will return it back. if CONF.pxe.enable_netboot_fallback: pxe_utils.build_service_pxe_config( task, instance_image_info, task.node.driver_internal_info.get('root_uuid_or_disk_id'), ipxe_enabled=self.ipxe_enabled, # PXE config for whole disk images is identical to what # we need to boot from local disk, so use True even # for partition images. is_whole_disk_image=True) else: # Clean up the deployment configuration pxe_utils.clean_up_pxe_config(task, ipxe_enabled=self.ipxe_enabled) 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: persistent = True if node.driver_info.get('force_persistent_boot_device', 'Default') == 'Never': persistent = False manager_utils.node_set_boot_device(task, boot_device, persistent=persistent)
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 })
def pass_deploy_info(self, task, **kwargs): manager_utils.node_set_boot_device(task, boot_devices.PXE, True) super(IloPXEVendorPassthru, self).pass_deploy_info(task, **kwargs)
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 deploy_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 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 pass_deploy_info(self, task, **kwargs): """Continues the iSCSI deployment from where ramdisk left off. This method continues the iSCSI deployment from the conductor node and writes the deploy image to the bare metal's disk. After that, it does the following depending on boot_option for deploy: - If the boot_option requested for this deploy is 'local', then it sets the node to boot from disk (ramdisk installs the boot loader present within the image to the bare metal's disk). - If the boot_option requested is 'netboot' or no boot_option is requested, 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 TaskManager instance containing the node to act on. :param kwargs: kwargs containing parameters for iSCSI deployment. :raises: InvalidState """ node = task.node task.process_event('resume') iwdi = node.driver_internal_info.get('is_whole_disk_image') ilo_common.cleanup_vmedia_boot(task) uuid_dict = iscsi_deploy.continue_deploy(task, **kwargs) root_uuid_or_disk_id = uuid_dict.get('root uuid', uuid_dict.get('disk identifier')) try: # Set boot mode ilo_common.update_boot_mode(task) # Need to enable secure boot, if being requested _update_secure_boot_mode(task, True) # For iscsi_ilo driver, we boot from disk every time if the image # deployed is a whole disk image. if iscsi_deploy.get_boot_option(node) == "local" or iwdi: manager_utils.node_set_boot_device(task, boot_devices.DISK, persistent=True) # Ask the ramdisk to install bootloader and # wait for the call-back through the vendor passthru # 'pass_bootloader_install_info', if it's not a whole # disk image. if not iwdi: deploy_utils.notify_ramdisk_to_proceed(kwargs['address']) task.process_event('wait') return else: self._configure_vmedia_boot(task, root_uuid_or_disk_id) except Exception as e: LOG.error( _LE('Deploy failed for instance %(instance)s. ' 'Error: %(error)s'), { 'instance': node.instance_uuid, 'error': e }) msg = _('Failed to continue iSCSI deployment.') deploy_utils.set_failed_state(task, msg) else: iscsi_deploy.finish_deploy(task, kwargs.get('address'))
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. :param mode: Label indicating a deploy or rescue operation being carried out on the node. Supported values are 'deploy' and 'rescue'. Defaults to 'deploy', indicating deploy operation is being carried out. :returns: 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 mode = deploy_utils.rescue_or_deploy_mode(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_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(_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: 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: _cache_ramdisk_kernel(task.context, node, pxe_info)
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)