# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack LLC. # 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. """ Transparent image file caching middleware, designed to live on Glance API nodes. When images are requested from the API node, this middleware caches the returned image file to local filesystem. When subsequent requests for the same image file are received, the local cached copy of the image file is returned. """ import httplib import logging import re import webob from glance import image_cache from glance import registry from glance.api.v1 import images from glance.common import exception from glance.common import utils from glance.common import wsgi logger = logging.getLogger(__name__) get_images_re = re.compile(r'^(/v\d+)*/images/(.+)$') class CacheFilter(wsgi.Middleware): def __init__(self, app, conf): self.conf = conf self.cache = image_cache.ImageCache(conf) self.serializer = images.ImageSerializer() logger.info(_("Initialized image cache middleware")) super(CacheFilter, self).__init__(app) def process_request(self, request): """ For requests for an image file, we check the local image cache. If present, we return the image file, appending the image metadata in headers. If not present, we pass the request on to the next application in the pipeline. """ if request.method != 'GET': return None match = get_images_re.match(request.path) if not match: return None image_id = match.group(2) # /images/detail is unfortunately supported, so here we # cut out those requests and anything with a query # parameter... # See LP Bug #879136 if '?' in image_id or image_id == 'detail': return None if self.cache.is_cached(image_id): logger.debug(_("Cache hit for image '%s'"), image_id) image_iterator = self.get_from_cache(image_id) context = request.context try: image_meta = registry.get_image_metadata(context, image_id) response = webob.Response() return self.serializer.show(response, { 'image_iterator': image_iterator, 'image_meta': image_meta}) except exception.NotFound: msg = _("Image cache contained image file for image '%s', " "however the registry did not contain metadata for " "that image!" % image_id) logger.error(msg) return None def process_response(self, resp): """ We intercept the response coming back from the main images Resource, caching image files to the cache """ if not self.get_status_code(resp) == httplib.OK: return resp request = resp.request if request.method != 'GET': return resp match = get_images_re.match(request.path) if match is None: return resp image_id = match.group(2) if '?' in image_id or image_id == 'detail': return resp if self.cache.is_cached(image_id): return resp resp.app_iter = self.cache.get_caching_iter(image_id, resp.app_iter) return resp def get_status_code(self, response): """ Returns the integer status code from the response, which can be either a Webob.Response (used in testing) or httplib.Response """ if hasattr(response, 'status_int'): return response.status_int return response.status def get_from_cache(self, image_id): """Called if cache hit""" with self.cache.open_for_read(image_id) as cache_file: chunks = utils.chunkiter(cache_file) for chunk in chunks: yield chunk