Adding reaper script
This commit is contained in:
parent
89d5c0bc8e
commit
e54deefaff
33
bin/glance
33
bin/glance
|
@ -632,6 +632,25 @@ Removes all images from the cache"""
|
|||
print "done"
|
||||
|
||||
|
||||
@catch_error('reap invalid images')
|
||||
def cache_reap_invalid(options, args):
|
||||
"""
|
||||
%(prog)s cache-reap-invalid [options]
|
||||
|
||||
Reaps any invalid images that were left for
|
||||
debugging purposes"""
|
||||
if not options.force and \
|
||||
not user_confirm("Reap all invalid cached images?", default=False):
|
||||
print 'Not reaping any invalid cached images.'
|
||||
return FAILURE
|
||||
|
||||
client = get_client(options)
|
||||
client.reap_invalid_cached_images()
|
||||
|
||||
if options.verbose:
|
||||
print "done"
|
||||
|
||||
|
||||
@catch_error('prefetch the specified cached image')
|
||||
def cache_prefetch(options, args):
|
||||
"""
|
||||
|
@ -783,10 +802,11 @@ def lookup_command(parser, command_name):
|
|||
'cache-index': cache_index,
|
||||
'cache-invalid': cache_invalid,
|
||||
'cache-incomplete': cache_incomplete,
|
||||
'cache-prefetching': cache_prefetching,
|
||||
'cache-prefetch': cache_prefetch,
|
||||
'cache-purge': cache_purge,
|
||||
'cache-clear': cache_clear,
|
||||
'cache-prefetch': cache_prefetch,
|
||||
'cache-prefetching': cache_prefetching}
|
||||
'cache-reap-invalid': cache_reap_invalid}
|
||||
|
||||
commands = {}
|
||||
for command_set in (BASE_COMMANDS, IMAGE_COMMANDS, CACHE_COMMANDS):
|
||||
|
@ -856,13 +876,16 @@ Cache Commands:
|
|||
|
||||
cache-incomplete List images currently being fetched
|
||||
|
||||
cache-prefetching List images that are being prefetched
|
||||
|
||||
cache-prefetch Pre-fetch an image or list of images into the cache
|
||||
|
||||
cache-purge Purges an image from the cache
|
||||
|
||||
cache-clear Removes all images from the cache
|
||||
|
||||
cache-prefetch Pre-fetch an image or list of images into the cache
|
||||
|
||||
cache-prefetching List images that are being prefetched
|
||||
cache-reap-invalid Reaps any invalid images that were left for
|
||||
debugging purposes
|
||||
"""
|
||||
|
||||
oparser = optparse.OptionParser(version='%%prog %s'
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Glance Image Cache Invalid Cache Entry Reaper
|
||||
|
||||
This is meant to be run as a periodic task from cron.
|
||||
|
||||
If something goes wrong while we're caching an image (for example the fetch
|
||||
times out, or an exception is raised), we create an 'invalid' entry. These
|
||||
entires are left around for debugging purposes. However, after some period of
|
||||
time, we want to cleans these up, aka reap them.
|
||||
"""
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
# If ../glance/__init__.py exists, add ../ to Python search path, so that
|
||||
# it will override what happens to be installed in /usr/(local/)lib/python...
|
||||
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
|
||||
os.pardir,
|
||||
os.pardir))
|
||||
if os.path.exists(os.path.join(possible_topdir, 'glance', '__init__.py')):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
from glance import version
|
||||
from glance.common import config
|
||||
from glance.common import wsgi
|
||||
|
||||
|
||||
def create_options(parser):
|
||||
"""
|
||||
Sets up the CLI and config-file options that may be
|
||||
parsed and program commands.
|
||||
|
||||
:param parser: The option parser
|
||||
"""
|
||||
config.add_common_options(parser)
|
||||
config.add_log_options(parser)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
oparser = optparse.OptionParser(version='%%prog %s'
|
||||
% version.version_string())
|
||||
create_options(oparser)
|
||||
(options, args) = config.parse_options(oparser)
|
||||
|
||||
try:
|
||||
conf, app = config.load_paste_app('glance-reaper', options, args)
|
||||
app.run()
|
||||
except RuntimeError, e:
|
||||
sys.exit("ERROR: %s" % e)
|
|
@ -27,15 +27,5 @@ image_cache_percent_extra_to_free = 0.20
|
|||
# Make sure this is also set in glance-api.conf
|
||||
image_cache_datadir = /var/lib/glance/image-cache/
|
||||
|
||||
# image_cache_invalid_entry_grace_period - seconds
|
||||
#
|
||||
# If an exception is raised as we're writing to the cache, the cache-entry is
|
||||
# deemed invalid and moved to <image_cache_datadir>/invalid so that it can be
|
||||
# inspected for debugging purposes.
|
||||
#
|
||||
# This is number of seconds to leave these invalid images around before they
|
||||
# are elibible to be pruned.
|
||||
image_cache_invalid_entry_grace_period = 3600
|
||||
|
||||
[app:glance-pruner]
|
||||
paste.app_factory = glance.image_cache.pruner:app_factory
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
[DEFAULT]
|
||||
# Show more verbose log output (sets INFO log level output)
|
||||
verbose = True
|
||||
|
||||
# Show debugging output in logs (sets DEBUG log level output)
|
||||
debug = False
|
||||
|
||||
log_file = /var/log/glance/reaper.log
|
||||
|
||||
# Whether to enable the caching of image data
|
||||
image_cache_enabled = False
|
||||
|
||||
# Directory that the Image Cache writes data to
|
||||
# Make sure this is also set in glance-api.conf
|
||||
image_cache_datadir = /var/lib/glance/image-cache/
|
||||
|
||||
# image_cache_invalid_entry_grace_period - seconds
|
||||
#
|
||||
# If an exception is raised as we're writing to the cache, the cache-entry is
|
||||
# deemed invalid and moved to <image_cache_datadir>/invalid so that it can be
|
||||
# inspected for debugging purposes.
|
||||
#
|
||||
# This is number of seconds to leave these invalid images around before they
|
||||
# are elibible to be pruned.
|
||||
image_cache_invalid_entry_grace_period = 3600
|
||||
|
||||
[app:glance-reaper]
|
||||
paste.app_factory = glance.image_cache.reaper:app_factory
|
|
@ -60,6 +60,10 @@ class Controller(api.BaseController):
|
|||
def clear(self, req):
|
||||
self.cache.clear()
|
||||
|
||||
def reap(self, req):
|
||||
"""Reaps any invalid cached images"""
|
||||
self.cache.reap_invalid()
|
||||
|
||||
def update(self, req, id):
|
||||
"""PUT /cached_images/1 is used to prefetch an image into the cache"""
|
||||
image_meta = self.get_image_meta_or_404(req, id)
|
||||
|
|
|
@ -34,7 +34,8 @@ class ImageCacheFilter(wsgi.Middleware):
|
|||
map = app.map
|
||||
resource = cached_images.create_resource(options)
|
||||
map.resource("cached_image", "cached_images",
|
||||
controller=resource)
|
||||
controller=resource,
|
||||
collection={'reap': 'POST'})
|
||||
|
||||
map.connect("/cached_images",
|
||||
controller=resource,
|
||||
|
|
|
@ -226,6 +226,13 @@ class V1Client(base_client.BaseClient):
|
|||
self.do_request("DELETE", "/cached_images")
|
||||
return True
|
||||
|
||||
def reap_invalid_cached_images(self):
|
||||
"""
|
||||
Reaps any invalid cached images
|
||||
"""
|
||||
self.do_request("POST", "/cached_images/reap")
|
||||
return True
|
||||
|
||||
def prefetch_cache_image(self, image_id):
|
||||
"""
|
||||
Pre-fetch a specified image from the cache
|
||||
|
|
|
@ -24,6 +24,7 @@ import itertools
|
|||
import logging
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from glance.common import config
|
||||
from glance.common import exception
|
||||
|
@ -404,3 +405,28 @@ class ImageCache(object):
|
|||
path = entry['path']
|
||||
entry['hits'] = utils.get_xattr(path, 'hits', default='UNKNOWN')
|
||||
yield entry
|
||||
|
||||
def reap_invalid(self, grace=None):
|
||||
"""Remove any invalid cache entries
|
||||
|
||||
:param grace: Number of seconds to keep an invalid entry around for
|
||||
debugging purposes. If None, then delete immediately.
|
||||
"""
|
||||
now = time.time()
|
||||
|
||||
reaped = 0
|
||||
for path in self.get_all_regular_files(self.invalid_path):
|
||||
mtime = os.path.getmtime(path)
|
||||
age = now - mtime
|
||||
if not grace:
|
||||
logger.debug("No grace period, reaping '%(path)s' immediately"
|
||||
% locals())
|
||||
self._delete_file(path)
|
||||
reaped += 1
|
||||
elif age > grace:
|
||||
logger.debug("Cache entry '%(path)s' exceeds grace period, "
|
||||
"(%(age)i s > %(grace)i s)" % locals())
|
||||
self._delete_file(path)
|
||||
reaped += 1
|
||||
|
||||
logger.info("Reaped %(reaped)s invalid cache entries" % locals())
|
||||
|
|
|
@ -47,12 +47,6 @@ class Pruner(object):
|
|||
self.options, 'image_cache_percent_extra_to_free',
|
||||
type='float', default=0.05)
|
||||
|
||||
@property
|
||||
def image_cache_invalid_entry_grace_period(self):
|
||||
return config.get_option(
|
||||
self.options, 'image_cache_invalid_entry_grace_period',
|
||||
type='int', default=3600)
|
||||
|
||||
def run(self):
|
||||
if not self.cache.enabled:
|
||||
logger.debug(
|
||||
|
@ -60,24 +54,6 @@ class Pruner(object):
|
|||
return
|
||||
|
||||
self.prune_cache()
|
||||
self.prune_invalid_cache_entries()
|
||||
|
||||
def prune_invalid_cache_entries(self):
|
||||
"""Prune invalid cache entries that are older than the grace period"""
|
||||
grace = self.image_cache_invalid_entry_grace_period
|
||||
now = time.time()
|
||||
|
||||
pruned = 0
|
||||
for path in self.cache.get_all_regular_files(self.cache.invalid_path):
|
||||
mtime = os.path.getmtime(path)
|
||||
age = now - mtime
|
||||
if age > grace:
|
||||
logger.debug("Cache entry '%(path)s' exceeds grace period, "
|
||||
"(%(age)i s > %(grace)i s)" % locals())
|
||||
self.cache._delete_file(path)
|
||||
pruned += 1
|
||||
|
||||
logger.info("Pruned %(pruned)s invalid cache entries" % locals())
|
||||
|
||||
def prune_cache(self):
|
||||
"""Prune the cache using an LRU strategy"""
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
# 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.
|
||||
|
||||
"""
|
||||
Reaps any invalid cache entries that exceed the grace period
|
||||
"""
|
||||
import logging
|
||||
|
||||
from glance.common import config
|
||||
from glance.image_cache import ImageCache
|
||||
|
||||
|
||||
logger = logging.getLogger('glance.image_cache.reaper')
|
||||
|
||||
|
||||
class Reaper(object):
|
||||
def __init__(self, options):
|
||||
self.options = options
|
||||
self.cache = ImageCache(options)
|
||||
|
||||
@property
|
||||
def image_cache_invalid_entry_grace_period(self):
|
||||
return config.get_option(
|
||||
self.options, 'image_cache_invalid_entry_grace_period',
|
||||
type='int', default=3600)
|
||||
|
||||
def run(self):
|
||||
if not self.cache.enabled:
|
||||
logger.debug(
|
||||
"Image caching is not enabled, going back to sleep...")
|
||||
return
|
||||
|
||||
grace = self.image_cache_invalid_entry_grace_period
|
||||
self.cache.reap_invalid(grace=grace)
|
||||
|
||||
|
||||
def app_factory(global_config, **local_conf):
|
||||
conf = global_config.copy()
|
||||
conf.update(local_conf)
|
||||
return Reaper(conf)
|
Loading…
Reference in New Issue