def validate(task): """Validates the pre-requisites for iSCSI deploy. Validates whether node in the task provided has some ports enrolled. This method validates whether conductor url is available either from CONF file or from keystone. :param task: a TaskManager instance containing the node to act on. :raises: InvalidParameterValue if the URL of the Ironic API service is not configured in config file and is not accessible via Keystone catalog. :raises: MissingParameterValue if no ports are enrolled for the given node. """ try: # TODO(lucasagomes): Validate the format of the URL CONF.conductor.api_url or keystone.get_service_url() except (exception.KeystoneFailure, exception.CatalogNotFound, exception.KeystoneUnauthorized) as e: raise exception.InvalidParameterValue(_( "Couldn't get the URL of the Ironic API service from the " "configuration file or keystone catalog. Keystone error: %s") % e) # Validate the root device hints deploy_utils.parse_root_device_hints(task.node) deploy_utils.parse_instance_info(task.node)
def validate(task): """Validates the pre-requisites for iSCSI deploy. Validates whether node in the task provided has some ports enrolled. This method validates whether conductor url is available either from CONF file or from keystone. :param task: a TaskManager instance containing the node to act on. :raises: InvalidParameterValue if the URL of the Ironic API service is not configured in config file and is not accessible via Keystone catalog. :raises: MissingParameterValue if no ports are enrolled for the given node. """ # TODO(lucasagomes): Validate the format of the URL deploy_utils.get_ironic_api_url() # Validate the root device hints try: root_device = task.node.properties.get('root_device') il_utils.parse_root_device_hints(root_device) except ValueError as e: raise exception.InvalidParameterValue( _('Failed to validate the root device hints for node ' '%(node)s. Error: %(error)s') % {'node': task.node.uuid, 'error': e}) deploy_utils.parse_instance_info(task.node)
def validate(task): """Validates the pre-requisites for iSCSI deploy. Validates whether node in the task provided has some ports enrolled. This method validates whether conductor url is available either from CONF file or from keystone. :param task: a TaskManager instance containing the node to act on. :raises: InvalidParameterValue if the URL of the Ironic API service is not configured in config file and is not accessible via Keystone catalog. :raises: MissingParameterValue if no ports are enrolled for the given node. """ try: # TODO(lucasagomes): Validate the format of the URL CONF.conductor.api_url or keystone.get_service_url() except (exception.KeystoneFailure, exception.CatalogNotFound, exception.KeystoneUnauthorized) as e: raise exception.InvalidParameterValue( _("Couldn't get the URL of the Ironic API service from the " "configuration file or keystone catalog. Keystone error: %s") % e) # Validate the root device hints deploy_utils.parse_root_device_hints(task.node) deploy_utils.parse_instance_info(task.node)
def cache_instance_image(ctx, node): """Fetch the instance's image from Glance This method pulls the AMI and writes them to the appropriate place on local disk. :param ctx: context :param node: an ironic node object :returns: a tuple containing the uuid of the image and the path in the filesystem where image is cached. """ i_info = deploy_utils.parse_instance_info(node) fileutils.ensure_tree(_get_image_dir_path(node.uuid)) image_path = _get_image_file_path(node.uuid) uuid = i_info['image_source'] LOG.debug("Fetching image %(ami)s for node %(uuid)s", { 'ami': uuid, 'uuid': node.uuid }) deploy_utils.fetch_images(ctx, InstanceImageCache(), [(uuid, image_path)], CONF.force_raw_images) return (uuid, image_path)
def check_image_size(task): """Check if the requested image is larger than the root partition size. Does nothing for whole-disk images. :param task: a TaskManager instance containing the node to act on. :raises: InstanceDeployFailure if size of the image is greater than root partition. """ if task.node.driver_internal_info['is_whole_disk_image']: # The root partition is already created and populated, no use # validating its size return i_info = deploy_utils.parse_instance_info(task.node) image_path = deploy_utils._get_image_file_path(task.node.uuid) image_mb = disk_utils.get_image_mb(image_path) root_mb = 1024 * int(i_info['root_gb']) 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)
def get_deploy_info(node, address, iqn, port=None, lun='1', conv_flags=None): """Returns the information required for doing iSCSI deploy in a dictionary. :param node: ironic node object :param address: iSCSI address :param iqn: iSCSI iqn for the target disk :param port: iSCSI port, defaults to one specified in the configuration :param lun: iSCSI lun, defaults to '1' :param conv_flags: flag that will modify the behaviour of the image copy to disk. :raises: MissingParameterValue, if some required parameters were not passed. :raises: InvalidParameterValue, if any of the parameters have invalid value. """ i_info = deploy_utils.parse_instance_info(node) params = { 'address': address, 'port': port or CONF.iscsi.portal_port, 'iqn': iqn, 'lun': lun, 'image_path': deploy_utils._get_image_file_path(node.uuid), 'node_uuid': node.uuid} is_whole_disk_image = node.driver_internal_info['is_whole_disk_image'] if not is_whole_disk_image: params.update({'root_mb': i_info['root_mb'], 'swap_mb': i_info['swap_mb'], 'ephemeral_mb': i_info['ephemeral_mb'], 'preserve_ephemeral': i_info['preserve_ephemeral'], 'boot_option': deploy_utils.get_boot_option(node), 'boot_mode': _get_boot_mode(node), 'cpu_arch': node.properties.get('cpu_arch')}) # Append disk label if specified disk_label = deploy_utils.get_disk_label(node) if disk_label is not None: params['disk_label'] = disk_label missing = [key for key in params if params[key] is None] if missing: raise exception.MissingParameterValue( _("Parameters %s were not passed to ironic" " for deploy.") % missing) # configdrive is nullable params['configdrive'] = i_info.get('configdrive') if is_whole_disk_image: return params if conv_flags: params['conv_flags'] = conv_flags # ephemeral_format is nullable params['ephemeral_format'] = i_info.get('ephemeral_format') return params
def validate(task): """Validates the pre-requisites for iSCSI deploy. Validates whether node in the task provided has some ports enrolled. This method validates whether conductor url is available either from CONF file or from keystone. :param task: a TaskManager instance containing the node to act on. :raises: InvalidParameterValue if the URL of the Ironic API service is not configured in config file and is not accessible via Keystone catalog. :raises: MissingParameterValue if no ports are enrolled for the given node. """ # TODO(lucasagomes): Validate the format of the URL deploy_utils.get_ironic_api_url() # Validate the root device hints deploy_utils.get_root_device_for_deploy(task.node) deploy_utils.parse_instance_info(task.node)
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 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) 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 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 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) 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 get_deploy_info(node, **kwargs): """Returns the information required for doing iSCSI deploy in a dictionary. :param node: ironic node object :param kwargs: the keyword args passed from the conductor node. :raises: MissingParameterValue, if some required parameters were not passed. :raises: InvalidParameterValue, if any of the parameters have invalid value. """ deploy_key = kwargs.get('key') i_info = deploy_utils.parse_instance_info(node) if i_info['deploy_key'] != deploy_key: raise exception.InvalidParameterValue(_("Deploy key does not match")) params = { 'address': kwargs.get('address'), 'port': kwargs.get('port', CONF.iscsi.portal_port), 'iqn': kwargs.get('iqn'), 'lun': kwargs.get('lun', '1'), 'image_path': _get_image_file_path(node.uuid), 'node_uuid': node.uuid } is_whole_disk_image = node.driver_internal_info['is_whole_disk_image'] if not is_whole_disk_image: params.update({ 'root_mb': 1024 * int(i_info['root_gb']), 'swap_mb': int(i_info['swap_mb']), 'ephemeral_mb': 1024 * int(i_info['ephemeral_gb']), 'preserve_ephemeral': i_info['preserve_ephemeral'], 'boot_option': deploy_utils.get_boot_option(node), 'boot_mode': _get_boot_mode(node) }) # Append disk label if specified disk_label = deploy_utils.get_disk_label(node) if disk_label is not None: params['disk_label'] = disk_label missing = [key for key in params if params[key] is None] if missing: raise exception.MissingParameterValue( _("Parameters %s were not passed to ironic" " for deploy.") % missing) if is_whole_disk_image: return params # configdrive and ephemeral_format are nullable params['ephemeral_format'] = i_info.get('ephemeral_format') params['configdrive'] = i_info.get('configdrive') return params
def get_deploy_info(node, address, iqn, port=None, lun='1'): """Returns the information required for doing iSCSI deploy in a dictionary. :param node: ironic node object :param address: iSCSI address :param iqn: iSCSI iqn for the target disk :param port: iSCSI port, defaults to one specified in the configuration :param lun: iSCSI lun, defaults to '1' :raises: MissingParameterValue, if some required parameters were not passed. :raises: InvalidParameterValue, if any of the parameters have invalid value. """ i_info = deploy_utils.parse_instance_info(node) params = { 'address': address, 'port': port or CONF.iscsi.portal_port, 'iqn': iqn, 'lun': lun, 'image_path': _get_image_file_path(node.uuid), 'node_uuid': node.uuid} is_whole_disk_image = node.driver_internal_info['is_whole_disk_image'] if not is_whole_disk_image: params.update({'root_mb': i_info['root_mb'], 'swap_mb': i_info['swap_mb'], 'ephemeral_mb': i_info['ephemeral_mb'], 'preserve_ephemeral': i_info['preserve_ephemeral'], 'boot_option': deploy_utils.get_boot_option(node), 'boot_mode': _get_boot_mode(node)}) # Append disk label if specified disk_label = deploy_utils.get_disk_label(node) if disk_label is not None: params['disk_label'] = disk_label missing = [key for key in params if params[key] is None] if missing: raise exception.MissingParameterValue( _("Parameters %s were not passed to ironic" " for deploy.") % missing) # configdrive is nullable params['configdrive'] = i_info.get('configdrive') if is_whole_disk_image: return params # ephemeral_format is nullable params['ephemeral_format'] = i_info.get('ephemeral_format') return params
def get_deploy_info(node, **kwargs): """Returns the information required for doing iSCSI deploy in a dictionary. :param node: ironic node object :param kwargs: the keyword args passed from the conductor node. :raises: MissingParameterValue, if some required parameters were not passed. :raises: InvalidParameterValue, if any of the parameters have invalid value. """ deploy_key = kwargs.get('key') i_info = deploy_utils.parse_instance_info(node) if i_info['deploy_key'] != deploy_key: raise exception.InvalidParameterValue(_("Deploy key does not match")) params = { 'address': kwargs.get('address'), 'port': kwargs.get('port', '3260'), 'iqn': kwargs.get('iqn'), 'lun': kwargs.get('lun', '1'), 'image_path': _get_image_file_path(node.uuid), 'node_uuid': node.uuid} is_whole_disk_image = node.driver_internal_info['is_whole_disk_image'] if not is_whole_disk_image: params.update({'root_mb': 1024 * int(i_info['root_gb']), 'swap_mb': int(i_info['swap_mb']), 'ephemeral_mb': 1024 * int(i_info['ephemeral_gb']), 'preserve_ephemeral': i_info['preserve_ephemeral'], 'boot_option': deploy_utils.get_boot_option(node), 'boot_mode': _get_boot_mode(node)}) # Append disk label if specified disk_label = deploy_utils.get_disk_label(node) if disk_label is not None: params['disk_label'] = disk_label missing = [key for key in params if params[key] is None] if missing: raise exception.MissingParameterValue( _("Parameters %s were not passed to ironic" " for deploy.") % missing) if is_whole_disk_image: return params # configdrive and ephemeral_format are nullable params['ephemeral_format'] = i_info.get('ephemeral_format') params['configdrive'] = i_info.get('configdrive') return params
def build_instance_info_for_deploy(task): """Build instance_info necessary for deploying to a node. :param task: a TaskManager object containing the node :returns: a dictionary containing the properties to be updated in instance_info :raises: exception.ImageRefValidationFailed if image_source is not Glance href and is not HTTP(S) URL. """ node = task.node instance_info = node.instance_info iwdi = node.driver_internal_info.get('is_whole_disk_image') image_source = instance_info['image_source'] if service_utils.is_glance_image(image_source): glance = image_service.GlanceImageService(version=2, context=task.context) image_info = glance.show(image_source) swift_temp_url = glance.swift_temp_url(image_info) LOG.debug('Got image info: %(info)s for node %(node)s.', { 'info': image_info, 'node': node.uuid }) instance_info['image_url'] = swift_temp_url instance_info['image_checksum'] = image_info['checksum'] instance_info['image_disk_format'] = image_info['disk_format'] instance_info['image_container_format'] = ( image_info['container_format']) if not iwdi: instance_info['kernel'] = image_info['properties']['kernel_id'] instance_info['ramdisk'] = image_info['properties']['ramdisk_id'] else: try: image_service.HttpImageService().validate_href(image_source) except exception.ImageRefValidationFailed: with excutils.save_and_reraise_exception(): LOG.error( _LE("Agent deploy supports only HTTP(S) URLs as " "instance_info['image_source']. Either %s " "is not a valid HTTP(S) URL or " "is not reachable."), image_source) instance_info['image_url'] = image_source if not iwdi: instance_info['image_type'] = 'partition' i_info = deploy_utils.parse_instance_info(node) instance_info.update(i_info) else: instance_info['image_type'] = 'whole-disk-image' return instance_info
def check_image_size(task): """Check if the requested image is larger than the root partition size. :param task: a TaskManager instance containing the node to act on. :raises: InstanceDeployFailure if size of the image is greater than root partition. """ i_info = deploy_utils.parse_instance_info(task.node) image_path = _get_image_file_path(task.node.uuid) image_mb = disk_utils.get_image_mb(image_path) root_mb = 1024 * int(i_info['root_gb']) 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)
def build_instance_info_for_deploy(task): """Build instance_info necessary for deploying to a node. :param task: a TaskManager object containing the node :returns: a dictionary containing the properties to be updated in instance_info :raises: exception.ImageRefValidationFailed if image_source is not Glance href and is not HTTP(S) URL. """ node = task.node instance_info = node.instance_info iwdi = node.driver_internal_info.get('is_whole_disk_image') image_source = instance_info['image_source'] if service_utils.is_glance_image(image_source): glance = image_service.GlanceImageService(version=2, context=task.context) image_info = glance.show(image_source) swift_temp_url = glance.swift_temp_url(image_info) LOG.debug('Got image info: %(info)s for node %(node)s.', {'info': image_info, 'node': node.uuid}) instance_info['image_url'] = swift_temp_url instance_info['image_checksum'] = image_info['checksum'] instance_info['image_disk_format'] = image_info['disk_format'] instance_info['image_container_format'] = ( image_info['container_format']) if not iwdi: instance_info['kernel'] = image_info['properties']['kernel_id'] instance_info['ramdisk'] = image_info['properties']['ramdisk_id'] else: try: image_service.HttpImageService().validate_href(image_source) except exception.ImageRefValidationFailed: with excutils.save_and_reraise_exception(): LOG.error(_LE("Agent deploy supports only HTTP(S) URLs as " "instance_info['image_source']. Either %s " "is not a valid HTTP(S) URL or " "is not reachable."), image_source) instance_info['image_url'] = image_source if not iwdi: instance_info['image_type'] = 'partition' i_info = deploy_utils.parse_instance_info(node) instance_info.update(i_info) else: instance_info['image_type'] = 'whole-disk-image' return instance_info
def check_image_size(task): """Check if the requested image is larger than the root partition size. Does nothing for whole-disk images. :param task: a TaskManager instance containing the node to act on. :raises: InstanceDeployFailure if size of the image is greater than root partition. """ if task.node.driver_internal_info['is_whole_disk_image']: # The root partition is already created and populated, no use # validating its size return i_info = deploy_utils.parse_instance_info(task.node) image_path = deploy_utils._get_image_file_path(task.node.uuid) image_mb = disk_utils.get_image_mb(image_path) root_mb = 1024 * int(i_info['root_gb']) 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)
def cache_instance_image(ctx, node): """Fetch the instance's image from Glance This method pulls the AMI and writes them to the appropriate place on local disk. :param ctx: context :param node: an ironic node object :returns: a tuple containing the uuid of the image and the path in the filesystem where image is cached. """ i_info = deploy_utils.parse_instance_info(node) fileutils.ensure_tree(_get_image_dir_path(node.uuid)) image_path = _get_image_file_path(node.uuid) uuid = i_info['image_source'] LOG.debug("Fetching image %(ami)s for node %(uuid)s", {'ami': uuid, 'uuid': node.uuid}) deploy_utils.fetch_images(ctx, InstanceImageCache(), [(uuid, image_path)], CONF.force_raw_images) return (uuid, image_path)
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) i_info = deploy_utils.parse_instance_info(node) wipe_disk_metadata = not i_info['preserve_ephemeral'] iqn = iscsi_options['iscsi_target_iqn'] portal_port = iscsi_options['iscsi_portal_port'] 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 # TODO(lucasagomes): The 'error' and 'key' parameters in the # dictionary below are just being passed because it's needed for # the 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 # 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 continue_deploy(task, **kwargs): """Resume a deployment upon getting POST data from deploy ramdisk. This method raises no exceptions because it is intended to be invoked asynchronously as a callback from the deploy ramdisk. :param task: a TaskManager instance containing the node to act on. :param kwargs: the kwargs to be passed to deploy. :raises: InvalidState if the event is not allowed by the associated state machine. :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. """ node = task.node params = get_deploy_info(node, **kwargs) def _fail_deploy(task, msg, raise_exception=True): """Fail the deploy after logging and setting error states.""" if isinstance(msg, Exception): msg = (_('Deploy failed for instance %(instance)s. ' 'Error: %(error)s') % {'instance': node.instance_uuid, 'error': msg}) deploy_utils.set_failed_state(task, msg) deploy_utils.destroy_images(task.node.uuid) if raise_exception: raise exception.InstanceDeployFailure(msg) # NOTE(lucasagomes): Let's make sure we don't log the full content # of the config drive here because it can be up to 64MB in size, # so instead let's log "***" in case config drive is enabled. if LOG.isEnabledFor(logging.logging.DEBUG): log_params = { k: params[k] if k != 'configdrive' else '***' for k in params } LOG.debug('Continuing deployment for node %(node)s, params %(params)s', {'node': node.uuid, 'params': log_params}) uuid_dict_returned = {} try: if node.driver_internal_info['is_whole_disk_image']: uuid_dict_returned = deploy_utils.deploy_disk_image(**params) else: uuid_dict_returned = deploy_utils.deploy_partition_image(**params) except exception.IronicException as e: with excutils.save_and_reraise_exception(): LOG.error('Deploy of instance %(instance)s on node %(node)s ' 'failed: %(error)s', {'instance': node.instance_uuid, 'node': node.uuid, 'error': e}) _fail_deploy(task, e, raise_exception=False) except Exception as e: LOG.exception('Deploy of instance %(instance)s on node %(node)s ' 'failed with exception', {'instance': node.instance_uuid, 'node': node.uuid}) _fail_deploy(task, e) root_uuid_or_disk_id = uuid_dict_returned.get( 'root uuid', uuid_dict_returned.get('disk identifier')) if not root_uuid_or_disk_id: msg = (_("Couldn't determine the UUID of the root " "partition or the disk identifier after deploying " "node %s") % node.uuid) LOG.error(msg) _fail_deploy(task, msg) if params.get('preserve_ephemeral', False): # Save disk layout information, to check that they are unchanged # for any future rebuilds _save_disk_layout(node, deploy_utils.parse_instance_info(node)) deploy_utils.destroy_images(node.uuid) return uuid_dict_returned
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) i_info = deploy_utils.parse_instance_info(node) wipe_disk_metadata = not i_info['preserve_ephemeral'] iqn = iscsi_options['iscsi_target_iqn'] portal_port = iscsi_options['iscsi_portal_port'] 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 # TODO(lucasagomes): The 'error' and 'key' parameters in the # dictionary below are just being passed because it's needed for # the 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 # 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 continue_deploy(task, **kwargs): """Resume a deployment upon getting POST data from deploy ramdisk. This method raises no exceptions because it is intended to be invoked asynchronously as a callback from the deploy ramdisk. :param task: a TaskManager instance containing the node to act on. :param kwargs: the kwargs to be passed to deploy. :raises: InvalidState if the event is not allowed by the associated state machine. :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. """ node = task.node params = get_deploy_info(node, **kwargs) def _fail_deploy(task, msg, raise_exception=True): """Fail the deploy after logging and setting error states.""" if isinstance(msg, Exception): msg = (_('Deploy failed for instance %(instance)s. ' 'Error: %(error)s') % {'instance': node.instance_uuid, 'error': msg}) deploy_utils.set_failed_state(task, msg) destroy_images(task.node.uuid) if raise_exception: raise exception.InstanceDeployFailure(msg) # NOTE(lucasagomes): Let's make sure we don't log the full content # of the config drive here because it can be up to 64MB in size, # so instead let's log "***" in case config drive is enabled. if LOG.isEnabledFor(logging.logging.DEBUG): log_params = { k: params[k] if k != 'configdrive' else '***' for k in params } LOG.debug('Continuing deployment for node %(node)s, params %(params)s', {'node': node.uuid, 'params': log_params}) uuid_dict_returned = {} try: if node.driver_internal_info['is_whole_disk_image']: uuid_dict_returned = deploy_utils.deploy_disk_image(**params) else: uuid_dict_returned = deploy_utils.deploy_partition_image(**params) except exception.IronicException as e: with excutils.save_and_reraise_exception(): LOG.error('Deploy of instance %(instance)s on node %(node)s ' 'failed: %(error)s', {'instance': node.instance_uuid, 'node': node.uuid, 'error': e}) _fail_deploy(task, e, raise_exception=False) except Exception as e: LOG.exception('Deploy of instance %(instance)s on node %(node)s ' 'failed with exception', {'instance': node.instance_uuid, 'node': node.uuid}) _fail_deploy(task, e) root_uuid_or_disk_id = uuid_dict_returned.get( 'root uuid', uuid_dict_returned.get('disk identifier')) if not root_uuid_or_disk_id: msg = (_("Couldn't determine the UUID of the root " "partition or the disk identifier after deploying " "node %s") % node.uuid) LOG.error(msg) _fail_deploy(task, msg) if params.get('preserve_ephemeral', False): # Save disk layout information, to check that they are unchanged # for any future rebuilds _save_disk_layout(node, deploy_utils.parse_instance_info(node)) destroy_images(node.uuid) return uuid_dict_returned