
Since the default value is None when can't get a key from a dict, So there is no need to use dict.get('key', None). Change-Id: Ie8008e83f432263e7a97e482e83f240b1056feee
523 lines
16 KiB
Python
523 lines
16 KiB
Python
#!/usr/bin/env python
|
|
|
|
# Copyright 2011 OpenStack Foundation
|
|
# 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.
|
|
"""
|
|
from __future__ import print_function
|
|
|
|
import datetime
|
|
import functools
|
|
import optparse
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
from oslo_utils import encodeutils
|
|
import prettytable
|
|
|
|
from six.moves import input
|
|
|
|
# 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.common import exception
|
|
import glance.image_cache.client
|
|
from glance.version import version_info as version
|
|
|
|
|
|
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.Forbidden:
|
|
print("Not authorized to make this request.")
|
|
return FAILURE
|
|
except Exception as e:
|
|
options = args[0]
|
|
if options.debug:
|
|
raise
|
|
print("Failed to %s. Got error:" % action)
|
|
pieces = encodeutils.exception_to_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 = prettytable.PrettyTable(("ID",
|
|
"Last Accessed (UTC)",
|
|
"Last Modified (UTC)",
|
|
"Size",
|
|
"Hits"))
|
|
pretty_table.align['Size'] = "r"
|
|
pretty_table.align['Hits'] = "r"
|
|
|
|
for image in images:
|
|
last_accessed = image['last_accessed']
|
|
if last_accessed == 0:
|
|
last_accessed = "N/A"
|
|
else:
|
|
last_accessed = datetime.datetime.utcfromtimestamp(
|
|
last_accessed).isoformat()
|
|
|
|
pretty_table.add_row((
|
|
image['image_id'],
|
|
last_accessed,
|
|
datetime.datetime.utcfromtimestamp(
|
|
image['last_modified']).isoformat(),
|
|
image['size'],
|
|
image['hits']))
|
|
|
|
print(pretty_table.get_string())
|
|
|
|
|
|
@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 = prettytable.PrettyTable(("ID",))
|
|
|
|
for image in images:
|
|
pretty_table.add_row((image,))
|
|
|
|
print(pretty_table.get_string())
|
|
|
|
|
|
@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
|
|
"""
|
|
if len(args) == 1:
|
|
image_id = args.pop()
|
|
else:
|
|
print("Please specify one and only ID of the image you wish to ")
|
|
print("queue from the cache as the first argument")
|
|
return FAILURE
|
|
|
|
if (not options.force and
|
|
not user_confirm("Queue image %(image_id)s for caching?" %
|
|
{'image_id': 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" %
|
|
{'image_id': image_id})
|
|
|
|
return SUCCESS
|
|
|
|
|
|
@catch_error('delete the specified cached image')
|
|
def delete_cached_image(options, args):
|
|
"""
|
|
%(prog)s delete-cached-image <IMAGE_ID> [options]
|
|
|
|
Deletes an image from the cache
|
|
"""
|
|
if len(args) == 1:
|
|
image_id = args.pop()
|
|
else:
|
|
print("Please specify one and only ID of the image you wish to ")
|
|
print("delete from the cache as the first argument")
|
|
return FAILURE
|
|
|
|
if (not options.force and
|
|
not user_confirm("Delete cached image %(image_id)s?" %
|
|
{'image_id': 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" % {'image_id': image_id})
|
|
|
|
return SUCCESS
|
|
|
|
|
|
@catch_error('Delete all cached images')
|
|
def delete_all_cached_images(options, args):
|
|
"""%(prog)s delete-all-cached-images [options]
|
|
|
|
Remove 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" %
|
|
{'num_deleted': num_deleted})
|
|
|
|
return SUCCESS
|
|
|
|
|
|
@catch_error('delete the specified queued image')
|
|
def delete_queued_image(options, args):
|
|
"""
|
|
%(prog)s delete-queued-image <IMAGE_ID> [options]
|
|
|
|
Deletes an image from the cache
|
|
"""
|
|
if len(args) == 1:
|
|
image_id = args.pop()
|
|
else:
|
|
print("Please specify one and only ID of the image you wish to ")
|
|
print("delete from the cache as the first argument")
|
|
return FAILURE
|
|
|
|
if (not options.force and
|
|
not user_confirm("Delete queued image %(image_id)s?" %
|
|
{'image_id': 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" % {'image_id': image_id})
|
|
|
|
return SUCCESS
|
|
|
|
|
|
@catch_error('Delete all queued images')
|
|
def delete_all_queued_images(options, args):
|
|
"""%(prog)s delete-all-queued-images [options]
|
|
|
|
Remove 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" %
|
|
{'num_deleted': num_deleted})
|
|
|
|
return SUCCESS
|
|
|
|
|
|
def get_client(options):
|
|
"""Return a new client object to a Glance server.
|
|
|
|
specified by the --host and --port options
|
|
supplied to the CLI
|
|
"""
|
|
return glance.image_cache.client.get_client(
|
|
host=options.host,
|
|
port=options.port,
|
|
username=options.os_username,
|
|
password=options.os_password,
|
|
tenant=options.os_tenant_name,
|
|
auth_url=options.os_auth_url,
|
|
auth_strategy=options.os_auth_strategy,
|
|
auth_token=options.os_auth_token,
|
|
region=options.os_region_name,
|
|
insecure=options.insecure)
|
|
|
|
|
|
def env(*vars, **kwargs):
|
|
"""Search for the first defined of possibly many env vars.
|
|
|
|
Returns the first environment variable defined in vars, or
|
|
returns the default defined in kwargs.
|
|
"""
|
|
for v in vars:
|
|
value = os.environ.get(v)
|
|
if value:
|
|
return value
|
|
return kwargs.get('default', '')
|
|
|
|
|
|
def create_options(parser):
|
|
"""Set 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 debugging 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('-k', '--insecure', dest="insecure",
|
|
default=False, action="store_true",
|
|
help="Explicitly allow glance to perform \"insecure\" "
|
|
"SSL (https) requests. The server's certificate will "
|
|
"not be verified against any certificate authorities. "
|
|
"This option should be used with caution.")
|
|
parser.add_option('-f', '--force', dest="force", metavar="FORCE",
|
|
default=False, action="store_true",
|
|
help="Prevent select actions from requesting "
|
|
"user confirmation.")
|
|
|
|
parser.add_option('--os-auth-token',
|
|
dest='os_auth_token',
|
|
default=env('OS_AUTH_TOKEN'),
|
|
help='Defaults to env[OS_AUTH_TOKEN].')
|
|
parser.add_option('-A', '--os_auth_token', '--auth_token',
|
|
dest='os_auth_token',
|
|
help=optparse.SUPPRESS_HELP)
|
|
|
|
parser.add_option('--os-username',
|
|
dest='os_username',
|
|
default=env('OS_USERNAME'),
|
|
help='Defaults to env[OS_USERNAME].')
|
|
parser.add_option('-I', '--os_username',
|
|
dest='os_username',
|
|
help=optparse.SUPPRESS_HELP)
|
|
|
|
parser.add_option('--os-password',
|
|
dest='os_password',
|
|
default=env('OS_PASSWORD'),
|
|
help='Defaults to env[OS_PASSWORD].')
|
|
parser.add_option('-K', '--os_password',
|
|
dest='os_password',
|
|
help=optparse.SUPPRESS_HELP)
|
|
|
|
parser.add_option('--os-region-name',
|
|
dest='os_region_name',
|
|
default=env('OS_REGION_NAME'),
|
|
help='Defaults to env[OS_REGION_NAME].')
|
|
parser.add_option('-R', '--os_region_name',
|
|
dest='os_region_name',
|
|
help=optparse.SUPPRESS_HELP)
|
|
|
|
parser.add_option('--os-tenant-id',
|
|
dest='os_tenant_id',
|
|
default=env('OS_TENANT_ID'),
|
|
help='Defaults to env[OS_TENANT_ID].')
|
|
parser.add_option('--os_tenant_id',
|
|
dest='os_tenant_id',
|
|
help=optparse.SUPPRESS_HELP)
|
|
|
|
parser.add_option('--os-tenant-name',
|
|
dest='os_tenant_name',
|
|
default=env('OS_TENANT_NAME'),
|
|
help='Defaults to env[OS_TENANT_NAME].')
|
|
parser.add_option('-T', '--os_tenant_name',
|
|
dest='os_tenant_name',
|
|
help=optparse.SUPPRESS_HELP)
|
|
|
|
parser.add_option('--os-auth-url',
|
|
default=env('OS_AUTH_URL'),
|
|
help='Defaults to env[OS_AUTH_URL].')
|
|
parser.add_option('-N', '--os_auth_url',
|
|
dest='os_auth_url',
|
|
help=optparse.SUPPRESS_HELP)
|
|
|
|
parser.add_option('-S', '--os_auth_strategy', dest="os_auth_strategy",
|
|
metavar="STRATEGY",
|
|
help="Authentication strategy (keystone or noauth).")
|
|
|
|
|
|
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
|
|
"""
|
|
parser = options.__parser
|
|
|
|
if not args:
|
|
parser.print_help()
|
|
else:
|
|
number_of_commands = len(args)
|
|
if number_of_commands == 1:
|
|
command_name = args.pop()
|
|
command = lookup_command(parser, command_name)
|
|
print(command.__doc__ % {'prog': os.path.basename(sys.argv[0])})
|
|
else:
|
|
sys.exit("Please specify one command")
|
|
|
|
|
|
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: %(cmd_name)s" % {'cmd_name': 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 = input("%s %s " % (prompt, prompt_default))
|
|
|
|
if answer == "":
|
|
return default
|
|
else:
|
|
return answer.lower() in ("yes", "y")
|
|
|
|
|
|
def 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
|
|
"""
|
|
|
|
version_string = version.cached_version_string()
|
|
oparser = optparse.OptionParser(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) as e:
|
|
sys.exit("ERROR: %s" % e)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|