Example #1
0
    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
Example #2
0
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)
Example #3
0
    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)
Example #4
0
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
Example #6
0
 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)
Example #7
0
    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)
Example #8
0
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)
Example #9
0
    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})
Example #10
0
    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()
Example #11
0
 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.'))
Example #12
0
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)))
Example #13
0
    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()
Example #14
0
    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)
Example #15
0
    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})
Example #16
0
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
Example #17
0
    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)
Example #19
0
    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})
Example #20
0
    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)