def _wait_volume_status(self, volume, status_transition, status_expected): max_recheck_wait = 15 timeout = self.store_conf.cinder_state_transition_timeout volume = volume.manager.get(volume.id) tries = 0 elapsed = 0 while volume.status == status_transition: if elapsed >= timeout: msg = (_('Timeout while waiting while volume %(volume_id)s ' 'status is %(status)s.') % { 'volume_id': volume.id, 'status': status_transition }) LOG.error(msg) raise exceptions.BackendException(msg) wait = min(0.5 * 2**tries, max_recheck_wait) time.sleep(wait) tries += 1 elapsed += wait volume = volume.manager.get(volume.id) if volume.status != status_expected: msg = (_('The status of volume %(volume_id)s is unexpected: ' 'status = %(status)s, expected = %(expected)s.') % { 'volume_id': volume.id, 'status': volume.status, 'expected': status_expected }) LOG.error(msg) raise exceptions.BackendException(msg) return volume
def store_add_to_backend(image_id, data, size, store, context=None): """ A wrapper around a call to each stores add() method. This gives glance a common place to check the output :param image_id: The image add to which data is added :param data: The data to be stored :param size: The length of the data in bytes :param store: The store to which the data is being added :return: The url location of the file, the size amount of data, the checksum of the data the storage systems metadata dictionary for the location """ (location, size, checksum, metadata) = store.add(image_id, data, size) if metadata is not None: if not isinstance(metadata, dict): msg = (_("The storage driver %(driver)s returned invalid " " metadata %(metadata)s. This must be a dictionary type") % dict(driver=str(store), metadata=str(metadata))) LOG.error(msg) raise exceptions.BackendException(msg) try: check_location_metadata(metadata) except exceptions.BackendException as e: e_msg = (_("A bad metadata structure was returned from the " "%(driver)s storage driver: %(metadata)s. %(e)s.") % dict(driver=unicode(store), metadata=unicode(metadata), e=unicode(e))) LOG.error(e_msg) raise exceptions.BackendException(e_msg) return (location, size, checksum, metadata)
def get(self, location, offset=0, chunk_size=None, context=None): """ Takes a `glance_store.location.Location` object that indicates where to find the image file, and returns a tuple of generator (for reading the image file) and image_size :param location: `glance_store.location.Location` object, supplied from glance_store.location.get_location_from_uri() :param offset: offset to start reading :param chunk_size: size to read, or None to get all the image :param context: Request context :raises: `glance_store.exceptions.NotFound` if image does not exist """ loc = location.store_location self._check_context(context) try: client = self.get_cinderclient(context) volume = client.volumes.get(loc.volume_id) size = int(volume.metadata.get('image_size', volume.size * units.Gi)) iterator = self._cinder_volume_data_iterator( client, volume, size, offset=offset, chunk_size=self.READ_CHUNKSIZE, partial_length=chunk_size) return (iterator, chunk_size or size) except cinder_exception.NotFound: reason = _("Failed to get image size due to " "volume can not be found: %s") % loc.volume_id LOG.error(reason) raise exceptions.NotFound(reason) except cinder_exception.ClientException as e: msg = (_('Failed to get image volume %(volume_id)s: %(error)s') % {'volume_id': loc.volume_id, 'error': e}) LOG.error(msg) raise exceptions.BackendException(msg)
def create_stores(conf=CONF): """ Registers all store modules and all schemes from the given config. Duplicates are not re-registered. """ store_count = 0 for (store_entry, store_instance) in _load_stores(conf): try: schemes = store_instance.get_schemes() store_instance.configure(re_raise_bsc=False) except NotImplementedError: continue if not schemes: raise exceptions.BackendException('Unable to register store %s. ' 'No schemes associated with it.' % store_entry) else: LOG.debug("Registering store %s with schemes %s", store_entry, schemes) scheme_map = {} loc_cls = store_instance.get_store_location_class() for scheme in schemes: scheme_map[scheme] = { 'store': store_instance, 'location_class': loc_cls, 'store_entry': store_entry } location.register_scheme_map(scheme_map) store_count += 1 return store_count
def create_multi_stores(conf=CONF): """Registers all store modules and all schemes from the given config.""" store_count = 0 scheme_map = {} for (store_entry, store_instance, store_identifier) in _load_multi_stores(conf): try: schemes = store_instance.get_schemes() store_instance.configure(re_raise_bsc=False) except NotImplementedError: continue if not schemes: raise exceptions.BackendException( _('Unable to register store %s. No schemes associated ' 'with it.') % store_entry) else: LOG.debug("Registering store %s with schemes %s", store_entry, schemes) loc_cls = store_instance.get_store_location_class() for scheme in schemes: if scheme not in scheme_map: scheme_map[scheme] = {} scheme_map[scheme][store_identifier] = { 'store': store_instance, 'location_class': loc_cls, 'store_entry': store_entry } location.register_scheme_backend_map(scheme_map) store_count += 1 return store_count
def configure_add(self): """ Configure the Store to use the stored configuration options Any store that needs special configuration should implement this method. If the store was not able to successfully configure itself, it should raise `exceptions.BadStoreConfiguration` :raises: `exceptions.BadStoreConfiguration` if multiple stores are defined and particular store wasn't able to configure successfully :raises: `exceptions.BackendException` if single store is defined and it wasn't able to configure successfully """ if self.backend_group: cinder_volume_type = self.store_conf.cinder_volume_type if cinder_volume_type: # NOTE: `cinder_volume_type` is configured, check # configured volume_type is available in cinder or not cinder_client = self.get_cinderclient() try: # We don't even need the volume type object, as long # as this returns clean, we know the name is good. cinder_client.volume_types.find(name=cinder_volume_type) # No need to worry NoUniqueMatch as volume type name is # unique except cinder_exception.NotFound: reason = _("Invalid `cinder_volume_type %s`" % cinder_volume_type) if len(self.conf.enabled_backends) > 1: LOG.error(reason) raise exceptions.BadStoreConfiguration( store_name=self.backend_group, reason=reason) else: LOG.critical(reason) raise exceptions.BackendException(reason)
def delete(self, location, context=None): """ Takes a `glance_store.location.Location` object that indicates where to find the image file to delete :location `glance_store.location.Location` object, supplied from glance_store.location.get_location_from_uri() :raises NotFound if image does not exist :raises Forbidden if cannot delete because of permissions """ loc = location.store_location self._check_context(context) try: volume = get_cinderclient(self.conf, context).volumes.get(loc.volume_id) volume.delete() except cinder_exception.NotFound: raise exceptions.NotFound(image=loc.volume_id) except cinder_exception.ClientException as e: msg = (_('Failed to delete volume %(volume_id)s: %(error)s') % { 'volume_id': loc.volume_id, 'error': e }) raise exceptions.BackendException(msg)
def _check_metadata(store, metadata): if not isinstance(metadata, dict): msg = (_("The storage driver %(driver)s returned invalid " " metadata %(metadata)s. This must be a dictionary type") % dict(driver=str(store), metadata=str(metadata))) LOG.error(msg) raise exceptions.BackendException(msg) try: check_location_metadata(metadata) except exceptions.BackendException as e: e_msg = (_("A bad metadata structure was returned from the " "%(driver)s storage driver: %(metadata)s. %(e)s.") % dict(driver=encodeutils.exception_to_unicode(store), metadata=encodeutils.exception_to_unicode(metadata), e=encodeutils.exception_to_unicode(e))) LOG.error(e_msg) raise exceptions.BackendException(e_msg)
def _open_cinder_volume(self, client, volume, mode): attach_mode = 'rw' if mode == 'wb' else 'ro' device = None root_helper = get_root_helper() host = socket.gethostname() properties = connector.get_connector_properties(root_helper, host, False, False) try: volume.reserve(volume) except cinder_exception.ClientException as e: msg = (_('Failed to reserve volume %(volume_id)s: %(error)s') % {'volume_id': volume.id, 'error': e}) LOG.error(msg) raise exceptions.BackendException(msg) try: connection_info = volume.initialize_connection(volume, properties) conn = connector.InitiatorConnector.factory( connection_info['driver_volume_type'], root_helper, conn=connection_info) device = conn.connect_volume(connection_info['data']) volume.attach(None, None, attach_mode, host_name=host) volume = self._wait_volume_status(volume, 'attaching', 'in-use') LOG.debug('Opening host device "%s"', device['path']) with temporary_chown(device['path']), \ open(device['path'], mode) as f: yield f except Exception: LOG.exception(_LE('Exception while accessing to cinder volume ' '%(volume_id)s.'), {'volume_id': volume.id}) raise finally: if volume.status == 'in-use': volume.begin_detaching(volume) elif volume.status == 'attaching': volume.unreserve(volume) if device: try: conn.disconnect_volume(connection_info['data'], device) except Exception: LOG.exception(_LE('Failed to disconnect volume ' '%(volume_id)s.'), {'volume_id': volume.id}) try: volume.terminate_connection(volume, properties) except Exception: LOG.exception(_LE('Failed to terminate connection of volume ' '%(volume_id)s.'), {'volume_id': volume.id}) try: client.volumes.detach(volume) except Exception: LOG.exception(_LE('Failed to detach volume %(volume_id)s.'), {'volume_id': volume.id})
def get_connection(self, conffile, rados_id): client = rados.Rados(conffile=conffile, rados_id=rados_id) try: client.connect(timeout=self.connect_timeout) except rados.Error: msg = _LE("Error connecting to ceph cluster.") LOG.exception(msg) raise exceptions.BackendException() try: yield client finally: client.shutdown()
def _si_poll(self, si, tenant): TIMEOUT = 10 retry = 0 poll = True while poll and not retry >= TIMEOUT: LOG.debug("Polling StorageInstance: %s", si.name) retry += 1 si = si.reload(tenant=tenant) if si.op_state == 'available': poll = False else: eventlet.sleep(1) if retry >= TIMEOUT: raise exceptions.BackendException(message=_('Resource not ready.'))
def check_location_metadata(val, key=''): if isinstance(val, dict): for key in val: check_location_metadata(val[key], key=key) elif isinstance(val, list): ndx = 0 for v in val: check_location_metadata(v, key='%s[%d]' % (key, ndx)) ndx = ndx + 1 elif not isinstance(val, six.text_type): raise exceptions.BackendException(_("The image metadata key %(key)s " "has an invalid type of %(type)s. " "Only dict, list, and unicode are " "supported.") % dict(key=key, type=type(val)))
def get_connection(self, conffile, rados_id): client = rados.Rados(conffile=conffile, rados_id=rados_id) try: client.connect(timeout=self.connect_timeout) except (rados.Error, rados.ObjectNotFound) as e: if self.backend_group and len(self.conf.enabled_backends) > 1: reason = _("Error in store configuration: %s") % e LOG.debug(reason) raise exceptions.BadStoreConfiguration( store_name=self.backend_group, reason=reason) else: msg = _LE("Error connecting to ceph cluster.") LOG.exception(msg) raise exceptions.BackendException() try: yield client finally: client.shutdown()
def _get_storage_url(self): """Get swift endpoint from keystone Return endpoint for swift from service catalog. The method works only Keystone v3. If you are using different version (1 or 2) it returns None. :return: swift endpoint """ if self.store.auth_version == '3': try: return self.client.session.get_endpoint( service_type=self.store.service_type, interface=self.store.endpoint_type, region_name=self.store.region) except Exception as e: # do the same that swift driver does # when catching ClientException msg = _("Cannot find swift service endpoint : " "%s") % encodeutils.exception_to_unicode(e) raise exceptions.BackendException(msg)
def _open_cinder_volume(self, client, volume, mode): attach_mode = 'rw' if mode == 'wb' else 'ro' device = None root_helper = self.get_root_helper() priv_context.init(root_helper=shlex.split(root_helper)) host = socket.gethostname() use_multipath = self.store_conf.cinder_use_multipath enforce_multipath = self.store_conf.cinder_enforce_multipath mount_point_base = self.store_conf.cinder_mount_point_base properties = connector.get_connector_properties( root_helper, host, use_multipath, enforce_multipath) try: volume.reserve(volume) except cinder_exception.ClientException as e: msg = (_('Failed to reserve volume %(volume_id)s: %(error)s') % { 'volume_id': volume.id, 'error': e }) LOG.error(msg) raise exceptions.BackendException(msg) try: connection_info = volume.initialize_connection(volume, properties) conn = connector.InitiatorConnector.factory( connection_info['driver_volume_type'], root_helper, conn=connection_info) if connection_info['driver_volume_type'] == 'nfs': if volume.encrypted: volume.unreserve(volume) volume.delete() msg = (_('Encrypted volume creation for cinder nfs is not ' 'supported from glance_store. Failed to create ' 'volume %(volume_id)s') % { 'volume_id': volume.id }) LOG.error(msg) raise exceptions.BackendException(msg) @utils.synchronized(connection_info['data']['export']) def connect_volume_nfs(): data = connection_info['data'] export = data['export'] vol_name = data['name'] mountpoint = self._get_mount_path( export, os.path.join(mount_point_base, 'nfs')) options = data['options'] self.mount.mount('nfs', export, vol_name, mountpoint, host, root_helper, options) return {'path': os.path.join(mountpoint, vol_name)} device = connect_volume_nfs() else: device = conn.connect_volume(connection_info['data']) volume.attach(None, 'glance_store', attach_mode, host_name=host) volume = self._wait_volume_status(volume, 'attaching', 'in-use') if (connection_info['driver_volume_type'] == 'rbd' and not conn.do_local_attach): yield device['path'] else: with self.temporary_chown(device['path']), open( device['path'], mode) as f: yield f except Exception: LOG.exception( _LE('Exception while accessing to cinder volume ' '%(volume_id)s.'), {'volume_id': volume.id}) raise finally: if volume.status == 'in-use': volume.begin_detaching(volume) elif volume.status == 'attaching': volume.unreserve(volume) if device: try: if connection_info['driver_volume_type'] == 'nfs': @utils.synchronized(connection_info['data']['export']) def disconnect_volume_nfs(): path, vol_name = device['path'].rsplit('/', 1) self.mount.umount(vol_name, path, host, root_helper) disconnect_volume_nfs() else: conn.disconnect_volume(connection_info['data'], device) except Exception: LOG.exception( _LE('Failed to disconnect volume ' '%(volume_id)s.'), {'volume_id': volume.id}) try: volume.terminate_connection(volume, properties) except Exception: LOG.exception( _LE('Failed to terminate connection of volume ' '%(volume_id)s.'), {'volume_id': volume.id}) try: client.volumes.detach(volume) except Exception: LOG.exception(_LE('Failed to detach volume %(volume_id)s.'), {'volume_id': volume.id})
def create_multi_stores(conf=CONF, reserved_stores=None): """ Registers all store modules and all schemes from the given configuration object. :param conf: A oslo_config (or compatible) object :param reserved_stores: A list of stores for the consuming service's internal use. The list must be the same format as the ``enabled_backends`` configuration setting. The default value is None :return: The number of stores configured :raises: ``glance_store.exceptions.BackendException`` *Configuring Multiple Backends* The backends to be configured are expected to be found in the ``enabled_backends`` configuration variable in the DEFAULT group of the object. The format for the variable is a dictionary of key:value pairs where the key is an arbitrary store identifier and the value is the store type identifier for the store. The type identifiers must be defined in the ``[entry points]`` section of the glance_store ``setup.cfg`` file as values for the ``glance_store.drivers`` configuration. (See the default ``setup.cfg`` file for an example.) The store type identifiers for the currently supported drivers are already defined in the file. Thus an example value for ``enabled_backends`` is:: {'store_one': 'http', 'store_two': 'file', 'store_three': 'rbd'} The ``reserved_stores`` parameter, if included, must have the same format. There is no difference between the ``enabled_backends`` and ``reserved_stores`` from the glance_store point of view: the reserved stores are a convenience for the consuming service, which may wish to handle the two sets of stores differently. *The Default Store* If you wish to set a default store, its store identifier should be defined as the value of the ``default_backend`` configuration option in the ``glance_store`` group of the ``conf`` parameter. The store identifier, or course, should be specified as one of the keys in the ``enabled_backends`` dict. It is recommended that a default store be set. *Configuring Individual Backends* To configure each store mentioned in the ``enabled_backends`` configuration option, you must define an option group with the same name as the store identifier. The options defined for that backend will depend upon the store type; consult the documentation for the appropriate backend driver to determine what these are. For example, given the ``enabled_backends`` example above, you would put the following in the configuration file that loads the ``conf`` object:: [DEFAULT] enabled_backends = store_one:rbd,store_two:file,store_three:http [store_one] store_description = "A human-readable string aimed at end users" rbd_store_chunk_size = 8 rbd_store_pool = images rbd_store_user = admin rbd_store_ceph_conf = /etc/ceph/ceph.conf [store_two] store_description = "Human-readable description of this store" filesystem_store_datadir = /opt/stack/data/glance/store_two [store_three] store_description = "A read-only store" https_ca_certificates_file = /opt/stack/certs/gs.cert [glance_store] default_backend = store_two The ``store_description`` options may be used by a consuming service. As recommended above, this file also defines a default backend. """ store_count = 0 scheme_map = {} for (store_entry, store_instance, store_identifier) in _load_multi_stores( conf, reserved_stores=reserved_stores): try: schemes = store_instance.get_schemes() store_instance.configure(re_raise_bsc=False) except NotImplementedError: continue if not schemes: raise exceptions.BackendException( _('Unable to register store %s. No schemes associated ' 'with it.') % store_entry) else: LOG.debug("Registering store %s with schemes %s", store_entry, schemes) loc_cls = store_instance.get_store_location_class() for scheme in schemes: if scheme not in scheme_map: scheme_map[scheme] = {} scheme_map[scheme][store_identifier] = { 'store': store_instance, 'location_class': loc_cls, 'store_entry': store_entry } location.register_scheme_backend_map(scheme_map) store_count += 1 return store_count
def add(self, image_id, image_file, image_size, context=None): """Stores an image file with supplied identifier to the backend storage system and returns a tuple containing information about the stored image. :param image_id: The opaque image identifier :param image_file: The image data to write, as a file-like object :param image_size: The size of the image data to write, in bytes :retval tuple of URL in backing store, bytes written, checksum and a dictionary with storage system specific information :raises `glance.common.exceptions.Duplicate` if the image already existed `glance.common.exceptions.UnexpectedStatus` if the upload request returned an unexpected status. The expected responses are 201 Created and 200 OK. """ ds = self.select_datastore(image_size) if image_size > 0: headers = {'Content-Length': image_size} image_file = _Reader(image_file) else: # NOTE (arnaud): use chunk encoding when the image is still being # generated by the server (ex: stream optimized disks generated by # Nova). headers = {'Transfer-Encoding': 'chunked'} image_file = _ChunkReader(image_file) loc = StoreLocation( { 'scheme': self.scheme, 'server_host': self.server_host, 'image_dir': self.store_image_dir, 'datacenter_path': ds.datacenter.path, 'datastore_name': ds.name, 'image_id': image_id }, self.conf) # NOTE(arnaud): use a decorator when the config is not tied to self cookie = self._build_vim_cookie_header(True) headers = dict(headers) headers['Cookie'] = cookie conn_class = self._get_http_conn_class() conn = conn_class(loc.server_host) url = urllib.parse.quote('%s?%s' % (loc.path, loc.query)) try: conn.request('PUT', url, image_file, headers) except IOError as e: # When a session is not authenticated, the socket is closed by # the server after sending the response. http_client has an open # issue with https that raises Broken Pipe # error instead of returning the response. # See http://bugs.python.org/issue16062. Here, we log the error # and continue to look into the response. msg = _LE('Communication error sending http %(method)s request ' 'to the url %(url)s.\n' 'Got IOError %(e)s') % { 'method': 'PUT', 'url': url, 'e': e } LOG.error(msg) except Exception: with excutils.save_and_reraise_exception(): LOG.exception( _LE('Failed to upload content of image ' '%(image)s'), {'image': image_id}) res = conn.getresponse() if res.status == http_client.CONFLICT: raise exceptions.Duplicate( _("Image file %(image_id)s already " "exists!") % {'image_id': image_id}) if res.status not in (http_client.CREATED, http_client.OK): msg = (_LE('Failed to upload content of image %(image)s. ' 'The request returned an unexpected status: %(status)s.' '\nThe response body:\n%(body)s') % { 'image': image_id, 'status': res.status, 'body': getattr(res, 'body', None) }) LOG.error(msg) raise exceptions.BackendException(msg) return (loc.get_uri(), image_file.size, image_file.checksum.hexdigest(), {})
def add(self, image_id, image_file, image_size, hashing_algo, context=None, verifier=None): """Stores an image file with supplied identifier to the backend storage system and returns a tuple containing information about the stored image. :param image_id: The opaque image identifier :param image_file: The image data to write, as a file-like object :param image_size: The size of the image data to write, in bytes :param hashing_algo: A hashlib algorithm identifier (string) :param context: A context object :param verifier: An object used to verify signatures for images :returns: tuple of: (1) URL in backing store, (2) bytes written, (3) checksum, (4) multihash value, and (5) a dictionary with storage system specific information :raises: `glance_store.exceptions.Duplicate` if the image already exists :raises: `glance.common.exceptions.UnexpectedStatus` if the upload request returned an unexpected status. The expected responses are 201 Created and 200 OK. """ ds = self.select_datastore(image_size) image_file = _Reader(image_file, hashing_algo, verifier) headers = {} if image_size > 0: headers.update({'Content-Length': six.text_type(image_size)}) data = image_file else: data = utils.chunkiter(image_file, CHUNKSIZE) loc = StoreLocation( { 'scheme': self.scheme, 'server_host': self.server_host, 'image_dir': self.store_image_dir, 'datacenter_path': ds.datacenter.path, 'datastore_name': ds.name, 'image_id': image_id }, self.conf, backend_group=self.backend_group) # NOTE(arnaud): use a decorator when the config is not tied to self cookie = self._build_vim_cookie_header(True) headers = dict(headers) headers.update({'Cookie': cookie}) session = new_session(self.api_insecure, self.ca_file) url = loc.https_url try: response = session.put(url, data=data, headers=headers) except IOError as e: # TODO(sigmavirus24): Figure out what the new exception type would # be in requests. # When a session is not authenticated, the socket is closed by # the server after sending the response. http_client has an open # issue with https that raises Broken Pipe # error instead of returning the response. # See http://bugs.python.org/issue16062. Here, we log the error # and continue to look into the response. msg = _LE('Communication error sending http %(method)s request ' 'to the url %(url)s.\n' 'Got IOError %(e)s') % { 'method': 'PUT', 'url': url, 'e': e } LOG.error(msg) raise exceptions.BackendException(msg) except Exception: with excutils.save_and_reraise_exception(): LOG.exception( _LE('Failed to upload content of image ' '%(image)s'), {'image': image_id}) res = response.raw if res.status == requests.codes.conflict: raise exceptions.Duplicate( _("Image file %(image_id)s already " "exists!") % {'image_id': image_id}) if res.status not in (requests.codes.created, requests.codes.ok): msg = (_LE('Failed to upload content of image %(image)s. ' 'The request returned an unexpected status: %(status)s.' '\nThe response body:\n%(body)s') % { 'image': image_id, 'status': res.status, 'body': getattr(res, 'body', None) }) LOG.error(msg) raise exceptions.BackendException(msg) metadata = {} if self.backend_group: metadata['backend'] = u"%s" % self.backend_group return (loc.get_uri(), image_file.size, image_file.checksum.hexdigest(), image_file.os_hash_value.hexdigest(), metadata)
def _open_cinder_volume(self, client, volume, mode): attach_mode = 'rw' if mode == 'wb' else 'ro' device = None root_helper = get_root_helper(backend=self.backend_group) priv_context.init(root_helper=shlex.split(root_helper)) host = socket.gethostname() if self.backend_group: use_multipath = getattr( self.conf, self.backend_group).cinder_use_multipath enforce_multipath = getattr( self.conf, self.backend_group).cinder_enforce_multipath else: use_multipath = self.conf.glance_store.cinder_use_multipath enforce_multipath = self.conf.glance_store.cinder_enforce_multipath properties = connector.get_connector_properties( root_helper, host, use_multipath, enforce_multipath) try: volume.reserve(volume) except cinder_exception.ClientException as e: msg = (_('Failed to reserve volume %(volume_id)s: %(error)s') % {'volume_id': volume.id, 'error': e}) LOG.error(msg) raise exceptions.BackendException(msg) try: connection_info = volume.initialize_connection(volume, properties) conn = connector.InitiatorConnector.factory( connection_info['driver_volume_type'], root_helper, conn=connection_info) device = conn.connect_volume(connection_info['data']) volume.attach(None, 'glance_store', attach_mode, host_name=host) volume = self._wait_volume_status(volume, 'attaching', 'in-use') if (connection_info['driver_volume_type'] == 'rbd' and not conn.do_local_attach): yield device['path'] else: with temporary_chown(device['path'], backend=self.backend_group), \ open(device['path'], mode) as f: yield f except Exception: LOG.exception(_LE('Exception while accessing to cinder volume ' '%(volume_id)s.'), {'volume_id': volume.id}) raise finally: if volume.status == 'in-use': volume.begin_detaching(volume) elif volume.status == 'attaching': volume.unreserve(volume) if device: try: conn.disconnect_volume(connection_info['data'], device) except Exception: LOG.exception(_LE('Failed to disconnect volume ' '%(volume_id)s.'), {'volume_id': volume.id}) try: volume.terminate_connection(volume, properties) except Exception: LOG.exception(_LE('Failed to terminate connection of volume ' '%(volume_id)s.'), {'volume_id': volume.id}) try: client.volumes.detach(volume) except Exception: LOG.exception(_LE('Failed to detach volume %(volume_id)s.'), {'volume_id': volume.id})
def add(self, image_id, image_file, image_size, hashing_algo, context=None, verifier=None): """ Stores an image file with supplied identifier to the backend storage system and returns a tuple containing information about the stored image. :param image_id: The opaque image identifier :param image_file: The image data to write, as a file-like object :param image_size: The size of the image data to write, in bytes :param hashing_algo: A hashlib algorithm identifier (string) :param context: The request context :param verifier: An object used to verify signatures for images :returns: tuple of: (1) URL in backing store, (2) bytes written, (3) checksum, (4) multihash value, and (5) a dictionary with storage system specific information :raises: `glance_store.exceptions.Duplicate` if the image already exists """ self._check_context(context, require_tenant=True) client = self.get_cinderclient(context) os_hash_value = utils.get_hasher(hashing_algo, False) checksum = utils.get_hasher('md5', False) bytes_written = 0 size_gb = int(math.ceil(float(image_size) / units.Gi)) if size_gb == 0: size_gb = 1 name = "image-%s" % image_id owner = context.project_id metadata = {'glance_image_id': image_id, 'image_size': str(image_size), 'image_owner': owner} volume_type = self.store_conf.cinder_volume_type LOG.debug('Creating a new volume: image_size=%d size_gb=%d type=%s', image_size, size_gb, volume_type or 'None') if image_size == 0: LOG.info(_LI("Since image size is zero, we will be doing " "resize-before-write for each GB which " "will be considerably slower than normal.")) try: volume = client.volumes.create(size_gb, name=name, metadata=metadata, volume_type=volume_type) except cinder_exception.NotFound: LOG.error(_LE("Invalid volume type %s configured. Please check " "the `cinder_volume_type` configuration parameter." % volume_type)) msg = (_("Failed to create image-volume due to invalid " "`cinder_volume_type` configured.")) raise exceptions.BackendException(msg) volume = self._wait_volume_status(volume, 'creating', 'available') size_gb = volume.size failed = True need_extend = True buf = None try: while need_extend: with self._open_cinder_volume(client, volume, 'wb') as f: f.seek(bytes_written) if buf: f.write(buf) bytes_written += len(buf) while True: buf = image_file.read(self.WRITE_CHUNKSIZE) if not buf: need_extend = False break os_hash_value.update(buf) checksum.update(buf) if verifier: verifier.update(buf) if (bytes_written + len(buf) > size_gb * units.Gi and image_size == 0): break f.write(buf) bytes_written += len(buf) if need_extend: size_gb += 1 LOG.debug("Extending volume %(volume_id)s to %(size)s GB.", {'volume_id': volume.id, 'size': size_gb}) volume.extend(volume, size_gb) try: volume = self._wait_volume_status(volume, 'extending', 'available') size_gb = volume.size except exceptions.BackendException: raise exceptions.StorageFull() failed = False except IOError as e: # Convert IOError reasons to Glance Store exceptions errors = {errno.EFBIG: exceptions.StorageFull(), errno.ENOSPC: exceptions.StorageFull(), errno.EACCES: exceptions.StorageWriteDenied()} raise errors.get(e.errno, e) finally: if failed: LOG.error(_LE("Failed to write to volume %(volume_id)s."), {'volume_id': volume.id}) try: volume.delete() except Exception: LOG.exception(_LE('Failed to delete of volume ' '%(volume_id)s.'), {'volume_id': volume.id}) if image_size == 0: metadata.update({'image_size': str(bytes_written)}) volume.update_all_metadata(metadata) volume.update_readonly_flag(volume, True) hash_hex = os_hash_value.hexdigest() checksum_hex = checksum.hexdigest() LOG.debug("Wrote %(bytes_written)d bytes to volume %(volume_id)s " "with checksum %(checksum_hex)s.", {'bytes_written': bytes_written, 'volume_id': volume.id, 'checksum_hex': checksum_hex}) image_metadata = {} location_url = 'cinder://%s' % volume.id if self.backend_group: image_metadata['store'] = u"%s" % self.backend_group location_url = 'cinder://%s/%s' % (self.backend_group, volume.id) return (location_url, bytes_written, checksum_hex, hash_hex, image_metadata)