Fixes LP Bug#837817 - bin/glance cache disabled
This patch removes the cache management commands from the main bin/glance client and puts them in a bin/glance-cache-manage program. It also adds a number of cache management calls to manage the cache queue via the HTTP API. The reason I moved the cache management commands into a separate utility is that I'm thinking towards the future, where we have a separate admin API, and having a separate management utility seemed like the best way to go. Change-Id: Ic916a72f39516a06be27ea6c8ef69eb9e1a70936
This commit is contained in:
parent
d105e954a8
commit
8878046089
256
bin/glance
256
bin/glance
|
@ -584,249 +584,6 @@ Deletes all images from a Glance server"""
|
|||
return SUCCESS
|
||||
|
||||
|
||||
@catch_error('show cached images')
|
||||
def cache_index(options, args):
|
||||
"""
|
||||
%(prog)s cache-index [options]
|
||||
|
||||
List all images currently cached"""
|
||||
client = get_client(options)
|
||||
images = client.get_cached_images()
|
||||
if not images:
|
||||
print "No cached images."
|
||||
return SUCCESS
|
||||
|
||||
print "Found %d cached images..." % len(images)
|
||||
|
||||
pretty_table = utils.PrettyTable()
|
||||
pretty_table.add_column(36, label="ID")
|
||||
pretty_table.add_column(30, label="Name")
|
||||
pretty_table.add_column(19, label="Last Accessed (UTC)")
|
||||
# 1 TB takes 13 characters to display: len(str(2**40)) == 13
|
||||
pretty_table.add_column(14, label="Size", just="r")
|
||||
pretty_table.add_column(10, label="Hits", just="r")
|
||||
|
||||
print pretty_table.make_header()
|
||||
|
||||
for image in images:
|
||||
print pretty_table.make_row(
|
||||
image['id'],
|
||||
image['name'],
|
||||
image['last_accessed'],
|
||||
image['size'],
|
||||
image['hits'])
|
||||
|
||||
|
||||
@catch_error('show invalid cache images')
|
||||
def cache_invalid(options, args):
|
||||
"""
|
||||
%(prog)s cache-invalid [options]
|
||||
|
||||
List current invalid cache images"""
|
||||
client = get_client(options)
|
||||
images = client.get_invalid_cached_images()
|
||||
if not images:
|
||||
print "No invalid cached images."
|
||||
return SUCCESS
|
||||
|
||||
print "Found %d invalid cached images..." % len(images)
|
||||
|
||||
pretty_table = utils.PrettyTable()
|
||||
pretty_table.add_column(36, label="ID")
|
||||
pretty_table.add_column(30, label="Name")
|
||||
pretty_table.add_column(30, label="Error")
|
||||
pretty_table.add_column(19, label="Last Modified (UTC)")
|
||||
# 1 TB takes 13 characters to display: len(str(2**40)) == 13
|
||||
pretty_table.add_column(14, label="Size", just="r")
|
||||
pretty_table.add_column(14, label="Expected Size", just="r")
|
||||
pretty_table.add_column(7, label="% Done", just="r")
|
||||
|
||||
print pretty_table.make_header()
|
||||
|
||||
for image in images:
|
||||
print pretty_table.make_row(
|
||||
image['id'],
|
||||
image['name'],
|
||||
image['error'],
|
||||
image['last_accessed'],
|
||||
image['size'],
|
||||
image['expected_size'],
|
||||
get_percent_done(image))
|
||||
|
||||
|
||||
@catch_error('show incomplete cache images')
|
||||
def cache_incomplete(options, args):
|
||||
"""
|
||||
%(prog)s cache-incomplete [options]
|
||||
|
||||
List images currently being fetched"""
|
||||
client = get_client(options)
|
||||
images = client.get_incomplete_cached_images()
|
||||
if not images:
|
||||
print "No incomplete cached images."
|
||||
return SUCCESS
|
||||
|
||||
print "Found %d incomplete cached images..." % len(images)
|
||||
|
||||
pretty_table = utils.PrettyTable()
|
||||
pretty_table.add_column(36, label="ID")
|
||||
pretty_table.add_column(30, label="Name")
|
||||
pretty_table.add_column(19, label="Last Modified (UTC)")
|
||||
# 1 TB takes 13 characters to display: len(str(2**40)) == 13
|
||||
pretty_table.add_column(14, label="Size", just="r")
|
||||
pretty_table.add_column(14, label="Expected Size", just="r")
|
||||
pretty_table.add_column(7, label="% Done", just="r")
|
||||
|
||||
print pretty_table.make_header()
|
||||
|
||||
for image in images:
|
||||
print pretty_table.make_row(
|
||||
image['id'],
|
||||
image['name'],
|
||||
image['last_modified'],
|
||||
image['size'],
|
||||
image['expected_size'],
|
||||
get_percent_done(image))
|
||||
|
||||
|
||||
@catch_error('purge the specified cached image')
|
||||
def cache_purge(options, args):
|
||||
"""
|
||||
%(prog)s cache-purge [options]
|
||||
|
||||
Purges an image from the cache"""
|
||||
try:
|
||||
image_id = args.pop()
|
||||
except IndexError:
|
||||
print "Please specify the ID of the image you wish to purge "
|
||||
print "from the cache as the first argument"
|
||||
return FAILURE
|
||||
|
||||
if not options.force and \
|
||||
not user_confirm("Purge cached image %s?" % (image_id,), default=False):
|
||||
print 'Not purging cached image %s' % (image_id,)
|
||||
return FAILURE
|
||||
|
||||
client = get_client(options)
|
||||
client.purge_cached_image(image_id)
|
||||
|
||||
if options.verbose:
|
||||
print "done"
|
||||
|
||||
|
||||
@catch_error('clear all cached images')
|
||||
def cache_clear(options, args):
|
||||
"""
|
||||
%(prog)s cache-clear [options]
|
||||
|
||||
Removes all images from the cache"""
|
||||
if not options.force and \
|
||||
not user_confirm("Clear all cached images?", default=False):
|
||||
print 'Not purging any cached images.'
|
||||
return FAILURE
|
||||
|
||||
client = get_client(options)
|
||||
num_purged = client.clear_cached_images()
|
||||
|
||||
if options.verbose:
|
||||
print "Purged %(num_purged)s cached images" % locals()
|
||||
|
||||
|
||||
@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)
|
||||
num_reaped = client.reap_invalid_cached_images()
|
||||
|
||||
if options.verbose:
|
||||
print "Reaped %(num_reaped)s invalid cached images" % locals()
|
||||
|
||||
|
||||
@catch_error('reap stalled images')
|
||||
def cache_reap_stalled(options, args):
|
||||
"""
|
||||
%(prog)s cache-reap-stalled [options]
|
||||
|
||||
Reaps any stalled incomplete images"""
|
||||
if not options.force and \
|
||||
not user_confirm("Reap all stalled cached images?", default=False):
|
||||
print 'Not reaping any stalled cached images.'
|
||||
return FAILURE
|
||||
|
||||
client = get_client(options)
|
||||
num_reaped = client.reap_stalled_cached_images()
|
||||
|
||||
if options.verbose:
|
||||
print "Reaped %(num_reaped)s stalled cached images" % locals()
|
||||
|
||||
|
||||
@catch_error('prefetch the specified cached image')
|
||||
def cache_prefetch(options, args):
|
||||
"""
|
||||
%(prog)s cache-prefetch [options]
|
||||
|
||||
Pre-fetch an image or list of images into the cache"""
|
||||
image_ids = args
|
||||
if not image_ids:
|
||||
print "Please specify the ID or a list of image IDs of the images "\
|
||||
"you wish to "
|
||||
print "prefetch from the cache as the first argument"
|
||||
return FAILURE
|
||||
|
||||
client = get_client(options)
|
||||
for image_id in image_ids:
|
||||
if options.verbose:
|
||||
print "Prefetching image '%s'" % image_id
|
||||
|
||||
try:
|
||||
client.prefetch_cache_image(image_id)
|
||||
except exception.NotFound:
|
||||
print "No image with ID %s was found" % image_id
|
||||
continue
|
||||
|
||||
if options.verbose:
|
||||
print "done"
|
||||
|
||||
|
||||
@catch_error('show prefetching images')
|
||||
def cache_prefetching(options, args):
|
||||
"""
|
||||
%(prog)s cache-prefetching [options]
|
||||
|
||||
List images that are being prefetched"""
|
||||
client = get_client(options)
|
||||
images = client.get_prefetching_cache_images()
|
||||
if not images:
|
||||
print "No images being prefetched."
|
||||
return SUCCESS
|
||||
|
||||
print "Found %d images being prefetched..." % len(images)
|
||||
|
||||
pretty_table = utils.PrettyTable()
|
||||
pretty_table.add_column(36, label="ID")
|
||||
pretty_table.add_column(30, label="Name")
|
||||
pretty_table.add_column(19, label="Last Accessed (UTC)")
|
||||
pretty_table.add_column(10, label="Status", just="r")
|
||||
|
||||
print pretty_table.make_header()
|
||||
|
||||
for image in images:
|
||||
print pretty_table.make_row(
|
||||
image['id'],
|
||||
image['name'],
|
||||
image['last_accessed'],
|
||||
image['status'])
|
||||
|
||||
|
||||
@catch_error('show image members')
|
||||
def image_members(options, args):
|
||||
"""
|
||||
|
@ -1088,17 +845,6 @@ def lookup_command(parser, command_name):
|
|||
'show': image_show,
|
||||
'clear': images_clear}
|
||||
|
||||
CACHE_COMMANDS = {
|
||||
'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-reap-invalid': cache_reap_invalid,
|
||||
'cache-reap-stalled': cache_reap_stalled}
|
||||
|
||||
MEMBER_COMMANDS = {
|
||||
'image-members': image_members,
|
||||
'member-images': member_images,
|
||||
|
@ -1107,7 +853,7 @@ def lookup_command(parser, command_name):
|
|||
'member-delete': member_delete}
|
||||
|
||||
commands = {}
|
||||
for command_set in (BASE_COMMANDS, IMAGE_COMMANDS, CACHE_COMMANDS,
|
||||
for command_set in (BASE_COMMANDS, IMAGE_COMMANDS,
|
||||
MEMBER_COMMANDS):
|
||||
commands.update(command_set)
|
||||
|
||||
|
|
|
@ -0,0 +1,435 @@
|
|||
#!/usr/bin/env 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.
|
||||
|
||||
"""
|
||||
A simple cache management utility for Glance.
|
||||
"""
|
||||
|
||||
import functools
|
||||
import gettext
|
||||
import optparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
# 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)
|
||||
|
||||
gettext.install('glance', unicode=1)
|
||||
|
||||
from glance import client as glance_client
|
||||
from glance import version
|
||||
from glance.common import exception
|
||||
from glance.common import utils
|
||||
|
||||
|
||||
SUCCESS = 0
|
||||
FAILURE = 1
|
||||
|
||||
|
||||
def catch_error(action):
|
||||
"""Decorator to provide sensible default error handling for actions."""
|
||||
def wrap(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
ret = func(*args, **kwargs)
|
||||
return SUCCESS if ret is None else ret
|
||||
except exception.NotFound:
|
||||
options = args[0]
|
||||
print ("Cache management middleware not enabled on host %s" %
|
||||
options.host)
|
||||
return FAILURE
|
||||
except exception.NotAuthorized:
|
||||
print "Not authorized to make this request. Check "\
|
||||
"your credentials (OS_AUTH_USER, OS_AUTH_KEY, ...)."
|
||||
return FAILURE
|
||||
except Exception, e:
|
||||
options = args[0]
|
||||
if options.debug:
|
||||
raise
|
||||
print "Failed to %s. Got error:" % action
|
||||
pieces = unicode(e).split('\n')
|
||||
for piece in pieces:
|
||||
print piece
|
||||
return FAILURE
|
||||
|
||||
return wrapper
|
||||
return wrap
|
||||
|
||||
|
||||
@catch_error('show cached images')
|
||||
def list_cached(options, args):
|
||||
"""
|
||||
%(prog)s list-cached [options]
|
||||
|
||||
List all images currently cached"""
|
||||
client = get_client(options)
|
||||
images = client.get_cached_images()
|
||||
if not images:
|
||||
print "No cached images."
|
||||
return SUCCESS
|
||||
|
||||
print "Found %d cached images..." % len(images)
|
||||
|
||||
pretty_table = utils.PrettyTable()
|
||||
pretty_table.add_column(36, label="ID")
|
||||
pretty_table.add_column(19, label="Last Accessed (UTC)")
|
||||
# 1 TB takes 13 characters to display: len(str(2**40)) == 13
|
||||
pretty_table.add_column(14, label="Size", just="r")
|
||||
pretty_table.add_column(10, label="Hits", just="r")
|
||||
|
||||
print pretty_table.make_header()
|
||||
|
||||
for image in images:
|
||||
print pretty_table.make_row(
|
||||
image['image_id'],
|
||||
image['last_accessed'],
|
||||
image['last_modified'],
|
||||
image['size'],
|
||||
image['hits'])
|
||||
|
||||
|
||||
@catch_error('show queued images')
|
||||
def list_queued(options, args):
|
||||
"""
|
||||
%(prog)s list-queued [options]
|
||||
|
||||
List all images currently queued for caching"""
|
||||
client = get_client(options)
|
||||
images = client.get_queued_images()
|
||||
if not images:
|
||||
print "No queued images."
|
||||
return SUCCESS
|
||||
|
||||
print "Found %d queued images..." % len(images)
|
||||
|
||||
pretty_table = utils.PrettyTable()
|
||||
pretty_table.add_column(36, label="ID")
|
||||
|
||||
print pretty_table.make_header()
|
||||
|
||||
for image in images:
|
||||
print pretty_table.make_row(image)
|
||||
|
||||
|
||||
@catch_error('queue the specified image for caching')
|
||||
def queue_image(options, args):
|
||||
"""
|
||||
%(prog)s queue-image <IMAGE_ID> [options]
|
||||
|
||||
Queues an image for caching"""
|
||||
try:
|
||||
image_id = args.pop()
|
||||
except IndexError:
|
||||
print "Please specify the ID of the image you wish to queue "
|
||||
print "from the cache as the first argument"
|
||||
return FAILURE
|
||||
|
||||
if not options.force and \
|
||||
not user_confirm("Queue image %s for caching?" % (image_id,),
|
||||
default=False):
|
||||
return SUCCESS
|
||||
|
||||
client = get_client(options)
|
||||
client.queue_image_for_caching(image_id)
|
||||
|
||||
if options.verbose:
|
||||
print "Queued image %(image_id)s for caching" % locals()
|
||||
|
||||
return SUCCESS
|
||||
|
||||
|
||||
@catch_error('delete the specified cached image')
|
||||
def delete_cached_image(options, args):
|
||||
"""
|
||||
%(prog)s delete-cached-image [options]
|
||||
|
||||
Deletes an image from the cache"""
|
||||
try:
|
||||
image_id = args.pop()
|
||||
except IndexError:
|
||||
print "Please specify the ID of the image you wish to delete "
|
||||
print "from the cache as the first argument"
|
||||
return FAILURE
|
||||
|
||||
if not options.force and \
|
||||
not user_confirm("Delete cached image %s?" % (image_id,),
|
||||
default=False):
|
||||
return SUCCESS
|
||||
|
||||
client = get_client(options)
|
||||
client.delete_cached_image(image_id)
|
||||
|
||||
if options.verbose:
|
||||
print "Deleted cached image %(image_id)s" % locals()
|
||||
|
||||
return SUCCESS
|
||||
|
||||
|
||||
@catch_error('Delete all cached images')
|
||||
def delete_all_cached_images(options, args):
|
||||
"""
|
||||
%(prog)s delete-all-cached-images [options]
|
||||
|
||||
Removes all images from the cache"""
|
||||
if not options.force and \
|
||||
not user_confirm("Delete all cached images?", default=False):
|
||||
return SUCCESS
|
||||
|
||||
client = get_client(options)
|
||||
num_deleted = client.delete_all_cached_images()
|
||||
|
||||
if options.verbose:
|
||||
print "Deleted %(num_deleted)s cached images" % locals()
|
||||
|
||||
return SUCCESS
|
||||
|
||||
|
||||
@catch_error('delete the specified queued image')
|
||||
def delete_queued_image(options, args):
|
||||
"""
|
||||
%(prog)s delete-queued-image [options]
|
||||
|
||||
Deletes an image from the cache"""
|
||||
try:
|
||||
image_id = args.pop()
|
||||
except IndexError:
|
||||
print "Please specify the ID of the image you wish to delete "
|
||||
print "from the cache as the first argument"
|
||||
return FAILURE
|
||||
|
||||
if not options.force and \
|
||||
not user_confirm("Delete queued image %s?" % (image_id,),
|
||||
default=False):
|
||||
return SUCCESS
|
||||
|
||||
client = get_client(options)
|
||||
client.delete_queued_image(image_id)
|
||||
|
||||
if options.verbose:
|
||||
print "Deleted queued image %(image_id)s" % locals()
|
||||
|
||||
return SUCCESS
|
||||
|
||||
|
||||
@catch_error('Delete all queued images')
|
||||
def delete_all_queued_images(options, args):
|
||||
"""
|
||||
%(prog)s delete-all-queued-images [options]
|
||||
|
||||
Removes all images from the cache queue"""
|
||||
if not options.force and \
|
||||
not user_confirm("Delete all queued images?", default=False):
|
||||
return SUCCESS
|
||||
|
||||
client = get_client(options)
|
||||
num_deleted = client.delete_all_queued_images()
|
||||
|
||||
if options.verbose:
|
||||
print "Deleted %(num_deleted)s queued images" % locals()
|
||||
|
||||
return SUCCESS
|
||||
|
||||
|
||||
def get_client(options):
|
||||
"""
|
||||
Returns a new client object to a Glance server
|
||||
specified by the --host and --port options
|
||||
supplied to the CLI
|
||||
"""
|
||||
creds = dict(username=os.getenv('OS_AUTH_USER'),
|
||||
password=os.getenv('OS_AUTH_KEY'),
|
||||
tenant=os.getenv('OS_AUTH_TENANT'),
|
||||
auth_url=os.getenv('OS_AUTH_URL'),
|
||||
strategy=os.getenv('OS_AUTH_STRATEGY', 'noauth'))
|
||||
|
||||
use_ssl = (options.host.find('https') != -1 or (
|
||||
creds['auth_url'] is not None and
|
||||
creds['auth_url'].find('https') != -1))
|
||||
|
||||
return glance_client.Client(host=options.host, port=options.port,
|
||||
use_ssl=use_ssl, auth_tok=options.auth_token,
|
||||
creds=creds)
|
||||
|
||||
|
||||
def create_options(parser):
|
||||
"""
|
||||
Sets up the CLI and config-file options that may be
|
||||
parsed and program commands.
|
||||
|
||||
:param parser: The option parser
|
||||
"""
|
||||
parser.add_option('-v', '--verbose', default=False, action="store_true",
|
||||
help="Print more verbose output")
|
||||
parser.add_option('-d', '--debug', default=False, action="store_true",
|
||||
help="Print more verbose output")
|
||||
parser.add_option('-H', '--host', metavar="ADDRESS", default="0.0.0.0",
|
||||
help="Address of Glance API host. "
|
||||
"Default: %default")
|
||||
parser.add_option('-p', '--port', dest="port", metavar="PORT",
|
||||
type=int, default=9292,
|
||||
help="Port the Glance API host listens on. "
|
||||
"Default: %default")
|
||||
parser.add_option('-A', '--auth_token', dest="auth_token",
|
||||
metavar="TOKEN", default=None,
|
||||
help="Authentication token to use to identify the "
|
||||
"client to the glance server")
|
||||
parser.add_option('-f', '--force', dest="force", metavar="FORCE",
|
||||
default=False, action="store_true",
|
||||
help="Prevent select actions from requesting "
|
||||
"user confirmation")
|
||||
|
||||
|
||||
def parse_options(parser, cli_args):
|
||||
"""
|
||||
Returns the parsed CLI options, command to run and its arguments, merged
|
||||
with any same-named options found in a configuration file
|
||||
|
||||
:param parser: The option parser
|
||||
"""
|
||||
if not cli_args:
|
||||
cli_args.append('-h') # Show options in usage output...
|
||||
|
||||
(options, args) = parser.parse_args(cli_args)
|
||||
|
||||
# HACK(sirp): Make the parser available to the print_help method
|
||||
# print_help is a command, so it only accepts (options, args); we could
|
||||
# one-off have it take (parser, options, args), however, for now, I think
|
||||
# this little hack will suffice
|
||||
options.__parser = parser
|
||||
|
||||
if not args:
|
||||
parser.print_usage()
|
||||
sys.exit(0)
|
||||
|
||||
command_name = args.pop(0)
|
||||
command = lookup_command(parser, command_name)
|
||||
|
||||
return (options, command, args)
|
||||
|
||||
|
||||
def print_help(options, args):
|
||||
"""
|
||||
Print help specific to a command
|
||||
"""
|
||||
if len(args) != 1:
|
||||
sys.exit("Please specify a command")
|
||||
|
||||
parser = options.__parser
|
||||
command_name = args.pop()
|
||||
command = lookup_command(parser, command_name)
|
||||
|
||||
print command.__doc__ % {'prog': os.path.basename(sys.argv[0])}
|
||||
|
||||
|
||||
def lookup_command(parser, command_name):
|
||||
BASE_COMMANDS = {'help': print_help}
|
||||
|
||||
CACHE_COMMANDS = {
|
||||
'list-cached': list_cached,
|
||||
'list-queued': list_queued,
|
||||
'queue-image': queue_image,
|
||||
'delete-cached-image': delete_cached_image,
|
||||
'delete-all-cached-images': delete_all_cached_images,
|
||||
'delete-queued-image': delete_queued_image,
|
||||
'delete-all-queued-images': delete_all_queued_images,
|
||||
}
|
||||
|
||||
commands = {}
|
||||
for command_set in (BASE_COMMANDS, CACHE_COMMANDS):
|
||||
commands.update(command_set)
|
||||
|
||||
try:
|
||||
command = commands[command_name]
|
||||
except KeyError:
|
||||
parser.print_usage()
|
||||
sys.exit("Unknown command: %s" % command_name)
|
||||
|
||||
return command
|
||||
|
||||
|
||||
def user_confirm(prompt, default=False):
|
||||
"""
|
||||
Yes/No question dialog with user.
|
||||
|
||||
:param prompt: question/statement to present to user (string)
|
||||
:param default: boolean value to return if empty string
|
||||
is received as response to prompt
|
||||
|
||||
"""
|
||||
if default:
|
||||
prompt_default = "[Y/n]"
|
||||
else:
|
||||
prompt_default = "[y/N]"
|
||||
|
||||
answer = raw_input("%s %s " % (prompt, prompt_default))
|
||||
|
||||
if answer == "":
|
||||
return default
|
||||
else:
|
||||
return answer.lower() in ("yes", "y")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
usage = """
|
||||
%prog <command> [options] [args]
|
||||
|
||||
Commands:
|
||||
|
||||
help <command> Output help for one of the commands below
|
||||
|
||||
list-cached List all images currently cached
|
||||
|
||||
list-queued List all images currently queued for caching
|
||||
|
||||
queue-image Queue an image for caching
|
||||
|
||||
delete-cached-image Purges an image from the cache
|
||||
|
||||
delete-all-cached-images Removes all images from the cache
|
||||
|
||||
delete-queued-image Deletes an image from the cache queue
|
||||
|
||||
delete-all-queued-images Deletes all images from the cache queue
|
||||
|
||||
clean Removes any stale or invalid image files
|
||||
from the cache
|
||||
"""
|
||||
|
||||
oparser = optparse.OptionParser(version='%%prog %s'
|
||||
% version.version_string(),
|
||||
usage=usage.strip())
|
||||
create_options(oparser)
|
||||
(options, command, args) = parse_options(oparser, sys.argv[1:])
|
||||
|
||||
try:
|
||||
start_time = time.time()
|
||||
result = command(options, args)
|
||||
end_time = time.time()
|
||||
if options.verbose:
|
||||
print "Completed in %-0.4f sec." % (end_time - start_time)
|
||||
sys.exit(result)
|
||||
except (RuntimeError, NotImplementedError), e:
|
||||
print "ERROR: ", e
|
|
@ -74,9 +74,17 @@ from a local cache.
|
|||
To queue an image for prefetching, you can use one of the following methods:
|
||||
|
||||
* If the ``cache_manage`` middleware is enabled in the application pipeline,
|
||||
you may call ``PUT /cached-images/<IMAGE_ID>`` to queue the image with
|
||||
you may call ``PUT /queued-images/<IMAGE_ID>`` to queue the image with
|
||||
identifier ``<IMAGE_ID>``
|
||||
|
||||
Alternately, you can use the ``glance-cache-manage`` program to queue the
|
||||
image. This program may be run from a different host than the host
|
||||
containing the image cache. Example usage::
|
||||
|
||||
$> glance-cache-manage --host=<HOST> queue-image <IMAGE_ID>
|
||||
|
||||
This will queue the image with identifier ``<IMAGE_ID>`` for prefetching
|
||||
|
||||
* You may use the ``glance-cache-queue-image`` executable, supplying a list
|
||||
of image identifiers to queue for prefetching into the cache.
|
||||
|
||||
|
@ -88,7 +96,7 @@ To queue an image for prefetching, you can use one of the following methods:
|
|||
prefetching.
|
||||
|
||||
Once you have queued the images you wish to prefetch, call the
|
||||
``glance-cache-prefetch`` executable, which will prefetch all queued images
|
||||
``glance-cache-prefetcher`` executable, which will prefetch all queued images
|
||||
concurrently, reporting the results of the fetch for each image, as shown
|
||||
below::
|
||||
|
||||
|
@ -105,7 +113,14 @@ following methods:
|
|||
mappings that show cached images, the number of cache hits on each image,
|
||||
the size of the image, and the times they were last accessed.
|
||||
|
||||
* You can issue the following call on \*nix systems::
|
||||
Alternately, you can use the ``glance-cache-manage`` program. This program
|
||||
may be run from a different host than the host containing the image cache.
|
||||
Example usage::
|
||||
|
||||
$> glance-cache-manage --host=<HOST> list-cached
|
||||
|
||||
* You can issue the following call on \*nix systems (on the host that contains
|
||||
the image cache)::
|
||||
|
||||
$> ls -lhR $IMAGE_CACHE_DIR
|
||||
|
||||
|
@ -120,3 +135,7 @@ Manually Removing Images from the Image Cache
|
|||
If the ``cache_manage`` middleware is enabled, you may call
|
||||
``DELETE /cached-images/<IMAGE_ID>`` to remove the image file for image
|
||||
with identifier ``<IMAGE_ID>`` from the cache.
|
||||
|
||||
Alternately, you can use the ``glance-cache-manage`` program. Example usage::
|
||||
|
||||
$> glance-cache-manage --host=<HOST> delete-cached-image <IMAGE_ID>
|
||||
|
|
|
@ -41,7 +41,7 @@ class Controller(api.BaseController):
|
|||
self.options = options
|
||||
self.cache = image_cache.ImageCache(self.options)
|
||||
|
||||
def index(self, req):
|
||||
def get_cached_images(self, req):
|
||||
"""
|
||||
GET /cached_images
|
||||
|
||||
|
@ -50,31 +50,56 @@ class Controller(api.BaseController):
|
|||
images = self.cache.get_cached_images()
|
||||
return dict(cached_images=images)
|
||||
|
||||
def delete(self, req, id):
|
||||
def delete_cached_image(self, req, image_id):
|
||||
"""
|
||||
DELETE /cached_images/1
|
||||
DELETE /cached_images/<IMAGE_ID>
|
||||
|
||||
Removes an image from the cache.
|
||||
"""
|
||||
self.cache.delete(id)
|
||||
self.cache.delete_cached_image(image_id)
|
||||
|
||||
def delete_collection(self, req):
|
||||
def delete_cached_images(self, req):
|
||||
"""
|
||||
DELETE /cached_images - Clear all active cached images
|
||||
|
||||
Removes all images from the cache.
|
||||
"""
|
||||
self.cache.delete_all()
|
||||
return dict(num_deleted=self.cache.delete_all_cached_images())
|
||||
|
||||
def update(self, req, id):
|
||||
def get_queued_images(self, req):
|
||||
"""
|
||||
PUT /cached_images/1
|
||||
GET /queued_images
|
||||
|
||||
Returns a mapping of records about queued images.
|
||||
"""
|
||||
images = self.cache.get_queued_images()
|
||||
return dict(queued_images=images)
|
||||
|
||||
def queue_image(self, req, image_id):
|
||||
"""
|
||||
PUT /queued_images/<IMAGE_ID>
|
||||
|
||||
Queues an image for caching. We do not check to see if
|
||||
the image is in the registry here. That is done by the
|
||||
prefetcher...
|
||||
"""
|
||||
self.cache.queue_image(id)
|
||||
self.cache.queue_image(image_id)
|
||||
|
||||
def delete_queued_image(self, req, image_id):
|
||||
"""
|
||||
DELETE /queued_images/<IMAGE_ID>
|
||||
|
||||
Removes an image from the cache.
|
||||
"""
|
||||
self.cache.delete_queued_image(image_id)
|
||||
|
||||
def delete_queued_images(self, req):
|
||||
"""
|
||||
DELETE /queued_images - Clear all active queued images
|
||||
|
||||
Removes all images from the cache.
|
||||
"""
|
||||
return dict(num_deleted=self.cache.delete_all_queued_images())
|
||||
|
||||
|
||||
class CachedImageDeserializer(wsgi.JSONRequestDeserializer):
|
||||
|
|
|
@ -32,12 +32,40 @@ class CacheManageFilter(wsgi.Middleware):
|
|||
|
||||
map = app.map
|
||||
resource = cached_images.create_resource(options)
|
||||
map.resource("cached_image", "cached_images",
|
||||
controller=resource)
|
||||
|
||||
map.connect("/cached_images",
|
||||
controller=resource,
|
||||
action="delete_collection",
|
||||
action="get_cached_images",
|
||||
conditions=dict(method=["GET"]))
|
||||
|
||||
map.connect("/cached_images/{image_id}",
|
||||
controller=resource,
|
||||
action="delete_cached_image",
|
||||
conditions=dict(method=["DELETE"]))
|
||||
|
||||
map.connect("/cached_images",
|
||||
controller=resource,
|
||||
action="delete_cached_images",
|
||||
conditions=dict(method=["DELETE"]))
|
||||
|
||||
map.connect("/queued_images/{image_id}",
|
||||
controller=resource,
|
||||
action="queue_image",
|
||||
conditions=dict(method=["PUT"]))
|
||||
|
||||
map.connect("/queued_images",
|
||||
controller=resource,
|
||||
action="get_queued_images",
|
||||
conditions=dict(method=["GET"]))
|
||||
|
||||
map.connect("/queued_images/{image_id}",
|
||||
controller=resource,
|
||||
action="delete_queued_image",
|
||||
conditions=dict(method=["DELETE"]))
|
||||
|
||||
map.connect("/queued_images",
|
||||
controller=resource,
|
||||
action="delete_queued_images",
|
||||
conditions=dict(method=["DELETE"]))
|
||||
|
||||
logger.info(_("Initialized image cache management middleware"))
|
||||
|
|
110
glance/client.py
110
glance/client.py
|
@ -185,117 +185,57 @@ class V1Client(base_client.BaseClient):
|
|||
def get_cached_images(self, **kwargs):
|
||||
"""
|
||||
Returns a list of images stored in the image cache.
|
||||
|
||||
:param filters: dictionary of attributes by which the resulting
|
||||
collection of images should be filtered
|
||||
:param marker: id after which to start the page of images
|
||||
:param limit: maximum number of items to return
|
||||
:param sort_key: results will be ordered by this image attribute
|
||||
:param sort_dir: direction in which to to order results (asc, desc)
|
||||
"""
|
||||
params = self._extract_params(kwargs, v1_images.SUPPORTED_PARAMS)
|
||||
res = self.do_request("GET", "/cached_images", params=params)
|
||||
res = self.do_request("GET", "/cached_images")
|
||||
data = json.loads(res.read())['cached_images']
|
||||
return data
|
||||
|
||||
def get_invalid_cached_images(self, **kwargs):
|
||||
def get_queued_images(self, **kwargs):
|
||||
"""
|
||||
Returns a list of invalid images stored in the image cache.
|
||||
|
||||
:param filters: dictionary of attributes by which the resulting
|
||||
collection of images should be filtered
|
||||
:param marker: id after which to start the page of images
|
||||
:param limit: maximum number of items to return
|
||||
:param sort_key: results will be ordered by this image attribute
|
||||
:param sort_dir: direction in which to to order results (asc, desc)
|
||||
Returns a list of images queued for caching
|
||||
"""
|
||||
params = self._extract_params(kwargs, v1_images.SUPPORTED_PARAMS)
|
||||
params['status'] = 'invalid'
|
||||
res = self.do_request("GET", "/cached_images", params=params)
|
||||
data = json.loads(res.read())['cached_images']
|
||||
res = self.do_request("GET", "/queued_images")
|
||||
data = json.loads(res.read())['queued_images']
|
||||
return data
|
||||
|
||||
def get_incomplete_cached_images(self, **kwargs):
|
||||
"""
|
||||
Returns a list of incomplete images being fetched into cache
|
||||
|
||||
:param filters: dictionary of attributes by which the resulting
|
||||
collection of images should be filtered
|
||||
:param marker: id after which to start the page of images
|
||||
:param limit: maximum number of items to return
|
||||
:param sort_key: results will be ordered by this image attribute
|
||||
:param sort_dir: direction in which to to order results (asc, desc)
|
||||
"""
|
||||
params = self._extract_params(kwargs, v1_images.SUPPORTED_PARAMS)
|
||||
params['status'] = 'incomplete'
|
||||
res = self.do_request("GET", "/cached_images", params=params)
|
||||
data = json.loads(res.read())['cached_images']
|
||||
return data
|
||||
|
||||
def purge_cached_image(self, image_id):
|
||||
def delete_cached_image(self, image_id):
|
||||
"""
|
||||
Delete a specified image from the cache
|
||||
"""
|
||||
self.do_request("DELETE", "/cached_images/%s" % image_id)
|
||||
return True
|
||||
|
||||
def clear_cached_images(self):
|
||||
def delete_all_cached_images(self):
|
||||
"""
|
||||
Clear all cached images
|
||||
Delete all cached images
|
||||
"""
|
||||
res = self.do_request("DELETE", "/cached_images")
|
||||
data = json.loads(res.read())
|
||||
num_purged = data['num_purged']
|
||||
return num_purged
|
||||
num_deleted = data['num_deleted']
|
||||
return num_deleted
|
||||
|
||||
def reap_invalid_cached_images(self, **kwargs):
|
||||
def queue_image_for_caching(self, image_id):
|
||||
"""
|
||||
Reaps any invalid cached images
|
||||
Queue an image for prefetching into cache
|
||||
"""
|
||||
params = self._extract_params(kwargs, v1_images.SUPPORTED_PARAMS)
|
||||
params['status'] = 'invalid'
|
||||
res = self.do_request("DELETE", "/cached_images", params=params)
|
||||
data = json.loads(res.read())
|
||||
num_reaped = data['num_reaped']
|
||||
return num_reaped
|
||||
|
||||
def reap_stalled_cached_images(self, **kwargs):
|
||||
"""
|
||||
Reaps any stalled cached images
|
||||
"""
|
||||
params = self._extract_params(kwargs, v1_images.SUPPORTED_PARAMS)
|
||||
params['status'] = 'incomplete'
|
||||
res = self.do_request("DELETE", "/cached_images", params=params)
|
||||
data = json.loads(res.read())
|
||||
num_reaped = data['num_reaped']
|
||||
return num_reaped
|
||||
|
||||
def prefetch_cache_image(self, image_id):
|
||||
"""
|
||||
Pre-fetch a specified image from the cache
|
||||
"""
|
||||
res = self.do_request("HEAD", "/images/%s" % image_id)
|
||||
image = wsgi.get_image_meta_from_headers(res)
|
||||
self.do_request("PUT", "/cached_images/%s" % image_id)
|
||||
self.do_request("PUT", "/queued_images/%s" % image_id)
|
||||
return True
|
||||
|
||||
def get_prefetching_cache_images(self, **kwargs):
|
||||
def delete_queued_image(self, image_id):
|
||||
"""
|
||||
Returns a list of images which are actively being prefetched or are
|
||||
queued to be prefetched in the future.
|
||||
Delete a specified image from the cache queue
|
||||
"""
|
||||
self.do_request("DELETE", "/queued_images/%s" % image_id)
|
||||
return True
|
||||
|
||||
:param filters: dictionary of attributes by which the resulting
|
||||
collection of images should be filtered
|
||||
:param marker: id after which to start the page of images
|
||||
:param limit: maximum number of items to return
|
||||
:param sort_key: results will be ordered by this image attribute
|
||||
:param sort_dir: direction in which to to order results (asc, desc)
|
||||
def delete_all_queued_images(self):
|
||||
"""
|
||||
params = self._extract_params(kwargs, v1_images.SUPPORTED_PARAMS)
|
||||
params['status'] = 'prefetching'
|
||||
res = self.do_request("GET", "/cached_images", params=params)
|
||||
data = json.loads(res.read())['cached_images']
|
||||
return data
|
||||
Delete all queued images
|
||||
"""
|
||||
res = self.do_request("DELETE", "/queued_images")
|
||||
data = json.loads(res.read())
|
||||
num_deleted = data['num_deleted']
|
||||
return num_deleted
|
||||
|
||||
def get_image_members(self, image_id):
|
||||
"""Returns a mapping of image memberships from Registry"""
|
||||
|
|
|
@ -114,20 +114,35 @@ class ImageCache(object):
|
|||
"""
|
||||
return self.driver.get_cached_images()
|
||||
|
||||
def delete_all(self):
|
||||
def delete_all_cached_images(self):
|
||||
"""
|
||||
Removes all cached image files and any attributes about the images
|
||||
and returns the number of cached image files that were deleted.
|
||||
"""
|
||||
return self.driver.delete_all()
|
||||
return self.driver.delete_all_cached_images()
|
||||
|
||||
def delete(self, image_id):
|
||||
def delete_cached_image(self, image_id):
|
||||
"""
|
||||
Removes a specific cached image file and any attributes about the image
|
||||
|
||||
:param image_id: Image ID
|
||||
"""
|
||||
self.driver.delete(image_id)
|
||||
self.driver.delete_cached_image(image_id)
|
||||
|
||||
def delete_all_queued_images(self):
|
||||
"""
|
||||
Removes all queued image files and any attributes about the images
|
||||
and returns the number of queued image files that were deleted.
|
||||
"""
|
||||
return self.driver.delete_all_queued_images()
|
||||
|
||||
def delete_queued_image(self, image_id):
|
||||
"""
|
||||
Removes a specific queued image file and any attributes about the image
|
||||
|
||||
:param image_id: Image ID
|
||||
"""
|
||||
self.driver.delete_queued_image(image_id)
|
||||
|
||||
def prune(self):
|
||||
"""
|
||||
|
@ -154,7 +169,7 @@ class ImageCache(object):
|
|||
image_id, size = entry
|
||||
logger.debug(_("Pruning '%(image_id)s' to free %(size)d bytes"),
|
||||
{'image_id': image_id, 'size': size})
|
||||
self.driver.delete(image_id)
|
||||
self.driver.delete_cached_image(image_id)
|
||||
total_bytes_pruned = total_bytes_pruned + size
|
||||
total_files_pruned = total_files_pruned + 1
|
||||
current_size = current_size - size
|
||||
|
@ -250,10 +265,10 @@ class ImageCache(object):
|
|||
"""
|
||||
return self.driver.open_for_read(image_id)
|
||||
|
||||
def get_cache_queue(self):
|
||||
def get_queued_images(self):
|
||||
"""
|
||||
Returns a list of image IDs that are in the queue. The
|
||||
list should be sorted by the time the image ID was inserted
|
||||
into the queue.
|
||||
"""
|
||||
return self.driver.get_cache_queue()
|
||||
return self.driver.get_queued_images()
|
||||
|
|
|
@ -130,14 +130,14 @@ class Driver(object):
|
|||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def delete_all(self):
|
||||
def delete_all_cached_images(self):
|
||||
"""
|
||||
Removes all cached image files and any attributes about the images
|
||||
and returns the number of cached image files that were deleted.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def delete(self, image_id):
|
||||
def delete_cached_image(self, image_id):
|
||||
"""
|
||||
Removes a specific cached image file and any attributes about the image
|
||||
|
||||
|
@ -145,6 +145,21 @@ class Driver(object):
|
|||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def delete_all_queued_images(self):
|
||||
"""
|
||||
Removes all queued image files and any attributes about the images
|
||||
and returns the number of queued image files that were deleted.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def delete_queued_image(self, image_id):
|
||||
"""
|
||||
Removes a specific queued image file and any attributes about the image
|
||||
|
||||
:param image_id: Image ID
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def queue_image(self, image_id):
|
||||
"""
|
||||
Puts an image identifier in a queue for caching. Return True
|
||||
|
@ -185,7 +200,7 @@ class Driver(object):
|
|||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_cache_queue(self):
|
||||
def get_queued_images(self):
|
||||
"""
|
||||
Returns a list of image IDs that are in the queue. The
|
||||
list should be sorted by the time the image ID was inserted
|
||||
|
|
|
@ -103,7 +103,7 @@ class Driver(base.Driver):
|
|||
conn.executescript("""
|
||||
CREATE TABLE IF NOT EXISTS cached_images (
|
||||
image_id TEXT PRIMARY KEY,
|
||||
last_access REAL DEFAULT 0.0,
|
||||
last_accessed REAL DEFAULT 0.0,
|
||||
last_modified REAL DEFAULT 0.0,
|
||||
size INTEGER DEFAULT 0,
|
||||
hits INTEGER DEFAULT 0,
|
||||
|
@ -154,7 +154,7 @@ class Driver(base.Driver):
|
|||
logger.debug(_("Gathering cached image entries."))
|
||||
with self.get_db() as db:
|
||||
cur = db.execute("""SELECT
|
||||
image_id, hits, last_access, last_modified, size
|
||||
image_id, hits, last_accessed, last_modified, size
|
||||
FROM cached_images
|
||||
ORDER BY image_id""")
|
||||
cur.row_factory = dict_factory
|
||||
|
@ -199,7 +199,7 @@ class Driver(base.Driver):
|
|||
path = self.get_image_filepath(image_id, 'queue')
|
||||
return os.path.exists(path)
|
||||
|
||||
def delete_all(self):
|
||||
def delete_all_cached_images(self):
|
||||
"""
|
||||
Removes all cached image files and any attributes about the images
|
||||
"""
|
||||
|
@ -212,7 +212,7 @@ class Driver(base.Driver):
|
|||
db.commit()
|
||||
return deleted
|
||||
|
||||
def delete(self, image_id):
|
||||
def delete_cached_image(self, image_id):
|
||||
"""
|
||||
Removes a specific cached image file and any attributes about the image
|
||||
|
||||
|
@ -225,6 +225,25 @@ class Driver(base.Driver):
|
|||
(image_id, ))
|
||||
db.commit()
|
||||
|
||||
def delete_all_queued_images(self):
|
||||
"""
|
||||
Removes all queued image files and any attributes about the images
|
||||
"""
|
||||
files = [f for f in self.get_cache_files(self.queue_dir)]
|
||||
for file in files:
|
||||
os.unlink(file)
|
||||
return len(files)
|
||||
|
||||
def delete_queued_image(self, image_id):
|
||||
"""
|
||||
Removes a specific queued image file and any attributes about the image
|
||||
|
||||
:param image_id: Image ID
|
||||
"""
|
||||
path = self.get_image_filepath(image_id, 'queue')
|
||||
if os.path.exists(path):
|
||||
os.unlink(path)
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Delete any image files in the invalid directory and any
|
||||
|
@ -246,7 +265,7 @@ class Driver(base.Driver):
|
|||
"""
|
||||
with self.get_db() as db:
|
||||
cur = db.execute("""SELECT image_id FROM cached_images
|
||||
ORDER BY last_access LIMIT 1""")
|
||||
ORDER BY last_accessed LIMIT 1""")
|
||||
image_id = cur.fetchone()[0]
|
||||
|
||||
path = self.get_image_filepath(image_id)
|
||||
|
@ -280,7 +299,7 @@ class Driver(base.Driver):
|
|||
now = time.time()
|
||||
|
||||
db.execute("""INSERT INTO cached_images
|
||||
(image_id, last_access, last_modified, hits, size)
|
||||
(image_id, last_accessed, last_modified, hits, size)
|
||||
VALUES (?, 0, ?, 0, ?)""",
|
||||
(image_id, now, filesize))
|
||||
db.commit()
|
||||
|
@ -319,7 +338,7 @@ class Driver(base.Driver):
|
|||
now = time.time()
|
||||
with self.get_db() as db:
|
||||
db.execute("""UPDATE cached_images
|
||||
SET hits = hits + 1, last_access = ?
|
||||
SET hits = hits + 1, last_accessed = ?
|
||||
WHERE image_id = ?""",
|
||||
(now, image_id))
|
||||
db.commit()
|
||||
|
@ -411,7 +430,7 @@ class Driver(base.Driver):
|
|||
os.unlink(path)
|
||||
logger.info("Removed stalled cache file %s", path)
|
||||
|
||||
def get_cache_queue(self):
|
||||
def get_queued_images(self):
|
||||
"""
|
||||
Returns a list of image IDs that are in the queue. The
|
||||
list should be sorted by the time the image ID was inserted
|
||||
|
|
|
@ -199,7 +199,7 @@ class Driver(base.Driver):
|
|||
path = self.get_image_filepath(image_id, 'queue')
|
||||
return os.path.exists(path)
|
||||
|
||||
def delete_all(self):
|
||||
def delete_all_cached_images(self):
|
||||
"""
|
||||
Removes all cached image files and any attributes about the images
|
||||
"""
|
||||
|
@ -209,7 +209,7 @@ class Driver(base.Driver):
|
|||
deleted += 1
|
||||
return deleted
|
||||
|
||||
def delete(self, image_id):
|
||||
def delete_cached_image(self, image_id):
|
||||
"""
|
||||
Removes a specific cached image file and any attributes about the image
|
||||
|
||||
|
@ -218,6 +218,25 @@ class Driver(base.Driver):
|
|||
path = self.get_image_filepath(image_id)
|
||||
delete_cached_file(path)
|
||||
|
||||
def delete_all_queued_images(self):
|
||||
"""
|
||||
Removes all queued image files and any attributes about the images
|
||||
"""
|
||||
files = [f for f in self.get_cache_files(self.queue_dir)]
|
||||
for file in files:
|
||||
os.unlink(file)
|
||||
return len(files)
|
||||
|
||||
def delete_queued_image(self, image_id):
|
||||
"""
|
||||
Removes a specific queued image file and any attributes about the image
|
||||
|
||||
:param image_id: Image ID
|
||||
"""
|
||||
path = self.get_image_filepath(image_id, 'queue')
|
||||
if os.path.exists(path):
|
||||
os.unlink(path)
|
||||
|
||||
def get_least_recently_accessed(self):
|
||||
"""
|
||||
Return a tuple containing the image_id and size of the least recently
|
||||
|
@ -342,7 +361,7 @@ class Driver(base.Driver):
|
|||
|
||||
return True
|
||||
|
||||
def get_cache_queue(self):
|
||||
def get_queued_images(self):
|
||||
"""
|
||||
Returns a list of image IDs that are in the queue. The
|
||||
list should be sorted by the time the image ID was inserted
|
||||
|
|
|
@ -70,7 +70,7 @@ class Prefetcher(object):
|
|||
|
||||
def run(self):
|
||||
|
||||
images = self.cache.get_cache_queue()
|
||||
images = self.cache.get_queued_images()
|
||||
if not images:
|
||||
logger.debug(_("Nothing to prefetch."))
|
||||
return True
|
||||
|
|
|
@ -50,7 +50,7 @@ class TestBinGlance(functional.FunctionalTest):
|
|||
4. Verify no longer in index
|
||||
"""
|
||||
self.cleanup()
|
||||
self.start_servers()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
api_port = self.api_port
|
||||
registry_port = self.registry_port
|
||||
|
@ -128,7 +128,7 @@ class TestBinGlance(functional.FunctionalTest):
|
|||
6. Verify the updated name is shown
|
||||
"""
|
||||
self.cleanup()
|
||||
self.start_servers()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
api_port = self.api_port
|
||||
registry_port = self.registry_port
|
||||
|
@ -263,7 +263,7 @@ class TestBinGlance(functional.FunctionalTest):
|
|||
4. Run SQL against DB to verify no undeleted properties
|
||||
"""
|
||||
self.cleanup()
|
||||
self.start_servers()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
api_port = self.api_port
|
||||
registry_port = self.registry_port
|
||||
|
@ -303,7 +303,7 @@ class TestBinGlance(functional.FunctionalTest):
|
|||
|
||||
def test_results_filtering(self):
|
||||
self.cleanup()
|
||||
self.start_servers()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
api_port = self.api_port
|
||||
registry_port = self.registry_port
|
||||
|
@ -448,7 +448,7 @@ class TestBinGlance(functional.FunctionalTest):
|
|||
|
||||
def test_results_pagination(self):
|
||||
self.cleanup()
|
||||
self.start_servers()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
_base_cmd = "bin/glance --port=%d" % self.api_port
|
||||
index_cmd = "%s index -f" % _base_cmd
|
||||
|
@ -528,9 +528,11 @@ class TestBinGlance(functional.FunctionalTest):
|
|||
self.assertTrue(image_lines[1].split()[1], image_ids[2])
|
||||
self.assertTrue(image_lines[12].split()[1], image_ids[1])
|
||||
|
||||
self.stop_servers()
|
||||
|
||||
def test_results_sorting(self):
|
||||
self.cleanup()
|
||||
self.start_servers()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
_base_cmd = "bin/glance --port=%d" % self.api_port
|
||||
index_cmd = "%s index -f" % _base_cmd
|
||||
|
@ -603,9 +605,11 @@ class TestBinGlance(functional.FunctionalTest):
|
|||
self.assertTrue(image_lines[12].split()[1], image_ids[1])
|
||||
self.assertTrue(image_lines[23].split()[1], image_ids[4])
|
||||
|
||||
self.stop_servers()
|
||||
|
||||
def test_show_image_format(self):
|
||||
self.cleanup()
|
||||
self.start_servers()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
api_port = self.api_port
|
||||
registry_port = self.registry_port
|
||||
|
@ -653,3 +657,5 @@ class TestBinGlance(functional.FunctionalTest):
|
|||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertEqual('Deleted image %s' % image_id, out.strip())
|
||||
|
||||
self.stop_servers()
|
||||
|
|
|
@ -0,0 +1,313 @@
|
|||
# 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.
|
||||
|
||||
"""Functional test case that utilizes the bin/glance-cache-manage CLI tool"""
|
||||
|
||||
import datetime
|
||||
import hashlib
|
||||
import httplib2
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from glance.common import utils
|
||||
from glance.tests import functional
|
||||
from glance.tests.utils import execute
|
||||
|
||||
FIVE_KB = 5 * 1024
|
||||
|
||||
|
||||
class TestBinGlanceCacheManage(functional.FunctionalTest):
|
||||
"""Functional tests for the bin/glance CLI tool"""
|
||||
|
||||
def setUp(self):
|
||||
self.cache_pipeline = "cache cache_manage"
|
||||
self.image_cache_driver = "sqlite"
|
||||
|
||||
super(TestBinGlanceCacheManage, self).setUp()
|
||||
|
||||
# NOTE(sirp): This is needed in case we are running the tests under an
|
||||
# environment in which OS_AUTH_STRATEGY=keystone. The test server we
|
||||
# spin up won't have keystone support, so we need to switch to the
|
||||
# NoAuth strategy.
|
||||
os.environ['OS_AUTH_STRATEGY'] = 'noauth'
|
||||
|
||||
def add_image(self, name):
|
||||
"""
|
||||
Adds an image with supplied name and returns the newly-created
|
||||
image identifier.
|
||||
"""
|
||||
image_data = "*" * FIVE_KB
|
||||
headers = {'Content-Type': 'application/octet-stream',
|
||||
'X-Image-Meta-Name': name,
|
||||
'X-Image-Meta-Is-Public': 'true'}
|
||||
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'POST', headers=headers,
|
||||
body=image_data)
|
||||
self.assertEqual(response.status, 201)
|
||||
data = json.loads(content)
|
||||
self.assertEqual(data['image']['checksum'],
|
||||
hashlib.md5(image_data).hexdigest())
|
||||
self.assertEqual(data['image']['size'], FIVE_KB)
|
||||
self.assertEqual(data['image']['name'], name)
|
||||
self.assertEqual(data['image']['is_public'], True)
|
||||
return data['image']['id']
|
||||
|
||||
def is_image_cached(self, image_id):
|
||||
"""
|
||||
Return True if supplied image ID is cached, False otherwise
|
||||
"""
|
||||
cmd = "bin/glance-cache-manage --port=%d list-cached" % self.api_port
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
return image_id in out
|
||||
|
||||
def test_no_cache_enabled(self):
|
||||
"""
|
||||
Test that cache index command works
|
||||
"""
|
||||
self.cleanup()
|
||||
self.start_servers() # Not passing in cache_manage in pipeline...
|
||||
|
||||
api_port = self.api_port
|
||||
registry_port = self.registry_port
|
||||
|
||||
# Verify decent error message returned
|
||||
cmd = "bin/glance-cache-manage --port=%d list-cached" % api_port
|
||||
|
||||
exitcode, out, err = execute(cmd, raise_error=False)
|
||||
|
||||
self.assertEqual(1, exitcode)
|
||||
self.assertTrue('Cache management middleware not enabled on host'
|
||||
in out.strip())
|
||||
|
||||
self.stop_servers()
|
||||
|
||||
def test_cache_index(self):
|
||||
"""
|
||||
Test that cache index command works
|
||||
"""
|
||||
self.cleanup()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
api_port = self.api_port
|
||||
registry_port = self.registry_port
|
||||
|
||||
# Verify no cached images
|
||||
cmd = "bin/glance-cache-manage --port=%d list-cached" % api_port
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertTrue('No cached images' in out.strip())
|
||||
|
||||
ids = {}
|
||||
|
||||
# Add a few images and cache the second one of them
|
||||
# by GETing the image...
|
||||
for x in xrange(0, 4):
|
||||
ids[x] = self.add_image("Image%s" % x)
|
||||
|
||||
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", api_port,
|
||||
ids[1])
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(response.status, 200)
|
||||
|
||||
self.assertTrue(self.is_image_cached(ids[1]),
|
||||
"%s is not cached." % ids[1])
|
||||
|
||||
self.stop_servers()
|
||||
|
||||
def test_queue(self):
|
||||
"""
|
||||
Test that we can queue and fetch images using the
|
||||
CLI utility
|
||||
"""
|
||||
self.cleanup()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
api_port = self.api_port
|
||||
registry_port = self.registry_port
|
||||
|
||||
# Verify no cached images
|
||||
cmd = "bin/glance-cache-manage --port=%d list-cached" % api_port
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertTrue('No cached images' in out.strip())
|
||||
|
||||
# Verify no queued images
|
||||
cmd = "bin/glance-cache-manage --port=%d list-queued" % api_port
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertTrue('No queued images' in out.strip())
|
||||
|
||||
ids = {}
|
||||
|
||||
# Add a few images and cache the second one of them
|
||||
# by GETing the image...
|
||||
for x in xrange(0, 4):
|
||||
ids[x] = self.add_image("Image%s" % x)
|
||||
|
||||
# Queue second image and then cache it
|
||||
cmd = "bin/glance-cache-manage --port=%d --force queue-image %s" % (
|
||||
api_port, ids[1])
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
|
||||
# Verify queued second image
|
||||
cmd = "bin/glance-cache-manage --port=%d list-queued" % api_port
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertTrue(ids[1] in out, 'Image %s was not queued!' % ids[1])
|
||||
|
||||
# Cache images in the queue by running the prefetcher
|
||||
cache_config_filepath = os.path.join(self.test_dir, 'etc',
|
||||
'glance-cache.conf')
|
||||
cache_file_options = {
|
||||
'image_cache_dir': self.api_server.image_cache_dir,
|
||||
'image_cache_driver': self.image_cache_driver,
|
||||
'registry_port': self.api_server.registry_port,
|
||||
'log_file': os.path.join(self.test_dir, 'cache.log'),
|
||||
'metadata_encryption_key': "012345678901234567890123456789ab"
|
||||
}
|
||||
with open(cache_config_filepath, 'w') as cache_file:
|
||||
cache_file.write("""[DEFAULT]
|
||||
debug = True
|
||||
verbose = True
|
||||
image_cache_dir = %(image_cache_dir)s
|
||||
image_cache_driver = %(image_cache_driver)s
|
||||
registry_host = 0.0.0.0
|
||||
registry_port = %(registry_port)s
|
||||
metadata_encryption_key = %(metadata_encryption_key)s
|
||||
log_file = %(log_file)s
|
||||
|
||||
[app:glance-pruner]
|
||||
paste.app_factory = glance.image_cache.pruner:app_factory
|
||||
|
||||
[app:glance-prefetcher]
|
||||
paste.app_factory = glance.image_cache.prefetcher:app_factory
|
||||
|
||||
[app:glance-cleaner]
|
||||
paste.app_factory = glance.image_cache.cleaner:app_factory
|
||||
|
||||
[app:glance-queue-image]
|
||||
paste.app_factory = glance.image_cache.queue_image:app_factory
|
||||
""" % cache_file_options)
|
||||
cache_file.flush()
|
||||
|
||||
cmd = "bin/glance-cache-prefetcher %s" % cache_config_filepath
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertEqual('', out.strip(), out)
|
||||
|
||||
# Verify no queued images
|
||||
cmd = "bin/glance-cache-manage --port=%d list-queued" % api_port
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertTrue('No queued images' in out.strip())
|
||||
|
||||
# Verify second image now cached
|
||||
cmd = "bin/glance-cache-manage --port=%d list-cached" % api_port
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertTrue(ids[1] in out, 'Image %s was not cached!' % ids[1])
|
||||
|
||||
# Queue third image and then delete it from queue
|
||||
cmd = "bin/glance-cache-manage --port=%d --force queue-image %s" % (
|
||||
api_port, ids[2])
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
|
||||
# Verify queued third image
|
||||
cmd = "bin/glance-cache-manage --port=%d list-queued" % api_port
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertTrue(ids[2] in out, 'Image %s was not queued!' % ids[2])
|
||||
|
||||
# Delete the image from the queue
|
||||
cmd = ("bin/glance-cache-manage --port=%d --force "
|
||||
"delete-queued-image %s") % (api_port, ids[2])
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
|
||||
# Verify no queued images
|
||||
cmd = "bin/glance-cache-manage --port=%d list-queued" % api_port
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertTrue('No queued images' in out.strip())
|
||||
|
||||
# Queue all images
|
||||
for x in xrange(0, 4):
|
||||
cmd = ("bin/glance-cache-manage --port=%d --force "
|
||||
"queue-image %s") % (api_port, ids[x])
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
|
||||
# Verify queued third image
|
||||
cmd = "bin/glance-cache-manage --port=%d list-queued" % api_port
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertTrue('Found 3 queued images' in out)
|
||||
|
||||
# Delete the image from the queue
|
||||
cmd = ("bin/glance-cache-manage --port=%d --force "
|
||||
"delete-all-queued-images") % (api_port)
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
|
||||
# Verify nothing in queue anymore
|
||||
cmd = "bin/glance-cache-manage --port=%d list-queued" % api_port
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertTrue('No queued images' in out.strip())
|
||||
|
||||
self.stop_servers()
|
|
@ -362,7 +362,7 @@ paste.app_factory = glance.image_cache.queue_image:app_factory
|
|||
# Queue the first image, verify no images still in cache after queueing
|
||||
# then run the prefetcher and verify that the image is then in the
|
||||
# cache
|
||||
path = "http://%s:%d/v1/cached_images/%s" % ("0.0.0.0", self.api_port,
|
||||
path = "http://%s:%d/v1/queued_images/%s" % ("0.0.0.0", self.api_port,
|
||||
ids[0])
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'PUT')
|
||||
|
|
|
@ -99,7 +99,7 @@ class ImageCacheTestCase(object):
|
|||
|
||||
self.assertTrue(self.cache.is_cached(1))
|
||||
|
||||
self.cache.delete(1)
|
||||
self.cache.delete_cached_image(1)
|
||||
|
||||
self.assertFalse(self.cache.is_cached(1))
|
||||
|
||||
|
@ -119,7 +119,7 @@ class ImageCacheTestCase(object):
|
|||
for image_id in (1, 2):
|
||||
self.assertTrue(self.cache.is_cached(image_id))
|
||||
|
||||
self.cache.delete_all()
|
||||
self.cache.delete_all_cached_images()
|
||||
|
||||
for image_id in (1, 2):
|
||||
self.assertFalse(self.cache.is_cached(image_id))
|
||||
|
@ -212,12 +212,12 @@ class ImageCacheTestCase(object):
|
|||
|
||||
self.assertFalse(self.cache.queue_image(1))
|
||||
|
||||
self.cache.delete(1)
|
||||
self.cache.delete_cached_image(1)
|
||||
|
||||
for x in xrange(0, 3):
|
||||
self.assertTrue(self.cache.queue_image(x))
|
||||
|
||||
self.assertEqual(self.cache.get_cache_queue(),
|
||||
self.assertEqual(self.cache.get_queued_images(),
|
||||
['0', '1', '2'])
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue