def finish_deploy(task, address): """Notifies the ramdisk to reboot the node and makes the instance active. This method notifies the ramdisk to proceed to reboot and then makes the instance active. :param task: a TaskManager object. :param address: The IP address of the bare metal node. :raises: InstanceDeployFailure, if notifying ramdisk failed. """ node = task.node try: deploy_utils.notify_ramdisk_to_proceed(address) 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 notify ramdisk to reboot after bootloader ' 'installation. Error: %s') % e) deploy_utils.set_failed_state(task, msg) raise exception.InstanceDeployFailure(msg) # TODO(lucasagomes): When deploying a node with the DIB ramdisk # Ironic will not power control the node at the end of the deployment, # it's the DIB ramdisk that reboots the node. But, for the SSH driver # some changes like setting the boot device only gets applied when the # machine is powered off and on again. So the code below is enforcing # it. For Liberty we need to change the DIB ramdisk so that Ironic # always controls the power state of the node for all drivers. if deploy_utils.get_boot_option(node) == "local" and 'ssh' in node.driver: manager_utils.node_power_action(task, states.REBOOT) LOG.info(_LI('Deployment to node %s done'), node.uuid) task.process_event('done')
def test_continue_deploy_fail(self, deploy_mock, power_mock, mock_image_cache, mock_disk_layout, mock_collect_logs): kwargs = {'address': '123456', 'iqn': 'aaa-bbb'} deploy_mock.side_effect = exception.InstanceDeployFailure( "test deploy error") self.node.provision_state = states.DEPLOYWAIT self.node.target_provision_state = states.ACTIVE self.node.save() with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: params = iscsi_deploy.get_deploy_info(task.node, **kwargs) self.assertRaises(exception.InstanceDeployFailure, iscsi_deploy.continue_deploy, task, **kwargs) self.assertEqual(states.DEPLOYFAIL, task.node.provision_state) self.assertEqual(states.ACTIVE, task.node.target_provision_state) self.assertIsNotNone(task.node.last_error) deploy_mock.assert_called_once_with(**params) power_mock.assert_called_once_with(task, states.POWER_OFF) mock_image_cache.assert_called_once_with() mock_image_cache.return_value.clean_up.assert_called_once_with() self.assertFalse(mock_disk_layout.called) mock_collect_logs.assert_called_once_with(task.node)
def validate_bootloader_install_status(task, input_params): """Validate if bootloader was installed. This method first validates if deploy key sent in vendor passthru was correct one, and then validates whether bootloader installation was successful or not. :param task: A TaskManager object. :param input_params: A dictionary of params sent as input to passthru. :raises: InstanceDeployFailure, if bootloader installation was reported from ramdisk as failure. """ node = task.node if input_params['status'] != 'SUCCEEDED': msg = (_('Failed to install bootloader on node %(node)s. ' 'Error: %(error)s.') % { 'node': node.uuid, 'error': input_params.get('error') }) LOG.error(msg) deploy_utils.set_failed_state(task, msg) raise exception.InstanceDeployFailure(msg) LOG.info(_LI('Bootloader successfully installed on node %s'), node.uuid)
def update_firmware(self, task, **kwargs): """Updates the firmware. :param task: a TaskManager object. :raises: InvalidParameterValue if update firmware mode is not 'ilo'. Even applicable for invalid input cases. :raises: NodeCleaningFailure, on failure to execute of clean step. :raises: InstanceDeployFailure, on failure to execute of deploy step. """ node = task.node fw_location_objs_n_components = [] firmware_images = kwargs['firmware_images'] # Note(deray): Processing of firmware images happens here. As part # of processing checksum validation is also done for the firmware file. # Processing of firmware file essentially means downloading the file # on the conductor, validating the checksum of the downloaded content, # extracting the raw firmware file from its compact format, if it is, # and hosting the file on a web server or a swift store based on the # need of the baremetal server iLO firmware update method. try: for firmware_image_info in firmware_images: url, checksum, component = ( firmware_processor.get_and_validate_firmware_image_info( firmware_image_info, kwargs['firmware_update_mode'])) LOG.debug("Processing of firmware file: %(firmware_file)s on " "node: %(node)s ... in progress", {'firmware_file': url, 'node': node.uuid}) fw_processor = firmware_processor.FirmwareProcessor(url) fw_location_obj = fw_processor.process_fw_on(node, checksum) fw_location_objs_n_components.append( (fw_location_obj, component)) LOG.debug("Processing of firmware file: %(firmware_file)s on " "node: %(node)s ... done", {'firmware_file': url, 'node': node.uuid}) except exception.IronicException as ilo_exc: # delete all the files extracted so far from the extracted list # and re-raise the exception for fw_loc_obj_n_comp_tup in fw_location_objs_n_components: fw_loc_obj_n_comp_tup[0].remove() LOG.error("Processing of firmware image: %(firmware_image)s " "on node: %(node)s ... failed", {'firmware_image': firmware_image_info, 'node': node.uuid}) if node.clean_step: raise exception.NodeCleaningFailure(node=node.uuid, reason=ilo_exc) raise exception.InstanceDeployFailure(reason=ilo_exc) # Updating of firmware images happen here. try: for fw_location_obj, component in fw_location_objs_n_components: fw_location = fw_location_obj.fw_image_location LOG.debug("Firmware update for %(firmware_file)s on " "node: %(node)s ... in progress", {'firmware_file': fw_location, 'node': node.uuid}) _execute_ilo_step( node, 'update_firmware', fw_location, component) LOG.debug("Firmware update for %(firmware_file)s on " "node: %(node)s ... done", {'firmware_file': fw_location, 'node': node.uuid}) except (exception.NodeCleaningFailure, exception.InstanceDeployFailure): with excutils.save_and_reraise_exception(): LOG.error("Firmware update for %(firmware_file)s on " "node: %(node)s failed.", {'firmware_file': fw_location, 'node': node.uuid}) finally: for fw_loc_obj_n_comp_tup in fw_location_objs_n_components: fw_loc_obj_n_comp_tup[0].remove() # Firmware might have ejected the virtual media, if it was used. # Re-create the environment for agent boot, if required task.driver.boot.clean_up_ramdisk(task) deploy_opts = deploy_utils.build_agent_options(node) task.driver.boot.prepare_ramdisk(task, deploy_opts) LOG.info("All Firmware update operations completed successfully " "for node: %s.", node.uuid)
def _log_and_raise_deployment_error(self, task, msg): """Helper method to log the error and raise exception.""" LOG.error(msg) deploy_utils.set_failed_state(task, msg) raise exception.InstanceDeployFailure(msg)
def do_agent_iscsi_deploy(task, agent_client): """Method invoked when deployed with the agent ramdisk. This method is invoked by drivers for doing iSCSI deploy using agent ramdisk. This method assumes that the agent is booted up on the node and is heartbeating. :param task: a TaskManager object containing the node. :param agent_client: an instance of agent_client.AgentClient which will be used during iscsi deploy (for exposing node's target disk via iSCSI, for install boot loader, etc). :returns: a dictionary containing the following keys: For partition image: * 'root uuid': UUID of root partition * 'efi system partition uuid': UUID of the uefi system partition (if boot mode is uefi). .. note:: If key exists but value is None, it means partition doesn't exist. For whole disk image: * 'disk identifier': ID of the disk to which image was deployed. :raises: InstanceDeployFailure if it encounters some error during the deploy. """ node = task.node i_info = deploy_utils.parse_instance_info(node) wipe_disk_metadata = not i_info['preserve_ephemeral'] iqn = 'iqn.2008-10.org.openstack:%s' % node.uuid portal_port = CONF.iscsi.portal_port conv_flags = CONF.iscsi.conv_flags result = agent_client.start_iscsi_target( node, iqn, portal_port, wipe_disk_metadata=wipe_disk_metadata) if result['command_status'] == 'FAILED': msg = (_("Failed to start the iSCSI target to deploy the " "node %(node)s. Error: %(error)s") % {'node': node.uuid, 'error': result['command_error']}) deploy_utils.set_failed_state(task, msg) raise exception.InstanceDeployFailure(reason=msg) address = parse.urlparse(node.driver_internal_info['agent_url']) address = address.hostname uuid_dict_returned = continue_deploy(task, iqn=iqn, address=address, conv_flags=conv_flags) root_uuid_or_disk_id = uuid_dict_returned.get( 'root uuid', uuid_dict_returned.get('disk identifier')) # TODO(lucasagomes): Move this bit saving the root_uuid to # continue_deploy() driver_internal_info = node.driver_internal_info driver_internal_info['root_uuid_or_disk_id'] = root_uuid_or_disk_id node.driver_internal_info = driver_internal_info node.save() return uuid_dict_returned
def start_deploy(task, manager, configdrive=None, event='deploy'): """Start deployment or rebuilding on a node. This function does not check the node suitability for deployment, it's left up to the caller. :param task: a TaskManager instance. :param manager: a ConductorManager to run tasks on. :param configdrive: a configdrive, if requested. :param event: event to process: deploy or rebuild. """ node = task.node # Record of any pre-existing agent_url should be removed # except when we are in fast track conditions. if not utils.is_fast_track(task): utils.remove_agent_url(node) if event == 'rebuild': # Note(gilliard) Clear these to force the driver to # check whether they have been changed in glance # NOTE(vdrok): If image_source is not from Glance we should # not clear kernel and ramdisk as they're input manually if glance_utils.is_glance_image( node.instance_info.get('image_source')): instance_info = node.instance_info instance_info.pop('kernel', None) instance_info.pop('ramdisk', None) node.instance_info = instance_info driver_internal_info = node.driver_internal_info # Infer the image type to make sure the deploy driver # validates only the necessary variables for different # image types. # NOTE(sirushtim): The iwdi variable can be None. It's up to # the deploy driver to validate this. iwdi = images.is_whole_disk_image(task.context, node.instance_info) driver_internal_info['is_whole_disk_image'] = iwdi node.driver_internal_info = driver_internal_info node.save() try: task.driver.power.validate(task) task.driver.deploy.validate(task) utils.validate_instance_info_traits(task.node) conductor_steps.validate_deploy_templates(task) except exception.InvalidParameterValue as e: raise exception.InstanceDeployFailure( _("Failed to validate deploy or power info for node " "%(node_uuid)s. Error: %(msg)s") % { 'node_uuid': node.uuid, 'msg': e }, code=e.code) try: task.process_event(event, callback=manager._spawn_worker, call_args=(do_node_deploy, task, manager.conductor.id, configdrive), err_handler=utils.provisioning_error_handler) except exception.InvalidState: raise exception.InvalidStateRequested(action=event, node=task.node.uuid, state=task.node.provision_state)
def prepare_instance(self, task): """Prepares the boot of instance over virtual media. This method prepares the boot of the instance after reading relevant information from the node's instance_info. The internal logic is as follows: - If `boot_option` requested for this deploy is 'local', then set the node to boot from disk. - Unless `boot_option` requested for this deploy is 'ramdisk', pass root disk/partition ID to virtual media boot image - Otherwise build boot image, insert it into virtual media device and set node to boot from CD. :param task: a task from TaskManager. :returns: None :raises: InstanceDeployFailure, if its try to boot iSCSI volume in 'BIOS' boot mode. """ node = task.node boot_mode_utils.sync_boot_mode(task) boot_option = deploy_utils.get_boot_option(node) self.clean_up_instance(task) iwdi = node.driver_internal_info.get('is_whole_disk_image') if boot_option == "local" or iwdi: self._set_boot_device(task, boot_devices.DISK, persistent=True) LOG.debug( "Node %(node)s is set to permanently boot from local " "%(device)s", { 'node': task.node.uuid, 'device': boot_devices.DISK }) return params = {} if boot_option != 'ramdisk': root_uuid = node.driver_internal_info.get('root_uuid_or_disk_id') if not root_uuid and task.driver.storage.should_write_image(task): LOG.warning( "The UUID of the root partition could not be found for " "node %s. Booting instance from disk anyway.", node.uuid) self._set_boot_device(task, boot_devices.DISK, persistent=True) return params.update(root_uuid=root_uuid) managers = redfish_utils.get_system(task.node).managers deploy_info = _parse_deploy_info(node) configdrive = node.instance_info.get('configdrive') iso_ref = image_utils.prepare_boot_iso(task, deploy_info, **params) _eject_vmedia(task, managers, sushy.VIRTUAL_MEDIA_CD) _insert_vmedia(task, managers, iso_ref, sushy.VIRTUAL_MEDIA_CD) if configdrive and boot_option == 'ramdisk': _eject_vmedia(task, managers, sushy.VIRTUAL_MEDIA_USBSTICK) cd_ref = image_utils.prepare_configdrive_image(task, configdrive) try: _insert_vmedia(task, managers, cd_ref, sushy.VIRTUAL_MEDIA_USBSTICK) except exception.InvalidParameterValue: raise exception.InstanceDeployFailure( _('Cannot attach configdrive for node %s: no suitable ' 'virtual USB slot has been found') % node.uuid) del managers self._set_boot_device(task, boot_devices.CDROM, persistent=True) LOG.debug( "Node %(node)s is set to permanently boot from " "%(device)s", { 'node': task.node.uuid, 'device': boot_devices.CDROM })
def test__do_node_deploy_driver_ironic_exception(self): self._test__do_node_deploy_driver_exception( exception.InstanceDeployFailure('test'))
def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, image_path, node_uuid, preserve_ephemeral=False, configdrive=None, boot_option="netboot", boot_mode="bios"): """Create partitions and copy an image to the root partition. :param dev: Path for the device to work on. :param root_mb: Size of the root partition in megabytes. :param swap_mb: Size of the swap partition in megabytes. :param ephemeral_mb: Size of the ephemeral partition in megabytes. If 0, no ephemeral partition will be created. :param ephemeral_format: The type of file system to format the ephemeral partition. :param image_path: Path for the instance's disk image. :param node_uuid: node's uuid. Used for logging. :param preserve_ephemeral: If True, no filesystem is written to the ephemeral block device, preserving whatever content it had (if the partition table has not changed). :param configdrive: Optional. Base64 encoded Gzipped configdrive content or configdrive HTTP URL. :param boot_option: Can be "local" or "netboot". "netboot" by default. :param boot_mode: Can be "bios" or "uefi". "bios" by default. :returns: a dictionary containing the following keys: 'root uuid': UUID of root partition 'efi system partition uuid': UUID of the uefi system partition (if boot mode is uefi). NOTE: If key exists but value is None, it means partition doesn't exist. """ # the only way for preserve_ephemeral to be set to true is if we are # rebuilding an instance with --preserve_ephemeral. commit = not preserve_ephemeral # now if we are committing the changes to disk clean first. if commit: destroy_disk_metadata(dev, node_uuid) try: # If requested, get the configdrive file and determine the size # of the configdrive partition configdrive_mb = 0 configdrive_file = None if configdrive: configdrive_mb, configdrive_file = _get_configdrive( configdrive, node_uuid) part_dict = make_partitions(dev, root_mb, swap_mb, ephemeral_mb, configdrive_mb, commit=commit, boot_option=boot_option, boot_mode=boot_mode) ephemeral_part = part_dict.get('ephemeral') swap_part = part_dict.get('swap') configdrive_part = part_dict.get('configdrive') root_part = part_dict.get('root') if not is_block_device(root_part): raise exception.InstanceDeployFailure( _("Root device '%s' not found") % root_part) for part in ('swap', 'ephemeral', 'configdrive', 'efi system partition'): part_device = part_dict.get(part) LOG.debug( "Checking for %(part)s device (%(dev)s) on node " "%(node)s.", { 'part': part, 'dev': part_device, 'node': node_uuid }) if part_device and not is_block_device(part_device): raise exception.InstanceDeployFailure( _("'%(partition)s' device '%(part_device)s' not found") % { 'partition': part, 'part_device': part_device }) # If it's a uefi localboot, then we have created the efi system # partition. Create a fat filesystem on it. if boot_mode == "uefi" and boot_option == "local": efi_system_part = part_dict.get('efi system partition') mkfs(dev=efi_system_part, fs='vfat', label='efi-part') if configdrive_part: # Copy the configdrive content to the configdrive partition dd(configdrive_file, configdrive_part) finally: # If the configdrive was requested make sure we delete the file # after copying the content to the partition if configdrive_file: utils.unlink_without_raise(configdrive_file) populate_image(image_path, root_part) if swap_part: mkfs(dev=swap_part, fs='swap', label='swap1') if ephemeral_part and not preserve_ephemeral: mkfs(dev=ephemeral_part, fs=ephemeral_format, label="ephemeral0") uuids_to_return = { 'root uuid': root_part, 'efi system partition uuid': part_dict.get('efi system partition') } try: for part, part_dev in six.iteritems(uuids_to_return): if part_dev: uuids_to_return[part] = block_uuid(part_dev) except processutils.ProcessExecutionError: with excutils.save_and_reraise_exception(): LOG.error(_LE("Failed to detect %s"), part) return uuids_to_return
def delete_configuration(self, task): """Delete the RAID configuration. :param task: a TaskManager instance containing the node to act on. :raises: NodeCleaningFailure, on failure to execute clean step. :raises: InstanceDeployFailure, on failure to execute deploy step. """ node = task.node LOG.debug("OOB RAID delete_configuration invoked for node %s.", node.uuid) driver_internal_info = node.driver_internal_info ilo_object = ilo_common.get_ilo_object(node) try: # Raid configuration in progress, checking status if not driver_internal_info.get('ilo_raid_delete_in_progress'): ilo_object.delete_raid_configuration() self._prepare_for_read_raid(task, 'delete_raid') return deploy_utils.get_async_step_return_state(node) else: # Raid configuration is done, updating raid_config raid_conf = ilo_object.read_raid_configuration() fields = ['ilo_raid_delete_in_progress'] if node.clean_step: fields.append('skip_current_clean_step') else: fields.append('skip_current_deploy_step') self._pop_driver_internal_values(task, *fields) if not len(raid_conf['logical_disks']): node.raid_config = {} LOG.debug("Node %(uuid)s raid delete clean step is done.", {'uuid': node.uuid}) else: # Raid configuration failed err_msg = (_("Step delete_configuration failed " "on node %(node)s with error: " "Unable to delete these logical disks: " "%(disks)s") % { 'node': node.uuid, 'disks': raid_conf['logical_disks'] }) if node.clean_step: raise exception.NodeCleaningFailure(err_msg) else: raise exception.InstanceDeployFailure(reason=err_msg) except ilo_error.IloLogicalDriveNotFoundError: LOG.info("No logical drive found to delete on node %(node)s", {'node': node.uuid}) except ilo_error.IloError as ilo_exception: operation = (_("Failed to delete raid configuration on node %s") % node.uuid) self._pop_driver_internal_values(task, 'ilo_raid_delete_in_progress', 'skip_current_clean_step') fields = ['ilo_raid_delete_in_progress'] if node.clean_step: fields.append('skip_current_clean_step') else: fields.append('skip_current_deploy_step') self._pop_driver_internal_values(task, *fields) self._set_step_failed(task, operation, ilo_exception)
def create_configuration(self, task, create_root_volume=True, create_nonroot_volumes=True): """Create a RAID configuration on a bare metal using agent ramdisk. This method creates a RAID configuration on the given node. :param task: a TaskManager instance. :param create_root_volume: If True, a root volume is created during RAID configuration. Otherwise, no root volume is created. Default is True. :param create_nonroot_volumes: If True, non-root volumes are created. If False, no non-root volumes are created. Default is True. :raises: MissingParameterValue, if node.target_raid_config is missing or was found to be empty after skipping root volume and/or non-root volumes. :raises: NodeCleaningFailure, on failure to execute clean step. :raises: InstanceDeployFailure, on failure to execute deploy step. """ node = task.node target_raid_config = raid.filter_target_raid_config( node, create_root_volume=create_root_volume, create_nonroot_volumes=create_nonroot_volumes) driver_internal_info = node.driver_internal_info driver_internal_info['target_raid_config'] = target_raid_config node.driver_internal_info = driver_internal_info node.save() LOG.debug( "Calling OOB RAID create_configuration for node %(node)s " "with the following target RAID configuration: %(target)s", { 'node': node.uuid, 'target': target_raid_config }) ilo_object = ilo_common.get_ilo_object(node) try: # Raid configuration in progress, checking status if not driver_internal_info.get('ilo_raid_create_in_progress'): ilo_object.create_raid_configuration(target_raid_config) self._prepare_for_read_raid(task, 'create_raid') return deploy_utils.get_async_step_return_state(node) else: # Raid configuration is done, updating raid_config raid_conf = (ilo_object.read_raid_configuration( raid_config=target_raid_config)) fields = ['ilo_raid_create_in_progress'] if node.clean_step: fields.append('skip_current_clean_step') else: fields.append('skip_current_deploy_step') self._pop_driver_internal_values(task, *fields) if len(raid_conf['logical_disks']): raid.update_raid_info(node, raid_conf) LOG.debug("Node %(uuid)s raid create clean step is done.", {'uuid': node.uuid}) else: # Raid configuration failed msg = (_("Step create_configuration failed " "on node %(node)s with error: " "Unable to create raid") % { 'node': node.uuid }) if node.clean_step: raise exception.NodeCleaningFailure(msg) else: raise exception.InstanceDeployFailure(reason=msg) except ilo_error.IloError as ilo_exception: operation = (_("Failed to create raid configuration on node %s") % node.uuid) fields = ['ilo_raid_create_in_progress'] if node.clean_step: fields.append('skip_current_clean_step') else: fields.append('skip_current_deploy_step') self._pop_driver_internal_values(task, *fields) self._set_step_failed(task, operation, ilo_exception)
def do_agent_iscsi_deploy(task, agent_client): """Method invoked when deployed with the agent ramdisk. This method is invoked by drivers for doing iSCSI deploy using agent ramdisk. This method assumes that the agent is booted up on the node and is heartbeating. :param task: a TaskManager object containing the node. :param agent_client: an instance of agent_client.AgentClient which will be used during iscsi deploy (for exposing node's target disk via iSCSI, for install boot loader, etc). :returns: a dictionary containing the following keys: For partition image: 'root uuid': UUID of root partition 'efi system partition uuid': UUID of the uefi system partition (if boot mode is uefi). NOTE: If key exists but value is None, it means partition doesn't exist. For whole disk image: 'disk identifier': ID of the disk to which image was deployed. :raises: InstanceDeployFailure, if it encounters some error during the deploy. """ node = task.node iscsi_options = build_deploy_ramdisk_options(node) iqn = iscsi_options['iscsi_target_iqn'] result = agent_client.start_iscsi_target(node, iqn) if result['command_status'] == 'FAILED': msg = (_("Failed to start the iSCSI target to deploy the " "node %(node)s. Error: %(error)s") % { 'node': node.uuid, 'error': result['command_error'] }) deploy_utils.set_failed_state(task, msg) raise exception.InstanceDeployFailure(reason=msg) address = parse.urlparse(node.driver_internal_info['agent_url']) address = address.hostname # TODO(lucasagomes): The 'error' and 'key' parameters in the # dictionary below are just being passed because it's needed for # the iscsi_deploy.continue_deploy() method, we are fooling it # for now. The agent driver doesn't use/need those. So we need to # refactor this bits here later. iscsi_params = { 'error': result['command_error'], 'iqn': iqn, 'key': iscsi_options['deployment_key'], 'address': address } uuid_dict_returned = continue_deploy(task, **iscsi_params) root_uuid_or_disk_id = uuid_dict_returned.get( 'root uuid', uuid_dict_returned.get('disk identifier')) # TODO(lucasagomes): Move this bit saving the root_uuid to # iscsi_deploy.continue_deploy() driver_internal_info = node.driver_internal_info driver_internal_info['root_uuid_or_disk_id'] = root_uuid_or_disk_id node.driver_internal_info = driver_internal_info node.save() return uuid_dict_returned
def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, image_path, node_uuid, preserve_ephemeral=False): """Create partitions and copy an image to the root partition. :param dev: Path for the device to work on. :param root_mb: Size of the root partition in megabytes. :param swap_mb: Size of the swap partition in megabytes. :param ephemeral_mb: Size of the ephemeral partition in megabytes. If 0, no ephemeral partition will be created. :param ephemeral_format: The type of file system to format the ephemeral partition. :param image_path: Path for the instance's disk image. :param node_uuid: node's uuid. Used for logging. :param preserve_ephemeral: If True, no filesystem is written to the ephemeral block device, preserving whatever content it had (if the partition table has not changed). """ if not is_block_device(dev): raise exception.InstanceDeployFailure( _("Parent device '%s' not found") % dev) # the only way for preserve_ephemeral to be set to true is if we are # rebuilding an instance with --preserve_ephemeral. commit = not preserve_ephemeral # now if we are committing the changes to disk clean first. if commit: destroy_disk_metadata(dev, node_uuid) part_dict = make_partitions(dev, root_mb, swap_mb, ephemeral_mb, commit=commit) ephemeral_part = part_dict.get('ephemeral') swap_part = part_dict.get('swap') root_part = part_dict.get('root') if not is_block_device(root_part): raise exception.InstanceDeployFailure( _("Root device '%s' not found") % root_part) if swap_part and not is_block_device(swap_part): raise exception.InstanceDeployFailure( _("Swap device '%s' not found") % swap_part) if ephemeral_part and not is_block_device(ephemeral_part): raise exception.InstanceDeployFailure( _("Ephemeral device '%s' not found") % ephemeral_part) dd(image_path, root_part) if swap_part: mkswap(swap_part) if ephemeral_part and not preserve_ephemeral: mkfs_ephemeral(ephemeral_part, ephemeral_format) try: root_uuid = block_uuid(root_part) except processutils.ProcessExecutionError: with excutils.save_and_reraise_exception(): LOG.error(_("Failed to detect root device UUID.")) return root_uuid
def do_node_deploy(task, conductor_id=None, configdrive=None, deploy_steps=None): """Prepare the environment and deploy a node.""" node = task.node utils.wipe_deploy_internal_info(task) try: if configdrive: if isinstance(configdrive, dict): configdrive = utils.build_configdrive(node, configdrive) _store_configdrive(node, configdrive) except (exception.SwiftOperationError, exception.ConfigInvalid) as e: with excutils.save_and_reraise_exception(): utils.deploying_error_handler( task, ('Error while uploading the configdrive for %(node)s ' 'to Swift') % {'node': node.uuid}, _('Failed to upload the configdrive to Swift. ' 'Error: %s') % e, clean_up=False) except db_exception.DBDataError as e: with excutils.save_and_reraise_exception(): # NOTE(hshiina): This error happens when the configdrive is # too large. Remove the configdrive from the # object to update DB successfully in handling # the failure. node.obj_reset_changes() utils.deploying_error_handler( task, ('Error while storing the configdrive for %(node)s into ' 'the database: %(err)s') % { 'node': node.uuid, 'err': e }, _("Failed to store the configdrive in the database. " "%s") % e, clean_up=False) except Exception as e: with excutils.save_and_reraise_exception(): utils.deploying_error_handler( task, ('Unexpected error while preparing the configdrive for ' 'node %(node)s') % {'node': node.uuid}, _("Failed to prepare the configdrive. Exception: %s") % e, traceback=True, clean_up=False) try: task.driver.deploy.prepare(task) except exception.IronicException as e: with excutils.save_and_reraise_exception(): utils.deploying_error_handler( task, ('Error while preparing to deploy to node %(node)s: ' '%(err)s') % { 'node': node.uuid, 'err': e }, _("Failed to prepare to deploy: %s") % e, clean_up=False) except Exception as e: with excutils.save_and_reraise_exception(): utils.deploying_error_handler( task, ('Unexpected error while preparing to deploy to node ' '%(node)s') % {'node': node.uuid}, _("Failed to prepare to deploy. Exception: %s") % e, traceback=True, clean_up=False) try: # If any deploy steps provided by user, save them to node. They will be # validated & processed later together with driver and deploy template # steps. if deploy_steps: info = node.driver_internal_info info['user_deploy_steps'] = deploy_steps node.driver_internal_info = info node.save() # This gets the deploy steps (if any) from driver, deploy template and # deploy_steps argument and updates them in the node's # driver_internal_info['deploy_steps']. In-band steps are skipped since # we know that an agent is not running yet. conductor_steps.set_node_deployment_steps(task, skip_missing=True) except exception.InstanceDeployFailure as e: with excutils.save_and_reraise_exception(): utils.deploying_error_handler( task, 'Error while getting deploy steps; cannot deploy to node ' '%(node)s. Error: %(err)s' % { 'node': node.uuid, 'err': e }, _("Cannot get deploy steps; failed to deploy: %s") % e) if not node.driver_internal_info.get('deploy_steps'): msg = _('Error while getting deploy steps: no steps returned for ' 'node %s') % node.uuid utils.deploying_error_handler( task, msg, _("No deploy steps returned by the driver")) raise exception.InstanceDeployFailure(msg) if conductor_id is not None: # Update conductor_affinity to reference this conductor's ID # since there may be local persistent state node.conductor_affinity = conductor_id node.save() do_next_deploy_step(task, 0)
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 _get_configdrive(configdrive, node_uuid): """Get the information about size and location of the configdrive. :param configdrive: Base64 encoded Gzipped configdrive content or configdrive HTTP URL. :param node_uuid: Node's uuid. Used for logging. :raises: InstanceDeployFailure if it can't download or decode the config drive. :returns: A tuple with the size in MiB and path to the uncompressed configdrive file. """ # Check if the configdrive option is a HTTP URL or the content directly is_url = utils.is_http_url(configdrive) if is_url: try: data = requests.get(configdrive).content except requests.exceptions.RequestException as e: raise exception.InstanceDeployFailure( _("Can't download the configdrive content for node %(node)s " "from '%(url)s'. Reason: %(reason)s") % { 'node': node_uuid, 'url': configdrive, 'reason': e }) else: data = configdrive try: data = six.BytesIO(base64.b64decode(data)) except TypeError: error_msg = (_('Config drive for node %s is not base64 encoded ' 'or the content is malformed.') % node_uuid) if is_url: error_msg += _(' Downloaded from "%s".') % configdrive raise exception.InstanceDeployFailure(error_msg) configdrive_file = tempfile.NamedTemporaryFile(delete=False, prefix='configdrive', dir=CONF.tempdir) configdrive_mb = 0 with gzip.GzipFile('configdrive', 'rb', fileobj=data) as gunzipped: try: shutil.copyfileobj(gunzipped, configdrive_file) except EnvironmentError as e: # Delete the created file utils.unlink_without_raise(configdrive_file.name) raise exception.InstanceDeployFailure( _('Encountered error while decompressing and writing ' 'config drive for node %(node)s. Error: %(exc)s') % { 'node': node_uuid, 'exc': e }) else: # Get the file size and convert to MiB configdrive_file.seek(0, os.SEEK_END) bytes_ = configdrive_file.tell() configdrive_mb = int(math.ceil(float(bytes_) / units.Mi)) finally: configdrive_file.close() return (configdrive_mb, configdrive_file.name)
def _fail_deploy(task, msg): """Fail the deploy after logging and setting error states.""" LOG.error(msg) deploy_utils.set_failed_state(task, msg) destroy_images(task.node.uuid) raise exception.InstanceDeployFailure(msg)
def deploy_partition_image(address, port, iqn, lun, image_path, root_mb, swap_mb, ephemeral_mb, ephemeral_format, node_uuid, preserve_ephemeral=False, configdrive=None, boot_option="netboot", boot_mode="bios"): """All-in-one function to deploy a partition image to a node. :param address: The iSCSI IP address. :param port: The iSCSI port number. :param iqn: The iSCSI qualified name. :param lun: The iSCSI logical unit number. :param image_path: Path for the instance's disk image. :param root_mb: Size of the root partition in megabytes. :param swap_mb: Size of the swap partition in megabytes. :param ephemeral_mb: Size of the ephemeral partition in megabytes. If 0, no ephemeral partition will be created. :param ephemeral_format: The type of file system to format the ephemeral partition. :param node_uuid: node's uuid. Used for logging. :param preserve_ephemeral: If True, no filesystem is written to the ephemeral block device, preserving whatever content it had (if the partition table has not changed). :param configdrive: Optional. Base64 encoded Gzipped configdrive content or configdrive HTTP URL. :param boot_option: Can be "local" or "netboot". "netboot" by default. :param boot_mode: Can be "bios" or "uefi". "bios" by default. :raises: InstanceDeployFailure if image virtual size is bigger than root partition size. :returns: a dictionary containing the following keys: 'root uuid': UUID of root partition 'efi system partition uuid': UUID of the uefi system partition (if boot mode is uefi). NOTE: If key exists but value is None, it means partition doesn't exist. """ image_mb = get_image_mb(image_path) if image_mb > root_mb: msg = (_('Root partition is too small for requested image. Image ' 'virtual size: %(image_mb)d MB, Root size: %(root_mb)d MB') % { 'image_mb': image_mb, 'root_mb': root_mb }) raise exception.InstanceDeployFailure(msg) with _iscsi_setup_and_handle_errors(address, port, iqn, lun) as dev: uuid_dict_returned = work_on_disk( dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, image_path, node_uuid, preserve_ephemeral=preserve_ephemeral, configdrive=configdrive, boot_option=boot_option, boot_mode=boot_mode) return uuid_dict_returned
def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, image_path, node_uuid, preserve_ephemeral=False, configdrive=None): """Create partitions and copy an image to the root partition. :param dev: Path for the device to work on. :param root_mb: Size of the root partition in megabytes. :param swap_mb: Size of the swap partition in megabytes. :param ephemeral_mb: Size of the ephemeral partition in megabytes. If 0, no ephemeral partition will be created. :param ephemeral_format: The type of file system to format the ephemeral partition. :param image_path: Path for the instance's disk image. :param node_uuid: node's uuid. Used for logging. :param preserve_ephemeral: If True, no filesystem is written to the ephemeral block device, preserving whatever content it had (if the partition table has not changed). :param configdrive: Optional. Base64 encoded Gzipped configdrive content or configdrive HTTP URL. :returns: the UUID of the root partition. """ if not is_block_device(dev): raise exception.InstanceDeployFailure( _("Parent device '%s' not found") % dev) # the only way for preserve_ephemeral to be set to true is if we are # rebuilding an instance with --preserve_ephemeral. commit = not preserve_ephemeral # now if we are committing the changes to disk clean first. if commit: destroy_disk_metadata(dev, node_uuid) try: # If requested, get the configdrive file and determine the size # of the configdrive partition configdrive_mb = 0 configdrive_file = None if configdrive: configdrive_mb, configdrive_file = _get_configdrive( configdrive, node_uuid) part_dict = make_partitions(dev, root_mb, swap_mb, ephemeral_mb, configdrive_mb, commit=commit) ephemeral_part = part_dict.get('ephemeral') swap_part = part_dict.get('swap') configdrive_part = part_dict.get('configdrive') root_part = part_dict.get('root') if not is_block_device(root_part): raise exception.InstanceDeployFailure( _("Root device '%s' not found") % root_part) for part in ('swap', 'ephemeral', 'configdrive'): part_device = part_dict.get(part) if part_device and not is_block_device(part_device): raise exception.InstanceDeployFailure( _("'%(partition)s' device '%(part_device)s' not found") % { 'partition': part, 'part_device': part_device }) if configdrive_part: # Copy the configdrive content to the configdrive partition dd(configdrive_file, configdrive_part) finally: # If the configdrive was requested make sure we delete the file # after copying the content to the partition if configdrive_file: utils.unlink_without_raise(configdrive_file) populate_image(image_path, root_part) if swap_part: mkswap(swap_part) if ephemeral_part and not preserve_ephemeral: mkfs_ephemeral(ephemeral_part, ephemeral_format) try: root_uuid = block_uuid(root_part) except processutils.ProcessExecutionError: with excutils.save_and_reraise_exception(): LOG.error(_LE("Failed to detect root device UUID.")) return root_uuid
def fake_deploy(**kwargs): raise exception.InstanceDeployFailure("test deploy error")
def do_node_deploy(task, conductor_id=None, configdrive=None): """Prepare the environment and deploy a node.""" node = task.node utils.del_secret_token(node) try: if configdrive: if isinstance(configdrive, dict): configdrive = utils.build_configdrive(node, configdrive) _store_configdrive(node, configdrive) except (exception.SwiftOperationError, exception.ConfigInvalid) as e: with excutils.save_and_reraise_exception(): utils.deploying_error_handler( task, ('Error while uploading the configdrive for %(node)s ' 'to Swift') % {'node': node.uuid}, _('Failed to upload the configdrive to Swift. ' 'Error: %s') % e, clean_up=False) except db_exception.DBDataError as e: with excutils.save_and_reraise_exception(): # NOTE(hshiina): This error happens when the configdrive is # too large. Remove the configdrive from the # object to update DB successfully in handling # the failure. node.obj_reset_changes() utils.deploying_error_handler( task, ('Error while storing the configdrive for %(node)s into ' 'the database: %(err)s') % { 'node': node.uuid, 'err': e }, _("Failed to store the configdrive in the database. " "%s") % e, clean_up=False) except Exception as e: with excutils.save_and_reraise_exception(): utils.deploying_error_handler( task, ('Unexpected error while preparing the configdrive for ' 'node %(node)s') % {'node': node.uuid}, _("Failed to prepare the configdrive. Exception: %s") % e, traceback=True, clean_up=False) try: task.driver.deploy.prepare(task) except exception.IronicException as e: with excutils.save_and_reraise_exception(): utils.deploying_error_handler( task, ('Error while preparing to deploy to node %(node)s: ' '%(err)s') % { 'node': node.uuid, 'err': e }, _("Failed to prepare to deploy: %s") % e, clean_up=False) except Exception as e: with excutils.save_and_reraise_exception(): utils.deploying_error_handler( task, ('Unexpected error while preparing to deploy to node ' '%(node)s') % {'node': node.uuid}, _("Failed to prepare to deploy. Exception: %s") % e, traceback=True, clean_up=False) try: # This gets the deploy steps and puts them in the node's # driver_internal_info['deploy_steps']. conductor_steps.set_node_deployment_steps(task) except exception.InstanceDeployFailure as e: with excutils.save_and_reraise_exception(): utils.deploying_error_handler( task, 'Error while getting deploy steps; cannot deploy to node ' '%(node)s. Error: %(err)s' % { 'node': node.uuid, 'err': e }, _("Cannot get deploy steps; failed to deploy: %s") % e) if not node.driver_internal_info.get('deploy_steps'): msg = _('Error while getting deploy steps: no steps returned for ' 'node %s') % node.uuid utils.deploying_error_handler( task, msg, _("No deploy steps returned by the driver")) raise exception.InstanceDeployFailure(msg) do_next_deploy_step(task, 0, conductor_id)