diff options
Diffstat (limited to 'glance/store/gridfs.py')
-rw-r--r-- | glance/store/gridfs.py | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/glance/store/gridfs.py b/glance/store/gridfs.py new file mode 100644 index 0000000..14f0b5f --- /dev/null +++ b/glance/store/gridfs.py @@ -0,0 +1,212 @@ +# Copyright 2013 Red Hat, Inc +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Storage backend for GridFS""" +from __future__ import absolute_import + +from oslo.config import cfg +import urlparse + +from glance.common import exception +from glance.openstack.common import excutils +import glance.openstack.common.log as logging +import glance.store.base +import glance.store.location + +try: + import gridfs + import gridfs.errors + import pymongo + import pymongo.uri_parser as uri_parser +except ImportError: + pymongo = None + +LOG = logging.getLogger(__name__) + +gridfs_opts = [ + cfg.StrOpt('mongodb_store_uri', + help="Hostname or IP address of the instance to connect to, " + "or a mongodb URI, or a list of hostnames / mongodb URIs. " + "If host is an IPv6 literal it must be enclosed " + "in '[' and ']' characters following the RFC2732 " + "URL syntax (e.g. '[::1]' for localhost)"), + cfg.StrOpt('mongodb_store_db', default=None, help='Database to use'), +] + +CONF = cfg.CONF +CONF.register_opts(gridfs_opts) + + +class StoreLocation(glance.store.location.StoreLocation): + """ + Class describing an gridfs URI: + + gridfs://<IMAGE_ID> + + Connection information has been consciously omitted for + security reasons, since this location will be stored in glance's + database and can be queried from outside. + + Note(flaper87): Make connection info available if user wants so + by adding a new configuration parameter `mongdb_store_insecure`. + """ + + def get_uri(self): + return "gridfs://%s" % self.specs.get("image_id") + + def parse_uri(self, uri): + """ + This method should fix any issue with the passed URI. Right now, + it just sets image_id value in the specs dict. + + :param uri: Current set URI + """ + parsed = urlparse.urlparse(uri) + assert parsed.scheme in ('gridfs',) + self.specs["image_id"] = parsed.netloc + + +class Store(glance.store.base.Store): + """GridFS adapter""" + + EXAMPLE_URL = "gridfs://<IMAGE_ID>" + + def get_schemes(self): + return ('gridfs',) + + 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 `exception.BadStoreConfiguration` + """ + if pymongo is None: + msg = _("Missing dependencies: pymongo") + raise exception.BadStoreConfiguration(store_name="gridfs", + reason=msg) + + self.mongodb_uri = self._option_get('mongodb_store_uri') + + parsed = uri_parser.parse_uri(self.mongodb_uri) + self.mongodb_db = self._option_get('mongodb_store_db') or \ + parsed.get("database") + + self.mongodb = pymongo.MongoClient(self.mongodb_uri) + self.fs = gridfs.GridFS(self.mongodb[self.mongodb_db]) + + def _option_get(self, param): + result = getattr(CONF, param) + if not result: + reason = (_("Could not find %(param)s in configuration " + "options.") % {'param': param}) + LOG.debug(reason) + raise exception.BadStoreConfiguration(store_name="gridfs", + reason=reason) + return result + + def get(self, location): + """ + 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() + :raises `glance.exception.NotFound` if image does not exist + """ + image = self._get_file(location) + return (image, image.length) + + def get_size(self, location): + """ + Takes a `glance.store.location.Location` object that indicates + where to find the image file, and returns the image_size (or 0 + if unavailable) + + :param location `glance.store.location.Location` object, supplied + from glance.store.location.get_location_from_uri() + """ + try: + key = self._get_file(location) + return key.length + except Exception: + return 0 + + def _get_file(self, location): + store_location = location + if isinstance(location, glance.store.location.Location): + store_location = location.store_location + try: + + parsed = urlparse.urlparse(store_location.get_uri()) + return self.fs.get(parsed.netloc) + except gridfs.errors.NoFile: + msg = _("Could not find %s image in GridFS") % \ + store_location.get_uri() + LOG.debug(msg) + raise exception.NotFound(msg) + + def add(self, image_id, image_file, image_size): + """ + 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.exception.Duplicate` if the image already + existed + """ + loc = StoreLocation({'image_id': image_id}) + + if self.fs.exists(image_id): + raise exception.Duplicate(_("GridFS already has an image at " + "location %s") % loc.get_uri()) + + LOG.debug(_("Adding a new image to GridFS with id %s and size %s") % + (image_id, image_size)) + + try: + self.fs.put(image_file, _id=image_id) + image = self._get_file(loc) + except Exception: + # Note(zhiyan): clean up already received data when + # error occurs such as ImageSizeLimitExceeded exception. + with excutils.save_and_reraise_exception(): + self.fs.delete(image_id) + + LOG.debug(_("Uploaded image %s, md5 %s, length %s to GridFS") % + (image._id, image.md5, image.length)) + + return (loc.get_uri(), image.length, image.md5, {}) + + def delete(self, location): + """ + 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 + """ + image = self._get_file(location) + self.fs.delete(image._id) + LOG.debug("Deleted image %s from GridFS") |