diff options
22 files changed, 1013 insertions, 213 deletions
diff --git a/.zuul.yaml b/.zuul.yaml index f65fe4ef5..4be1e86fe 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -28,8 +28,13 @@ parent: tox abstract: true description: Abstract job for Glance vs. oslo libraries - # NOTE(rosmaita): only need functional test jobs, oslo is - # already running periodic jobs using our unit tests + # NOTE(rosmaita): we only need functional test jobs, oslo is + # already running periodic jobs using our unit tests. Those + # jobs are configured for glance in openstack-infra/project-config/ + # zuul.d/projects.yaml using the template 'periodic-jobs-with-oslo-master' + # which is defined in openstack-infra/openstack-zuul-jobs/zuul.d/ + # project-templates.yaml; the jobs the template refers to are + # defined in openstack-infra/openstack-zuul-jobs/zuul.d/jobs.yaml required-projects: - name: openstack/debtcollector - name: openstack/futurist @@ -196,6 +201,7 @@ - openstack-python-jobs - openstack-python35-jobs - openstack-python36-jobs + - openstack-python37-jobs - periodic-stable-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 diff --git a/doc/source/configuration/configuring.rst b/doc/source/configuration/configuring.rst index 6a664b0fb..cb2dcb251 100644 --- a/doc/source/configuration/configuring.rst +++ b/doc/source/configuration/configuring.rst @@ -444,6 +444,17 @@ Configuring the Filesystem Storage Backend permissions, a file will still be saved, but a warning message will appear in the Glance log. +``filesystem_store_chunk_size=SIZE_IN_BYTES`` + Optional. Default: ``65536`` + + Can only be specified in configuration files. + + `This option is specific to the filesystem storage backend.` + + The chunk size used when reading or writing image files. Raising this value + may improve the throughput but it may also slightly increase the memory + usage when handling a large number of requests. + Configuring the Filesystem Storage Backend with multiple stores ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/source/index.rst b/doc/source/index.rst index 12593e5af..17fbaf6c0 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -23,7 +23,10 @@ About Glance The Image service (glance) project provides a service where users can upload and discover data assets that are meant to be used with other services. -This currently includes images and metadata definitions. +This currently includes *images* and *metadata definitions*. + +Images +------ Glance image services include discovering, registering, and retrieving virtual machine (VM) images. Glance has a RESTful API that allows @@ -35,6 +38,27 @@ VM images made available through Glance can be stored in a variety of locations from simple filesystems to object-storage systems like the OpenStack Swift project. +Metadata Definitions +-------------------- + +Glance hosts a *metadefs* catalog. This provides the OpenStack community +with a way to programmatically determine various metadata key names and +valid values that can be applied to OpenStack resources. + +Note that what we're talking about here is simply a *catalog*; the keys and +values don't actually do anything unless they are applied to individual +OpenStack resources using the APIs or client tools provided by the services +responsible for those resources. + +It's also worth noting that there is no special relationship between the +Image Service and the Metadefs Service. If you want to apply the keys and +values defined in the Metadefs Service to images, you must use the Image +Service API or client tools just as you would for any other OpenStack +service. + +Design Principles +----------------- + Glance, as with all OpenStack projects, is written with the following design guidelines in mind: diff --git a/glance/api/middleware/cache_manage.py b/glance/api/middleware/cache_manage.py index 77c695419..44af6742e 100644 --- a/glance/api/middleware/cache_manage.py +++ b/glance/api/middleware/cache_manage.py @@ -20,7 +20,7 @@ Image Cache Management API from oslo_log import log as logging import routes -from glance.api import cached_images +from glance.api.v2 import cached_images from glance.common import wsgi from glance.i18n import _LI @@ -32,37 +32,37 @@ class CacheManageFilter(wsgi.Middleware): mapper = routes.Mapper() resource = cached_images.create_resource() - mapper.connect("/v1/cached_images", + mapper.connect("/v2/cached_images", controller=resource, action="get_cached_images", conditions=dict(method=["GET"])) - mapper.connect("/v1/cached_images/{image_id}", + mapper.connect("/v2/cached_images/{image_id}", controller=resource, action="delete_cached_image", conditions=dict(method=["DELETE"])) - mapper.connect("/v1/cached_images", + mapper.connect("/v2/cached_images", controller=resource, action="delete_cached_images", conditions=dict(method=["DELETE"])) - mapper.connect("/v1/queued_images/{image_id}", + mapper.connect("/v2/queued_images/{image_id}", controller=resource, action="queue_image", conditions=dict(method=["PUT"])) - mapper.connect("/v1/queued_images", + mapper.connect("/v2/queued_images", controller=resource, action="get_queued_images", conditions=dict(method=["GET"])) - mapper.connect("/v1/queued_images/{image_id}", + mapper.connect("/v2/queued_images/{image_id}", controller=resource, action="delete_queued_image", conditions=dict(method=["DELETE"])) - mapper.connect("/v1/queued_images", + mapper.connect("/v2/queued_images", controller=resource, action="delete_queued_images", conditions=dict(method=["DELETE"])) diff --git a/glance/api/v2/cached_images.py b/glance/api/v2/cached_images.py new file mode 100644 index 000000000..72f405b3f --- /dev/null +++ b/glance/api/v2/cached_images.py @@ -0,0 +1,128 @@ +# Copyright 2018 RedHat Inc. +# 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. + +""" +Controller for Image Cache Management API +""" + +from oslo_log import log as logging +import webob.exc + +from glance.api import policy +from glance.common import exception +from glance.common import wsgi +from glance import image_cache + +LOG = logging.getLogger(__name__) + + +class CacheController(object): + """ + A controller for managing cached images. + """ + + def __init__(self): + self.cache = image_cache.ImageCache() + self.policy = policy.Enforcer() + + def _enforce(self, req): + """Authorize request against 'manage_image_cache' policy""" + try: + self.policy.enforce(req.context, 'manage_image_cache', {}) + except exception.Forbidden: + LOG.debug("User not permitted to manage the image cache") + raise webob.exc.HTTPForbidden() + + def get_cached_images(self, req): + """ + GET /cached_images + + Returns a mapping of records about cached images. + """ + self._enforce(req) + images = self.cache.get_cached_images() + return dict(cached_images=images) + + def delete_cached_image(self, req, image_id): + """ + DELETE /cached_images/<IMAGE_ID> + + Removes an image from the cache. + """ + self._enforce(req) + self.cache.delete_cached_image(image_id) + + def delete_cached_images(self, req): + """ + DELETE /cached_images - Clear all active cached images + + Removes all images from the cache. + """ + self._enforce(req) + return dict(num_deleted=self.cache.delete_all_cached_images()) + + def get_queued_images(self, req): + """ + GET /queued_images + + Returns a mapping of records about queued images. + """ + self._enforce(req) + 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._enforce(req) + 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._enforce(req) + 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. + """ + self._enforce(req) + return dict(num_deleted=self.cache.delete_all_queued_images()) + + +class CachedImageDeserializer(wsgi.JSONRequestDeserializer): + pass + + +class CachedImageSerializer(wsgi.JSONResponseSerializer): + pass + + +def create_resource(): + """Cached Images resource factory method""" + deserializer = CachedImageDeserializer() + serializer = CachedImageSerializer() + return wsgi.Resource(CacheController(), deserializer, serializer) diff --git a/glance/cmd/cache_manage.py b/glance/cmd/cache_manage.py new file mode 100644 index 000000000..26021a481 --- /dev/null +++ b/glance/cmd/cache_manage.py @@ -0,0 +1,528 @@ +#!/usr/bin/env python + +# Copyright 2018 RedHat Inc. +# 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 argparse +import collections +import datetime +import functools +import os +import sys +import time +import uuid + +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 validate_input(func): + """Decorator to enforce validation on input""" + @functools.wraps(func) + def wrapped(*args, **kwargs): + if len(args[0].command) > 2: + print("Please specify the ID of the image you wish for command " + "'%s' from the cache as the first and only " + "argument." % args[0].command[0]) + return FAILURE + if len(args[0].command) == 2: + image_id = args[0].command[1] + try: + image_id = uuid.UUID(image_id) + except ValueError: + print("Image ID '%s' is not a valid UUID." % image_id) + return FAILURE + + return func(args[0], **kwargs) + return wrapped + + +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(args): + """%(prog)s list-cached [options] + + List all images currently cached. + """ + client = get_client(args) + 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()) + return SUCCESS + + +@catch_error('show queued images') +def list_queued(args): + """%(prog)s list-queued [options] + + List all images currently queued for caching. + """ + client = get_client(args) + 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') +@validate_input +def queue_image(args): + """%(prog)s queue-image <IMAGE_ID> [options] + + Queues an image for caching. + """ + image_id = args.command[1] + if (not args.force and + not user_confirm("Queue image %(image_id)s for caching?" % + {'image_id': image_id}, default=False)): + return SUCCESS + + client = get_client(args) + client.queue_image_for_caching(image_id) + + if args.verbose: + print("Queued image %(image_id)s for caching" % + {'image_id': image_id}) + + return SUCCESS + + +@catch_error('delete the specified cached image') +@validate_input +def delete_cached_image(args): + """%(prog)s delete-cached-image <IMAGE_ID> [options] + + Deletes an image from the cache. + """ + image_id = args.command[1] + if (not args.force and + not user_confirm("Delete cached image %(image_id)s?" % + {'image_id': image_id}, default=False)): + return SUCCESS + + client = get_client(args) + client.delete_cached_image(image_id) + + if args.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(args): + """%(prog)s delete-all-cached-images [options] + + Remove all images from the cache. + """ + if (not args.force and + not user_confirm("Delete all cached images?", default=False)): + return SUCCESS + + client = get_client(args) + num_deleted = client.delete_all_cached_images() + + if args.verbose: + print("Deleted %(num_deleted)s cached images" % + {'num_deleted': num_deleted}) + + return SUCCESS + + +@catch_error('delete the specified queued image') +@validate_input +def delete_queued_image(args): + """%(prog)s delete-queued-image <IMAGE_ID> [options] + + Deletes an image from the cache. + """ + image_id = args.command[1] + if (not args.force and + not user_confirm("Delete queued image %(image_id)s?" % + {'image_id': image_id}, default=False)): + return SUCCESS + + client = get_client(args) + client.delete_queued_image(image_id) + + if args.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(args): + """%(prog)s delete-all-queued-images [options] + + Remove all images from the cache queue. + """ + if (not args.force and + not user_confirm("Delete all queued images?", default=False)): + return SUCCESS + + client = get_client(args) + num_deleted = client.delete_all_queued_images() + + if args.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 + """ + # Generate auth_url based on identity_api_version + identity_version = env('OS_IDENTITY_API_VERSION', default='3') + auth_url = options.os_auth_url + if identity_version == '3' and "/v3" not in auth_url: + auth_url = auth_url + "/v3" + elif identity_version == '2' and "/v2" not in auth_url: + auth_url = auth_url + "/v2.0" + + user_domain_id = options.os_user_domain_id + if not user_domain_id: + user_domain_id = options.os_domain_id + project_domain_id = options.os_project_domain_id + if not user_domain_id: + project_domain_id = options.os_domain_id + + return glance.image_cache.client.get_client( + host=options.host, + port=options.port, + username=options.os_username, + password=options.os_password, + project=options.os_project_name, + user_domain_id=user_domain_id, + project_domain_id=project_domain_id, + auth_url=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 print_help(args): + """ + Print help specific to a command + """ + command = lookup_command(args.command[1]) + print(command.__doc__ % {'prog': os.path.basename(sys.argv[0])}) + + +def parse_args(parser): + """Set up the CLI and config-file options that may be + parsed and program commands. + + :param parser: The option parser + """ + parser.add_argument('command', default='help', nargs='+', + help='The command to execute') + parser.add_argument('-v', '--verbose', default=False, action="store_true", + help="Print more verbose output.") + parser.add_argument('-d', '--debug', default=False, action="store_true", + help="Print debugging output.") + parser.add_argument('-H', '--host', metavar="ADDRESS", default="0.0.0.0", + help="Address of Glance API host.") + parser.add_argument('-p', '--port', dest="port", metavar="PORT", + type=int, default=9292, + help="Port the Glance API host listens on.") + parser.add_argument('-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_argument('-f', '--force', dest="force", + default=False, action="store_true", + help="Prevent select actions from requesting " + "user confirmation.") + + parser.add_argument('--os-auth-token', + dest='os_auth_token', + default=env('OS_AUTH_TOKEN'), + help='Defaults to env[OS_AUTH_TOKEN].') + parser.add_argument('-A', '--os_auth_token', '--auth_token', + dest='os_auth_token', + help=argparse.SUPPRESS) + + parser.add_argument('--os-username', + dest='os_username', + default=env('OS_USERNAME'), + help='Defaults to env[OS_USERNAME].') + parser.add_argument('-I', '--os_username', + dest='os_username', + help=argparse.SUPPRESS) + + parser.add_argument('--os-password', + dest='os_password', + default=env('OS_PASSWORD'), + help='Defaults to env[OS_PASSWORD].') + parser.add_argument('-K', '--os_password', + dest='os_password', + help=argparse.SUPPRESS) + + parser.add_argument('--os-region-name', + dest='os_region_name', + default=env('OS_REGION_NAME'), + help='Defaults to env[OS_REGION_NAME].') + parser.add_argument('-R', '--os_region_name', + dest='os_region_name', + help=argparse.SUPPRESS) + + parser.add_argument('--os-project-id', + dest='os_project_id', + default=env('OS_PROJECT_ID'), + help='Defaults to env[OS_PROJECT_ID].') + parser.add_argument('--os_project_id', + dest='os_project_id', + help=argparse.SUPPRESS) + + parser.add_argument('--os-project-name', + dest='os_project_name', + default=env('OS_PROJECT_NAME'), + help='Defaults to env[OS_PROJECT_NAME].') + parser.add_argument('-T', '--os_project_name', + dest='os_project_name', + help=argparse.SUPPRESS) + + # arguments related user, project domain + parser.add_argument('--os-user-domain-id', + dest='os_user_domain_id', + default=env('OS_USER_DOMAIN_ID'), + help='Defaults to env[OS_USER_DOMAIN_ID].') + parser.add_argument('--os-project-domain-id', + dest='os_project_domain_id', + default=env('OS_PROJECT_DOMAIN_ID'), + help='Defaults to env[OS_PROJECT_DOMAIN_ID].') + parser.add_argument('--os-domain-id', + dest='os_domain_id', + default=env('OS_DOMAIN_ID', default='default'), + help='Defaults to env[OS_DOMAIN_ID].') + + parser.add_argument('--os-auth-url', + default=env('OS_AUTH_URL'), + help='Defaults to env[OS_AUTH_URL].') + parser.add_argument('-N', '--os_auth_url', + dest='os_auth_url', + help=argparse.SUPPRESS) + + parser.add_argument('-S', '--os_auth_strategy', dest="os_auth_strategy", + metavar="STRATEGY", + help="Authentication strategy (keystone or noauth).") + + version_string = version.cached_version_string() + parser.add_argument('--version', action='version', + version=version_string) + + return parser.parse_args() + + +CACHE_COMMANDS = collections.OrderedDict() +CACHE_COMMANDS['help'] = ( + print_help, 'Output help for one of the commands below') +CACHE_COMMANDS['list-cached'] = ( + list_cached, 'List all images currently cached') +CACHE_COMMANDS['list-queued'] = ( + list_queued, 'List all images currently queued for caching') +CACHE_COMMANDS['queue-image'] = ( + queue_image, 'Queue an image for caching') +CACHE_COMMANDS['delete-cached-image'] = ( + delete_cached_image, 'Purges an image from the cache') +CACHE_COMMANDS['delete-all-cached-images'] = ( + delete_all_cached_images, 'Removes all images from the cache') +CACHE_COMMANDS['delete-queued-image'] = ( + delete_queued_image, 'Deletes an image from the cache queue') +CACHE_COMMANDS['delete-all-queued-images'] = ( + delete_all_queued_images, 'Deletes all images from the cache queue') + + +def _format_command_help(): + """Formats the help string for subcommands.""" + help_msg = "Commands:\n\n" + + for command, info in CACHE_COMMANDS.items(): + if command == 'help': + command = 'help <command>' + help_msg += " %-28s%s\n\n" % (command, info[1]) + + return help_msg + + +def lookup_command(command_name): + try: + command = CACHE_COMMANDS[command_name] + return command[0] + except KeyError: + print('\nError: "%s" is not a valid command.\n' % command_name) + print(_format_command_help()) + sys.exit("Unknown command: %(cmd_name)s" % {'cmd_name': command_name}) + + +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(): + parser = argparse.ArgumentParser( + description=_format_command_help(), + formatter_class=argparse.RawDescriptionHelpFormatter) + args = parse_args(parser) + + if args.command[0] == 'help' and len(args.command) == 1: + parser.print_help() + return + + # Look up the command to run + command = lookup_command(args.command[0]) + + try: + start_time = time.time() + result = command(args) + end_time = time.time() + if args.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() diff --git a/glance/common/auth.py b/glance/common/auth.py index 2f9734754..ea76932b3 100644 --- a/glance/common/auth.py +++ b/glance/common/auth.py @@ -94,6 +94,11 @@ class KeystoneStrategy(BaseStrategy): if self.creds.get("tenant") is None: raise exception.MissingCredentialError(required='tenant') + # For v3 also check project is present + if self.creds['auth_url'].rstrip('/').endswith('v3'): + if self.creds.get("project") is None: + raise exception.MissingCredentialError(required='project') + def authenticate(self): """Authenticate with the Keystone service. @@ -113,10 +118,15 @@ class KeystoneStrategy(BaseStrategy): # If OS_AUTH_URL is missing a trailing slash add one if not auth_url.endswith('/'): auth_url += '/' + token_url = urlparse.urljoin(auth_url, "tokens") # 1. Check Keystone version is_v2 = auth_url.rstrip('/').endswith('v2.0') - if is_v2: + is_v3 = auth_url.rstrip('/').endswith('v3') + if is_v3: + token_url = urlparse.urljoin(auth_url, "auth/tokens") + self._v3_auth(token_url) + elif is_v2: self._v2_auth(token_url) else: self._v1_auth(token_url) @@ -186,6 +196,52 @@ class KeystoneStrategy(BaseStrategy): else: raise Exception(_('Unexpected response: %s') % resp.status) + def _v3_auth(self, token_url): + creds = { + "auth": { + "identity": { + "methods": ["password"], + "password": { + "user": { + "name": self.creds['username'], + "domain": {"id": self.creds['user_domain_id']}, + "password": self.creds['password'] + } + } + }, + "scope": { + "project": { + "name": self.creds['project'], + "domain": { + "id": self.creds['project_domain_id'] + } + } + } + } + } + + headers = {'Content-Type': 'application/json'} + req_body = jsonutils.dumps(creds) + + resp, resp_body = self._do_request( + token_url, 'POST', headers=headers, body=req_body) + resp_body = jsonutils.loads(resp_body) + + if resp.status == 201: + resp_auth = resp['x-subject-token'] + creds_region = self.creds.get('region') + if self.configure_via_auth: + endpoint = get_endpoint(resp_body['token']['catalog'], + endpoint_region=creds_region) + self.management_url = endpoint + self.auth_token = resp_auth + elif resp.status == 305: + raise exception.RedirectException(resp['location']) + elif resp.status == 400: + raise exception.AuthBadRequest(url=token_url) + elif resp.status == 401: + raise Exception(_('Unexpected response: %s') % resp.status) + def _v2_auth(self, token_url): creds = self.creds diff --git a/glance/image_cache/client.py b/glance/image_cache/client.py new file mode 100644 index 000000000..fdc815527 --- /dev/null +++ b/glance/image_cache/client.py @@ -0,0 +1,136 @@ +# Copyright 2018 RedHat Inc. +# 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. + +import os + +from oslo_serialization import jsonutils as json + +from glance.common import client as base_client +from glance.common import exception +from glance.i18n import _ + + +class CacheClient(base_client.BaseClient): + + DEFAULT_PORT = 9292 + DEFAULT_DOC_ROOT = '/v2' + + 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 get_cached_images(self, **kwargs): + """ + Returns a list of images stored in the image cache. + """ + res = self.do_request("GET", "/cached_images") + data = json.loads(res.read())['cached_images'] + return data + + def get_queued_images(self, **kwargs): + """ + Returns a list of images queued for caching + """ + res = self.do_request("GET", "/queued_images") + data = json.loads(res.read())['queued_images'] + return data + + def delete_all_cached_images(self): + """ + Delete all cached images + """ + res = self.do_request("DELETE", "/cached_images") + data = json.loads(res.read()) + num_deleted = data['num_deleted'] + return num_deleted + + def queue_image_for_caching(self, image_id): + """ + Queue an image for prefetching into cache + """ + self.do_request("PUT", "/queued_images/%s" % image_id) + return True + + def delete_queued_image(self, image_id): + """ + Delete a specified image from the cache queue + """ + self.do_request("DELETE", "/queued_images/%s" % image_id) + return True + + def delete_all_queued_images(self): + """ + 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_client(host, port=None, timeout=None, use_ssl=False, username=None, + password=None, project=None, + user_domain_id=None, project_domain_id=None, + auth_url=None, auth_strategy=None, + auth_token=None, region=None, insecure=False): + """ + Returns a new client Glance client object based on common kwargs. + If an option isn't specified falls back to common environment variable + defaults. + """ + + if auth_url or os.getenv('OS_AUTH_URL'): + force_strategy = 'keystone' + else: + force_strategy = None + + creds = { + 'username': username or + os.getenv('OS_AUTH_USER', os.getenv('OS_USERNAME')), + 'password': password or + os.getenv('OS_AUTH_KEY', os.getenv('OS_PASSWORD')), + 'project': project or + os.getenv('OS_AUTH_PROJECT', os.getenv('OS_PROJECT_NAME')), + 'auth_url': auth_url or + os.getenv('OS_AUTH_URL'), + 'strategy': force_strategy or + auth_strategy or + os.getenv('OS_AUTH_STRATEGY', 'noauth'), + 'region': region or + os.getenv('OS_REGION_NAME'), + 'user_domain_id': user_domain_id or os.getenv( + 'OS_USER_DOMAIN_ID', 'default'), + 'project_domain_id': project_domain_id or os.getenv( + 'OS_PROJECT_DOMAIN_ID', 'default') + } + + if creds['strategy'] == 'keystone' and not creds['auth_url']: + msg = _("--os_auth_url option or OS_AUTH_URL environment variable " + "required when keystone authentication strategy is enabled\n") + raise exception.ClientConfigurationError(msg) + + return CacheClient( + host=host, + port=port, + timeout=timeout, + use_ssl=use_ssl, + auth_token=auth_token or + os.getenv('OS_TOKEN'), + creds=creds, + insecure=insecure, + configure_via_auth=False) diff --git a/glance/locale/de/LC_MESSAGES/glance.po b/glance/locale/de/LC_MESSAGES/glance.po index 93201b3de..b81e69115 100644 --- a/glance/locale/de/LC_MESSAGES/glance.po +++ b/glance/locale/de/LC_MESSAGES/glance.po @@ -13,7 +13,7 @@ msgid "" msgstr "" "Project-Id-Version: glance VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2018-08-09 04:23+0000\n" +"POT-Creation-Date: 2019-03-08 07:05+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -99,6 +99,13 @@ msgstr "%s ist bereits gestoppt" msgid "%s is stopped" msgstr "%s ist gestoppt" +msgid "" +"--os_auth_url option or OS_AUTH_URL environment variable required when " +"keystone authentication strategy is enabled\n" +msgstr "" +"Option --os_auth_url oder Umgebungsvariable OS_AUTH_URL erforderlich, wenn " +"die Keystone-Authentifizierungsstrategie aktiviert ist\n" + msgid "A body is not expected with this request." msgstr "Es wird kein Body bei dieser Anforderung erwartet. " @@ -1737,12 +1744,6 @@ msgstr "Schema kann nicht geladen werden: %(reason)s" msgid "Unable to locate paste config file for %s." msgstr "Konfigurationsdatei zum Einfügen für %s konnte nicht gefunden werden." -#, python-format -msgid "Unable to upload duplicate image data for image%(image_id)s: %(error)s" -msgstr "" -"Hochladen von doppelten Abbilddaten für Abbild %(image_id)s nicht möglich: " -"%(error)s" - msgid "Unexpected body type. Expected list/dict." msgstr "Unerwarteter Hauptteiltyp. Erwartet wurde list/dict." diff --git a/glance/locale/en_GB/LC_MESSAGES/glance.po b/glance/locale/en_GB/LC_MESSAGES/glance.po index 8dc94a45d..0f2b4dd16 100644 --- a/glance/locale/en_GB/LC_MESSAGES/glance.po +++ b/glance/locale/en_GB/LC_MESSAGES/glance.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: glance VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2018-11-20 06:34+0000\n" +"POT-Creation-Date: 2019-03-08 07:05+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -738,43 +738,6 @@ msgstr "" msgid "" "\n" -"Desired output format for image conversion plugin.\n" -"\n" -"Provide a valid image format to which the conversion plugin\n" -"will convert the image before storing it to the back-end.\n" -"\n" -"Note, if the Image Conversion plugin for image import is defined, users\n" -"should only upload disk formats that are supported by `quemu-img` otherwise\n" -"the conversion and import will fail.\n" -"\n" -"Possible values:\n" -" * qcow2\n" -" * raw\n" -" * vdmk\n" -"\n" -"Related Options:\n" -" * disk_formats\n" -msgstr "" -"\n" -"Desired output format for image conversion plugin.\n" -"\n" -"Provide a valid image format to which the conversion plugin\n" -"will convert the image before storing it to the back-end.\n" -"\n" -"Note, if the Image Conversion plugin for image import is defined, users\n" -"should only upload disk formats that are supported by `quemu-img` otherwise\n" -"the conversion and import will fail.\n" -"\n" -"Possible values:\n" -" * qcow2\n" -" * raw\n" -" * vdmk\n" -"\n" -"Related Options:\n" -" * disk_formats\n" - -msgid "" -"\n" "Dictionary contains metadata properties to be injected in image.\n" "\n" "Possible values:\n" @@ -2417,63 +2380,6 @@ msgstr "" msgid "" "\n" -"Show all image locations when returning an image.\n" -"\n" -"This configuration option indicates whether to show all the image\n" -"locations when returning image details to the user. When multiple\n" -"image locations exist for an image, the locations are ordered based\n" -"on the location strategy indicated by the configuration opt\n" -"``location_strategy``. The image locations are shown under the\n" -"image property ``locations``.\n" -"\n" -"NOTES:\n" -" * Revealing image locations can present a GRAVE SECURITY RISK as\n" -" image locations can sometimes include credentials. Hence, this\n" -" is set to ``False`` by default. Set this to ``True`` with\n" -" EXTREME CAUTION and ONLY IF you know what you are doing!\n" -" * If an operator wishes to avoid showing any image location(s)\n" -" to the user, then both this option and\n" -" ``show_image_direct_url`` MUST be set to ``False``.\n" -"\n" -"Possible values:\n" -" * True\n" -" * False\n" -"\n" -"Related options:\n" -" * show_image_direct_url\n" -" * location_strategy\n" -"\n" -msgstr "" -"\n" -"Show all image locations when returning an image.\n" -"\n" -"This configuration option indicates whether to show all the image\n" -"locations when returning image details to the user. When multiple\n" -"image locations exist for an image, the locations are ordered based\n" -"on the location strategy indicated by the configuration opt\n" -"``location_strategy``. The image locations are shown under the\n" -"image property ``locations``.\n" -"\n" -"NOTES:\n" -" * Revealing image locations can present a GRAVE SECURITY RISK as\n" -" image locations can sometimes include credentials. Hence, this\n" -" is set to ``False`` by default. Set this to ``True`` with\n" -" EXTREME CAUTION and ONLY IF you know what you are doing!\n" -" * If an operator wishes to avoid showing any image location(s)\n" -" to the user, then both this option and\n" -" ``show_image_direct_url`` MUST be set to ``False``.\n" -"\n" -"Possible values:\n" -" * True\n" -" * False\n" -"\n" -"Related options:\n" -" * show_image_direct_url\n" -" * location_strategy\n" -"\n" - -msgid "" -"\n" "Show direct image location when returning an image.\n" "\n" "This configuration option indicates whether to show the direct image\n" @@ -3455,6 +3361,13 @@ msgstr "'glance-direct' method is not available at this site." msgid "'node_staging_uri' is not set correctly. Could not load staging store." msgstr "'node_staging_uri' is not set correctly. Could not load staging store." +msgid "" +"--os_auth_url option or OS_AUTH_URL environment variable required when " +"keystone authentication strategy is enabled\n" +msgstr "" +"--os_auth_url option or OS_AUTH_URL environment variable required when " +"Keystone authentication strategy is enabled\n" + msgid "A body is not expected with this request." msgstr "A body is not expected with this request." @@ -4365,13 +4278,6 @@ msgstr "It's not allowed to replace locations if image status is %s." msgid "It's not allowed to update locations if locations are invisible." msgstr "It's not allowed to update locations if locations are invisible." -msgid "" -"Key:Value pair of store identifier and store type. In case of multiple " -"backends should be separatedusing comma." -msgstr "" -"Key:Value pair of store identifier and store type. In case of multiple " -"backends should be separated using a comma." - msgid "List of strings related to the image" msgstr "List of strings related to the image" @@ -5270,15 +5176,6 @@ msgstr "" "deleted after reaching the time based on their expires_at property." msgid "" -"This option will be removed in the Pike release or later because the same " -"functionality can be achieved with greater granularity by using policies. " -"Please see the Newton release notes for more information." -msgstr "" -"This option will be removed in the Pike release or later because the same " -"functionality can be achieved with greater granularity by using policies. " -"Please see the Newton release notes for more information." - -msgid "" "Time in hours for which a task lives after, either succeeding or failing" msgstr "" "Time in hours for which a task lives after, either succeeding or failing" @@ -5374,10 +5271,6 @@ msgstr "" "Unable to place database under Alembic's migration control. Unknown database " "state, can't proceed further." -#, python-format -msgid "Unable to upload duplicate image data for image%(image_id)s: %(error)s" -msgstr "Unable to upload duplicate image data for image%(image_id)s: %(error)s" - msgid "Unexpected body type. Expected list/dict." msgstr "Unexpected body type. Expected list/dict." diff --git a/glance/locale/es/LC_MESSAGES/glance.po b/glance/locale/es/LC_MESSAGES/glance.po index 9c18c72c1..0595d833f 100644 --- a/glance/locale/es/LC_MESSAGES/glance.po +++ b/glance/locale/es/LC_MESSAGES/glance.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: glance VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2018-08-09 04:23+0000\n" +"POT-Creation-Date: 2019-03-08 07:05+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -99,6 +99,13 @@ msgstr "%s ya se detuvo" msgid "%s is stopped" msgstr "%s se ha detenido" +msgid "" +"--os_auth_url option or OS_AUTH_URL environment variable required when " +"keystone authentication strategy is enabled\n" +msgstr "" +"Se necesita la opción --os_auth_url ovariable de ambiente OS_AUTH_URL cuando " +"la estrategia de autenticación keystone está habilitada\n" + msgid "A body is not expected with this request." msgstr "No se espera un cuerpo en esta solicitud." @@ -1709,10 +1716,6 @@ msgstr "No se ha podido cargar el esquema: %(reason)s" msgid "Unable to locate paste config file for %s." msgstr "No se puede ubicar el fichero de configuración de pegado para %s." -#, python-format -msgid "Unable to upload duplicate image data for image%(image_id)s: %(error)s" -msgstr "No se puede cargar datos de imagen duplicada %(image_id)s: %(error)s" - msgid "Unexpected body type. Expected list/dict." msgstr "Tipo de cuerpo inesperado. Se esperaba list/dict." diff --git a/glance/locale/fr/LC_MESSAGES/glance.po b/glance/locale/fr/LC_MESSAGES/glance.po index 8ce1cd86f..b5e2ac7ee 100644 --- a/glance/locale/fr/LC_MESSAGES/glance.po +++ b/glance/locale/fr/LC_MESSAGES/glance.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: glance VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2018-08-09 04:23+0000\n" +"POT-Creation-Date: 2019-03-08 07:05+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -100,6 +100,13 @@ msgstr "%s est déjà stoppé" msgid "%s is stopped" msgstr "%s est arrêté" +msgid "" +"--os_auth_url option or OS_AUTH_URL environment variable required when " +"keystone authentication strategy is enabled\n" +msgstr "" +"Option --os_auth_url ou variable d'environnement OS_AUTH_URL requise lorsque " +"la stratégie d'authentification keystone est activée\n" + msgid "A body is not expected with this request." msgstr "Un corps n'est pas attendu avec cette demande." @@ -1737,12 +1744,6 @@ msgid "Unable to locate paste config file for %s." msgstr "" "Impossible de localiser le fichier de configuration du collage pour %s." -#, python-format -msgid "Unable to upload duplicate image data for image%(image_id)s: %(error)s" -msgstr "" -"Impossible de télécharger des données image en double pour l'image " -"%(image_id)s : %(error)s" - msgid "Unexpected body type. Expected list/dict." msgstr "Type de corps inattendu. Type attendu : list/dict." diff --git a/glance/locale/it/LC_MESSAGES/glance.po b/glance/locale/it/LC_MESSAGES/glance.po index f7b4389a8..cd5981c46 100644 --- a/glance/locale/it/LC_MESSAGES/glance.po +++ b/glance/locale/it/LC_MESSAGES/glance.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: glance VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2018-08-09 04:23+0000\n" +"POT-Creation-Date: 2019-03-08 07:05+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -95,6 +95,13 @@ msgstr "%s è già stato arrestato" msgid "%s is stopped" msgstr "%s è stato arrestato" +msgid "" +"--os_auth_url option or OS_AUTH_URL environment variable required when " +"keystone authentication strategy is enabled\n" +msgstr "" +"l'opzione --os_auth_url o la variabile d'ambiente OS_AUTH_URL sono " +"obbligatori quando è abilitato il modo di autenticazione keystone\n" + msgid "A body is not expected with this request." msgstr "Un corpo non è previsto con questa richiesta." @@ -1722,12 +1729,6 @@ msgstr "Impossibile caricare lo schema: %(reason)s" msgid "Unable to locate paste config file for %s." msgstr "Impossibile individuare il file di configurazione paste per %s." -#, python-format -msgid "Unable to upload duplicate image data for image%(image_id)s: %(error)s" -msgstr "" -"Impossibile caricare i dati dell'immagine duplicata per l'immagine " -"%(image_id)s: %(error)s" - msgid "Unexpected body type. Expected list/dict." msgstr "Tipo di corpo imprevisto. Elenco/dizionario previsto." diff --git a/glance/locale/ja/LC_MESSAGES/glance.po b/glance/locale/ja/LC_MESSAGES/glance.po index c585db278..d3b35eae0 100644 --- a/glance/locale/ja/LC_MESSAGES/glance.po +++ b/glance/locale/ja/LC_MESSAGES/glance.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: glance VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2018-08-09 04:23+0000\n" +"POT-Creation-Date: 2019-03-08 07:05+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -100,6 +100,13 @@ msgstr "" "'node_staging_uri' が正しく設定されていません。ステージングストアをロードでき" "ませんでした。" +msgid "" +"--os_auth_url option or OS_AUTH_URL environment variable required when " +"keystone authentication strategy is enabled\n" +msgstr "" +"keystone 認証戦略が有効な場合は、--os_auth_url オプションまたはOS_AUTH_URL 環" +"境変数が必要です\n" + msgid "A body is not expected with this request." msgstr "この要求では本文は予期されません。" @@ -1849,11 +1856,6 @@ msgstr "スキーマをロードできません: %(reason)s" msgid "Unable to locate paste config file for %s." msgstr "%s の paste 設定ファイルが見つかりません。" -#, python-format -msgid "Unable to upload duplicate image data for image%(image_id)s: %(error)s" -msgstr "" -"イメージ %(image_id)s の重複イメージデータはアップロードできません: %(error)s" - msgid "Unexpected body type. Expected list/dict." msgstr "予期しない本文タイプ。予期されたのはリストまたは辞書です。" diff --git a/glance/locale/ko_KR/LC_MESSAGES/glance.po b/glance/locale/ko_KR/LC_MESSAGES/glance.po index 60a660294..d4d378461 100644 --- a/glance/locale/ko_KR/LC_MESSAGES/glance.po +++ b/glance/locale/ko_KR/LC_MESSAGES/glance.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: glance VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2018-08-09 04:23+0000\n" +"POT-Creation-Date: 2019-03-08 07:05+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -94,6 +94,13 @@ msgstr "%s이(가) 이미 중지되었습니다." msgid "%s is stopped" msgstr "%s이(가) 중지됨" +msgid "" +"--os_auth_url option or OS_AUTH_URL environment variable required when " +"keystone authentication strategy is enabled\n" +msgstr "" +"키스톤 인증 전략이 사용될 경우 --os_auth_url 옵션 또는 OS_AUTH_URL 환경 변수" +"가 필요합니다.\n" + msgid "A body is not expected with this request." msgstr "이 요청에는 본문이 없어야 합니다." @@ -1636,11 +1643,6 @@ msgstr "스키마를 로드할 수 없음: %(reason)s" msgid "Unable to locate paste config file for %s." msgstr "%s에 대한 붙여넣기 구성 파일을 찾을 수 없습니다." -#, python-format -msgid "Unable to upload duplicate image data for image%(image_id)s: %(error)s" -msgstr "" -"이미지 %(image_id)s에 대한 중복 이미지 데이터를 업로드할 수 없음: %(error)s" - msgid "Unexpected body type. Expected list/dict." msgstr "예기치않은 본문 타입. list/dict를 예상합니다." diff --git a/glance/locale/pt_BR/LC_MESSAGES/glance.po b/glance/locale/pt_BR/LC_MESSAGES/glance.po index bff556759..754ee685c 100644 --- a/glance/locale/pt_BR/LC_MESSAGES/glance.po +++ b/glance/locale/pt_BR/LC_MESSAGES/glance.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: glance VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2018-08-09 04:23+0000\n" +"POT-Creation-Date: 2019-03-08 07:05+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -97,6 +97,13 @@ msgstr "%s já está parado" msgid "%s is stopped" msgstr "%s está parado" +msgid "" +"--os_auth_url option or OS_AUTH_URL environment variable required when " +"keystone authentication strategy is enabled\n" +msgstr "" +"opção --os_auth_url ou variável de ambiente OS_AUTH_URL requerida quando " +"estratégia de autenticação keystone está ativada\n" + msgid "A body is not expected with this request." msgstr "Um corpo não é esperado com essa solicitação." @@ -1697,12 +1704,6 @@ msgstr "Não é possível carregar o esquema: %(reason)s" msgid "Unable to locate paste config file for %s." msgstr "Impossível localizar o arquivo de configuração de colagem para %s." -#, python-format -msgid "Unable to upload duplicate image data for image%(image_id)s: %(error)s" -msgstr "" -"Não é possível fazer upload de dados de imagem duplicados para a imagem " -"%(image_id)s: %(error)s" - msgid "Unexpected body type. Expected list/dict." msgstr "Tipo de corpo inesperado. Lista/dicionário esperados." diff --git a/glance/locale/ru/LC_MESSAGES/glance.po b/glance/locale/ru/LC_MESSAGES/glance.po index 47be06368..9553a90d0 100644 --- a/glance/locale/ru/LC_MESSAGES/glance.po +++ b/glance/locale/ru/LC_MESSAGES/glance.po @@ -3,7 +3,7 @@ msgid "" msgstr "" "Project-Id-Version: glance VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2018-08-09 04:23+0000\n" +"POT-Creation-Date: 2019-03-08 07:05+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -89,6 +89,13 @@ msgstr "%s уже остановлен" msgid "%s is stopped" msgstr "%s остановлен" +msgid "" +"--os_auth_url option or OS_AUTH_URL environment variable required when " +"keystone authentication strategy is enabled\n" +msgstr "" +"Опция --os_auth_url или переменная среды OS_AUTH_URL требуется, если " +"включена стратегия идентификации Keystone\n" + msgid "A body is not expected with this request." msgstr "В этом запросе не должно быть тела." @@ -1664,11 +1671,6 @@ msgstr "Не удалось загрузить схему: %(reason)s" msgid "Unable to locate paste config file for %s." msgstr "Не удается найти/вставить файл конфигурации для %s." -#, python-format -msgid "Unable to upload duplicate image data for image%(image_id)s: %(error)s" -msgstr "" -"Не удается загрузить данные для дубликата образа %(image_id)s: %(error)s" - msgid "Unexpected body type. Expected list/dict." msgstr "Непредвиденный тип тела. Ожидался список или словарь." diff --git a/glance/locale/tr_TR/LC_MESSAGES/glance.po b/glance/locale/tr_TR/LC_MESSAGES/glance.po index 48b13221c..f8ad086ee 100644 --- a/glance/locale/tr_TR/LC_MESSAGES/glance.po +++ b/glance/locale/tr_TR/LC_MESSAGES/glance.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: glance VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2018-08-09 04:23+0000\n" +"POT-Creation-Date: 2019-03-08 07:05+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -89,6 +89,13 @@ msgstr "%s zaten durdurulmuş" msgid "%s is stopped" msgstr "%s durduruldu" +msgid "" +"--os_auth_url option or OS_AUTH_URL environment variable required when " +"keystone authentication strategy is enabled\n" +msgstr "" +"--os_auth_url seçeneği ya da OS_AUTH_URL ortam değişkeni, keystone kimlik " +"doğrulama stratejisi etkinken gereklidir\n" + #, python-format msgid "" "A metadata definition object with name=%(object_name)s already exists in " @@ -1490,10 +1497,6 @@ msgid "Unable to locate paste config file for %s." msgstr "%s için yapıştırma yapılandırma dosyası yerleştirilemedi." #, python-format -msgid "Unable to upload duplicate image data for image%(image_id)s: %(error)s" -msgstr "%(image_id)s imajı için çift imaj verisi yüklenemedi: %(error)s" - -#, python-format msgid "Unexpected response: %s" msgstr "Beklenmeyen yanıt: %s" diff --git a/glance/locale/zh_CN/LC_MESSAGES/glance.po b/glance/locale/zh_CN/LC_MESSAGES/glance.po index 92243b270..a8a62880e 100644 --- a/glance/locale/zh_CN/LC_MESSAGES/glance.po +++ b/glance/locale/zh_CN/LC_MESSAGES/glance.po @@ -15,7 +15,7 @@ msgid "" msgstr "" "Project-Id-Version: glance VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2018-08-09 04:23+0000\n" +"POT-Creation-Date: 2019-03-08 07:05+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -95,6 +95,13 @@ msgstr "%s 已停止" msgid "%s is stopped" msgstr "%s 已停止" +msgid "" +"--os_auth_url option or OS_AUTH_URL environment variable required when " +"keystone authentication strategy is enabled\n" +msgstr "" +"当启用了 keystone 认证策略时,需要 --os_auth_url 选项或 OS_AUTH_URL 环境变" +"量\n" + msgid "A body is not expected with this request." msgstr "此请求不应有主体。" @@ -1618,10 +1625,6 @@ msgstr "无法装入模式:%(reason)s" msgid "Unable to locate paste config file for %s." msgstr "对于 %s,找不到粘贴配置文件。" -#, python-format -msgid "Unable to upload duplicate image data for image%(image_id)s: %(error)s" -msgstr "无法为镜像%(image_id)s上传重复的数据: %(error)s" - msgid "Unexpected body type. Expected list/dict." msgstr "意外主体类型。应该为 list/dict。" diff --git a/glance/locale/zh_TW/LC_MESSAGES/glance.po b/glance/locale/zh_TW/LC_MESSAGES/glance.po index da6dc386c..f32fb1953 100644 --- a/glance/locale/zh_TW/LC_MESSAGES/glance.po +++ b/glance/locale/zh_TW/LC_MESSAGES/glance.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: glance VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2018-08-09 04:23+0000\n" +"POT-Creation-Date: 2019-03-08 07:05+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -89,6 +89,12 @@ msgstr "已停止 %s" msgid "%s is stopped" msgstr "%s 已停止" +msgid "" +"--os_auth_url option or OS_AUTH_URL environment variable required when " +"keystone authentication strategy is enabled\n" +msgstr "" +"--os_auth_url 選項或 OS_AUTH_URL 環境變數(啟用 Keystone 鑑別策略時需要)\n" + msgid "A body is not expected with this request." msgstr "此要求預期不含內文。" @@ -1562,10 +1568,6 @@ msgstr "無法載入綱目:%(reason)s" msgid "Unable to locate paste config file for %s." msgstr "找不到 %s 的 paste 配置檔。" -#, python-format -msgid "Unable to upload duplicate image data for image%(image_id)s: %(error)s" -msgstr "無法上傳映像檔 %(image_id)s 的重複映像檔資料:%(error)s" - msgid "Unexpected body type. Expected list/dict." msgstr "非預期的內文類型。預期為清單/字典。" diff --git a/glance/tests/unit/api/middleware/test_cache_manage.py b/glance/tests/unit/api/middleware/test_cache_manage.py index d66aacb87..04751f578 100644 --- a/glance/tests/unit/api/middleware/test_cache_manage.py +++ b/glance/tests/unit/api/middleware/test_cache_manage.py @@ -10,8 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. -from glance.api import cached_images from glance.api.middleware import cache_manage +from glance.api.v2 import cached_images import glance.common.config import glance.common.wsgi import glance.image_cache @@ -44,14 +44,14 @@ class TestCacheManageFilter(test_utils.BaseTestCase): # check self.assertIsNone(resource) - @mock.patch.object(cached_images.Controller, "get_cached_images") + @mock.patch.object(cached_images.CacheController, "get_cached_images") def test_get_cached_images(self, mock_get_cached_images): # setup mock_get_cached_images.return_value = self.stub_value # prepare - request = webob.Request.blank("/v1/cached_images") + request = webob.Request.blank("/v2/cached_images") # call resource = self.cache_manage_filter.process_request(request) @@ -61,14 +61,14 @@ class TestCacheManageFilter(test_utils.BaseTestCase): self.assertEqual('"' + self.stub_value + '"', resource.body.decode('utf-8')) - @mock.patch.object(cached_images.Controller, "delete_cached_image") + @mock.patch.object(cached_images.CacheController, "delete_cached_image") def test_delete_cached_image(self, mock_delete_cached_image): # setup mock_delete_cached_image.return_value = self.stub_value # prepare - request = webob.Request.blank("/v1/cached_images/" + self.image_id, + request = webob.Request.blank("/v2/cached_images/" + self.image_id, environ={'REQUEST_METHOD': "DELETE"}) # call @@ -80,14 +80,14 @@ class TestCacheManageFilter(test_utils.BaseTestCase): self.assertEqual('"' + self.stub_value + '"', resource.body.decode('utf-8')) - @mock.patch.object(cached_images.Controller, "delete_cached_images") + @mock.patch.object(cached_images.CacheController, "delete_cached_images") def test_delete_cached_images(self, mock_delete_cached_images): # setup mock_delete_cached_images.return_value = self.stub_value # prepare - request = webob.Request.blank("/v1/cached_images", + request = webob.Request.blank("/v2/cached_images", environ={'REQUEST_METHOD': "DELETE"}) # call @@ -98,14 +98,14 @@ class TestCacheManageFilter(test_utils.BaseTestCase): self.assertEqual('"' + self.stub_value + '"', resource.body.decode('utf-8')) - @mock.patch.object(cached_images.Controller, "queue_image") + @mock.patch.object(cached_images.CacheController, "queue_image") def test_put_queued_image(self, mock_queue_image): # setup mock_queue_image.return_value = self.stub_value # prepare - request = webob.Request.blank("/v1/queued_images/" + self.image_id, + request = webob.Request.blank("/v2/queued_images/" + self.image_id, environ={'REQUEST_METHOD': "PUT"}) # call @@ -116,14 +116,14 @@ class TestCacheManageFilter(test_utils.BaseTestCase): self.assertEqual('"' + self.stub_value + '"', resource.body.decode('utf-8')) - @mock.patch.object(cached_images.Controller, "get_queued_images") + @mock.patch.object(cached_images.CacheController, "get_queued_images") def test_get_queued_images(self, mock_get_queued_images): # setup mock_get_queued_images.return_value = self.stub_value # prepare - request = webob.Request.blank("/v1/queued_images") + request = webob.Request.blank("/v2/queued_images") # call resource = self.cache_manage_filter.process_request(request) @@ -133,14 +133,14 @@ class TestCacheManageFilter(test_utils.BaseTestCase): self.assertEqual('"' + self.stub_value + '"', resource.body.decode('utf-8')) - @mock.patch.object(cached_images.Controller, "delete_queued_image") + @mock.patch.object(cached_images.CacheController, "delete_queued_image") def test_delete_queued_image(self, mock_delete_queued_image): # setup mock_delete_queued_image.return_value = self.stub_value # prepare - request = webob.Request.blank("/v1/queued_images/" + self.image_id, + request = webob.Request.blank("/v2/queued_images/" + self.image_id, environ={'REQUEST_METHOD': 'DELETE'}) # call @@ -152,14 +152,14 @@ class TestCacheManageFilter(test_utils.BaseTestCase): self.assertEqual('"' + self.stub_value + '"', resource.body.decode('utf-8')) - @mock.patch.object(cached_images.Controller, "delete_queued_images") + @mock.patch.object(cached_images.CacheController, "delete_queued_images") def test_delete_queued_images(self, mock_delete_queued_images): # setup mock_delete_queued_images.return_value = self.stub_value # prepare - request = webob.Request.blank("/v1/queued_images", + request = webob.Request.blank("/v2/queued_images", environ={'REQUEST_METHOD': 'DELETE'}) # call diff --git a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po index c68324623..382a06902 100644 --- a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po +++ b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po @@ -4,11 +4,11 @@ msgid "" msgstr "" "Project-Id-Version: Glance Release Notes\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-11-20 06:33+0000\n" +"POT-Creation-Date: 2019-03-08 07:04+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2018-12-04 05:26+0000\n" +"PO-Revision-Date: 2018-11-07 06:12+0000\n" "Last-Translator: Andi Chandler <andi@gowling.com>\n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" @@ -117,9 +117,6 @@ msgstr "17.0.0" msgid "17.0.0.0rc1" msgstr "17.0.0.0rc1" -msgid "17.0.0.0rc1-40" -msgstr "17.0.0.0rc1-40" - msgid "" "A new interoperable image import method, ``web-download`` is introduced." msgstr "" |