Remove Images API v1 entry points
This change removes option to configure Images API v1 This change removes Images API v1 endpoints from the router This change removes all v1 tests This change removes the v1 dependant glance-cache-manage command This change does not remove all v1 codebase. Further cleanup and decoupling will be needed. Change-Id: Ia086230cc8c92f7b7dfd5b001923110d5bc55d4d
This commit is contained in:
parent
f3496591ae
commit
3dde3204d5
|
@ -20,8 +20,6 @@ CONF = cfg.CONF
|
|||
|
||||
|
||||
def root_app_factory(loader, global_conf, **local_conf):
|
||||
if not CONF.enable_v1_api and '/v1' in local_conf:
|
||||
del local_conf['/v1']
|
||||
if not CONF.enable_v2_api and '/v2' in local_conf:
|
||||
del local_conf['/v2']
|
||||
return paste.urlmap.urlmap_factory(loader, global_conf, **local_conf)
|
||||
|
|
|
@ -31,7 +31,6 @@ import webob
|
|||
|
||||
from glance.api.common import size_checked_iter
|
||||
from glance.api import policy
|
||||
from glance.api.v1 import images
|
||||
from glance.common import exception
|
||||
from glance.common import utils
|
||||
from glance.common import wsgi
|
||||
|
@ -55,7 +54,6 @@ class CacheFilter(wsgi.Middleware):
|
|||
|
||||
def __init__(self, app):
|
||||
self.cache = image_cache.ImageCache()
|
||||
self.serializer = images.ImageSerializer()
|
||||
self.policy = policy.Enforcer()
|
||||
LOG.info(_LI("Initialized image cache middleware"))
|
||||
super(CacheFilter, self).__init__(app)
|
||||
|
@ -214,21 +212,6 @@ class CacheFilter(wsgi.Middleware):
|
|||
else:
|
||||
return (image_id, method, version)
|
||||
|
||||
def _process_v1_request(self, request, image_id, image_iterator,
|
||||
image_meta):
|
||||
# Don't display location
|
||||
if 'location' in image_meta:
|
||||
del image_meta['location']
|
||||
image_meta.pop('location_data', None)
|
||||
self._verify_metadata(image_meta)
|
||||
|
||||
response = webob.Response(request=request)
|
||||
raw_response = {
|
||||
'image_iterator': image_iterator,
|
||||
'image_meta': image_meta,
|
||||
}
|
||||
return self.serializer.show(response, raw_response)
|
||||
|
||||
def _process_v2_request(self, request, image_id, image_iterator,
|
||||
image_meta):
|
||||
# We do some contortions to get the image_metadata so
|
||||
|
|
|
@ -72,10 +72,6 @@ class VersionNegotiationFilter(wsgi.Middleware):
|
|||
|
||||
def _get_allowed_versions(self):
|
||||
allowed_versions = {}
|
||||
if CONF.enable_v1_api:
|
||||
allowed_versions['v1'] = 1
|
||||
allowed_versions['v1.0'] = 1
|
||||
allowed_versions['v1.1'] = 1
|
||||
if CONF.enable_v2_api:
|
||||
allowed_versions['v2'] = 2
|
||||
allowed_versions['v2.0'] = 2
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,248 +0,0 @@
|
|||
# Copyright 2012 OpenStack Foundation.
|
||||
# Copyright 2013 NTT corp.
|
||||
# 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import encodeutils
|
||||
import webob.exc
|
||||
|
||||
from glance.api import policy
|
||||
from glance.api.v1 import controller
|
||||
from glance.common import exception
|
||||
from glance.common import utils
|
||||
from glance.common import wsgi
|
||||
from glance.i18n import _
|
||||
import glance.registry.client.v1.api as registry
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('image_member_quota', 'glance.common.config')
|
||||
|
||||
|
||||
class Controller(controller.BaseController):
|
||||
|
||||
def __init__(self):
|
||||
self.policy = policy.Enforcer()
|
||||
|
||||
def _check_can_access_image_members(self, context):
|
||||
if context.owner is None and not context.is_admin:
|
||||
raise webob.exc.HTTPUnauthorized(_("No authenticated user"))
|
||||
|
||||
def _enforce(self, req, action):
|
||||
"""Authorize an action against our policies"""
|
||||
try:
|
||||
self.policy.enforce(req.context, action, {})
|
||||
except exception.Forbidden:
|
||||
LOG.debug("User not permitted to perform '%s' action", action)
|
||||
raise webob.exc.HTTPForbidden()
|
||||
|
||||
def _raise_404_if_image_deleted(self, req, image_id):
|
||||
image = self.get_image_meta_or_404(req, image_id)
|
||||
if image['status'] == 'deleted':
|
||||
msg = _("Image with identifier %s has been deleted.") % image_id
|
||||
raise webob.exc.HTTPNotFound(msg)
|
||||
|
||||
def index(self, req, image_id):
|
||||
"""
|
||||
Return a list of dictionaries indicating the members of the
|
||||
image, i.e., those tenants the image is shared with.
|
||||
|
||||
:param req: the Request object coming from the wsgi layer
|
||||
:param image_id: The opaque image identifier
|
||||
:returns: The response body is a mapping of the following form
|
||||
|
||||
::
|
||||
|
||||
{'members': [
|
||||
{'member_id': <MEMBER>,
|
||||
'can_share': <SHARE_PERMISSION>, ...}, ...
|
||||
]}
|
||||
|
||||
"""
|
||||
self._enforce(req, 'get_members')
|
||||
self._raise_404_if_image_deleted(req, image_id)
|
||||
|
||||
try:
|
||||
members = registry.get_image_members(req.context, image_id)
|
||||
except exception.NotFound:
|
||||
msg = _("Image with identifier %s not found") % image_id
|
||||
LOG.warn(msg)
|
||||
raise webob.exc.HTTPNotFound(msg)
|
||||
except exception.Forbidden:
|
||||
msg = _("Unauthorized image access")
|
||||
LOG.warn(msg)
|
||||
raise webob.exc.HTTPForbidden(msg)
|
||||
return dict(members=members)
|
||||
|
||||
@utils.mutating
|
||||
def delete(self, req, image_id, id):
|
||||
"""
|
||||
Removes a membership from the image.
|
||||
"""
|
||||
self._check_can_access_image_members(req.context)
|
||||
self._enforce(req, 'delete_member')
|
||||
self._raise_404_if_image_deleted(req, image_id)
|
||||
|
||||
try:
|
||||
registry.delete_member(req.context, image_id, id)
|
||||
self._update_store_acls(req, image_id)
|
||||
except exception.NotFound as e:
|
||||
LOG.debug(encodeutils.exception_to_unicode(e))
|
||||
raise webob.exc.HTTPNotFound(explanation=e.msg)
|
||||
except exception.Forbidden as e:
|
||||
LOG.debug("User not permitted to remove membership from image "
|
||||
"'%s'", image_id)
|
||||
raise webob.exc.HTTPNotFound(explanation=e.msg)
|
||||
|
||||
return webob.exc.HTTPNoContent()
|
||||
|
||||
def default(self, req, image_id, id, body=None):
|
||||
"""This will cover the missing 'show' and 'create' actions"""
|
||||
raise webob.exc.HTTPMethodNotAllowed()
|
||||
|
||||
def _enforce_image_member_quota(self, req, attempted):
|
||||
if CONF.image_member_quota < 0:
|
||||
# If value is negative, allow unlimited number of members
|
||||
return
|
||||
|
||||
maximum = CONF.image_member_quota
|
||||
if attempted > maximum:
|
||||
msg = _("The limit has been exceeded on the number of allowed "
|
||||
"image members for this image. Attempted: %(attempted)s, "
|
||||
"Maximum: %(maximum)s") % {'attempted': attempted,
|
||||
'maximum': maximum}
|
||||
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
|
||||
request=req)
|
||||
|
||||
@utils.mutating
|
||||
def update(self, req, image_id, id, body=None):
|
||||
"""
|
||||
Adds a membership to the image, or updates an existing one.
|
||||
If a body is present, it is a dict with the following format
|
||||
|
||||
::
|
||||
|
||||
{'member': {
|
||||
'can_share': [True|False]
|
||||
}}
|
||||
|
||||
If `can_share` is provided, the member's ability to share is
|
||||
set accordingly. If it is not provided, existing memberships
|
||||
remain unchanged and new memberships default to False.
|
||||
"""
|
||||
self._check_can_access_image_members(req.context)
|
||||
self._enforce(req, 'modify_member')
|
||||
self._raise_404_if_image_deleted(req, image_id)
|
||||
|
||||
new_number_of_members = len(registry.get_image_members(req.context,
|
||||
image_id)) + 1
|
||||
self._enforce_image_member_quota(req, new_number_of_members)
|
||||
|
||||
# Figure out can_share
|
||||
can_share = None
|
||||
if body and 'member' in body and 'can_share' in body['member']:
|
||||
can_share = bool(body['member']['can_share'])
|
||||
try:
|
||||
registry.add_member(req.context, image_id, id, can_share)
|
||||
self._update_store_acls(req, image_id)
|
||||
except exception.Invalid as e:
|
||||
LOG.debug(encodeutils.exception_to_unicode(e))
|
||||
raise webob.exc.HTTPBadRequest(explanation=e.msg)
|
||||
except exception.NotFound as e:
|
||||
LOG.debug(encodeutils.exception_to_unicode(e))
|
||||
raise webob.exc.HTTPNotFound(explanation=e.msg)
|
||||
except exception.Forbidden as e:
|
||||
LOG.debug(encodeutils.exception_to_unicode(e))
|
||||
raise webob.exc.HTTPNotFound(explanation=e.msg)
|
||||
|
||||
return webob.exc.HTTPNoContent()
|
||||
|
||||
@utils.mutating
|
||||
def update_all(self, req, image_id, body):
|
||||
"""
|
||||
Replaces the members of the image with those specified in the
|
||||
body. The body is a dict with the following format
|
||||
|
||||
::
|
||||
|
||||
{'memberships': [
|
||||
{'member_id': <MEMBER_ID>,
|
||||
['can_share': [True|False]]}, ...
|
||||
]}
|
||||
|
||||
"""
|
||||
self._check_can_access_image_members(req.context)
|
||||
self._enforce(req, 'modify_member')
|
||||
self._raise_404_if_image_deleted(req, image_id)
|
||||
|
||||
memberships = body.get('memberships')
|
||||
if memberships:
|
||||
new_number_of_members = len(body['memberships'])
|
||||
self._enforce_image_member_quota(req, new_number_of_members)
|
||||
|
||||
try:
|
||||
registry.replace_members(req.context, image_id, body)
|
||||
self._update_store_acls(req, image_id)
|
||||
except exception.Invalid as e:
|
||||
LOG.debug(encodeutils.exception_to_unicode(e))
|
||||
raise webob.exc.HTTPBadRequest(explanation=e.msg)
|
||||
except exception.NotFound as e:
|
||||
LOG.debug(encodeutils.exception_to_unicode(e))
|
||||
raise webob.exc.HTTPNotFound(explanation=e.msg)
|
||||
except exception.Forbidden as e:
|
||||
LOG.debug(encodeutils.exception_to_unicode(e))
|
||||
raise webob.exc.HTTPNotFound(explanation=e.msg)
|
||||
|
||||
return webob.exc.HTTPNoContent()
|
||||
|
||||
def index_shared_images(self, req, id):
|
||||
"""
|
||||
Retrieves list of image memberships for the given member.
|
||||
|
||||
:param req: the Request object coming from the wsgi layer
|
||||
:param id: the opaque member identifier
|
||||
:returns: The response body is a mapping of the following form
|
||||
|
||||
::
|
||||
|
||||
{'shared_images': [
|
||||
{'image_id': <IMAGE>,
|
||||
'can_share': <SHARE_PERMISSION>, ...}, ...
|
||||
]}
|
||||
|
||||
"""
|
||||
try:
|
||||
members = registry.get_member_images(req.context, id)
|
||||
except exception.NotFound as e:
|
||||
LOG.debug(encodeutils.exception_to_unicode(e))
|
||||
raise webob.exc.HTTPNotFound(explanation=e.msg)
|
||||
except exception.Forbidden as e:
|
||||
LOG.debug(encodeutils.exception_to_unicode(e))
|
||||
raise webob.exc.HTTPForbidden(explanation=e.msg)
|
||||
return dict(shared_images=members)
|
||||
|
||||
def _update_store_acls(self, req, image_id):
|
||||
image_meta = self.get_image_meta_or_404(req, image_id)
|
||||
location_uri = image_meta.get('location')
|
||||
public = image_meta.get('is_public')
|
||||
self.update_store_acls(req, image_id, location_uri, public)
|
||||
|
||||
|
||||
def create_resource():
|
||||
"""Image members resource factory method"""
|
||||
deserializer = wsgi.JSONRequestDeserializer()
|
||||
serializer = wsgi.JSONResponseSerializer()
|
||||
return wsgi.Resource(Controller(), deserializer, serializer)
|
|
@ -14,8 +14,6 @@
|
|||
# under the License.
|
||||
|
||||
|
||||
from glance.api.v1 import images
|
||||
from glance.api.v1 import members
|
||||
from glance.common import wsgi
|
||||
|
||||
|
||||
|
@ -26,84 +24,8 @@ class API(wsgi.Router):
|
|||
def __init__(self, mapper):
|
||||
reject_method_resource = wsgi.Resource(wsgi.RejectMethodController())
|
||||
|
||||
images_resource = images.create_resource()
|
||||
|
||||
mapper.connect("/",
|
||||
controller=images_resource,
|
||||
action="index")
|
||||
mapper.connect("/images",
|
||||
controller=images_resource,
|
||||
action='index',
|
||||
conditions={'method': ['GET']})
|
||||
mapper.connect("/images",
|
||||
controller=images_resource,
|
||||
action='create',
|
||||
conditions={'method': ['POST']})
|
||||
mapper.connect("/images",
|
||||
controller=reject_method_resource,
|
||||
action='reject',
|
||||
allowed_methods='GET, POST')
|
||||
mapper.connect("/images/detail",
|
||||
controller=images_resource,
|
||||
action='detail',
|
||||
conditions={'method': ['GET', 'HEAD']})
|
||||
mapper.connect("/images/detail",
|
||||
controller=reject_method_resource,
|
||||
action='reject',
|
||||
allowed_methods='GET, HEAD')
|
||||
mapper.connect("/images/{id}",
|
||||
controller=images_resource,
|
||||
action="meta",
|
||||
conditions=dict(method=["HEAD"]))
|
||||
mapper.connect("/images/{id}",
|
||||
controller=images_resource,
|
||||
action="show",
|
||||
conditions=dict(method=["GET"]))
|
||||
mapper.connect("/images/{id}",
|
||||
controller=images_resource,
|
||||
action="update",
|
||||
conditions=dict(method=["PUT"]))
|
||||
mapper.connect("/images/{id}",
|
||||
controller=images_resource,
|
||||
action="delete",
|
||||
conditions=dict(method=["DELETE"]))
|
||||
mapper.connect("/images/{id}",
|
||||
controller=reject_method_resource,
|
||||
action='reject',
|
||||
allowed_methods='GET, HEAD, PUT, DELETE')
|
||||
|
||||
members_resource = members.create_resource()
|
||||
|
||||
mapper.connect("/images/{image_id}/members",
|
||||
controller=members_resource,
|
||||
action="index",
|
||||
conditions={'method': ['GET']})
|
||||
mapper.connect("/images/{image_id}/members",
|
||||
controller=members_resource,
|
||||
action="update_all",
|
||||
conditions=dict(method=["PUT"]))
|
||||
mapper.connect("/images/{image_id}/members",
|
||||
controller=reject_method_resource,
|
||||
action='reject',
|
||||
allowed_methods='GET, PUT')
|
||||
mapper.connect("/images/{image_id}/members/{id}",
|
||||
controller=members_resource,
|
||||
action="show",
|
||||
conditions={'method': ['GET']})
|
||||
mapper.connect("/images/{image_id}/members/{id}",
|
||||
controller=members_resource,
|
||||
action="update",
|
||||
conditions={'method': ['PUT']})
|
||||
mapper.connect("/images/{image_id}/members/{id}",
|
||||
controller=members_resource,
|
||||
action="delete",
|
||||
conditions={'method': ['DELETE']})
|
||||
mapper.connect("/images/{image_id}/members/{id}",
|
||||
controller=reject_method_resource,
|
||||
action='reject',
|
||||
allowed_methods='GET, PUT, DELETE')
|
||||
mapper.connect("/shared-images/{id}",
|
||||
controller=members_resource,
|
||||
action="index_shared_images")
|
||||
action="reject")
|
||||
|
||||
super(API, self).__init__(mapper)
|
||||
|
|
|
@ -20,7 +20,7 @@ from six.moves import http_client
|
|||
import webob.dec
|
||||
|
||||
from glance.common import wsgi
|
||||
from glance.i18n import _, _LW
|
||||
from glance.i18n import _
|
||||
|
||||
|
||||
versions_opts = [
|
||||
|
@ -82,20 +82,6 @@ class Controller(object):
|
|||
build_version_object(2.1, 'v2', 'SUPPORTED'),
|
||||
build_version_object(2.0, 'v2', 'SUPPORTED'),
|
||||
])
|
||||
if CONF.enable_v1_api:
|
||||
LOG.warn(_LW('The Images (Glance) v1 API is deprecated and will '
|
||||
'be removed on or after the Pike release, following '
|
||||
'the standard OpenStack deprecation policy. '
|
||||
'Currently, the solution is to set '
|
||||
'enable_v1_api=False and enable_v2_api=True in your '
|
||||
'glance-api.conf file. Once those options are '
|
||||
'removed from the code, Images (Glance) v2 API will '
|
||||
'be switched on by default and will be the only '
|
||||
'option to deploy and use.'))
|
||||
version_objs.extend([
|
||||
build_version_object(1.1, 'v1', 'DEPRECATED'),
|
||||
build_version_object(1.0, 'v1', 'DEPRECATED'),
|
||||
])
|
||||
|
||||
status = explicit and http_client.OK or http_client.MULTIPLE_CHOICES
|
||||
response = webob.Response(request=req,
|
||||
|
|
|
@ -1,490 +0,0 @@
|
|||
#!/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 argparse
|
||||
import collections
|
||||
import datetime
|
||||
import functools
|
||||
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(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')
|
||||
def queue_image(args):
|
||||
"""%(prog)s queue-image <IMAGE_ID> [options]
|
||||
|
||||
Queues an image for caching.
|
||||
"""
|
||||
if len(args.command) == 2:
|
||||
image_id = args.command[1]
|
||||
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 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')
|
||||
def delete_cached_image(args):
|
||||
"""%(prog)s delete-cached-image <IMAGE_ID> [options]
|
||||
|
||||
Deletes an image from the cache.
|
||||
"""
|
||||
if len(args.command) == 2:
|
||||
image_id = args.command[1]
|
||||
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 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')
|
||||
def delete_queued_image(args):
|
||||
"""%(prog)s delete-queued-image <IMAGE_ID> [options]
|
||||
|
||||
Deletes an image from the cache.
|
||||
"""
|
||||
if len(args.command) == 2:
|
||||
image_id = args.command[1]
|
||||
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 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
|
||||
"""
|
||||
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 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-tenant-id',
|
||||
dest='os_tenant_id',
|
||||
default=env('OS_TENANT_ID'),
|
||||
help='Defaults to env[OS_TENANT_ID].')
|
||||
parser.add_argument('--os_tenant_id',
|
||||
dest='os_tenant_id',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-tenant-name',
|
||||
dest='os_tenant_name',
|
||||
default=env('OS_TENANT_NAME'),
|
||||
help='Defaults to env[OS_TENANT_NAME].')
|
||||
parser.add_argument('-T', '--os_tenant_name',
|
||||
dest='os_tenant_name',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
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()
|
|
@ -454,50 +454,6 @@ Possible values:
|
|||
Related options:
|
||||
* None
|
||||
|
||||
""")),
|
||||
# NOTE(nikhil): Even though deprecated, the configuration option
|
||||
# ``enable_v1_api`` is set to True by default on purpose. Having it enabled
|
||||
# helps the projects that haven't been able to fully move to v2 yet by
|
||||
# keeping the devstack setup to use glance v1 as well. We need to switch it
|
||||
# to False by default soon after Newton is cut so that we can identify the
|
||||
# projects that haven't moved to v2 yet and start having some interesting
|
||||
# conversations with them. Switching to False in Newton may result into
|
||||
# destabilizing the gate and affect the release.
|
||||
cfg.BoolOpt('enable_v1_api',
|
||||
default=True,
|
||||
deprecated_reason=_DEPRECATE_GLANCE_V1_MSG,
|
||||
deprecated_since='Newton',
|
||||
help=_("""
|
||||
Deploy the v1 OpenStack Images API.
|
||||
|
||||
When this option is set to ``True``, Glance service will respond to
|
||||
requests on registered endpoints conforming to the v1 OpenStack
|
||||
Images API.
|
||||
|
||||
NOTES:
|
||||
* If this option is enabled, then ``enable_v1_registry`` must
|
||||
also be set to ``True`` to enable mandatory usage of Registry
|
||||
service with v1 API.
|
||||
|
||||
* If this option is disabled, then the ``enable_v1_registry``
|
||||
option, which is enabled by default, is also recommended
|
||||
to be disabled.
|
||||
|
||||
* This option is separate from ``enable_v2_api``, both v1 and v2
|
||||
OpenStack Images API can be deployed independent of each
|
||||
other.
|
||||
|
||||
* If deploying only the v2 Images API, this option, which is
|
||||
enabled by default, should be disabled.
|
||||
|
||||
Possible values:
|
||||
* True
|
||||
* False
|
||||
|
||||
Related options:
|
||||
* enable_v1_registry
|
||||
* enable_v2_api
|
||||
|
||||
""")),
|
||||
cfg.BoolOpt('enable_v2_api',
|
||||
default=True,
|
||||
|
@ -523,20 +479,12 @@ NOTES:
|
|||
option, which is enabled by default, is also recommended
|
||||
to be disabled.
|
||||
|
||||
* This option is separate from ``enable_v1_api``, both v1 and v2
|
||||
OpenStack Images API can be deployed independent of each
|
||||
other.
|
||||
|
||||
* If deploying only the v1 Images API, this option, which is
|
||||
enabled by default, should be disabled.
|
||||
|
||||
Possible values:
|
||||
* True
|
||||
* False
|
||||
|
||||
Related options:
|
||||
* enable_v2_registry
|
||||
* enable_v1_api
|
||||
|
||||
""")),
|
||||
cfg.BoolOpt('enable_v1_registry',
|
||||
|
@ -544,25 +492,7 @@ Related options:
|
|||
deprecated_reason=_DEPRECATE_GLANCE_V1_MSG,
|
||||
deprecated_since='Newton',
|
||||
help=_("""
|
||||
Deploy the v1 API Registry service.
|
||||
|
||||
When this option is set to ``True``, the Registry service
|
||||
will be enabled in Glance for v1 API requests.
|
||||
|
||||
NOTES:
|
||||
* Use of Registry is mandatory in v1 API, so this option must
|
||||
be set to ``True`` if the ``enable_v1_api`` option is enabled.
|
||||
|
||||
* If deploying only the v2 OpenStack Images API, this option,
|
||||
which is enabled by default, should be disabled.
|
||||
|
||||
Possible values:
|
||||
* True
|
||||
* False
|
||||
|
||||
Related options:
|
||||
* enable_v1_api
|
||||
|
||||
DEPRECATED FOR REMOVAL
|
||||
""")),
|
||||
cfg.BoolOpt('enable_v2_registry',
|
||||
default=True,
|
||||
|
|
|
@ -27,6 +27,7 @@ from glance import scrubber
|
|||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('use_user_token', 'glance.registry.client')
|
||||
|
||||
RESTRICTED_URI_SCHEMAS = frozenset(['file', 'filesystem', 'swift+config'])
|
||||
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
# Copyright 2012 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.
|
||||
|
||||
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 = '/v1'
|
||||
|
||||
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, tenant=None,
|
||||
auth_url=None, auth_strategy=None,
|
||||
auth_token=None, region=None,
|
||||
is_silent_upload=False, 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')),
|
||||
'tenant': tenant or
|
||||
os.getenv('OS_AUTH_TENANT', os.getenv('OS_TENANT_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'),
|
||||
}
|
||||
|
||||
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)
|
|
@ -74,9 +74,7 @@ class Server(object):
|
|||
self.show_image_direct_url = False
|
||||
self.show_multiple_locations = False
|
||||
self.property_protection_file = ''
|
||||
self.enable_v1_api = True
|
||||
self.enable_v2_api = True
|
||||
self.enable_v1_registry = True
|
||||
self.enable_v2_registry = True
|
||||
self.needs_database = False
|
||||
self.log_file = None
|
||||
|
@ -346,7 +344,6 @@ sql_connection = %(sql_connection)s
|
|||
show_image_direct_url = %(show_image_direct_url)s
|
||||
show_multiple_locations = %(show_multiple_locations)s
|
||||
user_storage_quota = %(user_storage_quota)s
|
||||
enable_v1_api = %(enable_v1_api)s
|
||||
enable_v2_api = %(enable_v2_api)s
|
||||
lock_path = %(lock_path)s
|
||||
property_protection_file = %(property_protection_file)s
|
||||
|
|
|
@ -78,8 +78,10 @@ class TestScrubber(functional.FunctionalTest):
|
|||
scrubs them
|
||||
"""
|
||||
self.cleanup()
|
||||
kwargs = self.__dict__.copy()
|
||||
kwargs['use_user_token'] = True
|
||||
self.start_servers(delayed_delete=True, daemon=True,
|
||||
metadata_encryption_key='')
|
||||
metadata_encryption_key='', **kwargs)
|
||||
path = "http://%s:%d/v2/images" % ("127.0.0.1", self.api_port)
|
||||
response, content = self._send_create_image_http_request(path)
|
||||
self.assertEqual(http_client.CREATED, response.status)
|
||||
|
@ -112,8 +114,10 @@ class TestScrubber(functional.FunctionalTest):
|
|||
daemon mode
|
||||
"""
|
||||
self.cleanup()
|
||||
kwargs = self.__dict__.copy()
|
||||
kwargs['use_user_token'] = True
|
||||
self.start_servers(delayed_delete=True, daemon=False,
|
||||
metadata_encryption_key='')
|
||||
metadata_encryption_key='', **kwargs)
|
||||
path = "http://%s:%d/v2/images" % ("127.0.0.1", self.api_port)
|
||||
response, content = self._send_create_image_http_request(path)
|
||||
self.assertEqual(http_client.CREATED, response.status)
|
||||
|
@ -159,8 +163,10 @@ class TestScrubber(functional.FunctionalTest):
|
|||
|
||||
# Start servers.
|
||||
self.cleanup()
|
||||
kwargs = self.__dict__.copy()
|
||||
kwargs['use_user_token'] = True
|
||||
self.start_servers(delayed_delete=True, daemon=False,
|
||||
default_store='file')
|
||||
default_store='file', **kwargs)
|
||||
|
||||
# Check that we are using a file backend.
|
||||
self.assertEqual(self.api_server.default_store, 'file')
|
||||
|
@ -235,8 +241,10 @@ class TestScrubber(functional.FunctionalTest):
|
|||
|
||||
def test_scrubber_restore_image(self):
|
||||
self.cleanup()
|
||||
kwargs = self.__dict__.copy()
|
||||
kwargs['use_user_token'] = True
|
||||
self.start_servers(delayed_delete=True, daemon=False,
|
||||
metadata_encryption_key='')
|
||||
metadata_encryption_key='', **kwargs)
|
||||
path = "http://%s:%d/v2/images" % ("127.0.0.1", self.api_port)
|
||||
response, content = self._send_create_image_http_request(path)
|
||||
self.assertEqual(http_client.CREATED, response.status)
|
||||
|
|
|
@ -22,25 +22,6 @@ from six.moves import http_client
|
|||
|
||||
from glance.tests import functional
|
||||
|
||||
# TODO(rosmaita): all the EXPERIMENTAL stuff in this file can be ripped out
|
||||
# when v2.6 becomes CURRENT in Queens
|
||||
|
||||
|
||||
def _generate_v1_versions(url):
|
||||
v1_versions = {'versions': [
|
||||
{
|
||||
'id': 'v1.1',
|
||||
'status': 'DEPRECATED',
|
||||
'links': [{'rel': 'self', 'href': url % '1'}],
|
||||
},
|
||||
{
|
||||
'id': 'v1.0',
|
||||
'status': 'DEPRECATED',
|
||||
'links': [{'rel': 'self', 'href': url % '1'}],
|
||||
},
|
||||
]}
|
||||
return v1_versions
|
||||
|
||||
|
||||
def _generate_v2_versions(url):
|
||||
version_list = []
|
||||
|
@ -86,9 +67,8 @@ def _generate_v2_versions(url):
|
|||
|
||||
|
||||
def _generate_all_versions(url):
|
||||
v1 = _generate_v1_versions(url)
|
||||
v2 = _generate_v2_versions(url)
|
||||
all_versions = {'versions': v2['versions'] + v1['versions']}
|
||||
all_versions = {'versions': v2['versions']}
|
||||
return all_versions
|
||||
|
||||
|
||||
|
@ -96,7 +76,6 @@ class TestApiVersions(functional.FunctionalTest):
|
|||
|
||||
def test_version_configurations(self):
|
||||
"""Test that versioning is handled properly through all channels"""
|
||||
# v1 and v2 api enabled
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
url = 'http://127.0.0.1:%d/v%%s/' % self.api_port
|
||||
|
@ -111,7 +90,6 @@ class TestApiVersions(functional.FunctionalTest):
|
|||
self.assertEqual(versions, content)
|
||||
|
||||
def test_v2_api_configuration(self):
|
||||
self.api_server.enable_v1_api = False
|
||||
self.api_server.enable_v2_api = True
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
|
@ -126,22 +104,6 @@ class TestApiVersions(functional.FunctionalTest):
|
|||
content = jsonutils.loads(content_json.decode())
|
||||
self.assertEqual(versions, content)
|
||||
|
||||
def test_v1_api_configuration(self):
|
||||
self.api_server.enable_v1_api = True
|
||||
self.api_server.enable_v2_api = False
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
url = 'http://127.0.0.1:%d/v%%s/' % self.api_port
|
||||
versions = _generate_v1_versions(url)
|
||||
|
||||
# Verify version choices returned.
|
||||
path = 'http://%s:%d' % ('127.0.0.1', self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content_json = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
|
||||
content = jsonutils.loads(content_json.decode())
|
||||
self.assertEqual(versions, content)
|
||||
|
||||
|
||||
class TestApiPaths(functional.FunctionalTest):
|
||||
def setUp(self):
|
||||
|
@ -165,26 +127,6 @@ class TestApiPaths(functional.FunctionalTest):
|
|||
content = jsonutils.loads(content_json.decode())
|
||||
self.assertEqual(self.versions, content)
|
||||
|
||||
def test_get_images_path(self):
|
||||
"""Assert GET /images with `no Accept:` header.
|
||||
Verify version choices returned.
|
||||
"""
|
||||
path = 'http://%s:%d/images' % ('127.0.0.1', self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content_json = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
|
||||
content = jsonutils.loads(content_json.decode())
|
||||
self.assertEqual(self.versions, content)
|
||||
|
||||
def test_get_v1_images_path(self):
|
||||
"""GET /v1/images with `no Accept:` header.
|
||||
Verify empty images list returned.
|
||||
"""
|
||||
path = 'http://%s:%d/v1/images' % ('127.0.0.1', self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
def test_get_root_path_with_unknown_header(self):
|
||||
"""Assert GET / with Accept: unknown header
|
||||
Verify version choices returned. Verify message in API log about
|
||||
|
@ -198,49 +140,6 @@ class TestApiPaths(functional.FunctionalTest):
|
|||
content = jsonutils.loads(content_json.decode())
|
||||
self.assertEqual(self.versions, content)
|
||||
|
||||
def test_get_root_path_with_openstack_header(self):
|
||||
"""Assert GET / with an Accept: application/vnd.openstack.images-v1
|
||||
Verify empty image list returned
|
||||
"""
|
||||
path = 'http://%s:%d/images' % ('127.0.0.1', self.api_port)
|
||||
http = httplib2.Http()
|
||||
headers = {'Accept': 'application/vnd.openstack.images-v1'}
|
||||
response, content = http.request(path, 'GET', headers=headers)
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
self.assertEqual(self.images_json, content.decode())
|
||||
|
||||
def test_get_images_path_with_openstack_header(self):
|
||||
"""Assert GET /images with a
|
||||
`Accept: application/vnd.openstack.compute-v1` header.
|
||||
Verify version choices returned. Verify message in API log
|
||||
about unknown accept header.
|
||||
"""
|
||||
path = 'http://%s:%d/images' % ('127.0.0.1', self.api_port)
|
||||
http = httplib2.Http()
|
||||
headers = {'Accept': 'application/vnd.openstack.compute-v1'}
|
||||
response, content_json = http.request(path, 'GET', headers=headers)
|
||||
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
|
||||
content = jsonutils.loads(content_json.decode())
|
||||
self.assertEqual(self.versions, content)
|
||||
|
||||
def test_get_v10_images_path(self):
|
||||
"""Assert GET /v1.0/images with no Accept: header
|
||||
Verify version choices returned
|
||||
"""
|
||||
path = 'http://%s:%d/v1.a/images' % ('127.0.0.1', self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
|
||||
|
||||
def test_get_v1a_images_path(self):
|
||||
"""Assert GET /v1.a/images with no Accept: header
|
||||
Verify version choices returned
|
||||
"""
|
||||
path = 'http://%s:%d/v1.a/images' % ('127.0.0.1', self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
|
||||
|
||||
def test_get_va1_images_path(self):
|
||||
"""Assert GET /va.1/images with no Accept: header
|
||||
Verify version choices returned
|
||||
|
@ -263,28 +162,6 @@ class TestApiPaths(functional.FunctionalTest):
|
|||
content = jsonutils.loads(content_json.decode())
|
||||
self.assertEqual(self.versions, content)
|
||||
|
||||
def test_get_versions_path_with_openstack_header(self):
|
||||
"""Assert GET /versions with the
|
||||
`Accept: application/vnd.openstack.images-v1` header.
|
||||
Verify version choices returned.
|
||||
"""
|
||||
path = 'http://%s:%d/versions' % ('127.0.0.1', self.api_port)
|
||||
http = httplib2.Http()
|
||||
headers = {'Accept': 'application/vnd.openstack.images-v1'}
|
||||
response, content_json = http.request(path, 'GET', headers=headers)
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
content = jsonutils.loads(content_json.decode())
|
||||
self.assertEqual(self.versions, content)
|
||||
|
||||
def test_get_v1_versions_path(self):
|
||||
"""Assert GET /v1/versions with `no Accept:` header
|
||||
Verify 404 returned
|
||||
"""
|
||||
path = 'http://%s:%d/v1/versions' % ('127.0.0.1', self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.NOT_FOUND, response.status)
|
||||
|
||||
def test_get_versions_choices(self):
|
||||
"""Verify version choices returned"""
|
||||
path = 'http://%s:%d/v10' % ('127.0.0.1', self.api_port)
|
||||
|
@ -293,28 +170,3 @@ class TestApiPaths(functional.FunctionalTest):
|
|||
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
|
||||
content = jsonutils.loads(content_json.decode())
|
||||
self.assertEqual(self.versions, content)
|
||||
|
||||
def test_get_images_path_with_openstack_v2_header(self):
|
||||
"""Assert GET /images with a
|
||||
`Accept: application/vnd.openstack.compute-v2` header.
|
||||
Verify version choices returned. Verify message in API log
|
||||
about unknown version in accept header.
|
||||
"""
|
||||
path = 'http://%s:%d/images' % ('127.0.0.1', self.api_port)
|
||||
http = httplib2.Http()
|
||||
headers = {'Accept': 'application/vnd.openstack.images-v10'}
|
||||
response, content_json = http.request(path, 'GET', headers=headers)
|
||||
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
|
||||
content = jsonutils.loads(content_json.decode())
|
||||
self.assertEqual(self.versions, content)
|
||||
|
||||
def test_get_v12_images_path(self):
|
||||
"""Assert GET /v1.2/images with `no Accept:` header
|
||||
Verify version choices returned
|
||||
"""
|
||||
path = 'http://%s:%d/v1.2/images' % ('127.0.0.1', self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content_json = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.MULTIPLE_CHOICES, response.status)
|
||||
content = jsonutils.loads(content_json.decode())
|
||||
self.assertEqual(self.versions, content)
|
||||
|
|
|
@ -1,358 +0,0 @@
|
|||
# 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.
|
||||
|
||||
"""Functional test case that utilizes the bin/glance-cache-manage CLI tool"""
|
||||
|
||||
import datetime
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
|
||||
import httplib2
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import units
|
||||
from six.moves import http_client
|
||||
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
|
||||
from six.moves import range
|
||||
|
||||
from glance.tests import functional
|
||||
from glance.tests.utils import execute
|
||||
from glance.tests.utils import minimal_headers
|
||||
|
||||
FIVE_KB = 5 * units.Ki
|
||||
|
||||
|
||||
class TestBinGlanceCacheManage(functional.FunctionalTest):
|
||||
"""Functional tests for the bin/glance CLI tool"""
|
||||
|
||||
def setUp(self):
|
||||
self.image_cache_driver = "sqlite"
|
||||
|
||||
super(TestBinGlanceCacheManage, self).setUp()
|
||||
|
||||
self.api_server.deployment_flavor = "cachemanagement"
|
||||
|
||||
# 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'
|
||||
os.environ['OS_AUTH_URL'] = ''
|
||||
|
||||
def add_image(self, name):
|
||||
"""
|
||||
Adds an image with supplied name and returns the newly-created
|
||||
image identifier.
|
||||
"""
|
||||
image_data = b"*" * FIVE_KB
|
||||
headers = minimal_headers(name)
|
||||
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'POST', headers=headers,
|
||||
body=image_data)
|
||||
self.assertEqual(http_client.CREATED, response.status)
|
||||
data = jsonutils.loads(content)
|
||||
self.assertEqual(hashlib.md5(image_data).hexdigest(),
|
||||
data['image']['checksum'])
|
||||
self.assertEqual(FIVE_KB, data['image']['size'])
|
||||
self.assertEqual(name, data['image']['name'])
|
||||
self.assertTrue(data['image']['is_public'])
|
||||
return data['image']['id']
|
||||
|
||||
def is_image_cached(self, image_id):
|
||||
"""
|
||||
Return True if supplied image ID is cached, False otherwise
|
||||
"""
|
||||
exe_cmd = '%s -m glance.cmd.cache_manage' % sys.executable
|
||||
cmd = "%s --port=%d list-cached" % (exe_cmd, self.api_port)
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
out = out.decode('utf-8')
|
||||
return image_id in out
|
||||
|
||||
def iso_date(self, image_id):
|
||||
"""
|
||||
Return True if supplied image ID is cached, False otherwise
|
||||
"""
|
||||
exe_cmd = '%s -m glance.cmd.cache_manage' % sys.executable
|
||||
cmd = "%s --port=%d list-cached" % (exe_cmd, self.api_port)
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
out = out.decode('utf-8')
|
||||
|
||||
return datetime.datetime.utcnow().strftime("%Y-%m-%d") in out
|
||||
|
||||
def test_no_cache_enabled(self):
|
||||
"""
|
||||
Test that cache index command works
|
||||
"""
|
||||
self.cleanup()
|
||||
self.api_server.deployment_flavor = ''
|
||||
self.start_servers() # Not passing in cache_manage in pipeline...
|
||||
|
||||
api_port = self.api_port
|
||||
|
||||
# Verify decent error message returned
|
||||
exe_cmd = '%s -m glance.cmd.cache_manage' % sys.executable
|
||||
cmd = "%s --port=%d list-cached" % (exe_cmd, api_port)
|
||||
|
||||
exitcode, out, err = execute(cmd, raise_error=False)
|
||||
|
||||
self.assertEqual(1, exitcode)
|
||||
self.assertIn(b'Cache management middleware not enabled on host',
|
||||
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
|
||||
|
||||
# Verify no cached images
|
||||
exe_cmd = '%s -m glance.cmd.cache_manage' % sys.executable
|
||||
cmd = "%s --port=%d list-cached" % (exe_cmd, api_port)
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertIn(b'No cached images', out.strip())
|
||||
|
||||
ids = {}
|
||||
|
||||
# Add a few images and cache the second one of them
|
||||
# by GETing the image...
|
||||
for x in range(4):
|
||||
ids[x] = self.add_image("Image%s" % x)
|
||||
|
||||
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", api_port,
|
||||
ids[1])
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
self.assertTrue(self.is_image_cached(ids[1]),
|
||||
"%s is not cached." % ids[1])
|
||||
|
||||
self.assertTrue(self.iso_date(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
|
||||
|
||||
# Verify no cached images
|
||||
exe_cmd = '%s -m glance.cmd.cache_manage' % sys.executable
|
||||
cmd = "%s --port=%d list-cached" % (exe_cmd, api_port)
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertIn(b'No cached images', out.strip())
|
||||
|
||||
# Verify no queued images
|
||||
cmd = "%s --port=%d list-queued" % (exe_cmd, api_port)
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertIn(b'No queued images', out.strip())
|
||||
|
||||
ids = {}
|
||||
|
||||
# Add a few images and cache the second one of them
|
||||
# by GETing the image...
|
||||
for x in range(4):
|
||||
ids[x] = self.add_image("Image%s" % x)
|
||||
|
||||
# Queue second image and then cache it
|
||||
cmd = "%s --port=%d --force queue-image %s" % (
|
||||
exe_cmd, api_port, ids[1])
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
|
||||
# Verify queued second image
|
||||
cmd = "%s --port=%d list-queued" % (exe_cmd, api_port)
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
out = out.decode('utf-8')
|
||||
self.assertIn(ids[1], 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.registry_server.bind_port,
|
||||
'lock_path': self.test_dir,
|
||||
'log_file': os.path.join(self.test_dir, 'cache.log'),
|
||||
'metadata_encryption_key': "012345678901234567890123456789ab",
|
||||
'filesystem_store_datadir': self.test_dir
|
||||
}
|
||||
with open(cache_config_filepath, 'w') as cache_file:
|
||||
cache_file.write("""[DEFAULT]
|
||||
debug = True
|
||||
lock_path = %(lock_path)s
|
||||
image_cache_dir = %(image_cache_dir)s
|
||||
image_cache_driver = %(image_cache_driver)s
|
||||
registry_host = 127.0.0.1
|
||||
registry_port = %(registry_port)s
|
||||
metadata_encryption_key = %(metadata_encryption_key)s
|
||||
log_file = %(log_file)s
|
||||
|
||||
[glance_store]
|
||||
filesystem_store_datadir=%(filesystem_store_datadir)s
|
||||
""" % cache_file_options)
|
||||
|
||||
cmd = ("%s -m glance.cmd.cache_prefetcher --config-file %s" %
|
||||
(sys.executable, cache_config_filepath))
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertEqual(b'', out.strip(), out)
|
||||
|
||||
# Verify no queued images
|
||||
cmd = "%s --port=%d list-queued" % (exe_cmd, api_port)
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertIn(b'No queued images', out.strip())
|
||||
|
||||
# Verify second image now cached
|
||||
cmd = "%s --port=%d list-cached" % (exe_cmd, api_port)
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
out = out.decode('utf-8')
|
||||
self.assertIn(ids[1], out, 'Image %s was not cached!' % ids[1])
|
||||
|
||||
# Queue third image and then delete it from queue
|
||||
cmd = "%s --port=%d --force queue-image %s" % (
|
||||
exe_cmd, api_port, ids[2])
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
|
||||
# Verify queued third image
|
||||
cmd = "%s --port=%d list-queued" % (exe_cmd, api_port)
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
out = out.decode('utf-8')
|
||||
self.assertIn(ids[2], out, 'Image %s was not queued!' % ids[2])
|
||||
|
||||
# Delete the image from the queue
|
||||
cmd = ("%s --port=%d --force "
|
||||
"delete-queued-image %s") % (exe_cmd, api_port, ids[2])
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
|
||||
# Verify no queued images
|
||||
cmd = "%s --port=%d list-queued" % (exe_cmd, api_port)
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertIn(b'No queued images', out.strip())
|
||||
|
||||
# Queue all images
|
||||
for x in range(4):
|
||||
cmd = ("%s --port=%d --force "
|
||||
"queue-image %s") % (exe_cmd, api_port, ids[x])
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
|
||||
# Verify queued third image
|
||||
cmd = "%s --port=%d list-queued" % (exe_cmd, api_port)
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertIn(b'Found 3 queued images', out)
|
||||
|
||||
# Delete the image from the queue
|
||||
cmd = ("%s --port=%d --force "
|
||||
"delete-all-queued-images") % (exe_cmd, api_port)
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
|
||||
# Verify nothing in queue anymore
|
||||
cmd = "%s --port=%d list-queued" % (exe_cmd, api_port)
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertIn(b'No queued images', out.strip())
|
||||
|
||||
# verify two image id when queue-image
|
||||
cmd = ("%s --port=%d --force "
|
||||
"queue-image %s %s") % (exe_cmd, api_port, ids[0], ids[1])
|
||||
|
||||
exitcode, out, err = execute(cmd, raise_error=False)
|
||||
|
||||
self.assertEqual(1, exitcode)
|
||||
self.assertIn(b'Please specify one and only ID of '
|
||||
b'the image you wish to ', out.strip())
|
||||
|
||||
# verify two image id when delete-queued-image
|
||||
cmd = ("%s --port=%d --force delete-queued-image "
|
||||
"%s %s") % (exe_cmd, api_port, ids[0], ids[1])
|
||||
|
||||
exitcode, out, err = execute(cmd, raise_error=False)
|
||||
|
||||
self.assertEqual(1, exitcode)
|
||||
self.assertIn(b'Please specify one and only ID of '
|
||||
b'the image you wish to ', out.strip())
|
||||
|
||||
# verify two image id when delete-cached-image
|
||||
cmd = ("%s --port=%d --force delete-cached-image "
|
||||
"%s %s") % (exe_cmd, api_port, ids[0], ids[1])
|
||||
|
||||
exitcode, out, err = execute(cmd, raise_error=False)
|
||||
|
||||
self.assertEqual(1, exitcode)
|
||||
self.assertIn(b'Please specify one and only ID of '
|
||||
b'the image you wish to ', out.strip())
|
||||
|
||||
self.stop_servers()
|
|
@ -20,25 +20,16 @@ but that is really not relevant, as the image cache is transparent
|
|||
to the backend store.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
|
||||
import httplib2
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import units
|
||||
from six.moves import http_client
|
||||
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
|
||||
from six.moves import range
|
||||
|
||||
from glance.tests import functional
|
||||
from glance.tests.functional.store_utils import get_http_uri
|
||||
from glance.tests.functional.store_utils import setup_http
|
||||
from glance.tests.utils import execute
|
||||
from glance.tests.utils import minimal_headers
|
||||
from glance.tests.utils import skip_if_disabled
|
||||
from glance.tests.utils import xattr_writes_supported
|
||||
|
||||
|
@ -47,78 +38,6 @@ FIVE_KB = 5 * units.Ki
|
|||
|
||||
class BaseCacheMiddlewareTest(object):
|
||||
|
||||
@skip_if_disabled
|
||||
def test_cache_middleware_transparent_v1(self):
|
||||
"""
|
||||
We test that putting the cache middleware into the
|
||||
application pipeline gives us transparent image caching
|
||||
"""
|
||||
self.cleanup()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
# Add an image and verify a 200 OK is returned
|
||||
image_data = b"*" * FIVE_KB
|
||||
headers = minimal_headers('Image1')
|
||||
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'POST', headers=headers,
|
||||
body=image_data)
|
||||
self.assertEqual(http_client.CREATED, response.status)
|
||||
data = jsonutils.loads(content)
|
||||
self.assertEqual(hashlib.md5(image_data).hexdigest(),
|
||||
data['image']['checksum'])
|
||||
self.assertEqual(FIVE_KB, data['image']['size'])
|
||||
self.assertEqual("Image1", data['image']['name'])
|
||||
self.assertTrue(data['image']['is_public'])
|
||||
|
||||
image_id = data['image']['id']
|
||||
|
||||
# Verify image not in cache
|
||||
image_cached_path = os.path.join(self.api_server.image_cache_dir,
|
||||
image_id)
|
||||
self.assertFalse(os.path.exists(image_cached_path))
|
||||
|
||||
# Grab the image
|
||||
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
|
||||
image_id)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
# Verify image now in cache
|
||||
image_cached_path = os.path.join(self.api_server.image_cache_dir,
|
||||
image_id)
|
||||
|
||||
# You might wonder why the heck this is here... well, it's here
|
||||
# because it took me forever to figure out that the disk write
|
||||
# cache in Linux was causing random failures of the os.path.exists
|
||||
# assert directly below this. Basically, since the cache is writing
|
||||
# the image file to disk in a different process, the write buffers
|
||||
# don't flush the cache file during an os.rename() properly, resulting
|
||||
# in a false negative on the file existence check below. This little
|
||||
# loop pauses the execution of this process for no more than 1.5
|
||||
# seconds. If after that time the cached image file still doesn't
|
||||
# appear on disk, something really is wrong, and the assert should
|
||||
# trigger...
|
||||
i = 0
|
||||
while not os.path.exists(image_cached_path) and i < 30:
|
||||
time.sleep(0.05)
|
||||
i = i + 1
|
||||
|
||||
self.assertTrue(os.path.exists(image_cached_path))
|
||||
|
||||
# Now, we delete the image from the server and verify that
|
||||
# the image cache no longer contains the deleted image
|
||||
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
|
||||
image_id)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'DELETE')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
self.assertFalse(os.path.exists(image_cached_path))
|
||||
|
||||
self.stop_servers()
|
||||
|
||||
@skip_if_disabled
|
||||
def test_cache_middleware_transparent_v2(self):
|
||||
"""Ensure the v2 API image transfer calls trigger caching"""
|
||||
|
@ -354,102 +273,6 @@ class BaseCacheMiddlewareTest(object):
|
|||
|
||||
self.stop_servers()
|
||||
|
||||
@skip_if_disabled
|
||||
def test_cache_remote_image(self):
|
||||
"""
|
||||
We test that caching is no longer broken for remote images
|
||||
"""
|
||||
self.cleanup()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
setup_http(self)
|
||||
|
||||
# Add a remote image and verify a 201 Created is returned
|
||||
remote_uri = get_http_uri(self, '2')
|
||||
headers = {'X-Image-Meta-Name': 'Image2',
|
||||
'X-Image-Meta-disk_format': 'raw',
|
||||
'X-Image-Meta-container_format': 'ovf',
|
||||
'X-Image-Meta-Is-Public': 'True',
|
||||
'X-Image-Meta-Location': remote_uri}
|
||||
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'POST', headers=headers)
|
||||
self.assertEqual(http_client.CREATED, response.status)
|
||||
data = jsonutils.loads(content)
|
||||
self.assertEqual(FIVE_KB, data['image']['size'])
|
||||
|
||||
image_id = data['image']['id']
|
||||
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
|
||||
image_id)
|
||||
|
||||
# Grab the image
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
# Grab the image again to ensure it can be served out from
|
||||
# cache with the correct size
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
self.assertEqual(FIVE_KB, int(response['content-length']))
|
||||
|
||||
self.stop_servers()
|
||||
|
||||
@skip_if_disabled
|
||||
def test_cache_middleware_trans_v1_without_download_image_policy(self):
|
||||
"""
|
||||
Ensure the image v1 API image transfer applied 'download_image'
|
||||
policy enforcement.
|
||||
"""
|
||||
self.cleanup()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
# Add an image and verify a 200 OK is returned
|
||||
image_data = b"*" * FIVE_KB
|
||||
headers = minimal_headers('Image1')
|
||||
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'POST', headers=headers,
|
||||
body=image_data)
|
||||
self.assertEqual(http_client.CREATED, response.status)
|
||||
data = jsonutils.loads(content)
|
||||
self.assertEqual(hashlib.md5(image_data).hexdigest(),
|
||||
data['image']['checksum'])
|
||||
self.assertEqual(FIVE_KB, data['image']['size'])
|
||||
self.assertEqual("Image1", data['image']['name'])
|
||||
self.assertTrue(data['image']['is_public'])
|
||||
|
||||
image_id = data['image']['id']
|
||||
|
||||
# Verify image not in cache
|
||||
image_cached_path = os.path.join(self.api_server.image_cache_dir,
|
||||
image_id)
|
||||
self.assertFalse(os.path.exists(image_cached_path))
|
||||
|
||||
rules = {"context_is_admin": "role:admin", "default": "",
|
||||
"download_image": "!"}
|
||||
self.set_policy_rules(rules)
|
||||
|
||||
# Grab the image
|
||||
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
|
||||
image_id)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.FORBIDDEN, response.status)
|
||||
|
||||
# Now, we delete the image from the server and verify that
|
||||
# the image cache no longer contains the deleted image
|
||||
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
|
||||
image_id)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'DELETE')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
self.assertFalse(os.path.exists(image_cached_path))
|
||||
|
||||
self.stop_servers()
|
||||
|
||||
@skip_if_disabled
|
||||
def test_cache_middleware_trans_v2_without_download_image_policy(self):
|
||||
"""
|
||||
|
@ -511,489 +334,6 @@ class BaseCacheMiddlewareTest(object):
|
|||
|
||||
self.stop_servers()
|
||||
|
||||
@skip_if_disabled
|
||||
def test_cache_middleware_trans_with_deactivated_image(self):
|
||||
"""
|
||||
Ensure the image v1/v2 API image transfer forbids downloading
|
||||
deactivated images.
|
||||
Image deactivation is not available in v1. So, we'll deactivate the
|
||||
image using v2 but test image transfer with both v1 and v2.
|
||||
"""
|
||||
self.cleanup()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
# Add an image and verify a 200 OK is returned
|
||||
image_data = b"*" * FIVE_KB
|
||||
headers = minimal_headers('Image1')
|
||||
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'POST', headers=headers,
|
||||
body=image_data)
|
||||
self.assertEqual(http_client.CREATED, response.status)
|
||||
data = jsonutils.loads(content)
|
||||
self.assertEqual(hashlib.md5(image_data).hexdigest(),
|
||||
data['image']['checksum'])
|
||||
self.assertEqual(FIVE_KB, data['image']['size'])
|
||||
self.assertEqual("Image1", data['image']['name'])
|
||||
self.assertTrue(data['image']['is_public'])
|
||||
|
||||
image_id = data['image']['id']
|
||||
|
||||
# Grab the image
|
||||
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
|
||||
image_id)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
# Verify image in cache
|
||||
image_cached_path = os.path.join(self.api_server.image_cache_dir,
|
||||
image_id)
|
||||
self.assertTrue(os.path.exists(image_cached_path))
|
||||
|
||||
# Deactivate the image using v2
|
||||
path = "http://%s:%d/v2/images/%s/actions/deactivate"
|
||||
path = path % ("127.0.0.1", self.api_port, image_id)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'POST')
|
||||
self.assertEqual(http_client.NO_CONTENT, response.status)
|
||||
|
||||
# Download the image with v1. Ensure it is forbidden
|
||||
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
|
||||
image_id)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.FORBIDDEN, response.status)
|
||||
|
||||
# Download the image with v2. This succeeds because
|
||||
# we are in admin context.
|
||||
path = "http://%s:%d/v2/images/%s/file" % ("127.0.0.1", self.api_port,
|
||||
image_id)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
# Reactivate the image using v2
|
||||
path = "http://%s:%d/v2/images/%s/actions/reactivate"
|
||||
path = path % ("127.0.0.1", self.api_port, image_id)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'POST')
|
||||
self.assertEqual(http_client.NO_CONTENT, response.status)
|
||||
|
||||
# Download the image with v1. Ensure it is allowed
|
||||
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
|
||||
image_id)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
# Download the image with v2. Ensure it is allowed
|
||||
path = "http://%s:%d/v2/images/%s/file" % ("127.0.0.1", self.api_port,
|
||||
image_id)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
# Now, we delete the image from the server and verify that
|
||||
# the image cache no longer contains the deleted image
|
||||
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
|
||||
image_id)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'DELETE')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
self.assertFalse(os.path.exists(image_cached_path))
|
||||
|
||||
self.stop_servers()
|
||||
|
||||
|
||||
class BaseCacheManageMiddlewareTest(object):
|
||||
|
||||
"""Base test class for testing cache management middleware"""
|
||||
|
||||
def verify_no_images(self):
|
||||
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
data = jsonutils.loads(content)
|
||||
self.assertIn('images', data)
|
||||
self.assertEqual(0, len(data['images']))
|
||||
|
||||
def add_image(self, name):
|
||||
"""
|
||||
Adds an image and returns the newly-added image
|
||||
identifier
|
||||
"""
|
||||
image_data = b"*" * FIVE_KB
|
||||
headers = minimal_headers('%s' % name)
|
||||
|
||||
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'POST', headers=headers,
|
||||
body=image_data)
|
||||
self.assertEqual(http_client.CREATED, response.status)
|
||||
data = jsonutils.loads(content)
|
||||
self.assertEqual(hashlib.md5(image_data).hexdigest(),
|
||||
data['image']['checksum'])
|
||||
self.assertEqual(FIVE_KB, data['image']['size'])
|
||||
self.assertEqual(name, data['image']['name'])
|
||||
self.assertTrue(data['image']['is_public'])
|
||||
return data['image']['id']
|
||||
|
||||
def verify_no_cached_images(self):
|
||||
"""
|
||||
Verify no images in the image cache
|
||||
"""
|
||||
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
data = jsonutils.loads(content)
|
||||
self.assertIn('cached_images', data)
|
||||
self.assertEqual([], data['cached_images'])
|
||||
|
||||
@skip_if_disabled
|
||||
def test_user_not_authorized(self):
|
||||
self.cleanup()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
self.verify_no_images()
|
||||
|
||||
image_id1 = self.add_image("Image1")
|
||||
image_id2 = self.add_image("Image2")
|
||||
|
||||
# Verify image does not yet show up in cache (we haven't "hit"
|
||||
# it yet using a GET /images/1 ...
|
||||
self.verify_no_cached_images()
|
||||
|
||||
# Grab the image
|
||||
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
|
||||
image_id1)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
# Verify image now in cache
|
||||
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
data = jsonutils.loads(content)
|
||||
self.assertIn('cached_images', data)
|
||||
|
||||
cached_images = data['cached_images']
|
||||
self.assertEqual(1, len(cached_images))
|
||||
self.assertEqual(image_id1, cached_images[0]['image_id'])
|
||||
|
||||
# Set policy to disallow access to cache management
|
||||
rules = {"manage_image_cache": '!'}
|
||||
self.set_policy_rules(rules)
|
||||
|
||||
# Verify an unprivileged user cannot see cached images
|
||||
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.FORBIDDEN, response.status)
|
||||
|
||||
# Verify an unprivileged user cannot delete images from the cache
|
||||
path = "http://%s:%d/v1/cached_images/%s" % ("127.0.0.1",
|
||||
self.api_port, image_id1)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'DELETE')
|
||||
self.assertEqual(http_client.FORBIDDEN, response.status)
|
||||
|
||||
# Verify an unprivileged user cannot delete all cached images
|
||||
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'DELETE')
|
||||
self.assertEqual(http_client.FORBIDDEN, response.status)
|
||||
|
||||
# Verify an unprivileged user cannot queue an image
|
||||
path = "http://%s:%d/v1/queued_images/%s" % ("127.0.0.1",
|
||||
self.api_port, image_id2)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'PUT')
|
||||
self.assertEqual(http_client.FORBIDDEN, response.status)
|
||||
|
||||
self.stop_servers()
|
||||
|
||||
@skip_if_disabled
|
||||
def test_cache_manage_get_cached_images(self):
|
||||
"""
|
||||
Tests that cached images are queryable
|
||||
"""
|
||||
self.cleanup()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
self.verify_no_images()
|
||||
|
||||
image_id = self.add_image("Image1")
|
||||
|
||||
# Verify image does not yet show up in cache (we haven't "hit"
|
||||
# it yet using a GET /images/1 ...
|
||||
self.verify_no_cached_images()
|
||||
|
||||
# Grab the image
|
||||
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
|
||||
image_id)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
# Verify image now in cache
|
||||
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
data = jsonutils.loads(content)
|
||||
self.assertIn('cached_images', data)
|
||||
|
||||
# Verify the last_modified/last_accessed values are valid floats
|
||||
for cached_image in data['cached_images']:
|
||||
for time_key in ('last_modified', 'last_accessed'):
|
||||
time_val = cached_image[time_key]
|
||||
try:
|
||||
float(time_val)
|
||||
except ValueError:
|
||||
self.fail('%s time %s for cached image %s not a valid '
|
||||
'float' % (time_key, time_val,
|
||||
cached_image['image_id']))
|
||||
|
||||
cached_images = data['cached_images']
|
||||
self.assertEqual(1, len(cached_images))
|
||||
self.assertEqual(image_id, cached_images[0]['image_id'])
|
||||
self.assertEqual(0, cached_images[0]['hits'])
|
||||
|
||||
# Hit the image
|
||||
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
|
||||
image_id)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
# Verify image hits increased in output of manage GET
|
||||
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
data = jsonutils.loads(content)
|
||||
self.assertIn('cached_images', data)
|
||||
|
||||
cached_images = data['cached_images']
|
||||
self.assertEqual(1, len(cached_images))
|
||||
self.assertEqual(image_id, cached_images[0]['image_id'])
|
||||
self.assertEqual(1, cached_images[0]['hits'])
|
||||
|
||||
self.stop_servers()
|
||||
|
||||
@skip_if_disabled
|
||||
def test_cache_manage_delete_cached_images(self):
|
||||
"""
|
||||
Tests that cached images may be deleted
|
||||
"""
|
||||
self.cleanup()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
self.verify_no_images()
|
||||
|
||||
ids = {}
|
||||
|
||||
# Add a bunch of images...
|
||||
for x in range(4):
|
||||
ids[x] = self.add_image("Image%s" % str(x))
|
||||
|
||||
# Verify no images in cached_images because no image has been hit
|
||||
# yet using a GET /images/<IMAGE_ID> ...
|
||||
self.verify_no_cached_images()
|
||||
|
||||
# Grab the images, essentially caching them...
|
||||
for x in range(4):
|
||||
path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
|
||||
ids[x])
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status,
|
||||
"Failed to find image %s" % ids[x])
|
||||
|
||||
# Verify images now in cache
|
||||
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
data = jsonutils.loads(content)
|
||||
self.assertIn('cached_images', data)
|
||||
|
||||
cached_images = data['cached_images']
|
||||
self.assertEqual(4, len(cached_images))
|
||||
|
||||
for x in range(4, 0): # Cached images returned last modified order
|
||||
self.assertEqual(ids[x], cached_images[x]['image_id'])
|
||||
self.assertEqual(0, cached_images[x]['hits'])
|
||||
|
||||
# Delete third image of the cached images and verify no longer in cache
|
||||
path = "http://%s:%d/v1/cached_images/%s" % ("127.0.0.1",
|
||||
self.api_port, ids[2])
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'DELETE')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
data = jsonutils.loads(content)
|
||||
self.assertIn('cached_images', data)
|
||||
|
||||
cached_images = data['cached_images']
|
||||
self.assertEqual(3, len(cached_images))
|
||||
self.assertNotIn(ids[2], [x['image_id'] for x in cached_images])
|
||||
|
||||
# Delete all cached images and verify nothing in cache
|
||||
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'DELETE')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
data = jsonutils.loads(content)
|
||||
self.assertIn('cached_images', data)
|
||||
|
||||
cached_images = data['cached_images']
|
||||
self.assertEqual(0, len(cached_images))
|
||||
|
||||
self.stop_servers()
|
||||
|
||||
@skip_if_disabled
|
||||
def test_cache_manage_delete_queued_images(self):
|
||||
"""
|
||||
Tests that all queued images may be deleted at once
|
||||
"""
|
||||
self.cleanup()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
self.verify_no_images()
|
||||
|
||||
ids = {}
|
||||
NUM_IMAGES = 4
|
||||
|
||||
# Add and then queue some images
|
||||
for x in range(NUM_IMAGES):
|
||||
ids[x] = self.add_image("Image%s" % str(x))
|
||||
path = "http://%s:%d/v1/queued_images/%s" % ("127.0.0.1",
|
||||
self.api_port, ids[x])
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'PUT')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
# Delete all queued images
|
||||
path = "http://%s:%d/v1/queued_images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'DELETE')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
data = jsonutils.loads(content)
|
||||
num_deleted = data['num_deleted']
|
||||
self.assertEqual(NUM_IMAGES, num_deleted)
|
||||
|
||||
# Verify a second delete now returns num_deleted=0
|
||||
path = "http://%s:%d/v1/queued_images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'DELETE')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
data = jsonutils.loads(content)
|
||||
num_deleted = data['num_deleted']
|
||||
self.assertEqual(0, num_deleted)
|
||||
|
||||
self.stop_servers()
|
||||
|
||||
@skip_if_disabled
|
||||
def test_queue_and_prefetch(self):
|
||||
"""
|
||||
Tests that images may be queued and prefetched
|
||||
"""
|
||||
self.cleanup()
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
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.registry_server.bind_port,
|
||||
'log_file': os.path.join(self.test_dir, 'cache.log'),
|
||||
'lock_path': self.test_dir,
|
||||
'metadata_encryption_key': "012345678901234567890123456789ab",
|
||||
'filesystem_store_datadir': self.test_dir
|
||||
}
|
||||
with open(cache_config_filepath, 'w') as cache_file:
|
||||
cache_file.write("""[DEFAULT]
|
||||
debug = True
|
||||
lock_path = %(lock_path)s
|
||||
image_cache_dir = %(image_cache_dir)s
|
||||
image_cache_driver = %(image_cache_driver)s
|
||||
registry_host = 127.0.0.1
|
||||
registry_port = %(registry_port)s
|
||||
metadata_encryption_key = %(metadata_encryption_key)s
|
||||
log_file = %(log_file)s
|
||||
|
||||
[glance_store]
|
||||
filesystem_store_datadir=%(filesystem_store_datadir)s
|
||||
""" % cache_file_options)
|
||||
|
||||
self.verify_no_images()
|
||||
|
||||
ids = {}
|
||||
|
||||
# Add a bunch of images...
|
||||
for x in range(4):
|
||||
ids[x] = self.add_image("Image%s" % str(x))
|
||||
|
||||
# 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/queued_images/%s" % ("127.0.0.1",
|
||||
self.api_port, ids[0])
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'PUT')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
self.verify_no_cached_images()
|
||||
|
||||
cmd = ("%s -m glance.cmd.cache_prefetcher --config-file %s" %
|
||||
(sys.executable, cache_config_filepath))
|
||||
|
||||
exitcode, out, err = execute(cmd)
|
||||
|
||||
self.assertEqual(0, exitcode)
|
||||
self.assertEqual(b'', out.strip(), out)
|
||||
|
||||
# Verify first image now in cache
|
||||
path = "http://%s:%d/v1/cached_images" % ("127.0.0.1", self.api_port)
|
||||
http = httplib2.Http()
|
||||
response, content = http.request(path, 'GET')
|
||||
self.assertEqual(http_client.OK, response.status)
|
||||
|
||||
data = jsonutils.loads(content)
|
||||
self.assertIn('cached_images', data)
|
||||
|
||||
cached_images = data['cached_images']
|
||||
self.assertEqual(1, len(cached_images))
|
||||
self.assertIn(ids[0], [r['image_id']
|
||||
for r in data['cached_images']])
|
||||
|
||||
self.stop_servers()
|
||||
|
||||
|
||||
class TestImageCacheXattr(functional.FunctionalTest,
|
||||
BaseCacheMiddlewareTest):
|
||||
|
@ -1038,52 +378,6 @@ class TestImageCacheXattr(functional.FunctionalTest,
|
|||
shutil.rmtree(self.api_server.image_cache_dir)
|
||||
|
||||
|
||||
class TestImageCacheManageXattr(functional.FunctionalTest,
|
||||
BaseCacheManageMiddlewareTest):
|
||||
|
||||
"""
|
||||
Functional tests that exercise the image cache management
|
||||
with the Xattr cache driver
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Test to see if the pre-requisites for the image cache
|
||||
are working (python-xattr installed and xattr support on the
|
||||
filesystem)
|
||||
"""
|
||||
if getattr(self, 'disabled', False):
|
||||
return
|
||||
|
||||
if not getattr(self, 'inited', False):
|
||||
try:
|
||||
import xattr # noqa
|
||||
except ImportError:
|
||||
self.inited = True
|
||||
self.disabled = True
|
||||
self.disabled_message = ("python-xattr not installed.")
|
||||
return
|
||||
|
||||
self.inited = True
|
||||
self.disabled = False
|
||||
self.image_cache_driver = "xattr"
|
||||
|
||||
super(TestImageCacheManageXattr, self).setUp()
|
||||
|
||||
self.api_server.deployment_flavor = "cachemanagement"
|
||||
|
||||
if not xattr_writes_supported(self.test_dir):
|
||||
self.inited = True
|
||||
self.disabled = True
|
||||
self.disabled_message = ("filesystem does not support xattr")
|
||||
return
|
||||
|
||||
def tearDown(self):
|
||||
super(TestImageCacheManageXattr, self).tearDown()
|
||||
if os.path.exists(self.api_server.image_cache_dir):
|
||||
shutil.rmtree(self.api_server.image_cache_dir)
|
||||
|
||||
|
||||
class TestImageCacheSqlite(functional.FunctionalTest,
|
||||
BaseCacheMiddlewareTest):
|
||||
|
||||
|
@ -1121,43 +415,3 @@ class TestImageCacheSqlite(functional.FunctionalTest,
|
|||
super(TestImageCacheSqlite, self).tearDown()
|
||||
if os.path.exists(self.api_server.image_cache_dir):
|
||||
shutil.rmtree(self.api_server.image_cache_dir)
|
||||
|
||||
|
||||
class TestImageCacheManageSqlite(functional.FunctionalTest,
|
||||
BaseCacheManageMiddlewareTest):
|
||||
|
||||
"""
|
||||
Functional tests that exercise the image cache management using the
|
||||
SQLite driver
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Test to see if the pre-requisites for the image cache
|
||||
are working (python-xattr installed and xattr support on the
|
||||
filesystem)
|
||||
"""
|
||||
if getattr(self, 'disabled', False):
|
||||
return
|
||||
|
||||
if not getattr(self, 'inited', False):
|
||||
try:
|
||||
import sqlite3 # noqa
|
||||
except ImportError:
|
||||
self.inited = True
|
||||
self.disabled = True
|
||||
self.disabled_message = ("python-sqlite3 not installed.")
|
||||
return
|
||||
|
||||
self.inited = True
|
||||
self.disabled = False
|
||||
self.image_cache_driver = "sqlite"
|
||||
|
||||
super(TestImageCacheManageSqlite, self).setUp()
|
||||
|
||||
self.api_server.deployment_flavor = "cachemanagement"
|
||||
|
||||
def tearDown(self):
|
||||
super(TestImageCacheManageSqlite, self).tearDown()
|
||||
if os.path.exists(self.api_server.image_cache_dir):
|
||||
shutil.rmtree(self.api_server.image_cache_dir)
|
||||
|
|
|
@ -33,7 +33,7 @@ class TestCORSMiddleware(functional.FunctionalTest):
|
|||
# Cleanup is handled in teardown of the parent class.
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
self.http = httplib2.Http()
|
||||
self.api_path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
|
||||
self.api_path = "http://%s:%d/v2/images" % ("127.0.0.1", self.api_port)
|
||||
|
||||
def test_valid_cors_options_request(self):
|
||||
(r_headers, content) = self.http.request(
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
# 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 cases for glance-replicator"""
|
||||
|
||||
import sys
|
||||
|
||||
from glance.tests import functional
|
||||
from glance.tests.utils import execute
|
||||
|
||||
|
||||
class TestGlanceReplicator(functional.FunctionalTest):
|
||||
"""Functional tests for glance-replicator"""
|
||||
|
||||
def test_compare(self):
|
||||
# Test for issue: https://bugs.launchpad.net/glance/+bug/1598928
|
||||
cmd = ('%s -m glance.cmd.replicator '
|
||||
'compare az1:9292 az2:9292 --debug' %
|
||||
(sys.executable,))
|
||||
exitcode, out, err = execute(cmd, raise_error=False)
|
||||
self.assertIn(
|
||||
b'Request: GET http://az1:9292/v1/images/detail?is_public=None',
|
||||
err
|
||||
)
|
|
@ -49,6 +49,8 @@ class TestImages(functional.FunctionalTest):
|
|||
"foo_image%d" % i)
|
||||
setattr(self, 'http_server%d_pid' % i, ret[0])
|
||||
setattr(self, 'http_port%d' % i, ret[1])
|
||||
self.api_server.use_user_token = True
|
||||
self.api_server.send_identity_credentials = True
|
||||
|
||||
def tearDown(self):
|
||||
for i in range(3):
|
||||
|
@ -72,36 +74,6 @@ class TestImages(functional.FunctionalTest):
|
|||
base_headers.update(custom_headers or {})
|
||||
return base_headers
|
||||
|
||||
def test_v1_none_properties_v2(self):
|
||||
self.api_server.deployment_flavor = 'noauth'
|
||||
self.api_server.use_user_token = True
|
||||
self.api_server.send_identity_credentials = True
|
||||
self.registry_server.deployment_flavor = ''
|
||||
# Image list should be empty
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
# Create an image (with two deployer-defined properties)
|
||||
path = self._url('/v1/images')
|
||||
headers = self._headers({'content-type': 'application/octet-stream'})
|
||||
headers.update(test_utils.minimal_headers('image-1'))
|
||||
# NOTE(flaper87): Sending empty string, the server will use None
|
||||
headers['x-image-meta-property-my_empty_prop'] = ''
|
||||
|
||||
response = requests.post(path, headers=headers)
|
||||
self.assertEqual(http.CREATED, response.status_code)
|
||||
data = jsonutils.loads(response.text)
|
||||
image_id = data['image']['id']
|
||||
|
||||
# NOTE(flaper87): Get the image using V2 and verify
|
||||
# the returned value for `my_empty_prop` is an empty
|
||||
# string.
|
||||
path = self._url('/v2/images/%s' % image_id)
|
||||
response = requests.get(path, headers=self._headers())
|
||||
self.assertEqual(http.OK, response.status_code)
|
||||
image = jsonutils.loads(response.text)
|
||||
self.assertEqual('', image['my_empty_prop'])
|
||||
self.stop_servers()
|
||||
|
||||
def test_not_authenticated_in_registry_on_ops(self):
|
||||
# https://bugs.launchpad.net/glance/+bug/1451850
|
||||
# this configuration guarantees that authentication succeeds in
|
||||
|
@ -3353,6 +3325,7 @@ class TestImagesWithRegistry(TestImages):
|
|||
self.api_server.data_api = (
|
||||
'glance.tests.functional.v2.registry_data_api')
|
||||
self.registry_server.deployment_flavor = 'trusted-auth'
|
||||
self.api_server.use_user_token = True
|
||||
|
||||
|
||||
class TestImagesIPv6(functional.FunctionalTest):
|
||||
|
|
|
@ -1,222 +0,0 @@
|
|||
# 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 atexit
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
import fixtures
|
||||
import glance_store
|
||||
from oslo_config import cfg
|
||||
from oslo_db import options
|
||||
|
||||
import glance.common.client
|
||||
from glance.common import config
|
||||
import glance.db.sqlalchemy.api
|
||||
import glance.registry.client.v1.client
|
||||
from glance import tests as glance_tests
|
||||
from glance.tests import utils as test_utils
|
||||
|
||||
|
||||
TESTING_API_PASTE_CONF = """
|
||||
[pipeline:glance-api]
|
||||
pipeline = versionnegotiation gzip unauthenticated-context rootapp
|
||||
|
||||
[pipeline:glance-api-caching]
|
||||
pipeline = versionnegotiation gzip unauthenticated-context cache rootapp
|
||||
|
||||
[pipeline:glance-api-cachemanagement]
|
||||
pipeline =
|
||||
versionnegotiation
|
||||
gzip
|
||||
unauthenticated-context
|
||||
cache
|
||||
cache_manage
|
||||
rootapp
|
||||
|
||||
[pipeline:glance-api-fakeauth]
|
||||
pipeline = versionnegotiation gzip fakeauth context rootapp
|
||||
|
||||
[pipeline:glance-api-noauth]
|
||||
pipeline = versionnegotiation gzip context rootapp
|
||||
|
||||
[composite:rootapp]
|
||||
paste.composite_factory = glance.api:root_app_factory
|
||||
/: apiversions
|
||||
/v1: apiv1app
|
||||
/v2: apiv2app
|
||||
|
||||
[app:apiversions]
|
||||
paste.app_factory = glance.api.versions:create_resource
|
||||
|
||||
[app:apiv1app]
|
||||
paste.app_factory = glance.api.v1.router:API.factory
|
||||
|
||||
[app:apiv2app]
|
||||
paste.app_factory = glance.api.v2.router:API.factory
|
||||
|
||||
[filter:versionnegotiation]
|
||||
paste.filter_factory =
|
||||
glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory
|
||||
|
||||
[filter:gzip]
|
||||
paste.filter_factory = glance.api.middleware.gzip:GzipMiddleware.factory
|
||||
|
||||
[filter:cache]
|
||||
paste.filter_factory = glance.api.middleware.cache:CacheFilter.factory
|
||||
|
||||
[filter:cache_manage]
|
||||
paste.filter_factory =
|
||||
glance.api.middleware.cache_manage:CacheManageFilter.factory
|
||||
|
||||
[filter:context]
|
||||
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
|
||||
|
||||
[filter:unauthenticated-context]
|
||||
paste.filter_factory =
|
||||
glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
|
||||
|
||||
[filter:fakeauth]
|
||||
paste.filter_factory = glance.tests.utils:FakeAuthMiddleware.factory
|
||||
"""
|
||||
|
||||
TESTING_REGISTRY_PASTE_CONF = """
|
||||
[pipeline:glance-registry]
|
||||
pipeline = unauthenticated-context registryapp
|
||||
|
||||
[pipeline:glance-registry-fakeauth]
|
||||
pipeline = fakeauth context registryapp
|
||||
|
||||
[app:registryapp]
|
||||
paste.app_factory = glance.registry.api.v1:API.factory
|
||||
|
||||
[filter:context]
|
||||
paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
|
||||
|
||||
[filter:unauthenticated-context]
|
||||
paste.filter_factory =
|
||||
glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
|
||||
|
||||
[filter:fakeauth]
|
||||
paste.filter_factory = glance.tests.utils:FakeAuthMiddleware.factory
|
||||
"""
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class ApiTest(test_utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(ApiTest, self).setUp()
|
||||
self.init()
|
||||
|
||||
def init(self):
|
||||
self.test_dir = self.useFixture(fixtures.TempDir()).path
|
||||
self._configure_logging()
|
||||
self._configure_policy()
|
||||
self._setup_database()
|
||||
self._setup_stores()
|
||||
self._setup_property_protection()
|
||||
self.glance_registry_app = self._load_paste_app(
|
||||
'glance-registry',
|
||||
flavor=getattr(self, 'registry_flavor', ''),
|
||||
conf=getattr(self, 'registry_paste_conf',
|
||||
TESTING_REGISTRY_PASTE_CONF),
|
||||
)
|
||||
self._connect_registry_client()
|
||||
self.glance_api_app = self._load_paste_app(
|
||||
'glance-api',
|
||||
flavor=getattr(self, 'api_flavor', ''),
|
||||
conf=getattr(self, 'api_paste_conf', TESTING_API_PASTE_CONF),
|
||||
)
|
||||
self.http = test_utils.Httplib2WsgiAdapter(self.glance_api_app)
|
||||
|
||||
def _setup_property_protection(self):
|
||||
self._copy_data_file('property-protections.conf', self.test_dir)
|
||||
self.property_file = os.path.join(self.test_dir,
|
||||
'property-protections.conf')
|
||||
|
||||
def _configure_policy(self):
|
||||
policy_file = self._copy_data_file('policy.json', self.test_dir)
|
||||
self.config(policy_file=policy_file, group='oslo_policy')
|
||||
|
||||
def _configure_logging(self):
|
||||
self.config(default_log_levels=[
|
||||
'amqplib=WARN',
|
||||
'sqlalchemy=WARN',
|
||||
'boto=WARN',
|
||||
'suds=INFO',
|
||||
'keystone=INFO',
|
||||
'eventlet.wsgi.server=DEBUG'
|
||||
])
|
||||
|
||||
def _setup_database(self):
|
||||
sql_connection = 'sqlite:////%s/tests.sqlite' % self.test_dir
|
||||
options.set_defaults(CONF, connection=sql_connection)
|
||||
glance.db.sqlalchemy.api.clear_db_env()
|
||||
glance_db_env = 'GLANCE_DB_TEST_SQLITE_FILE'
|
||||
if glance_db_env in os.environ:
|
||||
# use the empty db created and cached as a tempfile
|
||||
# instead of spending the time creating a new one
|
||||
db_location = os.environ[glance_db_env]
|
||||
test_utils.execute('cp %s %s/tests.sqlite'
|
||||
% (db_location, self.test_dir))
|
||||
else:
|
||||
test_utils.db_sync()
|
||||
|
||||
# copy the clean db to a temp location so that it
|
||||
# can be reused for future tests
|
||||
(osf, db_location) = tempfile.mkstemp()
|
||||
os.close(osf)
|
||||
test_utils.execute('cp %s/tests.sqlite %s'
|
||||
% (self.test_dir, db_location))
|
||||
os.environ[glance_db_env] = db_location
|
||||
|
||||
# cleanup the temp file when the test suite is
|
||||
# complete
|
||||
def _delete_cached_db():
|
||||
try:
|
||||
os.remove(os.environ[glance_db_env])
|
||||
except Exception:
|
||||
glance_tests.logger.exception(
|
||||
"Error cleaning up the file %s" %
|
||||
os.environ[glance_db_env])
|
||||
atexit.register(_delete_cached_db)
|
||||
|
||||
def _setup_stores(self):
|
||||
glance_store.register_opts(CONF)
|
||||
|
||||
image_dir = os.path.join(self.test_dir, "images")
|
||||
self.config(group='glance_store',
|
||||
filesystem_store_datadir=image_dir)
|
||||
|
||||
glance_store.create_stores()
|
||||
|
||||
def _load_paste_app(self, name, flavor, conf):
|
||||
conf_file_path = os.path.join(self.test_dir, '%s-paste.ini' % name)
|
||||
with open(conf_file_path, 'w') as conf_file:
|
||||
conf_file.write(conf)
|
||||
conf_file.flush()
|
||||
return config.load_paste_app(name, flavor=flavor,
|
||||
conf_file=conf_file_path)
|
||||
|
||||
def _connect_registry_client(self):
|
||||
def get_connection_type(self2):
|
||||
def wrapped(*args, **kwargs):
|
||||
return test_utils.HttplibWsgiAdapter(self.glance_registry_app)
|
||||
return wrapped
|
||||
|
||||
self.stubs.Set(glance.common.client.BaseClient,
|
||||
'get_connection_type', get_connection_type)
|
||||
|
||||
def tearDown(self):
|
||||
glance.db.sqlalchemy.api.clear_db_env()
|
||||
super(ApiTest, self).tearDown()
|
File diff suppressed because it is too large
Load Diff
|
@ -1,298 +0,0 @@
|
|||
# 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 argparse
|
||||
import sys
|
||||
|
||||
import mock
|
||||
import prettytable
|
||||
|
||||
from glance.cmd import cache_manage
|
||||
from glance.common import exception
|
||||
import glance.common.utils
|
||||
import glance.image_cache.client
|
||||
from glance.tests import utils as test_utils
|
||||
|
||||
|
||||
@mock.patch('sys.stdout', mock.Mock())
|
||||
class TestGlanceCmdManage(test_utils.BaseTestCase):
|
||||
|
||||
def _run_command(self, cmd_args, return_code=None):
|
||||
"""Runs the cache-manage command.
|
||||
|
||||
:param cmd_args: The command line arguments.
|
||||
:param return_code: The expected return code of the command.
|
||||
"""
|
||||
testargs = ['cache_manage']
|
||||
testargs.extend(cmd_args)
|
||||
with mock.patch.object(sys, 'exit') as mock_exit:
|
||||
with mock.patch.object(sys, 'argv', testargs):
|
||||
try:
|
||||
cache_manage.main()
|
||||
except Exception:
|
||||
# See if we expected this failure
|
||||
if return_code is None:
|
||||
raise
|
||||
|
||||
if return_code is not None:
|
||||
mock_exit.called_with(return_code)
|
||||
|
||||
@mock.patch.object(argparse.ArgumentParser, 'print_help')
|
||||
def test_help(self, mock_print_help):
|
||||
self._run_command(['help'])
|
||||
self.assertEqual(1, mock_print_help.call_count)
|
||||
|
||||
@mock.patch.object(cache_manage, 'lookup_command')
|
||||
def test_help_with_command(self, mock_lookup_command):
|
||||
mock_lookup_command.return_value = cache_manage.print_help
|
||||
self._run_command(['help', 'list-cached'])
|
||||
mock_lookup_command.assert_any_call('help')
|
||||
mock_lookup_command.assert_any_call('list-cached')
|
||||
|
||||
def test_help_with_redundant_command(self):
|
||||
self._run_command(['help', 'list-cached', '1'], cache_manage.FAILURE)
|
||||
|
||||
@mock.patch.object(glance.image_cache.client.CacheClient,
|
||||
'get_cached_images')
|
||||
@mock.patch.object(prettytable.PrettyTable, 'add_row')
|
||||
def test_list_cached_images(self, mock_row_create, mock_images):
|
||||
"""
|
||||
Verify that list_cached() method correctly processes images with all
|
||||
filled data and images with not filled 'last_accessed' field.
|
||||
"""
|
||||
mock_images.return_value = [
|
||||
{'last_accessed': float(0),
|
||||
'last_modified': float(1378985797.124511),
|
||||
'image_id': '1', 'size': '128', 'hits': '1'},
|
||||
{'last_accessed': float(1378985797.124511),
|
||||
'last_modified': float(1378985797.124511),
|
||||
'image_id': '2', 'size': '255', 'hits': '2'}]
|
||||
self._run_command(['list-cached'], cache_manage.SUCCESS)
|
||||
self.assertEqual(len(mock_images.return_value),
|
||||
mock_row_create.call_count)
|
||||
|
||||
@mock.patch.object(glance.image_cache.client.CacheClient,
|
||||
'get_cached_images')
|
||||
def test_list_cached_images_empty(self, mock_images):
|
||||
"""
|
||||
Verify that list_cached() method handles a case when no images are
|
||||
cached without errors.
|
||||
"""
|
||||
self._run_command(['list-cached'], cache_manage.SUCCESS)
|
||||
|
||||
@mock.patch.object(glance.image_cache.client.CacheClient,
|
||||
'get_queued_images')
|
||||
@mock.patch.object(prettytable.PrettyTable, 'add_row')
|
||||
def test_list_queued_images(self, mock_row_create, mock_images):
|
||||
"""Verify that list_queued() method correctly processes images."""
|
||||
|
||||
mock_images.return_value = [
|
||||
{'image_id': '1'}, {'image_id': '2'}]
|
||||
# cache_manage.list_queued(mock.Mock())
|
||||
self._run_command(['list-queued'], cache_manage.SUCCESS)
|
||||
self.assertEqual(len(mock_images.return_value),
|
||||
mock_row_create.call_count)
|
||||
|
||||
@mock.patch.object(glance.image_cache.client.CacheClient,
|
||||
'get_queued_images')
|
||||
def test_list_queued_images_empty(self, mock_images):
|
||||
"""
|
||||
Verify that list_queued() method handles a case when no images were
|
||||
queued without errors.
|
||||
"""
|
||||
mock_images.return_value = []
|
||||
self._run_command(['list-queued'], cache_manage.SUCCESS)
|
||||
|
||||
def test_queue_image_without_index(self):
|
||||
self._run_command(['queue-image'], cache_manage.FAILURE)
|
||||
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
|
||||
def test_queue_image_not_forced_not_confirmed(self,
|
||||
mock_client, mock_confirm):
|
||||
# --force not set and queue confirmation return False.
|
||||
mock_confirm.return_value = False
|
||||
self._run_command(['queue-image', 'fakeimageid'], cache_manage.SUCCESS)
|
||||
self.assertFalse(mock_client.called)
|
||||
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
|
||||
def test_queue_image_not_forced_confirmed(self, mock_get_client,
|
||||
mock_confirm):
|
||||
# --force not set and confirmation return True.
|
||||
mock_confirm.return_value = True
|
||||
mock_client = mock.MagicMock()
|
||||
mock_get_client.return_value = mock_client
|
||||
|
||||
# verbose to cover additional condition and line
|
||||
self._run_command(['queue-image', 'fakeimageid', '-v'],
|
||||
cache_manage.SUCCESS)
|
||||
|
||||
self.assertTrue(mock_get_client.called)
|
||||
mock_client.queue_image_for_caching.assert_called_with('fakeimageid')
|
||||
|
||||
def test_delete_cached_image_without_index(self):
|
||||
self._run_command(['delete-cached-image'], cache_manage.FAILURE)
|
||||
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
|
||||
def test_delete_cached_image_not_forced_not_confirmed(self,
|
||||
mock_client,
|
||||
mock_confirm):
|
||||
# --force not set and confirmation return False.
|
||||
mock_confirm.return_value = False
|
||||
self._run_command(['delete-cached-image', 'fakeimageid'],
|
||||
cache_manage.SUCCESS)
|
||||
self.assertFalse(mock_client.called)
|
||||
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
|
||||
def test_delete_cached_image_not_forced_confirmed(self, mock_get_client,
|
||||
mock_confirm):
|
||||
# --force not set and confirmation return True.
|
||||
mock_confirm.return_value = True
|
||||
mock_client = mock.MagicMock()
|
||||
mock_get_client.return_value = mock_client
|
||||
|
||||
# verbose to cover additional condition and line
|
||||
self._run_command(['delete-cached-image', 'fakeimageid', '-v'],
|
||||
cache_manage.SUCCESS)
|
||||
|
||||
self.assertTrue(mock_get_client.called)
|
||||
mock_client.delete_cached_image.assert_called_with('fakeimageid')
|
||||
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
|
||||
def test_delete_cached_images_not_forced_not_confirmed(self,
|
||||
mock_client,
|
||||
mock_confirm):
|
||||
# --force not set and confirmation return False.
|
||||
mock_confirm.return_value = False
|
||||
self._run_command(['delete-all-cached-images'], cache_manage.SUCCESS)
|
||||
self.assertFalse(mock_client.called)
|
||||
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
|
||||
def test_delete_cached_images_not_forced_confirmed(self, mock_get_client,
|
||||
mock_confirm):
|
||||
# --force not set and confirmation return True.
|
||||
mock_confirm.return_value = True
|
||||
mock_client = mock.MagicMock()
|
||||
mock_get_client.return_value = mock_client
|
||||
|
||||
# verbose to cover additional condition and line
|
||||
self._run_command(['delete-all-cached-images', '-v'],
|
||||
cache_manage.SUCCESS)
|
||||
|
||||
self.assertTrue(mock_get_client.called)
|
||||
mock_client.delete_all_cached_images.assert_called()
|
||||
|
||||
def test_delete_queued_image_without_index(self):
|
||||
self._run_command(['delete-queued-image'], cache_manage.FAILURE)
|
||||
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
|
||||
def test_delete_queued_image_not_forced_not_confirmed(self,
|
||||
mock_client,
|
||||
mock_confirm):
|
||||
# --force not set and confirmation set to False.
|
||||
mock_confirm.return_value = False
|
||||
self._run_command(['delete-queued-image', 'img_id'],
|
||||
cache_manage.SUCCESS)
|
||||
self.assertFalse(mock_client.called)
|
||||
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
|
||||
def test_delete_queued_image_not_forced_confirmed(self, mock_get_client,
|
||||
mock_confirm):
|
||||
# --force not set and confirmation set to True.
|
||||
mock_confirm.return_value = True
|
||||
mock_client = mock.MagicMock()
|
||||
mock_get_client.return_value = mock_client
|
||||
|
||||
self._run_command(['delete-queued-image', 'img_id', '-v'],
|
||||
cache_manage.SUCCESS)
|
||||
|
||||
self.assertTrue(mock_get_client.called)
|
||||
mock_client.delete_queued_image.assert_called_with('img_id')
|
||||
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
|
||||
def test_delete_queued_images_not_forced_not_confirmed(self,
|
||||
mock_client,
|
||||
mock_confirm):
|
||||
# --force not set and confirmation set to False.
|
||||
mock_confirm.return_value = False
|
||||
self._run_command(['delete-all-queued-images'],
|
||||
cache_manage.SUCCESS)
|
||||
self.assertFalse(mock_client.called)
|
||||
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'user_confirm')
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
|
||||
def test_delete_queued_images_not_forced_confirmed(self, mock_get_client,
|
||||
mock_confirm):
|
||||
# --force not set and confirmation set to True.
|
||||
mock_confirm.return_value = True
|
||||
mock_client = mock.MagicMock()
|
||||
mock_get_client.return_value = mock_client
|
||||
|
||||
self._run_command(['delete-all-queued-images', '-v'],
|
||||
cache_manage.SUCCESS)
|
||||
|
||||
self.assertTrue(mock_get_client.called)
|
||||
mock_client.delete_all_queued_images.assert_called()
|
||||
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
|
||||
def test_catch_error_not_found(self, mock_function):
|
||||
mock_function.side_effect = exception.NotFound()
|
||||
|
||||
self.assertEqual(cache_manage.FAILURE,
|
||||
cache_manage.list_cached(mock.Mock()))
|
||||
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
|
||||
def test_catch_error_forbidden(self, mock_function):
|
||||
mock_function.side_effect = exception.Forbidden()
|
||||
|
||||
self.assertEqual(cache_manage.FAILURE,
|
||||
cache_manage.list_cached(mock.Mock()))
|
||||
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
|
||||
def test_catch_error_unhandled(self, mock_function):
|
||||
mock_function.side_effect = exception.Duplicate()
|
||||
my_mock = mock.Mock()
|
||||
my_mock.debug = False
|
||||
|
||||
self.assertEqual(cache_manage.FAILURE,
|
||||
cache_manage.list_cached(my_mock))
|
||||
|
||||
@mock.patch.object(glance.cmd.cache_manage, 'get_client')
|
||||
def test_catch_error_unhandled_debug_mode(self, mock_function):
|
||||
mock_function.side_effect = exception.Duplicate()
|
||||
my_mock = mock.Mock()
|
||||
my_mock.debug = True
|
||||
|
||||
self.assertRaises(exception.Duplicate,
|
||||
cache_manage.list_cached, my_mock)
|
||||
|
||||
def test_cache_manage_env(self):
|
||||
def_value = 'sometext12345678900987654321'
|
||||
self.assertNotEqual(def_value,
|
||||
cache_manage.env('PATH', default=def_value))
|
||||
|
||||
def test_cache_manage_env_default(self):
|
||||
def_value = 'sometext12345678900987654321'
|
||||
self.assertEqual(def_value,
|
||||
cache_manage.env('TMPVALUE1234567890',
|
||||
default=def_value))
|
||||
|
||||
def test_lookup_command_unsupported_command(self):
|
||||
self._run_command(['unsupported_command'], cache_manage.FAILURE)
|
|
@ -17,9 +17,7 @@ import testtools
|
|||
import webob
|
||||
|
||||
import glance.api.common
|
||||
from glance.common import config
|
||||
from glance.common import exception
|
||||
from glance.tests import utils as test_utils
|
||||
|
||||
|
||||
class SimpleIterator(object):
|
||||
|
@ -126,20 +124,3 @@ class TestSizeCheckedIter(testtools.TestCase):
|
|||
self.assertEqual('CD', next(checked_image))
|
||||
self.assertEqual('E', next(checked_image))
|
||||
self.assertRaises(exception.GlanceException, next, checked_image)
|
||||
|
||||
|
||||
class TestMalformedRequest(test_utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
"""Establish a clean test environment"""
|
||||
super(TestMalformedRequest, self).setUp()
|
||||
self.config(flavor='',
|
||||
group='paste_deploy',
|
||||
config_file='etc/glance-api-paste.ini')
|
||||
self.api = config.load_paste_app('glance-api')
|
||||
|
||||
def test_redirect_incomplete_url(self):
|
||||
"""Test Glance redirects /v# to /v#/ with correct Location header"""
|
||||
req = webob.Request.blank('/v1.1')
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(webob.exc.HTTPFound.code, res.status_int)
|
||||
self.assertEqual('http://localhost/v1/', res.location)
|
||||
|
|
|
@ -31,7 +31,6 @@ import six
|
|||
from six.moves import http_client as http
|
||||
import webob
|
||||
|
||||
from glance.api.v1 import router as router_v1
|
||||
from glance.api.v2 import router as router_v2
|
||||
from glance.common import exception
|
||||
from glance.common import utils
|
||||
|
@ -219,24 +218,6 @@ class RequestTest(test_utils.BaseTestCase):
|
|||
def test_http_error_response_codes(self):
|
||||
sample_id, member_id, tag_val, task_id = 'abc', '123', '1', '2'
|
||||
|
||||
"""Makes sure v1 unallowed methods return 405"""
|
||||
unallowed_methods = [
|
||||
('/images', ['PUT', 'DELETE', 'HEAD', 'PATCH']),
|
||||
('/images/detail', ['POST', 'PUT', 'DELETE', 'PATCH']),
|
||||
('/images/%s' % sample_id, ['POST', 'PATCH']),
|
||||
('/images/%s/members' % sample_id,
|
||||
['POST', 'DELETE', 'HEAD', 'PATCH']),
|
||||
('/images/%s/members/%s' % (sample_id, member_id),
|
||||
['POST', 'HEAD', 'PATCH']),
|
||||
]
|
||||
api = test_utils.FakeAuthMiddleware(router_v1.API(routes.Mapper()))
|
||||
for uri, methods in unallowed_methods:
|
||||
for method in methods:
|
||||
req = webob.Request.blank(uri)
|
||||
req.method = method
|
||||
res = req.get_response(api)
|
||||
self.assertEqual(http.METHOD_NOT_ALLOWED, res.status_int)
|
||||
|
||||
"""Makes sure v2 unallowed methods return 405"""
|
||||
unallowed_methods = [
|
||||
('/schemas/image', ['POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']),
|
||||
|
|
|
@ -24,7 +24,6 @@ import glance.api.middleware.cache
|
|||
import glance.api.policy
|
||||
from glance.common import exception
|
||||
from glance import context
|
||||
import glance.registry.client.v1.api as registry
|
||||
from glance.tests.unit import base
|
||||
from glance.tests.unit import utils as unit_test_utils
|
||||
|
||||
|
@ -42,21 +41,6 @@ class ImageStub(object):
|
|||
|
||||
|
||||
class TestCacheMiddlewareURLMatching(testtools.TestCase):
|
||||
def test_v1_no_match_detail(self):
|
||||
req = webob.Request.blank('/v1/images/detail')
|
||||
out = glance.api.middleware.cache.CacheFilter._match_request(req)
|
||||
self.assertIsNone(out)
|
||||
|
||||
def test_v1_no_match_detail_with_query_params(self):
|
||||
req = webob.Request.blank('/v1/images/detail?limit=10')
|
||||
out = glance.api.middleware.cache.CacheFilter._match_request(req)
|
||||
self.assertIsNone(out)
|
||||
|
||||
def test_v1_match_id_with_query_param(self):
|
||||
req = webob.Request.blank('/v1/images/asdf?ping=pong')
|
||||
out = glance.api.middleware.cache.CacheFilter._match_request(req)
|
||||
self.assertEqual(('v1', 'GET', 'asdf'), out)
|
||||
|
||||
def test_v2_match_id(self):
|
||||
req = webob.Request.blank('/v2/images/asdf/file')
|
||||
out = glance.api.middleware.cache.CacheFilter._match_request(req)
|
||||
|
@ -180,139 +164,6 @@ class TestCacheMiddlewareProcessRequest(base.IsolatedUnitTest):
|
|||
enforcer.set_rules(rules, overwrite=True)
|
||||
return enforcer
|
||||
|
||||
def test_v1_deleted_image_fetch(self):
|
||||
"""
|
||||
Test for determining that when an admin tries to download a deleted
|
||||
image it returns 404 Not Found error.
|
||||
"""
|
||||
def dummy_img_iterator():
|
||||
for i in range(3):
|
||||
yield i
|
||||
|
||||
image_id = 'test1'
|
||||
image_meta = {
|
||||
'id': image_id,
|
||||
'name': 'fake_image',
|
||||
'status': 'deleted',
|
||||
'created_at': '',
|
||||
'min_disk': '10G',
|
||||
'min_ram': '1024M',
|
||||
'protected': False,
|
||||
'locations': '',
|
||||
'checksum': 'c1234',
|
||||
'owner': '',
|
||||
'disk_format': 'raw',
|
||||
'container_format': 'bare',
|
||||
'size': '123456789',
|
||||
'virtual_size': '123456789',
|
||||
'is_public': 'public',
|
||||
'deleted': True,
|
||||
'updated_at': '',
|
||||
'properties': {},
|
||||
}
|
||||
request = webob.Request.blank('/v1/images/%s' % image_id)
|
||||
request.context = context.RequestContext()
|
||||
cache_filter = ProcessRequestTestCacheFilter()
|
||||
self.assertRaises(exception.NotFound, cache_filter._process_v1_request,
|
||||
request, image_id, dummy_img_iterator, image_meta)
|
||||
|
||||
def test_process_v1_request_for_deleted_but_cached_image(self):
|
||||
"""
|
||||
Test for determining image is deleted from cache when it is not found
|
||||
in Glance Registry.
|
||||
"""
|
||||
def fake_process_v1_request(request, image_id, image_iterator,
|
||||
image_meta):
|
||||
raise exception.ImageNotFound()
|
||||
|
||||
def fake_get_v1_image_metadata(request, image_id):
|
||||
return {'status': 'active', 'properties': {}}
|
||||
|
||||
image_id = 'test1'
|
||||
request = webob.Request.blank('/v1/images/%s' % image_id)
|
||||
request.context = context.RequestContext()
|
||||
|
||||
cache_filter = ProcessRequestTestCacheFilter()
|
||||
self.stubs.Set(cache_filter, '_get_v1_image_metadata',
|
||||
fake_get_v1_image_metadata)
|
||||
self.stubs.Set(cache_filter, '_process_v1_request',
|
||||
fake_process_v1_request)
|
||||
cache_filter.process_request(request)
|
||||
self.assertIn(image_id, cache_filter.cache.deleted_images)
|
||||
|
||||
def test_v1_process_request_image_fetch(self):
|
||||
|
||||
def dummy_img_iterator():
|
||||
for i in range(3):
|
||||
yield i
|
||||
|
||||
image_id = 'test1'
|
||||
image_meta = {
|
||||
'id': image_id,
|
||||
'name': 'fake_image',
|
||||
'status': 'active',
|
||||
'created_at': '',
|
||||
'min_disk': '10G',
|
||||
'min_ram': '1024M',
|
||||
'protected': False,
|
||||
'locations': '',
|
||||
'checksum': 'c1234',
|
||||
'owner': '',
|
||||
'disk_format': 'raw',
|
||||
'container_format': 'bare',
|
||||
'size': '123456789',
|
||||
'virtual_size': '123456789',
|
||||
'is_public': 'public',
|
||||
'deleted': False,
|
||||
'updated_at': '',
|
||||
'properties': {},
|
||||
}
|
||||
request = webob.Request.blank('/v1/images/%s' % image_id)
|
||||
request.context = context.RequestContext()
|
||||
cache_filter = ProcessRequestTestCacheFilter()
|
||||
actual = cache_filter._process_v1_request(
|
||||
request, image_id, dummy_img_iterator, image_meta)
|
||||
self.assertTrue(actual)
|
||||
|
||||
def test_v1_remove_location_image_fetch(self):
|
||||
|
||||
class CheckNoLocationDataSerializer(object):
|
||||
def show(self, response, raw_response):
|
||||
return 'location_data' in raw_response['image_meta']
|
||||
|
||||
def dummy_img_iterator():
|
||||
for i in range(3):
|
||||
yield i
|
||||
|
||||
image_id = 'test1'
|
||||
image_meta = {
|
||||
'id': image_id,
|
||||
'name': 'fake_image',
|
||||
'status': 'active',
|
||||
'created_at': '',
|
||||
'min_disk': '10G',
|
||||
'min_ram': '1024M',
|
||||
'protected': False,
|
||||
'locations': '',
|
||||
'checksum': 'c1234',
|
||||
'owner': '',
|
||||
'disk_format': 'raw',
|
||||
'container_format': 'bare',
|
||||
'size': '123456789',
|
||||
'virtual_size': '123456789',
|
||||
'is_public': 'public',
|
||||
'deleted': False,
|
||||
'updated_at': '',
|
||||
'properties': {},
|
||||
}
|
||||
request = webob.Request.blank('/v1/images/%s' % image_id)
|
||||
request.context = context.RequestContext()
|
||||
cache_filter = ProcessRequestTestCacheFilter()
|
||||
cache_filter.serializer = CheckNoLocationDataSerializer()
|
||||
actual = cache_filter._process_v1_request(
|
||||
request, image_id, dummy_img_iterator, image_meta)
|
||||
self.assertFalse(actual)
|
||||
|
||||
def test_verify_metadata_deleted_image(self):
|
||||
"""
|
||||
Test verify_metadata raises exception.NotFound for a deleted image
|
||||
|
@ -439,119 +290,6 @@ class TestCacheMiddlewareProcessRequest(base.IsolatedUnitTest):
|
|||
self.assertRaises(webob.exc.HTTPForbidden,
|
||||
cache_filter.process_request, request)
|
||||
|
||||
def test_v1_process_request_download_restricted(self):
|
||||
"""
|
||||
Test process_request for v1 api where _member_ role not able to
|
||||
download the image with custom property.
|
||||
"""
|
||||
image_id = 'test1'
|
||||
|
||||
def fake_get_v1_image_metadata(*args, **kwargs):
|
||||
return {
|
||||
'id': image_id,
|
||||
'name': 'fake_image',
|
||||
'status': 'active',
|
||||
'created_at': '',
|
||||
'min_disk': '10G',
|
||||
'min_ram': '1024M',
|
||||
'protected': False,
|
||||
'locations': '',
|
||||
'checksum': 'c1234',
|
||||
'owner': '',
|
||||
'disk_format': 'raw',
|
||||
'container_format': 'bare',
|
||||
'size': '123456789',
|
||||
'virtual_size': '123456789',
|
||||
'is_public': 'public',
|
||||
'deleted': False,
|
||||
'updated_at': '',
|
||||
'x_test_key': 'test_1234'
|
||||
}
|
||||
|
||||
enforcer = self._enforcer_from_rules({
|
||||
"restricted":
|
||||
"not ('test_1234':%(x_test_key)s and role:_member_)",
|
||||
"download_image": "role:admin or rule:restricted"
|
||||
})
|
||||
|
||||
request = webob.Request.blank('/v1/images/%s' % image_id)
|
||||
request.context = context.RequestContext(roles=['_member_'])
|
||||
cache_filter = ProcessRequestTestCacheFilter()
|
||||
cache_filter._get_v1_image_metadata = fake_get_v1_image_metadata
|
||||
cache_filter.policy = enforcer
|
||||
self.assertRaises(webob.exc.HTTPForbidden,
|
||||
cache_filter.process_request, request)
|
||||
|
||||
def test_v1_process_request_download_permitted(self):
|
||||
"""
|
||||
Test process_request for v1 api where member role able to
|
||||
download the image with custom property.
|
||||
"""
|
||||
image_id = 'test1'
|
||||
|
||||
def fake_get_v1_image_metadata(*args, **kwargs):
|
||||
return {
|
||||
'id': image_id,
|
||||
'name': 'fake_image',
|
||||
'status': 'active',
|
||||
'created_at': '',
|
||||
'min_disk': '10G',
|
||||
'min_ram': '1024M',
|
||||
'protected': False,
|
||||
'locations': '',
|
||||
'checksum': 'c1234',
|
||||
'owner': '',
|
||||
'disk_format': 'raw',
|
||||
'container_format': 'bare',
|
||||
'size': '123456789',
|
||||
'virtual_size': '123456789',
|
||||
'is_public': 'public',
|
||||
'deleted': False,
|
||||
'updated_at': '',
|
||||
'x_test_key': 'test_1234'
|
||||
}
|
||||
|
||||
request = webob.Request.blank('/v1/images/%s' % image_id)
|
||||
request.context = context.RequestContext(roles=['member'])
|
||||
cache_filter = ProcessRequestTestCacheFilter()
|
||||
cache_filter._get_v1_image_metadata = fake_get_v1_image_metadata
|
||||
|
||||
rules = {
|
||||
"restricted":
|
||||
"not ('test_1234':%(x_test_key)s and role:_member_)",
|
||||
"download_image": "role:admin or rule:restricted"
|
||||
}
|
||||
self.set_policy_rules(rules)
|
||||
cache_filter.policy = glance.api.policy.Enforcer()
|
||||
actual = cache_filter.process_request(request)
|
||||
self.assertTrue(actual)
|
||||
|
||||
def test_v1_process_request_image_meta_not_found(self):
|
||||
"""
|
||||
Test process_request for v1 api where registry raises NotFound
|
||||
exception as image metadata not found.
|
||||
"""
|
||||
image_id = 'test1'
|
||||
|
||||
def fake_get_v1_image_metadata(*args, **kwargs):
|
||||
raise exception.NotFound()
|
||||
|
||||
request = webob.Request.blank('/v1/images/%s' % image_id)
|
||||
request.context = context.RequestContext(roles=['_member_'])
|
||||
cache_filter = ProcessRequestTestCacheFilter()
|
||||
self.stubs.Set(registry, 'get_image_metadata',
|
||||
fake_get_v1_image_metadata)
|
||||
|
||||
rules = {
|
||||
"restricted":
|
||||
"not ('test_1234':%(x_test_key)s and role:_member_)",
|
||||
"download_image": "role:admin or rule:restricted"
|
||||
}
|
||||
self.set_policy_rules(rules)
|
||||
cache_filter.policy = glance.api.policy.Enforcer()
|
||||
self.assertRaises(webob.exc.HTTPNotFound,
|
||||
cache_filter.process_request, request)
|
||||
|
||||
def test_v2_process_request_download_restricted(self):
|
||||
"""
|
||||
Test process_request for v2 api where _member_ role not able to
|
||||
|
@ -614,15 +352,6 @@ class TestCacheMiddlewareProcessRequest(base.IsolatedUnitTest):
|
|||
|
||||
|
||||
class TestCacheMiddlewareProcessResponse(base.IsolatedUnitTest):
|
||||
def test_process_v1_DELETE_response(self):
|
||||
image_id = 'test1'
|
||||
request = webob.Request.blank('/v1/images/%s' % image_id)
|
||||
request.context = context.RequestContext()
|
||||
cache_filter = ProcessRequestTestCacheFilter()
|
||||
headers = {"x-image-meta-deleted": True}
|
||||
resp = webob.Response(request=request, headers=headers)
|
||||
actual = cache_filter._process_DELETE_response(resp, image_id)
|
||||
self.assertEqual(resp, actual)
|
||||
|
||||
def test_get_status_code(self):
|
||||
headers = {"x-image-meta-deleted": True}
|
||||
|
@ -631,181 +360,6 @@ class TestCacheMiddlewareProcessResponse(base.IsolatedUnitTest):
|
|||
actual = cache_filter.get_status_code(resp)
|
||||
self.assertEqual(http.OK, actual)
|
||||
|
||||
def test_process_response(self):
|
||||
def fake_fetch_request_info(*args, **kwargs):
|
||||
return ('test1', 'GET', 'v1')
|
||||
|
||||
def fake_get_v1_image_metadata(*args, **kwargs):
|
||||
return {'properties': {}}
|
||||
|
||||
cache_filter = ProcessRequestTestCacheFilter()
|
||||
cache_filter._fetch_request_info = fake_fetch_request_info
|
||||
cache_filter._get_v1_image_metadata = fake_get_v1_image_metadata
|
||||
image_id = 'test1'
|
||||
request = webob.Request.blank('/v1/images/%s' % image_id)
|
||||
request.context = context.RequestContext()
|
||||
headers = {"x-image-meta-deleted": True}
|
||||
resp = webob.Response(request=request, headers=headers)
|
||||
actual = cache_filter.process_response(resp)
|
||||
self.assertEqual(resp, actual)
|
||||
|
||||
def test_process_response_without_download_image_policy(self):
|
||||
"""
|
||||
Test for cache middleware raise webob.exc.HTTPForbidden directly
|
||||
when request context has not 'download_image' role.
|
||||
"""
|
||||
def fake_fetch_request_info(*args, **kwargs):
|
||||
return ('test1', 'GET', 'v1')
|
||||
|
||||
def fake_get_v1_image_metadata(*args, **kwargs):
|
||||
return {'properties': {}}
|
||||
|
||||
cache_filter = ProcessRequestTestCacheFilter()
|
||||
cache_filter._fetch_request_info = fake_fetch_request_info
|
||||
cache_filter._get_v1_image_metadata = fake_get_v1_image_metadata
|
||||
rules = {'download_image': '!'}
|
||||
self.set_policy_rules(rules)
|
||||
cache_filter.policy = glance.api.policy.Enforcer()
|
||||
|
||||
image_id = 'test1'
|
||||
request = webob.Request.blank('/v1/images/%s' % image_id)
|
||||
request.context = context.RequestContext()
|
||||
resp = webob.Response(request=request)
|
||||
self.assertRaises(webob.exc.HTTPForbidden,
|
||||
cache_filter.process_response, resp)
|
||||
self.assertEqual([b''], resp.app_iter)
|
||||
|
||||
def test_v1_process_response_download_restricted(self):
|
||||
"""
|
||||
Test process_response for v1 api where _member_ role not able to
|
||||
download the image with custom property.
|
||||
"""
|
||||
image_id = 'test1'
|
||||
|
||||
def fake_fetch_request_info(*args, **kwargs):
|
||||
return ('test1', 'GET', 'v1')
|
||||
|
||||
def fake_get_v1_image_metadata(*args, **kwargs):
|
||||
return {
|
||||
'id': image_id,
|
||||
'name': 'fake_image',
|
||||
'status': 'active',
|
||||
'created_at': '',
|
||||
'min_disk': '10G',
|
||||
'min_ram': '1024M',
|
||||
'protected': False,
|
||||
'locations': '',
|
||||
'checksum': 'c1234',
|
||||
'owner': '',
|
||||
'disk_format': 'raw',
|
||||
'container_format': 'bare',
|
||||
'size': '123456789',
|
||||
'virtual_size': '123456789',
|
||||
'is_public': 'public',
|
||||
'deleted': False,
|
||||
'updated_at': '',
|
||||
'x_test_key': 'test_1234'
|
||||
}
|
||||
|
||||
cache_filter = ProcessRequestTestCacheFilter()
|
||||
cache_filter._fetch_request_info = fake_fetch_request_info
|
||||
cache_filter._get_v1_image_metadata = fake_get_v1_image_metadata
|
||||
rules = {
|
||||
"restricted":
|
||||
"not ('test_1234':%(x_test_key)s and role:_member_)",
|
||||
"download_image": "role:admin or rule:restricted"
|
||||
}
|
||||
self.set_policy_rules(rules)
|
||||
cache_filter.policy = glance.api.policy.Enforcer()
|
||||
|
||||
request = webob.Request.blank('/v1/images/%s' % image_id)
|
||||
request.context = context.RequestContext(roles=['_member_'])
|
||||
resp = webob.Response(request=request)
|
||||
self.assertRaises(webob.exc.HTTPForbidden,
|
||||
cache_filter.process_response, resp)
|
||||
|
||||
def test_v1_process_response_download_permitted(self):
|
||||
"""
|
||||
Test process_response for v1 api where member role able to
|
||||
download the image with custom property.
|
||||
"""
|
||||
image_id = 'test1'
|
||||
|
||||
def fake_fetch_request_info(*args, **kwargs):
|
||||
return ('test1', 'GET', 'v1')
|
||||
|
||||
def fake_get_v1_image_metadata(*args, **kwargs):
|
||||
return {
|
||||
'id': image_id,
|
||||
'name': 'fake_image',
|
||||
'status': 'active',
|
||||
'created_at': '',
|
||||
'min_disk': '10G',
|
||||
'min_ram': '1024M',
|
||||
'protected': False,
|
||||
'locations': '',
|
||||
'checksum': 'c1234',
|
||||
'owner': '',
|
||||
'disk_format': 'raw',
|
||||
'container_format': 'bare',
|
||||
'size': '123456789',
|
||||
'virtual_size': '123456789',
|
||||
'is_public': 'public',
|
||||
'deleted': False,
|
||||
'updated_at': '',
|
||||
'x_test_key': 'test_1234'
|
||||
}
|
||||
|
||||
cache_filter = ProcessRequestTestCacheFilter()
|
||||
cache_filter._fetch_request_info = fake_fetch_request_info
|
||||
cache_filter._get_v1_image_metadata = fake_get_v1_image_metadata
|
||||
rules = {
|
||||
"restricted":
|
||||
"not ('test_1234':%(x_test_key)s and role:_member_)",
|
||||
"download_image": "role:admin or rule:restricted"
|
||||
}
|
||||
self.set_policy_rules(rules)
|
||||
cache_filter.policy = glance.api.policy.Enforcer()
|
||||
|
||||
request = webob.Request.blank('/v1/images/%s' % image_id)
|
||||
request.context = context.RequestContext(roles=['member'])
|
||||
resp = webob.Response(request=request)
|
||||
actual = cache_filter.process_response(resp)
|
||||
self.assertEqual(resp, actual)
|
||||
|
||||
def test_v1_process_response_image_meta_not_found(self):
|
||||
"""
|
||||
Test process_response for v1 api where registry raises NotFound
|
||||
exception as image metadata not found.
|
||||
"""
|
||||
image_id = 'test1'
|
||||
|
||||
def fake_fetch_request_info(*args, **kwargs):
|
||||
return ('test1', 'GET', 'v1')
|
||||
|
||||
def fake_get_v1_image_metadata(*args, **kwargs):
|
||||
raise exception.NotFound()
|
||||
|
||||
cache_filter = ProcessRequestTestCacheFilter()
|
||||
cache_filter._fetch_request_info = fake_fetch_request_info
|
||||
|
||||
self.stubs.Set(registry, 'get_image_metadata',
|
||||
fake_get_v1_image_metadata)
|
||||
|
||||
rules = {
|
||||
"restricted":
|
||||
"not ('test_1234':%(x_test_key)s and role:_member_)",
|
||||
"download_image": "role:admin or rule:restricted"
|
||||
}
|
||||
self.set_policy_rules(rules)
|
||||
cache_filter.policy = glance.api.policy.Enforcer()
|
||||
|
||||
request = webob.Request.blank('/v1/images/%s' % image_id)
|
||||
request.context = context.RequestContext(roles=['_member_'])
|
||||
resp = webob.Response(request=request)
|
||||
self.assertRaises(webob.exc.HTTPNotFound,
|
||||
cache_filter.process_response, resp)
|
||||
|
||||
def test_v2_process_response_download_restricted(self):
|
||||
"""
|
||||
Test process_response for v2 api where _member_ role not able to
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
# Copyright 2013 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.
|
||||
import os
|
||||
|
||||
import mock
|
||||
|
||||
from glance.common import exception
|
||||
from glance.image_cache import client
|
||||
from glance.tests import utils
|
||||
|
||||
|
||||
class CacheClientTestCase(utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(CacheClientTestCase, self).setUp()
|
||||
self.client = client.CacheClient('test_host')
|
||||
self.client.do_request = mock.Mock()
|
||||
|
||||
def test_delete_cached_image(self):
|
||||
self.client.do_request.return_value = utils.FakeHTTPResponse()
|
||||
self.assertTrue(self.client.delete_cached_image('test_id'))
|
||||
self.client.do_request.assert_called_with("DELETE",
|
||||
"/cached_images/test_id")
|
||||
|
||||
def test_get_cached_images(self):
|
||||
expected_data = b'{"cached_images": "some_images"}'
|
||||
self.client.do_request.return_value = utils.FakeHTTPResponse(
|
||||
data=expected_data)
|
||||
self.assertEqual("some_images", self.client.get_cached_images())
|
||||
self.client.do_request.assert_called_with("GET", "/cached_images")
|
||||
|
||||
def test_get_queued_images(self):
|
||||
expected_data = b'{"queued_images": "some_images"}'
|
||||
self.client.do_request.return_value = utils.FakeHTTPResponse(
|
||||
data=expected_data)
|
||||
self.assertEqual("some_images", self.client.get_queued_images())
|
||||
self.client.do_request.assert_called_with("GET", "/queued_images")
|
||||
|
||||
def test_delete_all_cached_images(self):
|
||||
expected_data = b'{"num_deleted": 4}'
|
||||
self.client.do_request.return_value = utils.FakeHTTPResponse(
|
||||
data=expected_data)
|
||||
self.assertEqual(4, self.client.delete_all_cached_images())
|
||||
self.client.do_request.assert_called_with("DELETE", "/cached_images")
|
||||
|
||||
def test_queue_image_for_caching(self):
|
||||
self.client.do_request.return_value = utils.FakeHTTPResponse()
|
||||
self.assertTrue(self.client.queue_image_for_caching('test_id'))
|
||||
self.client.do_request.assert_called_with("PUT",
|
||||
"/queued_images/test_id")
|
||||
|
||||
def test_delete_queued_image(self):
|
||||
self.client.do_request.return_value = utils.FakeHTTPResponse()
|
||||
self.assertTrue(self.client.delete_queued_image('test_id'))
|
||||
self.client.do_request.assert_called_with("DELETE",
|
||||
"/queued_images/test_id")
|
||||
|
||||
def test_delete_all_queued_images(self):
|
||||
expected_data = b'{"num_deleted": 4}'
|
||||
self.client.do_request.return_value = utils.FakeHTTPResponse(
|
||||
data=expected_data)
|
||||
self.assertEqual(4, self.client.delete_all_queued_images())
|
||||
self.client.do_request.assert_called_with("DELETE", "/queued_images")
|
||||
|
||||
|
||||
class GetClientTestCase(utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(GetClientTestCase, self).setUp()
|
||||
self.host = 'test_host'
|
||||
self.env = os.environ.copy()
|
||||
os.environ.clear()
|
||||
|
||||
def tearDown(self):
|
||||
os.environ = self.env
|
||||
super(GetClientTestCase, self).tearDown()
|
||||
|
||||
def test_get_client_host_only(self):
|
||||
expected_creds = {
|
||||
'username': None,
|
||||
'password': None,
|
||||
'tenant': None,
|
||||
'auth_url': None,
|
||||
'strategy': 'noauth',
|
||||
'region': None
|
||||
}
|
||||
self.assertEqual(expected_creds, client.get_client(self.host).creds)
|
||||
|
||||
def test_get_client_all_creds(self):
|
||||
expected_creds = {
|
||||
'username': 'name',
|
||||
'password': 'pass',
|
||||
'tenant': 'ten',
|
||||
'auth_url': 'url',
|
||||
'strategy': 'keystone',
|
||||
'region': 'reg'
|
||||
}
|
||||
creds = client.get_client(
|
||||
self.host,
|
||||
username='name',
|
||||
password='pass',
|
||||
tenant='ten',
|
||||
auth_url='url',
|
||||
auth_strategy='strategy',
|
||||
region='reg'
|
||||
).creds
|
||||
self.assertEqual(expected_creds, creds)
|
||||
|
||||
def test_get_client_using_provided_host(self):
|
||||
cli = client.get_client(self.host)
|
||||
cli._do_request = mock.MagicMock()
|
||||
cli.configure_from_url = mock.MagicMock()
|
||||
cli.auth_plugin.management_url = mock.MagicMock()
|
||||
cli.do_request("GET", "/queued_images")
|
||||
self.assertFalse(cli.configure_from_url.called)
|
||||
self.assertFalse(client.get_client(self.host).configure_via_auth)
|
||||
|
||||
def test_get_client_client_configuration_error(self):
|
||||
self.assertRaises(exception.ClientConfigurationError,
|
||||
client.get_client, self.host, username='name',
|
||||
password='pass', tenant='ten',
|
||||
auth_strategy='keystone', region='reg')
|
|
@ -71,18 +71,6 @@ class VersionsTest(base.IsolatedUnitTest):
|
|||
'links': [{'rel': 'self',
|
||||
'href': '%s/v2/' % url}],
|
||||
},
|
||||
{
|
||||
'id': 'v1.1',
|
||||
'status': 'DEPRECATED',
|
||||
'links': [{'rel': 'self',
|
||||
'href': '%s/v1/' % url}],
|
||||
},
|
||||
{
|
||||
'id': 'v1.0',
|
||||
'status': 'DEPRECATED',
|
||||
'links': [{'rel': 'self',
|
||||
'href': '%s/v1/' % url}],
|
||||
},
|
||||
]
|
||||
return versions
|
||||
|
||||
|
@ -142,27 +130,6 @@ class VersionNegotiationTest(base.IsolatedUnitTest):
|
|||
super(VersionNegotiationTest, self).setUp()
|
||||
self.middleware = version_negotiation.VersionNegotiationFilter(None)
|
||||
|
||||
def test_request_url_v1(self):
|
||||
request = webob.Request.blank('/v1/images')
|
||||
self.middleware.process_request(request)
|
||||
self.assertEqual('/v1/images', request.path_info)
|
||||
|
||||
def test_request_url_v1_0(self):
|
||||
request = webob.Request.blank('/v1.0/images')
|
||||
self.middleware.process_request(request)
|
||||
self.assertEqual('/v1/images', request.path_info)
|
||||
|
||||
def test_request_url_v1_1(self):
|
||||
request = webob.Request.blank('/v1.1/images')
|
||||
self.middleware.process_request(request)
|
||||
self.assertEqual('/v1/images', request.path_info)
|
||||
|
||||
def test_request_accept_v1(self):
|
||||
request = webob.Request.blank('/images')
|
||||
request.headers = {'accept': 'application/vnd.openstack.images-v1'}
|
||||
self.middleware.process_request(request)
|
||||
self.assertEqual('/v1/images', request.path_info)
|
||||
|
||||
def test_request_url_v2(self):
|
||||
request = webob.Request.blank('/v2/images')
|
||||
self.middleware.process_request(request)
|
||||
|
|
Loading…
Reference in New Issue