def setUp(self): super(TestSwiftTempUrlCache, self).setUp() client = stubs.StubGlanceClient() self.context = context.RequestContext() self.context.auth_token = 'fake' self.config(swift_temp_url_expected_download_start_delay=100, group='glance') self.config(swift_temp_url_key='correcthorsebatterystaple', group='glance') self.config(swift_endpoint_url='https://swift.example.com', group='glance') self.config(swift_account='AUTH_a422b2-91f3-2f46-74b7-d7c9e8958f5d30', group='glance') self.config(swift_api_version='v1', group='glance') self.config(swift_container='glance', group='glance') self.config(swift_temp_url_duration=1200, group='glance') self.config(swift_temp_url_cache_enabled=True, group='glance') self.config(swift_store_multiple_containers_seed=0, group='glance') self.glance_service = service.GlanceImageService(client, version=2, context=self.context)
def build_instance_info_for_deploy(task): """Build instance_info necessary for deploying to a node.""" node = task.node instance_info = node.instance_info 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'] else: try: image_service.HttpImageService().validate_href(image_source) except exception.ImageRefValidationFailed: with excutils.save_and_reraise_exception(): LOG.error( _LE("Ansible 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 return instance_info
def _get_image_info(node, ctx): """Generate the paths for tftp files for this instance Raises IronicException if - instance does not contain kernel or ramdisk - deploy_kernel or deploy_ramdisk can not be read from driver_info and defaults are not set """ d_info = _parse_deploy_info(node) image_info = {} root_dir = pxe_utils.get_root_dir() image_info.update(pxe_utils.get_deploy_kr_info(node.uuid, d_info)) if node.driver_internal_info.get('is_whole_disk_image'): return image_info i_info = node.instance_info labels = ('kernel', 'ramdisk') if not (i_info.get('kernel') and i_info.get('ramdisk')): glance_service = service.GlanceImageService(version=1, context=ctx) iproperties = glance_service.show(d_info['image_source'])['properties'] for label in labels: i_info[label] = str(iproperties[label + '_id']) node.instance_info = i_info node.save() for label in labels: image_info[label] = (i_info[label], os.path.join(root_dir, node.uuid, label)) return image_info
def test_download_with_retries(self, mock_sleep): tries = [0] class MyGlanceStubClient(stubs.StubGlanceClient): """A client that fails the first time, then succeeds.""" def get(self, image_id): if tries[0] == 0: tries[0] = 1 raise glance_exc.ServiceUnavailable('') else: return {} stub_client = MyGlanceStubClient() stub_context = context.RequestContext(auth_token=True) stub_context.user_id = 'fake' stub_context.project_id = 'fake' stub_service = service.GlanceImageService(stub_client, 1, stub_context) image_id = 1 # doesn't matter writer = NullWriter() # When retries are disabled, we should get an exception self.config(glance_num_retries=0, group='glance') self.assertRaises(exception.GlanceConnectionFailed, stub_service.download, image_id, writer) # Now lets enable retries. No exception should happen now. tries = [0] self.config(glance_num_retries=1, group='glance') stub_service.download(image_id, writer) self.assertTrue(mock_sleep.called)
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. """ def validate_image_url(url, secret=False): """Validates image URL through the HEAD request. :param url: URL to be validated :param secret: if URL is secret (e.g. swift temp url), it will not be shown in logs. """ try: image_service.HttpImageService().validate_href(url, secret) except exception.ImageRefValidationFailed as e: with excutils.save_and_reraise_exception(): LOG.error(_LE("Agent deploy supports only HTTP(S) URLs as " "instance_info['image_source'] or swift " "temporary URL. Either the specified URL is not " "a valid HTTP(S) URL or is not reachable " "for node %(node)s. Error: %(msg)s"), {'node': node.uuid, 'msg': e}) 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) LOG.debug('Got image info: %(info)s for node %(node)s.', {'info': image_info, 'node': node.uuid}) swift_temp_url = glance.swift_temp_url(image_info) validate_image_url(swift_temp_url, secret=True) 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']) instance_info['image_tags'] = image_info.get('tags', []) instance_info['image_properties'] = image_info['properties'] if not iwdi: instance_info['kernel'] = image_info['properties']['kernel_id'] instance_info['ramdisk'] = image_info['properties']['ramdisk_id'] else: validate_image_url(image_source) instance_info['image_url'] = image_source if not iwdi: instance_info['image_type'] = 'partition' i_info = parse_instance_info(node) instance_info.update(i_info) else: instance_info['image_type'] = 'whole-disk-image' return instance_info
def setUp(self): super(TestGlanceImageService, self).setUp() self.client = stubs.StubGlanceClient() self.context = context.RequestContext(auth_token=True) self.context.user_id = 'fake' self.context.project_id = 'fake' self.service = service.GlanceImageService(self.client, self.context)
def _get_instance_image_info(node, ctx): """Generate the paths for TFTP files for instance related images. This method generates the paths for instance kernel and instance ramdisk. This method also updates the node, so caller should already have a non-shared lock on the node. :param node: a node object :param ctx: context :returns: a dictionary whose keys are the names of the images (kernel, ramdisk) and values are the absolute paths of them. If it's a whole disk image, it returns an empty dictionary. """ image_info = {} if node.driver_internal_info.get('is_whole_disk_image'): return image_info root_dir = pxe_utils.get_root_dir() i_info = node.instance_info labels = ('kernel', 'ramdisk') d_info = deploy_utils.get_image_instance_info(node) if not (i_info.get('kernel') and i_info.get('ramdisk')): glance_service = service.GlanceImageService(version=1, context=ctx) iproperties = glance_service.show(d_info['image_source'])['properties'] for label in labels: i_info[label] = str(iproperties[label + '_id']) node.instance_info = i_info node.save() for label in labels: image_info[label] = (i_info[label], os.path.join(root_dir, node.uuid, label)) return image_info
def setUp(self): super(TestGlanceImageService, self).setUp() client = stubs.StubGlanceClient() self.context = context.RequestContext(auth_token=True) self.context.user_id = 'fake' self.context.project_id = 'fake' self.service = service.GlanceImageService(client, 1, self.context) self.config(glance_api_servers=['http://localhost'], group='glance') self.config(auth_strategy='keystone', group='glance')
def get_temp_url_for_glance_image(context, image_uuid): """Returns the tmp url for a glance image. :param context: context :param image_uuid: the UUID of the image in glance :returns: the tmp url for the glance image. """ glance_service = service.GlanceImageService(context=context) image_properties = glance_service.show(image_uuid) LOG.debug('Got image info: %(info)s for image %(image_uuid)s.', {'info': image_properties, 'image_uuid': image_uuid}) return glance_service.swift_temp_url(image_properties)
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']) instance_info['image_tags'] = image_info.get('tags', []) instance_info['image_properties'] = image_info['properties'] 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 = parse_instance_info(node) instance_info.update(i_info) else: instance_info['image_type'] = 'whole-disk-image' return instance_info
def get_instance_image_info(task, ipxe_enabled=False): """Generate the paths for TFTP files for instance related images. This method generates the paths for instance kernel and instance ramdisk. This method also updates the node, so caller should already have a non-shared lock on the node. :param task: A TaskManager instance containing node and context. :param ipxe_enabled: Default false boolean to indicate if ipxe is in use by the caller. :returns: a dictionary whose keys are the names of the images (kernel, ramdisk) and values are the absolute paths of them. If it's a whole disk image or node is configured for localboot, it returns an empty dictionary. """ ctx = task.context node = task.node image_info = {} # NOTE(pas-ha) do not report image kernel and ramdisk for # local boot or whole disk images so that they are not cached if (node.driver_internal_info.get('is_whole_disk_image') or deploy_utils.get_boot_option(node) == 'local'): return image_info if ipxe_enabled: root_dir = get_ipxe_root_dir() else: root_dir = get_root_dir() i_info = node.instance_info if i_info.get('boot_iso'): image_info['boot_iso'] = ( i_info['boot_iso'], os.path.join(root_dir, node.uuid, 'boot_iso')) return image_info labels = ('kernel', 'ramdisk') d_info = deploy_utils.get_image_instance_info(node) if not (i_info.get('kernel') and i_info.get('ramdisk')): glance_service = service.GlanceImageService(context=ctx) iproperties = glance_service.show(d_info['image_source'])['properties'] for label in labels: i_info[label] = str(iproperties[label + '_id']) node.instance_info = i_info node.save() for label in labels: image_info[label] = ( i_info[label], os.path.join(root_dir, node.uuid, label) ) return image_info
def test_client_httpnotfound_converts_to_imagenotfound(self): class MyGlanceStubClient(stubs.StubGlanceClient): """A client that raises a HTTPNotFound exception.""" def get(self, image_id): raise glance_exc.HTTPNotFound(image_id) stub_client = MyGlanceStubClient() stub_context = context.RequestContext(auth_token=True) stub_context.user_id = 'fake' stub_context.project_id = 'fake' stub_service = service.GlanceImageService(stub_client, 1, stub_context) image_id = 1 # doesn't matter writer = NullWriter() self.assertRaises(exception.ImageNotFound, stub_service.download, image_id, writer)
def test_client_httpforbidden_converts_to_imagenotauthed(self): class MyGlanceStubClient(stubs.StubGlanceClient): """A client that raises a HTTPForbidden exception.""" def get(self, image_id): raise glance_exc.HTTPForbidden(image_id) stub_client = MyGlanceStubClient() stub_context = context.RequestContext(auth_token=True) stub_context.user_id = 'fake' stub_context.project_id = 'fake' stub_service = service.GlanceImageService(stub_client, 1, stub_context) image_id = uuidutils.generate_uuid() writer = NullWriter() self.assertRaises(exception.ImageNotAuthorized, stub_service.download, image_id, writer)
def setUp(self): super(TestGlanceSwiftTempURL, self).setUp() client = stubs.StubGlanceClient() self.context = context.RequestContext() self.context.auth_token = 'fake' self.service = service.GlanceImageService(client, 2, self.context) self.config(swift_temp_url_key='correcthorsebatterystaple', group='glance') self.config(swift_endpoint_url='https://swift.example.com', group='glance') self.config(swift_account='AUTH_a422b2-91f3-2f46-74b7-d7c9e8958f5d30', group='glance') self.config(swift_api_version='v1', group='glance') self.config(swift_container='glance', group='glance') self.config(swift_temp_url_duration=1200, group='glance') self.config(swift_store_multiple_containers_seed=0, group='glance') self.fake_image = {'id': '757274c4-2856-4bd2-bb20-9a4a231e187b'}
def _get_deploy_data(context, image_source): glance = image_service.GlanceImageService(version=2, context=context) image_props = glance.show(image_source).get('properties', {}) LOG.debug('Image %s properties are: %s', image_source, image_props) try: disk_data = json.loads(image_props['fuel_disk_info']) except KeyError: raise exception.MissingParameterValue( _('Image %s does not contain ' 'disk layout data.') % image_source) except ValueError: raise exception.InvalidParameterValue( _('Invalid disk layout data for ' 'image %s') % image_source) data = FUEL_AGENT_PROVISION_TEMPLATE.copy() data['ks_meta']['pm_data']['ks_spaces'] = disk_data return data
def setUp(self): super(TestGlanceImageService, self).setUp() client = stubs.StubGlanceClient() self.context = context.RequestContext(auth_token=True) self.context.user_id = 'fake' self.context.project_id = 'fake' self.service = service.GlanceImageService(client, 1, self.context) self.config(glance_host='localhost', group='glance') try: self.config(auth_strategy='keystone', group='glance') except Exception: opts = [ cfg.StrOpt('auth_strategy', default='keystone'), ] CONF.register_opts(opts) return
def setUp(self): super(CheckImageServiceTestCase, self).setUp() self.context = context.RequestContext(global_request_id='global') self.service = service.GlanceImageService(None, self.context) # NOTE(pas-ha) register keystoneauth dynamic options manually plugin = kaloading.get_plugin_loader('password') opts = kaloading.get_auth_plugin_conf_options(plugin) self.cfg_fixture.register_opts(opts, group='glance') self.config(auth_type='password', auth_url='viking', username='******', password='******', project_name='parrot', service_type='image', region_name='SomeRegion', interface='internal', group='glance') image_service._GLANCE_SESSION = None
def test_download_file_url(self, mock_getsize, mock_sendfile): # NOTE: only in v2 API class MyGlanceStubClient(stubs.StubGlanceClient): """A client that returns a file url.""" s_tmpfname = '/whatever/source' def get(self, image_id): return type('GlanceTestDirectUrlMeta', (object,), {'direct_url': 'file://%s' + self.s_tmpfname}) stub_context = context.RequestContext(auth_token=True) stub_context.user_id = 'fake' stub_context.project_id = 'fake' stub_client = MyGlanceStubClient() stub_service = service.GlanceImageService(stub_client, context=stub_context, version=2) image_id = 1 # doesn't matter self.config(allowed_direct_url_schemes=['file'], group='glance') # patching open in base_image_service module namespace # to make call-spec assertions with mock.patch('ironic.common.glance_service.base_image_service.open', new=mock.mock_open(), create=True) as mock_ironic_open: with open('/whatever/target', 'w') as mock_target_fd: stub_service.download(image_id, mock_target_fd) # assert the image data was neither read nor written # but rather sendfiled mock_ironic_open.assert_called_once_with(MyGlanceStubClient.s_tmpfname, 'r') mock_source_fd = mock_ironic_open() self.assertFalse(mock_source_fd.read.called) self.assertFalse(mock_target_fd.write.called) mock_sendfile.assert_called_once_with( mock_target_fd.fileno(), mock_source_fd.fileno(), 0, mock_getsize(MyGlanceStubClient.s_tmpfname))
def test_download_file_url(self): # NOTE: only in v2 API class MyGlanceStubClient(stubs.StubGlanceClient): """A client that returns a file url.""" (outfd, s_tmpfname) = tempfile.mkstemp(prefix='directURLsrc') outf = os.fdopen(outfd, 'wb') inf = open('/dev/urandom', 'rb') for i in range(10): _data = inf.read(1024) outf.write(_data) outf.close() def get(self, image_id): return type('GlanceTestDirectUrlMeta', (object, ), {'direct_url': 'file://%s' + self.s_tmpfname}) stub_context = context.RequestContext(auth_token=True) stub_context.user_id = 'fake' stub_context.project_id = 'fake' stub_client = MyGlanceStubClient() (outfd, tmpfname) = tempfile.mkstemp(prefix='directURLdst') writer = os.fdopen(outfd, 'w') stub_service = service.GlanceImageService(stub_client, context=stub_context, version=2) image_id = 1 # doesn't matter self.config(allowed_direct_url_schemes=['file'], group='glance') stub_service.download(image_id, writer) writer.close() # compare the two files rc = filecmp.cmp(tmpfname, stub_client.s_tmpfname) self.assertTrue( rc, "The file %s and %s should be the same" % (tmpfname, stub_client.s_tmpfname)) os.remove(stub_client.s_tmpfname) os.remove(tmpfname)
def _create_rootfs_link(task): """Create Swift temp url for deployment root FS.""" rootfs = _get_boot_files(task.node)['deploy_squashfs'] if service_utils.is_glance_image(rootfs): glance = image_service.GlanceImageService(version=2, context=task.context) image_info = glance.show(rootfs) temp_url = glance.swift_temp_url(image_info) temp_url += '&filename=/root.squashfs' return temp_url try: image_service.HttpImageService().validate_href(rootfs) except exception.ImageRefValidationFailed: with excutils.save_and_reraise_exception(): LOG.error( _LE("Agent deploy supports only HTTP URLs as " "driver_info['deploy_squashfs']. Either %s " "is not a valid HTTP URL or " "is not reachable."), rootfs) return rootfs
def configure_local_boot(self, task, root_uuid=None, efi_system_part_uuid=None, prep_boot_part_uuid=None): """Helper method to configure local boot on the node. This method triggers bootloader installation on the node. On successful installation of bootloader, this method sets the node to boot from disk. :param task: a TaskManager object containing the node :param root_uuid: The UUID of the root partition. This is used for identifying the partition which contains the image deployed or None in case of whole disk images which we expect to already have a bootloader installed. :param efi_system_part_uuid: The UUID of the efi system partition. This is used only in uefi boot mode. :param prep_boot_part_uuid: The UUID of the PReP Boot partition. This is used only for booting ppc64* hardware. :raises: InstanceDeployFailure if bootloader installation failed or on encountering error while setting the boot device on the node. """ node = task.node LOG.debug('Configuring local boot for node %s', node.uuid) # If the target RAID configuration is set to 'software' for the # 'controller', we need to trigger the installation of grub on # the holder disks of the desired Software RAID. internal_info = node.driver_internal_info raid_config = node.target_raid_config logical_disks = raid_config.get('logical_disks', []) software_raid = False for logical_disk in logical_disks: if logical_disk.get('controller') == 'software': LOG.debug('Node %s has a Software RAID configuration', node.uuid) software_raid = True break # For software RAID try to get the UUID of the root fs from the # image's metadata (via Glance). Fall back to the driver internal # info in case it is not available (e.g. not set or there's no Glance). if software_raid: image_source = node.instance_info.get('image_source') try: context = task.context context.is_admin = True glance = image_service.GlanceImageService( context=context) image_info = glance.show(image_source) image_properties = image_info.get('properties') root_uuid = image_properties['rootfs_uuid'] LOG.debug('Got rootfs_uuid from Glance: %s', root_uuid) except Exception as e: LOG.warning('Could not get \'rootfs_uuid\' property for ' 'image %(image)s from Glance: %(error)s.', {'image': image_source, 'error': e}) root_uuid = internal_info.get('root_uuid_or_disk_id') LOG.debug('Got rootfs_uuid from driver internal info: ' ' %s', root_uuid) whole_disk_image = internal_info.get('is_whole_disk_image') if software_raid or (root_uuid and not whole_disk_image): LOG.debug('Installing the bootloader for node %(node)s on ' 'partition %(part)s, EFI system partition %(efi)s', {'node': node.uuid, 'part': root_uuid, 'efi': efi_system_part_uuid}) result = self._client.install_bootloader( node, root_uuid=root_uuid, efi_system_part_uuid=efi_system_part_uuid, prep_boot_part_uuid=prep_boot_part_uuid) if result['command_status'] == 'FAILED': msg = (_("Failed to install a bootloader when " "deploying node %(node)s. Error: %(error)s") % {'node': node.uuid, 'error': result['command_error']}) log_and_raise_deployment_error(task, msg) try: persistent = True if node.driver_info.get('force_persistent_boot_device', 'Default') == 'Never': persistent = False deploy_utils.try_set_boot_device(task, boot_devices.DISK, persistent=persistent) except Exception as e: msg = (_("Failed to change the boot device to %(boot_dev)s " "when deploying node %(node)s. Error: %(error)s") % {'boot_dev': boot_devices.DISK, 'node': node.uuid, 'error': e}) log_and_raise_deployment_error(task, msg, exc=e) LOG.info('Local boot successfully configured for node %s', node.uuid)
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. """ def validate_image_url(url, secret=False): """Validates image URL through the HEAD request. :param url: URL to be validated :param secret: if URL is secret (e.g. swift temp url), it will not be shown in logs. """ try: image_service.HttpImageService().validate_href(url, secret) except exception.ImageRefValidationFailed as e: with excutils.save_and_reraise_exception(): LOG.error( "Agent deploy supports only HTTP(S) URLs as " "instance_info['image_source'] or swift " "temporary URL. Either the specified URL is not " "a valid HTTP(S) URL or is not reachable " "for node %(node)s. Error: %(msg)s", { 'node': node.uuid, 'msg': e }) 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(context=task.context) image_info = glance.show(image_source) LOG.debug('Got image info: %(info)s for node %(node)s.', { 'info': image_info, 'node': node.uuid }) if CONF.agent.image_download_source == 'swift': swift_temp_url = glance.swift_temp_url(image_info) validate_image_url(swift_temp_url, secret=True) 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_os_hash_algo'] = image_info['os_hash_algo'] instance_info['image_os_hash_value'] = image_info['os_hash_value'] else: # Ironic cache and serve images from httpboot server force_raw = direct_deploy_should_convert_raw_image(node) _, image_path = cache_instance_image(task.context, node, force_raw=force_raw) if force_raw: instance_info['image_disk_format'] = 'raw' # Standard behavior is for image_checksum to be MD5, # so if the hash algorithm is None, then we will use # sha256. os_hash_algo = image_info.get('os_hash_algo') if os_hash_algo == 'md5': LOG.debug( 'Checksum calculation for image %(image)s is ' 'set to \'%(algo)s\', changing to \'sha256\'', { 'algo': os_hash_algo, 'image': image_path }) os_hash_algo = 'sha256' LOG.debug( 'Recalculating checksum for image %(image)s due to ' 'image conversion.', {'image': image_path}) instance_info['image_checksum'] = None hash_value = compute_image_checksum(image_path, os_hash_algo) instance_info['image_os_hash_algo'] = os_hash_algo instance_info['image_os_hash_value'] = hash_value else: instance_info['image_checksum'] = image_info['checksum'] instance_info['image_disk_format'] = image_info['disk_format'] instance_info['image_os_hash_algo'] = image_info[ 'os_hash_algo'] instance_info['image_os_hash_value'] = image_info[ 'os_hash_value'] # Create symlink and update image url symlink_dir = _get_http_image_symlink_dir_path() fileutils.ensure_tree(symlink_dir) symlink_path = _get_http_image_symlink_file_path(node.uuid) utils.create_link_without_raise(image_path, symlink_path) base_url = CONF.deploy.http_url if base_url.endswith('/'): base_url = base_url[:-1] http_image_url = '/'.join( [base_url, CONF.deploy.http_image_subdir, node.uuid]) validate_image_url(http_image_url, secret=True) instance_info['image_url'] = http_image_url instance_info['image_container_format'] = ( image_info['container_format']) instance_info['image_tags'] = image_info.get('tags', []) instance_info['image_properties'] = image_info['properties'] if not iwdi: instance_info['kernel'] = image_info['properties']['kernel_id'] instance_info['ramdisk'] = image_info['properties']['ramdisk_id'] else: validate_image_url(image_source) instance_info['image_url'] = image_source if not iwdi: instance_info['image_type'] = 'partition' i_info = parse_instance_info(node) instance_info.update(i_info) else: instance_info['image_type'] = 'whole-disk-image' return instance_info