135 lines
4.4 KiB
Python
135 lines
4.4 KiB
Python
# 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
|