merged glance trunk
This commit is contained in:
commit
3eeb9fec9c
|
@ -194,7 +194,7 @@ EXAMPLES
|
||||||
print "Returned the following metadata for the new image:"
|
print "Returned the following metadata for the new image:"
|
||||||
for k, v in sorted(image_meta.items()):
|
for k, v in sorted(image_meta.items()):
|
||||||
print " %(k)30s => %(v)s" % locals()
|
print " %(k)30s => %(v)s" % locals()
|
||||||
except client.ClientConnectionError, e:
|
except exception.ClientConnectionError, e:
|
||||||
host = options.host
|
host = options.host
|
||||||
port = options.port
|
port = options.port
|
||||||
print ("Failed to connect to the Glance API server "
|
print ("Failed to connect to the Glance API server "
|
||||||
|
@ -551,7 +551,8 @@ def print_help(options, args):
|
||||||
|
|
||||||
|
|
||||||
def user_confirm(prompt, default=False):
|
def user_confirm(prompt, default=False):
|
||||||
"""Yes/No question dialog with user.
|
"""
|
||||||
|
Yes/No question dialog with user.
|
||||||
|
|
||||||
:param prompt: question/statement to present to user (string)
|
:param prompt: question/statement to present to user (string)
|
||||||
:param default: boolean value to return if empty string
|
:param default: boolean value to return if empty string
|
||||||
|
|
|
@ -113,7 +113,36 @@ in size and in the `saving` status.
|
||||||
c = Client("glance.example.com", 9292)
|
c = Client("glance.example.com", 9292)
|
||||||
|
|
||||||
filters = {'status': 'saving', 'size_max': (5 * 1024 * 1024 * 1024)}
|
filters = {'status': 'saving', 'size_max': (5 * 1024 * 1024 * 1024)}
|
||||||
print c.get_images_detailed(filters)
|
print c.get_images_detailed(filters=filters)
|
||||||
|
|
||||||
|
Sorting Images Returned via ``get_images()`` and ``get_images_detailed()``
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Two parameters are available to sort the list of images returned by
|
||||||
|
these methods.
|
||||||
|
|
||||||
|
* ``sort_key: KEY``
|
||||||
|
|
||||||
|
Images can be ordered by the image attribute ``KEY``. Acceptable values:
|
||||||
|
``id``, ``name``, ``status``, ``container_format``, ``disk_format``,
|
||||||
|
``created_at`` (default) and ``updated_at``.
|
||||||
|
|
||||||
|
* ``sort_dir: DIR``
|
||||||
|
|
||||||
|
The direction of the sort may be defined by ``DIR``. Accepted values:
|
||||||
|
``asc`` for ascending or ``desc`` (default) for descending.
|
||||||
|
|
||||||
|
The following example will return a list of images sorted alphabetically
|
||||||
|
by name in ascending order.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from glance.client import Client
|
||||||
|
|
||||||
|
c = Client("glance.example.com", 9292)
|
||||||
|
|
||||||
|
print c.get_images(sort_key='name', sort_dir='asc')
|
||||||
|
|
||||||
|
|
||||||
Requesting Detailed Metadata on a Specific Image
|
Requesting Detailed Metadata on a Specific Image
|
||||||
------------------------------------------------
|
------------------------------------------------
|
||||||
|
|
|
@ -129,6 +129,20 @@ list details these query parameters.
|
||||||
|
|
||||||
Filters images having a ``size`` attribute less than or equal to ``BYTES``
|
Filters images having a ``size`` attribute less than or equal to ``BYTES``
|
||||||
|
|
||||||
|
These two resources also accept sort parameters:
|
||||||
|
|
||||||
|
* ``sort_key=KEY``
|
||||||
|
|
||||||
|
Results will be ordered by the specified image attribute ``KEY``. Accepted
|
||||||
|
values include ``id``, ``name``, ``status``, ``disk_format``,
|
||||||
|
``container_format``, ``size``, ``created_at`` (default) and ``updated_at``.
|
||||||
|
|
||||||
|
* ``sort_dir=DIR``
|
||||||
|
|
||||||
|
Results will be sorted in the direction ``DIR``. Accepted values are ``asc``
|
||||||
|
for ascending or ``desc`` (default) for descending.
|
||||||
|
|
||||||
|
|
||||||
Requesting Detailed Metadata on a Specific Image
|
Requesting Detailed Metadata on a Specific Image
|
||||||
------------------------------------------------
|
------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,20 @@ list details these query parameters.
|
||||||
|
|
||||||
Filters images having a ``size`` attribute less than or equal to ``BYTES``
|
Filters images having a ``size`` attribute less than or equal to ``BYTES``
|
||||||
|
|
||||||
|
These two resources also accept sort parameters:
|
||||||
|
|
||||||
|
* ``sort_key=KEY``
|
||||||
|
|
||||||
|
Results will be ordered by the specified image attribute ``KEY``. Accepted
|
||||||
|
values include ``id``, ``name``, ``status``, ``disk_format``,
|
||||||
|
``container_format``, ``size``, ``created_at`` (default) and ``updated_at``.
|
||||||
|
|
||||||
|
* ``sort_dir=DIR``
|
||||||
|
|
||||||
|
Results will be sorted in the direction ``DIR``. Accepted values are ``asc``
|
||||||
|
for ascending or ``desc`` (default) for descending.
|
||||||
|
|
||||||
|
|
||||||
``POST /images``
|
``POST /images``
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ class VersionNegotiationFilter(wsgi.Middleware):
|
||||||
req.environ['api.minor_version'])
|
req.environ['api.minor_version'])
|
||||||
return self.versions_app
|
return self.versions_app
|
||||||
|
|
||||||
accept = req.headers['Accept']
|
accept = str(req.accept)
|
||||||
if accept.startswith('application/vnd.openstack.images-'):
|
if accept.startswith('application/vnd.openstack.images-'):
|
||||||
token_loc = len('application/vnd.openstack.images-')
|
token_loc = len('application/vnd.openstack.images-')
|
||||||
accept_version = accept[token_loc:]
|
accept_version = accept[token_loc:]
|
||||||
|
|
|
@ -45,6 +45,8 @@ logger = logging.getLogger('glance.api.v1.images')
|
||||||
SUPPORTED_FILTERS = ['name', 'status', 'container_format', 'disk_format',
|
SUPPORTED_FILTERS = ['name', 'status', 'container_format', 'disk_format',
|
||||||
'size_min', 'size_max']
|
'size_min', 'size_max']
|
||||||
|
|
||||||
|
SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir')
|
||||||
|
|
||||||
|
|
||||||
class Controller(object):
|
class Controller(object):
|
||||||
|
|
||||||
|
@ -92,15 +94,12 @@ class Controller(object):
|
||||||
'size': <SIZE>}, ...
|
'size': <SIZE>}, ...
|
||||||
]}
|
]}
|
||||||
"""
|
"""
|
||||||
params = {'filters': self._get_filters(req)}
|
params = self._get_query_params(req)
|
||||||
|
try:
|
||||||
|
images = registry.get_images_list(self.options, **params)
|
||||||
|
except exception.Invalid, e:
|
||||||
|
raise HTTPBadRequest(explanation=str(e))
|
||||||
|
|
||||||
if 'limit' in req.str_params:
|
|
||||||
params['limit'] = req.str_params.get('limit')
|
|
||||||
|
|
||||||
if 'marker' in req.str_params:
|
|
||||||
params['marker'] = req.str_params.get('marker')
|
|
||||||
|
|
||||||
images = registry.get_images_list(self.options, **params)
|
|
||||||
return dict(images=images)
|
return dict(images=images)
|
||||||
|
|
||||||
def detail(self, req):
|
def detail(self, req):
|
||||||
|
@ -125,17 +124,26 @@ class Controller(object):
|
||||||
'properties': {'distro': 'Ubuntu 10.04 LTS', ...}}, ...
|
'properties': {'distro': 'Ubuntu 10.04 LTS', ...}}, ...
|
||||||
]}
|
]}
|
||||||
"""
|
"""
|
||||||
params = {'filters': self._get_filters(req)}
|
params = self._get_query_params(req)
|
||||||
|
try:
|
||||||
if 'limit' in req.str_params:
|
images = registry.get_images_detail(self.options, **params)
|
||||||
params['limit'] = req.str_params.get('limit')
|
except exception.Invalid, e:
|
||||||
|
raise HTTPBadRequest(explanation=str(e))
|
||||||
if 'marker' in req.str_params:
|
|
||||||
params['marker'] = req.str_params.get('marker')
|
|
||||||
|
|
||||||
images = registry.get_images_detail(self.options, **params)
|
|
||||||
return dict(images=images)
|
return dict(images=images)
|
||||||
|
|
||||||
|
def _get_query_params(self, req):
|
||||||
|
"""
|
||||||
|
Extracts necessary query params from request.
|
||||||
|
|
||||||
|
:param req: the WSGI Request object
|
||||||
|
:retval dict of parameters that can be used by registry client
|
||||||
|
"""
|
||||||
|
params = {'filters': self._get_filters(req)}
|
||||||
|
for PARAM in SUPPORTED_PARAMS:
|
||||||
|
if PARAM in req.str_params:
|
||||||
|
params[PARAM] = req.str_params.get(PARAM)
|
||||||
|
return params
|
||||||
|
|
||||||
def _get_filters(self, req):
|
def _get_filters(self, req):
|
||||||
"""
|
"""
|
||||||
Return a dictionary of query param filters from the request
|
Return a dictionary of query param filters from the request
|
||||||
|
|
189
glance/client.py
189
glance/client.py
|
@ -19,172 +19,17 @@
|
||||||
Client classes for callers of a Glance system
|
Client classes for callers of a Glance system
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import httplib
|
|
||||||
import json
|
import json
|
||||||
import logging
|
|
||||||
import urlparse
|
|
||||||
import socket
|
|
||||||
import sys
|
|
||||||
import urllib
|
|
||||||
|
|
||||||
from glance import utils
|
from glance.api.v1 import images as v1_images
|
||||||
|
from glance.common import client as base_client
|
||||||
from glance.common import exception
|
from glance.common import exception
|
||||||
|
from glance import utils
|
||||||
|
|
||||||
#TODO(jaypipes) Allow a logger param for client classes
|
#TODO(jaypipes) Allow a logger param for client classes
|
||||||
|
|
||||||
|
|
||||||
class ClientConnectionError(Exception):
|
class V1Client(base_client.BaseClient):
|
||||||
"""Error resulting from a client connecting to a server"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ImageBodyIterator(object):
|
|
||||||
|
|
||||||
"""
|
|
||||||
A class that acts as an iterator over an image file's
|
|
||||||
chunks of data. This is returned as part of the result
|
|
||||||
tuple from `glance.client.Client.get_image`
|
|
||||||
"""
|
|
||||||
|
|
||||||
CHUNKSIZE = 65536
|
|
||||||
|
|
||||||
def __init__(self, response):
|
|
||||||
"""
|
|
||||||
Constructs the object from an HTTPResponse object
|
|
||||||
"""
|
|
||||||
self.response = response
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
"""
|
|
||||||
Exposes an iterator over the chunks of data in the
|
|
||||||
image file.
|
|
||||||
"""
|
|
||||||
while True:
|
|
||||||
chunk = self.response.read(ImageBodyIterator.CHUNKSIZE)
|
|
||||||
if chunk:
|
|
||||||
yield chunk
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
class BaseClient(object):
|
|
||||||
|
|
||||||
"""A base client class"""
|
|
||||||
|
|
||||||
CHUNKSIZE = 65536
|
|
||||||
|
|
||||||
def __init__(self, host, port, use_ssl):
|
|
||||||
"""
|
|
||||||
Creates a new client to some service.
|
|
||||||
|
|
||||||
:param host: The host where service resides
|
|
||||||
:param port: The port where service resides
|
|
||||||
:param use_ssl: Should we use HTTPS?
|
|
||||||
"""
|
|
||||||
self.host = host
|
|
||||||
self.port = port
|
|
||||||
self.use_ssl = use_ssl
|
|
||||||
self.connection = None
|
|
||||||
|
|
||||||
def get_connection_type(self):
|
|
||||||
"""
|
|
||||||
Returns the proper connection type
|
|
||||||
"""
|
|
||||||
if self.use_ssl:
|
|
||||||
return httplib.HTTPSConnection
|
|
||||||
else:
|
|
||||||
return httplib.HTTPConnection
|
|
||||||
|
|
||||||
def do_request(self, method, action, body=None, headers=None,
|
|
||||||
params=None):
|
|
||||||
"""
|
|
||||||
Connects to the server and issues a request. Handles converting
|
|
||||||
any returned HTTP error status codes to OpenStack/Glance exceptions
|
|
||||||
and closing the server connection. Returns the result data, or
|
|
||||||
raises an appropriate exception.
|
|
||||||
|
|
||||||
:param method: HTTP method ("GET", "POST", "PUT", etc...)
|
|
||||||
:param action: part of URL after root netloc
|
|
||||||
:param body: string of data to send, or None (default)
|
|
||||||
:param headers: mapping of key/value pairs to add as headers
|
|
||||||
:param params: dictionary of key/value pairs to add to append
|
|
||||||
to action
|
|
||||||
|
|
||||||
:note
|
|
||||||
|
|
||||||
If the body param has a read attribute, and method is either
|
|
||||||
POST or PUT, this method will automatically conduct a chunked-transfer
|
|
||||||
encoding and use the body as a file object, transferring chunks
|
|
||||||
of data using the connection's send() method. This allows large
|
|
||||||
objects to be transferred efficiently without buffering the entire
|
|
||||||
body in memory.
|
|
||||||
"""
|
|
||||||
if type(params) is dict:
|
|
||||||
action += '?' + urllib.urlencode(params)
|
|
||||||
|
|
||||||
try:
|
|
||||||
connection_type = self.get_connection_type()
|
|
||||||
headers = headers or {}
|
|
||||||
c = connection_type(self.host, self.port)
|
|
||||||
|
|
||||||
# Do a simple request or a chunked request, depending
|
|
||||||
# on whether the body param is a file-like object and
|
|
||||||
# the method is PUT or POST
|
|
||||||
if hasattr(body, 'read') and method.lower() in ('post', 'put'):
|
|
||||||
# Chunk it, baby...
|
|
||||||
c.putrequest(method, action)
|
|
||||||
|
|
||||||
for header, value in headers.items():
|
|
||||||
c.putheader(header, value)
|
|
||||||
c.putheader('Transfer-Encoding', 'chunked')
|
|
||||||
c.endheaders()
|
|
||||||
|
|
||||||
chunk = body.read(self.CHUNKSIZE)
|
|
||||||
while chunk:
|
|
||||||
c.send('%x\r\n%s\r\n' % (len(chunk), chunk))
|
|
||||||
chunk = body.read(self.CHUNKSIZE)
|
|
||||||
c.send('0\r\n\r\n')
|
|
||||||
else:
|
|
||||||
# Simple request...
|
|
||||||
c.request(method, action, body, headers)
|
|
||||||
res = c.getresponse()
|
|
||||||
status_code = self.get_status_code(res)
|
|
||||||
if status_code in (httplib.OK,
|
|
||||||
httplib.CREATED,
|
|
||||||
httplib.ACCEPTED,
|
|
||||||
httplib.NO_CONTENT):
|
|
||||||
return res
|
|
||||||
elif status_code == httplib.UNAUTHORIZED:
|
|
||||||
raise exception.NotAuthorized
|
|
||||||
elif status_code == httplib.FORBIDDEN:
|
|
||||||
raise exception.NotAuthorized
|
|
||||||
elif status_code == httplib.NOT_FOUND:
|
|
||||||
raise exception.NotFound
|
|
||||||
elif status_code == httplib.CONFLICT:
|
|
||||||
raise exception.Duplicate(res.read())
|
|
||||||
elif status_code == httplib.BAD_REQUEST:
|
|
||||||
raise exception.Invalid(res.read())
|
|
||||||
elif status_code == httplib.INTERNAL_SERVER_ERROR:
|
|
||||||
raise Exception("Internal Server error: %s" % res.read())
|
|
||||||
else:
|
|
||||||
raise Exception("Unknown error occurred! %s" % res.read())
|
|
||||||
|
|
||||||
except (socket.error, IOError), e:
|
|
||||||
raise ClientConnectionError("Unable to connect to "
|
|
||||||
"server. Got error: %s" % e)
|
|
||||||
|
|
||||||
def get_status_code(self, response):
|
|
||||||
"""
|
|
||||||
Returns the integer status code from the response, which
|
|
||||||
can be either a Webob.Response (used in testing) or httplib.Response
|
|
||||||
"""
|
|
||||||
if hasattr(response, 'status_int'):
|
|
||||||
return response.status_int
|
|
||||||
else:
|
|
||||||
return response.status
|
|
||||||
|
|
||||||
|
|
||||||
class V1Client(BaseClient):
|
|
||||||
|
|
||||||
"""Main client class for accessing Glance resources"""
|
"""Main client class for accessing Glance resources"""
|
||||||
|
|
||||||
|
@ -199,7 +44,6 @@ class V1Client(BaseClient):
|
||||||
:param use_ssl: Should we use HTTPS? (defaults to False)
|
:param use_ssl: Should we use HTTPS? (defaults to False)
|
||||||
:param doc_root: Prefix for all URLs we request from host
|
:param doc_root: Prefix for all URLs we request from host
|
||||||
"""
|
"""
|
||||||
|
|
||||||
port = port or self.DEFAULT_PORT
|
port = port or self.DEFAULT_PORT
|
||||||
self.doc_root = doc_root
|
self.doc_root = doc_root
|
||||||
super(Client, self).__init__(host, port, use_ssl)
|
super(Client, self).__init__(host, port, use_ssl)
|
||||||
|
@ -209,7 +53,7 @@ class V1Client(BaseClient):
|
||||||
return super(V1Client, self).do_request(method, action, body,
|
return super(V1Client, self).do_request(method, action, body,
|
||||||
headers, params)
|
headers, params)
|
||||||
|
|
||||||
def get_images(self, filters=None, marker=None, limit=None):
|
def get_images(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns a list of image id/name mappings from Registry
|
Returns a list of image id/name mappings from Registry
|
||||||
|
|
||||||
|
@ -217,18 +61,15 @@ class V1Client(BaseClient):
|
||||||
collection of images should be filtered
|
collection of images should be filtered
|
||||||
:param marker: id after which to start the page of images
|
:param marker: id after which to start the page of images
|
||||||
:param limit: maximum number of items to return
|
:param limit: maximum number of items to return
|
||||||
|
:param sort_key: results will be ordered by this image attribute
|
||||||
|
:param sort_dir: direction in which to to order results (asc, desc)
|
||||||
"""
|
"""
|
||||||
|
params = self._extract_params(kwargs, v1_images.SUPPORTED_PARAMS)
|
||||||
params = filters or {}
|
|
||||||
if marker:
|
|
||||||
params['marker'] = marker
|
|
||||||
if limit:
|
|
||||||
params['limit'] = limit
|
|
||||||
res = self.do_request("GET", "/images", params=params)
|
res = self.do_request("GET", "/images", params=params)
|
||||||
data = json.loads(res.read())['images']
|
data = json.loads(res.read())['images']
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_images_detailed(self, filters=None, marker=None, limit=None):
|
def get_images_detailed(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns a list of detailed image data mappings from Registry
|
Returns a list of detailed image data mappings from Registry
|
||||||
|
|
||||||
|
@ -236,13 +77,10 @@ class V1Client(BaseClient):
|
||||||
collection of images should be filtered
|
collection of images should be filtered
|
||||||
:param marker: id after which to start the page of images
|
:param marker: id after which to start the page of images
|
||||||
:param limit: maximum number of items to return
|
:param limit: maximum number of items to return
|
||||||
|
:param sort_key: results will be ordered by this image attribute
|
||||||
|
:param sort_dir: direction in which to to order results (asc, desc)
|
||||||
"""
|
"""
|
||||||
|
params = self._extract_params(kwargs, v1_images.SUPPORTED_PARAMS)
|
||||||
params = filters or {}
|
|
||||||
if marker:
|
|
||||||
params['marker'] = marker
|
|
||||||
if limit:
|
|
||||||
params['limit'] = limit
|
|
||||||
res = self.do_request("GET", "/images/detail", params=params)
|
res = self.do_request("GET", "/images/detail", params=params)
|
||||||
data = json.loads(res.read())['images']
|
data = json.loads(res.read())['images']
|
||||||
return data
|
return data
|
||||||
|
@ -260,7 +98,7 @@ class V1Client(BaseClient):
|
||||||
res = self.do_request("GET", "/images/%s" % image_id)
|
res = self.do_request("GET", "/images/%s" % image_id)
|
||||||
|
|
||||||
image = utils.get_image_meta_from_headers(res)
|
image = utils.get_image_meta_from_headers(res)
|
||||||
return image, ImageBodyIterator(res)
|
return image, base_client.ImageBodyIterator(res)
|
||||||
|
|
||||||
def get_image_meta(self, image_id):
|
def get_image_meta(self, image_id):
|
||||||
"""
|
"""
|
||||||
|
@ -286,7 +124,6 @@ class V1Client(BaseClient):
|
||||||
|
|
||||||
:retval The newly-stored image's metadata.
|
:retval The newly-stored image's metadata.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
headers = utils.image_meta_to_http_headers(image_meta or {})
|
headers = utils.image_meta_to_http_headers(image_meta or {})
|
||||||
|
|
||||||
if image_data:
|
if image_data:
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
import httplib
|
||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
from glance.common import exception
|
||||||
|
|
||||||
|
|
||||||
|
class ImageBodyIterator(object):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A class that acts as an iterator over an image file's
|
||||||
|
chunks of data. This is returned as part of the result
|
||||||
|
tuple from `glance.client.Client.get_image`
|
||||||
|
"""
|
||||||
|
|
||||||
|
CHUNKSIZE = 65536
|
||||||
|
|
||||||
|
def __init__(self, response):
|
||||||
|
"""
|
||||||
|
Constructs the object from an HTTPResponse object
|
||||||
|
"""
|
||||||
|
self.response = response
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
"""
|
||||||
|
Exposes an iterator over the chunks of data in the
|
||||||
|
image file.
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
chunk = self.response.read(ImageBodyIterator.CHUNKSIZE)
|
||||||
|
if chunk:
|
||||||
|
yield chunk
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
class BaseClient(object):
|
||||||
|
|
||||||
|
"""A base client class"""
|
||||||
|
|
||||||
|
CHUNKSIZE = 65536
|
||||||
|
|
||||||
|
def __init__(self, host, port, use_ssl):
|
||||||
|
"""
|
||||||
|
Creates a new client to some service.
|
||||||
|
|
||||||
|
:param host: The host where service resides
|
||||||
|
:param port: The port where service resides
|
||||||
|
:param use_ssl: Should we use HTTPS?
|
||||||
|
"""
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self.use_ssl = use_ssl
|
||||||
|
self.connection = None
|
||||||
|
|
||||||
|
def get_connection_type(self):
|
||||||
|
"""
|
||||||
|
Returns the proper connection type
|
||||||
|
"""
|
||||||
|
if self.use_ssl:
|
||||||
|
return httplib.HTTPSConnection
|
||||||
|
else:
|
||||||
|
return httplib.HTTPConnection
|
||||||
|
|
||||||
|
def do_request(self, method, action, body=None, headers=None,
|
||||||
|
params=None):
|
||||||
|
"""
|
||||||
|
Connects to the server and issues a request. Handles converting
|
||||||
|
any returned HTTP error status codes to OpenStack/Glance exceptions
|
||||||
|
and closing the server connection. Returns the result data, or
|
||||||
|
raises an appropriate exception.
|
||||||
|
|
||||||
|
:param method: HTTP method ("GET", "POST", "PUT", etc...)
|
||||||
|
:param action: part of URL after root netloc
|
||||||
|
:param body: string of data to send, or None (default)
|
||||||
|
:param headers: mapping of key/value pairs to add as headers
|
||||||
|
:param params: dictionary of key/value pairs to add to append
|
||||||
|
to action
|
||||||
|
|
||||||
|
:note
|
||||||
|
|
||||||
|
If the body param has a read attribute, and method is either
|
||||||
|
POST or PUT, this method will automatically conduct a chunked-transfer
|
||||||
|
encoding and use the body as a file object, transferring chunks
|
||||||
|
of data using the connection's send() method. This allows large
|
||||||
|
objects to be transferred efficiently without buffering the entire
|
||||||
|
body in memory.
|
||||||
|
"""
|
||||||
|
if type(params) is dict:
|
||||||
|
|
||||||
|
# remove any params that are None
|
||||||
|
for (key, value) in params.items():
|
||||||
|
if value is None:
|
||||||
|
del params[key]
|
||||||
|
|
||||||
|
action += '?' + urllib.urlencode(params)
|
||||||
|
|
||||||
|
try:
|
||||||
|
connection_type = self.get_connection_type()
|
||||||
|
headers = headers or {}
|
||||||
|
c = connection_type(self.host, self.port)
|
||||||
|
|
||||||
|
# Do a simple request or a chunked request, depending
|
||||||
|
# on whether the body param is a file-like object and
|
||||||
|
# the method is PUT or POST
|
||||||
|
if hasattr(body, 'read') and method.lower() in ('post', 'put'):
|
||||||
|
# Chunk it, baby...
|
||||||
|
c.putrequest(method, action)
|
||||||
|
|
||||||
|
for header, value in headers.items():
|
||||||
|
c.putheader(header, value)
|
||||||
|
c.putheader('Transfer-Encoding', 'chunked')
|
||||||
|
c.endheaders()
|
||||||
|
|
||||||
|
chunk = body.read(self.CHUNKSIZE)
|
||||||
|
while chunk:
|
||||||
|
c.send('%x\r\n%s\r\n' % (len(chunk), chunk))
|
||||||
|
chunk = body.read(self.CHUNKSIZE)
|
||||||
|
c.send('0\r\n\r\n')
|
||||||
|
else:
|
||||||
|
# Simple request...
|
||||||
|
c.request(method, action, body, headers)
|
||||||
|
res = c.getresponse()
|
||||||
|
status_code = self.get_status_code(res)
|
||||||
|
if status_code in (httplib.OK,
|
||||||
|
httplib.CREATED,
|
||||||
|
httplib.ACCEPTED,
|
||||||
|
httplib.NO_CONTENT):
|
||||||
|
return res
|
||||||
|
elif status_code == httplib.UNAUTHORIZED:
|
||||||
|
raise exception.NotAuthorized
|
||||||
|
elif status_code == httplib.FORBIDDEN:
|
||||||
|
raise exception.NotAuthorized
|
||||||
|
elif status_code == httplib.NOT_FOUND:
|
||||||
|
raise exception.NotFound
|
||||||
|
elif status_code == httplib.CONFLICT:
|
||||||
|
raise exception.Duplicate(res.read())
|
||||||
|
elif status_code == httplib.BAD_REQUEST:
|
||||||
|
raise exception.Invalid(res.read())
|
||||||
|
elif status_code == httplib.INTERNAL_SERVER_ERROR:
|
||||||
|
raise Exception("Internal Server error: %s" % res.read())
|
||||||
|
else:
|
||||||
|
raise Exception("Unknown error occurred! %s" % res.read())
|
||||||
|
|
||||||
|
except (socket.error, IOError), e:
|
||||||
|
raise exception.ClientConnectionError("Unable to connect to "
|
||||||
|
"server. Got error: %s" % e)
|
||||||
|
|
||||||
|
def get_status_code(self, response):
|
||||||
|
"""
|
||||||
|
Returns the integer status code from the response, which
|
||||||
|
can be either a Webob.Response (used in testing) or httplib.Response
|
||||||
|
"""
|
||||||
|
if hasattr(response, 'status_int'):
|
||||||
|
return response.status_int
|
||||||
|
else:
|
||||||
|
return response.status
|
||||||
|
|
||||||
|
def _extract_params(self, actual_params, allowed_params):
|
||||||
|
"""
|
||||||
|
Extract a subset of keys from a dictionary. The filters key
|
||||||
|
will also be extracted, and each of its values will be returned
|
||||||
|
as an individual param.
|
||||||
|
|
||||||
|
:param actual_params: dict of keys to filter
|
||||||
|
:param allowed_params: list of keys that 'actual_params' will be
|
||||||
|
reduced to
|
||||||
|
:retval subset of 'params' dict
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# expect 'filters' param to be a dict here
|
||||||
|
result = dict(actual_params.get('filters'))
|
||||||
|
except TypeError:
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
for allowed_param in allowed_params:
|
||||||
|
if allowed_param in actual_params:
|
||||||
|
result[allowed_param] = actual_params[allowed_param]
|
||||||
|
|
||||||
|
return result
|
|
@ -83,6 +83,11 @@ class DatabaseMigrationError(Error):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ClientConnectionError(Exception):
|
||||||
|
"""Error resulting from a client connecting to a server"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def wrap_exception(f):
|
def wrap_exception(f):
|
||||||
def _wrap(*args, **kw):
|
def _wrap(*args, **kw):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -23,7 +23,8 @@ the Glance Registry API
|
||||||
import json
|
import json
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
from glance.client import BaseClient
|
from glance.common.client import BaseClient
|
||||||
|
from glance.registry import server
|
||||||
|
|
||||||
|
|
||||||
class RegistryClient(BaseClient):
|
class RegistryClient(BaseClient):
|
||||||
|
@ -40,46 +41,35 @@ class RegistryClient(BaseClient):
|
||||||
:param port: The port where Glance resides (defaults to 9191)
|
:param port: The port where Glance resides (defaults to 9191)
|
||||||
:param use_ssl: Should we use HTTPS? (defaults to False)
|
:param use_ssl: Should we use HTTPS? (defaults to False)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
port = port or self.DEFAULT_PORT
|
port = port or self.DEFAULT_PORT
|
||||||
super(RegistryClient, self).__init__(host, port, use_ssl)
|
super(RegistryClient, self).__init__(host, port, use_ssl)
|
||||||
|
|
||||||
def get_images(self, filters=None, marker=None, limit=None):
|
def get_images(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns a list of image id/name mappings from Registry
|
Returns a list of image id/name mappings from Registry
|
||||||
|
|
||||||
:param filters: dict of keys & expected values to filter results
|
:param filters: dict of keys & expected values to filter results
|
||||||
:param marker: image id after which to start page
|
:param marker: image id after which to start page
|
||||||
:param limit: max number of images to return
|
:param limit: max number of images to return
|
||||||
|
:param sort_key: results will be ordered by this image attribute
|
||||||
|
:param sort_dir: direction in which to to order results (asc, desc)
|
||||||
"""
|
"""
|
||||||
params = filters or {}
|
params = self._extract_params(kwargs, server.SUPPORTED_PARAMS)
|
||||||
|
|
||||||
if marker != None:
|
|
||||||
params['marker'] = marker
|
|
||||||
|
|
||||||
if limit != None:
|
|
||||||
params['limit'] = limit
|
|
||||||
|
|
||||||
res = self.do_request("GET", "/images", params=params)
|
res = self.do_request("GET", "/images", params=params)
|
||||||
data = json.loads(res.read())['images']
|
data = json.loads(res.read())['images']
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_images_detailed(self, filters=None, marker=None, limit=None):
|
def get_images_detailed(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns a list of detailed image data mappings from Registry
|
Returns a list of detailed image data mappings from Registry
|
||||||
|
|
||||||
:param filters: dict of keys & expected values to filter results
|
:param filters: dict of keys & expected values to filter results
|
||||||
:param marker: image id after which to start page
|
:param marker: image id after which to start page
|
||||||
:param limit: max number of images to return
|
:param limit: max number of images to return
|
||||||
|
:param sort_key: results will be ordered by this image attribute
|
||||||
|
:param sort_dir: direction in which to to order results (asc, desc)
|
||||||
"""
|
"""
|
||||||
params = filters or {}
|
params = self._extract_params(kwargs, server.SUPPORTED_PARAMS)
|
||||||
|
|
||||||
if marker != None:
|
|
||||||
params['marker'] = marker
|
|
||||||
|
|
||||||
if limit != None:
|
|
||||||
params['limit'] = limit
|
|
||||||
|
|
||||||
res = self.do_request("GET", "/images/detail", params=params)
|
res = self.do_request("GET", "/images/detail", params=params)
|
||||||
data = json.loads(res.read())['images']
|
data = json.loads(res.read())['images']
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -23,7 +23,7 @@ Defines interface for DB access
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from sqlalchemy import create_engine, desc
|
from sqlalchemy import asc, create_engine, desc
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.orm import exc
|
from sqlalchemy.orm import exc
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
|
@ -97,10 +97,10 @@ def image_create(context, values):
|
||||||
|
|
||||||
|
|
||||||
def image_update(context, image_id, values, purge_props=False):
|
def image_update(context, image_id, values, purge_props=False):
|
||||||
"""Set the given properties on an image and update it.
|
"""
|
||||||
|
Set the given properties on an image and update it.
|
||||||
Raises NotFound if image does not exist.
|
|
||||||
|
|
||||||
|
:raises NotFound if image does not exist.
|
||||||
"""
|
"""
|
||||||
return _image_update(context, values, image_id, purge_props)
|
return _image_update(context, values, image_id, purge_props)
|
||||||
|
|
||||||
|
@ -129,15 +129,18 @@ def image_get(context, image_id, session=None):
|
||||||
raise exception.NotFound("No image found with ID %s" % image_id)
|
raise exception.NotFound("No image found with ID %s" % image_id)
|
||||||
|
|
||||||
|
|
||||||
def image_get_all_public(context, filters=None, marker=None, limit=None):
|
def image_get_all_public(context, filters=None, marker=None, limit=None,
|
||||||
"""Get all public images that match zero or more filters.
|
sort_key='created_at', sort_dir='desc'):
|
||||||
|
"""
|
||||||
|
Get all public images that match zero or more filters.
|
||||||
|
|
||||||
:param filters: dict of filter keys and values. If a 'properties'
|
:param filters: dict of filter keys and values. If a 'properties'
|
||||||
key is present, it is treated as a dict of key/value
|
key is present, it is treated as a dict of key/value
|
||||||
filters on the image properties attribute
|
filters on the image properties attribute
|
||||||
:param marker: image id after which to start page
|
:param marker: image id after which to start page
|
||||||
:param limit: maximum number of images to return
|
:param limit: maximum number of images to return
|
||||||
|
:param sort_key: image attribute by which results should be sorted
|
||||||
|
:param sort_dir: direction in which results should be sorted (asc, desc)
|
||||||
"""
|
"""
|
||||||
filters = filters or {}
|
filters = filters or {}
|
||||||
|
|
||||||
|
@ -146,9 +149,17 @@ def image_get_all_public(context, filters=None, marker=None, limit=None):
|
||||||
options(joinedload(models.Image.properties)).\
|
options(joinedload(models.Image.properties)).\
|
||||||
filter_by(deleted=_deleted(context)).\
|
filter_by(deleted=_deleted(context)).\
|
||||||
filter_by(is_public=True).\
|
filter_by(is_public=True).\
|
||||||
filter(models.Image.status != 'killed').\
|
filter(models.Image.status != 'killed')
|
||||||
order_by(desc(models.Image.created_at)).\
|
|
||||||
order_by(desc(models.Image.id))
|
sort_dir_func = {
|
||||||
|
'asc': asc,
|
||||||
|
'desc': desc,
|
||||||
|
}[sort_dir]
|
||||||
|
|
||||||
|
sort_key_attr = getattr(models.Image, sort_key)
|
||||||
|
|
||||||
|
query = query.order_by(sort_dir_func(sort_key_attr)).\
|
||||||
|
order_by(sort_dir_func(models.Image.id))
|
||||||
|
|
||||||
if 'size_min' in filters:
|
if 'size_min' in filters:
|
||||||
query = query.filter(models.Image.size >= filters['size_min'])
|
query = query.filter(models.Image.size >= filters['size_min'])
|
||||||
|
@ -179,7 +190,8 @@ def image_get_all_public(context, filters=None, marker=None, limit=None):
|
||||||
|
|
||||||
|
|
||||||
def _drop_protected_attrs(model_class, values):
|
def _drop_protected_attrs(model_class, values):
|
||||||
"""Removed protected attributes from values dictionary using the models
|
"""
|
||||||
|
Removed protected attributes from values dictionary using the models
|
||||||
__protected_attributes__ field.
|
__protected_attributes__ field.
|
||||||
"""
|
"""
|
||||||
for attr in model_class.__protected_attributes__:
|
for attr in model_class.__protected_attributes__:
|
||||||
|
@ -194,7 +206,6 @@ def validate_image(values):
|
||||||
|
|
||||||
:param values: Mapping of image metadata to check
|
:param values: Mapping of image metadata to check
|
||||||
"""
|
"""
|
||||||
|
|
||||||
status = values.get('status')
|
status = values.get('status')
|
||||||
disk_format = values.get('disk_format')
|
disk_format = values.get('disk_format')
|
||||||
container_format = values.get('container_format')
|
container_format = values.get('container_format')
|
||||||
|
@ -227,13 +238,13 @@ def validate_image(values):
|
||||||
|
|
||||||
|
|
||||||
def _image_update(context, values, image_id, purge_props=False):
|
def _image_update(context, values, image_id, purge_props=False):
|
||||||
"""Used internally by image_create and image_update
|
"""
|
||||||
|
Used internally by image_create and image_update
|
||||||
|
|
||||||
:param context: Request context
|
:param context: Request context
|
||||||
:param values: A dict of attributes to set
|
:param values: A dict of attributes to set
|
||||||
:param image_id: If None, create the image, otherwise, find and update it
|
:param image_id: If None, create the image, otherwise, find and update it
|
||||||
"""
|
"""
|
||||||
|
|
||||||
session = get_session()
|
session = get_session()
|
||||||
with session.begin():
|
with session.begin():
|
||||||
|
|
||||||
|
@ -315,7 +326,8 @@ def image_property_update(context, prop_ref, values, session=None):
|
||||||
|
|
||||||
|
|
||||||
def _image_property_update(context, prop_ref, values, session=None):
|
def _image_property_update(context, prop_ref, values, session=None):
|
||||||
"""Used internally by image_property_create and image_property_update
|
"""
|
||||||
|
Used internally by image_property_create and image_property_update
|
||||||
"""
|
"""
|
||||||
_drop_protected_attrs(models.ImageProperty, values)
|
_drop_protected_attrs(models.ImageProperty, values)
|
||||||
values["deleted"] = False
|
values["deleted"] = False
|
||||||
|
@ -325,7 +337,8 @@ def _image_property_update(context, prop_ref, values, session=None):
|
||||||
|
|
||||||
|
|
||||||
def image_property_delete(context, prop_ref, session=None):
|
def image_property_delete(context, prop_ref, session=None):
|
||||||
"""Used internally by image_property_create and image_property_update
|
"""
|
||||||
|
Used internally by image_property_create and image_property_update
|
||||||
"""
|
"""
|
||||||
prop_ref.update(dict(deleted=True))
|
prop_ref.update(dict(deleted=True))
|
||||||
prop_ref.save(session=session)
|
prop_ref.save(session=session)
|
||||||
|
@ -334,8 +347,8 @@ def image_property_delete(context, prop_ref, session=None):
|
||||||
|
|
||||||
# pylint: disable-msg=C0111
|
# pylint: disable-msg=C0111
|
||||||
def _deleted(context):
|
def _deleted(context):
|
||||||
"""Calculates whether to include deleted objects based on context.
|
"""
|
||||||
|
Calculates whether to include deleted objects based on context.
|
||||||
Currently just looks for a flag called deleted in the context dict.
|
Currently just looks for a flag called deleted in the context dict.
|
||||||
"""
|
"""
|
||||||
if not hasattr(context, 'get'):
|
if not hasattr(context, 'get'):
|
||||||
|
|
|
@ -51,7 +51,8 @@ BigInteger = lambda: sqlalchemy.types.BigInteger()
|
||||||
|
|
||||||
|
|
||||||
def from_migration_import(module_name, fromlist):
|
def from_migration_import(module_name, fromlist):
|
||||||
"""Import a migration file and return the module
|
"""
|
||||||
|
Import a migration file and return the module
|
||||||
|
|
||||||
:param module_name: name of migration module to import from
|
:param module_name: name of migration module to import from
|
||||||
(ex: 001_add_images_table)
|
(ex: 001_add_images_table)
|
||||||
|
@ -84,7 +85,6 @@ def from_migration_import(module_name, fromlist):
|
||||||
images = define_images_table(meta)
|
images = define_images_table(meta)
|
||||||
|
|
||||||
# Refer to images table
|
# Refer to images table
|
||||||
|
|
||||||
"""
|
"""
|
||||||
module_path = 'glance.registry.db.migrate_repo.versions.%s' % module_name
|
module_path = 'glance.registry.db.migrate_repo.versions.%s' % module_name
|
||||||
module = __import__(module_path, globals(), locals(), fromlist, -1)
|
module = __import__(module_path, globals(), locals(), fromlist, -1)
|
||||||
|
|
|
@ -33,7 +33,8 @@ logger = logging.getLogger('glance.registry.db.migration')
|
||||||
|
|
||||||
|
|
||||||
def db_version(options):
|
def db_version(options):
|
||||||
"""Return the database's current migration number
|
"""
|
||||||
|
Return the database's current migration number
|
||||||
|
|
||||||
:param options: options dict
|
:param options: options dict
|
||||||
:retval version number
|
:retval version number
|
||||||
|
@ -49,7 +50,8 @@ def db_version(options):
|
||||||
|
|
||||||
|
|
||||||
def upgrade(options, version=None):
|
def upgrade(options, version=None):
|
||||||
"""Upgrade the database's current migration level
|
"""
|
||||||
|
Upgrade the database's current migration level
|
||||||
|
|
||||||
:param options: options dict
|
:param options: options dict
|
||||||
:param version: version to upgrade (defaults to latest)
|
:param version: version to upgrade (defaults to latest)
|
||||||
|
@ -65,7 +67,8 @@ def upgrade(options, version=None):
|
||||||
|
|
||||||
|
|
||||||
def downgrade(options, version):
|
def downgrade(options, version):
|
||||||
"""Downgrade the database's current migration level
|
"""
|
||||||
|
Downgrade the database's current migration level
|
||||||
|
|
||||||
:param options: options dict
|
:param options: options dict
|
||||||
:param version: version to downgrade to
|
:param version: version to downgrade to
|
||||||
|
@ -80,7 +83,8 @@ def downgrade(options, version):
|
||||||
|
|
||||||
|
|
||||||
def version_control(options):
|
def version_control(options):
|
||||||
"""Place a database under migration control
|
"""
|
||||||
|
Place a database under migration control
|
||||||
|
|
||||||
:param options: options dict
|
:param options: options dict
|
||||||
"""
|
"""
|
||||||
|
@ -94,7 +98,8 @@ def version_control(options):
|
||||||
|
|
||||||
|
|
||||||
def _version_control(options):
|
def _version_control(options):
|
||||||
"""Place a database under migration control
|
"""
|
||||||
|
Place a database under migration control
|
||||||
|
|
||||||
:param options: options dict
|
:param options: options dict
|
||||||
"""
|
"""
|
||||||
|
@ -104,7 +109,8 @@ def _version_control(options):
|
||||||
|
|
||||||
|
|
||||||
def db_sync(options, version=None):
|
def db_sync(options, version=None):
|
||||||
"""Place a database under migration control and perform an upgrade
|
"""
|
||||||
|
Place a database under migration control and perform an upgrade
|
||||||
|
|
||||||
:param options: options dict
|
:param options: options dict
|
||||||
:retval version number
|
:retval version number
|
||||||
|
|
|
@ -39,8 +39,15 @@ DISPLAY_FIELDS_IN_INDEX = ['id', 'name', 'size',
|
||||||
SUPPORTED_FILTERS = ['name', 'status', 'container_format', 'disk_format',
|
SUPPORTED_FILTERS = ['name', 'status', 'container_format', 'disk_format',
|
||||||
'size_min', 'size_max']
|
'size_min', 'size_max']
|
||||||
|
|
||||||
|
SUPPORTED_SORT_KEYS = ('name', 'status', 'container_format', 'disk_format',
|
||||||
|
'size', 'id', 'created_at', 'updated_at')
|
||||||
|
|
||||||
|
SUPPORTED_SORT_DIRS = ('asc', 'desc')
|
||||||
|
|
||||||
MAX_ITEM_LIMIT = 25
|
MAX_ITEM_LIMIT = 25
|
||||||
|
|
||||||
|
SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir')
|
||||||
|
|
||||||
|
|
||||||
class Controller(object):
|
class Controller(object):
|
||||||
"""Controller for the reference implementation registry server"""
|
"""Controller for the reference implementation registry server"""
|
||||||
|
@ -50,7 +57,8 @@ class Controller(object):
|
||||||
db_api.configure_db(options)
|
db_api.configure_db(options)
|
||||||
|
|
||||||
def index(self, req):
|
def index(self, req):
|
||||||
"""Return a basic filtered list of public, non-deleted images
|
"""
|
||||||
|
Return a basic filtered list of public, non-deleted images
|
||||||
|
|
||||||
:param req: the Request object coming from the wsgi layer
|
:param req: the Request object coming from the wsgi layer
|
||||||
:retval a mapping of the following form::
|
:retval a mapping of the following form::
|
||||||
|
@ -67,17 +75,13 @@ class Controller(object):
|
||||||
'container_format': <CONTAINER_FORMAT>,
|
'container_format': <CONTAINER_FORMAT>,
|
||||||
'checksum': <CHECKSUM>
|
'checksum': <CHECKSUM>
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
params = {
|
params = self._get_query_params(req)
|
||||||
'filters': self._get_filters(req),
|
try:
|
||||||
'limit': self._get_limit(req),
|
images = db_api.image_get_all_public(None, **params)
|
||||||
}
|
except exception.NotFound, e:
|
||||||
|
msg = "Invalid marker. Image could not be found."
|
||||||
if 'marker' in req.str_params:
|
raise exc.HTTPBadRequest(explanation=msg)
|
||||||
params['marker'] = self._get_marker(req)
|
|
||||||
|
|
||||||
images = db_api.image_get_all_public(None, **params)
|
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
for image in images:
|
for image in images:
|
||||||
|
@ -88,7 +92,8 @@ class Controller(object):
|
||||||
return dict(images=results)
|
return dict(images=results)
|
||||||
|
|
||||||
def detail(self, req):
|
def detail(self, req):
|
||||||
"""Return a filtered list of public, non-deleted images in detail
|
"""
|
||||||
|
Return a filtered list of public, non-deleted images in detail
|
||||||
|
|
||||||
:param req: the Request object coming from the wsgi layer
|
:param req: the Request object coming from the wsgi layer
|
||||||
:retval a mapping of the following form::
|
:retval a mapping of the following form::
|
||||||
|
@ -97,27 +102,44 @@ class Controller(object):
|
||||||
|
|
||||||
Where image_list is a sequence of mappings containing
|
Where image_list is a sequence of mappings containing
|
||||||
all image model fields.
|
all image model fields.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
params = {
|
params = self._get_query_params(req)
|
||||||
'filters': self._get_filters(req),
|
try:
|
||||||
'limit': self._get_limit(req),
|
images = db_api.image_get_all_public(None, **params)
|
||||||
}
|
except exception.NotFound, e:
|
||||||
|
msg = "Invalid marker. Image could not be found."
|
||||||
if 'marker' in req.str_params:
|
raise exc.HTTPBadRequest(explanation=msg)
|
||||||
params['marker'] = self._get_marker(req)
|
|
||||||
|
|
||||||
images = db_api.image_get_all_public(None, **params)
|
|
||||||
|
|
||||||
image_dicts = [make_image_dict(i) for i in images]
|
image_dicts = [make_image_dict(i) for i in images]
|
||||||
return dict(images=image_dicts)
|
return dict(images=image_dicts)
|
||||||
|
|
||||||
|
def _get_query_params(self, req):
|
||||||
|
"""
|
||||||
|
Extract necessary query parameters from http request.
|
||||||
|
|
||||||
|
:param req: the Request object coming from the wsgi layer
|
||||||
|
:retval dictionary of filters to apply to list of images
|
||||||
|
"""
|
||||||
|
params = {
|
||||||
|
'filters': self._get_filters(req),
|
||||||
|
'limit': self._get_limit(req),
|
||||||
|
'sort_key': self._get_sort_key(req),
|
||||||
|
'sort_dir': self._get_sort_dir(req),
|
||||||
|
'marker': self._get_marker(req),
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value in params.items():
|
||||||
|
if value is None:
|
||||||
|
del params[key]
|
||||||
|
|
||||||
|
return params
|
||||||
|
|
||||||
def _get_filters(self, req):
|
def _get_filters(self, req):
|
||||||
"""Return a dictionary of query param filters from the request
|
"""
|
||||||
|
Return a dictionary of query param filters from the request
|
||||||
|
|
||||||
:param req: the Request object coming from the wsgi layer
|
:param req: the Request object coming from the wsgi layer
|
||||||
:retval a dict of key/value filters
|
:retval a dict of key/value filters
|
||||||
|
|
||||||
"""
|
"""
|
||||||
filters = {}
|
filters = {}
|
||||||
properties = {}
|
properties = {}
|
||||||
|
@ -148,12 +170,35 @@ class Controller(object):
|
||||||
|
|
||||||
def _get_marker(self, req):
|
def _get_marker(self, req):
|
||||||
"""Parse a marker query param into something usable."""
|
"""Parse a marker query param into something usable."""
|
||||||
|
marker = req.str_params.get('marker', None)
|
||||||
|
|
||||||
|
if marker is None:
|
||||||
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
marker = int(req.str_params.get('marker', None))
|
marker = int(marker)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise exc.HTTPBadRequest("marker param must be an integer")
|
raise exc.HTTPBadRequest("marker param must be an integer")
|
||||||
return marker
|
return marker
|
||||||
|
|
||||||
|
def _get_sort_key(self, req):
|
||||||
|
"""Parse a sort key query param from the request object."""
|
||||||
|
sort_key = req.str_params.get('sort_key', None)
|
||||||
|
if sort_key is not None and sort_key not in SUPPORTED_SORT_KEYS:
|
||||||
|
_keys = ', '.join(SUPPORTED_SORT_KEYS)
|
||||||
|
msg = "Unsupported sort_key. Acceptable values: %s" % (_keys,)
|
||||||
|
raise exc.HTTPBadRequest(explanation=msg)
|
||||||
|
return sort_key
|
||||||
|
|
||||||
|
def _get_sort_dir(self, req):
|
||||||
|
"""Parse a sort direction query param from the request object."""
|
||||||
|
sort_dir = req.str_params.get('sort_dir', None)
|
||||||
|
if sort_dir is not None and sort_dir not in SUPPORTED_SORT_DIRS:
|
||||||
|
_keys = ', '.join(SUPPORTED_SORT_DIRS)
|
||||||
|
msg = "Unsupported sort_dir. Acceptable values: %s" % (_keys,)
|
||||||
|
raise exc.HTTPBadRequest(explanation=msg)
|
||||||
|
return sort_dir
|
||||||
|
|
||||||
def show(self, req, id):
|
def show(self, req, id):
|
||||||
"""Return data about the given image id."""
|
"""Return data about the given image id."""
|
||||||
try:
|
try:
|
||||||
|
@ -171,7 +216,6 @@ class Controller(object):
|
||||||
:param id: The opaque internal identifier for the image
|
:param id: The opaque internal identifier for the image
|
||||||
|
|
||||||
:retval Returns 200 if delete was successful, a fault if not.
|
:retval Returns 200 if delete was successful, a fault if not.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
context = None
|
context = None
|
||||||
try:
|
try:
|
||||||
|
@ -189,7 +233,6 @@ class Controller(object):
|
||||||
:retval Returns the newly-created image information as a mapping,
|
:retval Returns the newly-created image information as a mapping,
|
||||||
which will include the newly-created image's internal id
|
which will include the newly-created image's internal id
|
||||||
in the 'id' field
|
in the 'id' field
|
||||||
|
|
||||||
"""
|
"""
|
||||||
image_data = body['image']
|
image_data = body['image']
|
||||||
|
|
||||||
|
@ -210,14 +253,14 @@ class Controller(object):
|
||||||
return exc.HTTPBadRequest(msg)
|
return exc.HTTPBadRequest(msg)
|
||||||
|
|
||||||
def update(self, req, id, body):
|
def update(self, req, id, body):
|
||||||
"""Updates an existing image with the registry.
|
"""
|
||||||
|
Updates an existing image with the registry.
|
||||||
|
|
||||||
:param req: wsgi Request object
|
:param req: wsgi Request object
|
||||||
:param body: Dictionary of information about the image
|
:param body: Dictionary of information about the image
|
||||||
:param id: The opaque internal identifier for the image
|
:param id: The opaque internal identifier for the image
|
||||||
|
|
||||||
:retval Returns the updated image information as a mapping,
|
:retval Returns the updated image information as a mapping,
|
||||||
|
|
||||||
"""
|
"""
|
||||||
image_data = body['image']
|
image_data = body['image']
|
||||||
|
|
||||||
|
|
|
@ -116,7 +116,6 @@ def parse_uri_tokens(parsed_uri, example_url):
|
||||||
1) urlparse to split the tokens
|
1) urlparse to split the tokens
|
||||||
2) use RE to split on @ and /
|
2) use RE to split on @ and /
|
||||||
3) reassemble authurl
|
3) reassemble authurl
|
||||||
|
|
||||||
"""
|
"""
|
||||||
path = parsed_uri.path.lstrip('//')
|
path = parsed_uri.path.lstrip('//')
|
||||||
netloc = parsed_uri.netloc
|
netloc = parsed_uri.netloc
|
||||||
|
|
|
@ -65,11 +65,11 @@ class ChunkedFile(object):
|
||||||
class FilesystemBackend(glance.store.Backend):
|
class FilesystemBackend(glance.store.Backend):
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, parsed_uri, expected_size=None, options=None):
|
def get(cls, parsed_uri, expected_size=None, options=None):
|
||||||
""" Filesystem-based backend
|
"""
|
||||||
|
Filesystem-based backend
|
||||||
|
|
||||||
file:///path/to/file.tar.gz.0
|
file:///path/to/file.tar.gz.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
filepath = parsed_uri.path
|
filepath = parsed_uri.path
|
||||||
if not os.path.exists(filepath):
|
if not os.path.exists(filepath):
|
||||||
raise exception.NotFound("Image file %s not found" % filepath)
|
raise exception.NotFound("Image file %s not found" % filepath)
|
||||||
|
|
|
@ -25,10 +25,10 @@ class HTTPBackend(glance.store.Backend):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, parsed_uri, expected_size, options=None, conn_class=None):
|
def get(cls, parsed_uri, expected_size, options=None, conn_class=None):
|
||||||
"""Takes a parsed uri for an HTTP resource, fetches it, and yields the
|
|
||||||
data.
|
|
||||||
"""
|
"""
|
||||||
|
Takes a parsed uri for an HTTP resource, fetches it, and
|
||||||
|
yields the data.
|
||||||
|
"""
|
||||||
if conn_class:
|
if conn_class:
|
||||||
pass # use the conn_class passed in
|
pass # use the conn_class passed in
|
||||||
elif parsed_uri.scheme == "http":
|
elif parsed_uri.scheme == "http":
|
||||||
|
|
|
@ -32,9 +32,8 @@ logger = logging.getLogger('glance.store.swift')
|
||||||
|
|
||||||
|
|
||||||
class SwiftBackend(glance.store.Backend):
|
class SwiftBackend(glance.store.Backend):
|
||||||
"""
|
"""An implementation of the swift backend adapter."""
|
||||||
An implementation of the swift backend adapter.
|
|
||||||
"""
|
|
||||||
EXAMPLE_URL = "swift://<USER>:<KEY>@<AUTH_ADDRESS>/<CONTAINER>/<FILE>"
|
EXAMPLE_URL = "swift://<USER>:<KEY>@<AUTH_ADDRESS>/<CONTAINER>/<FILE>"
|
||||||
|
|
||||||
CHUNKSIZE = 65536
|
CHUNKSIZE = 65536
|
||||||
|
|
|
@ -38,7 +38,8 @@
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
"""Unittest runner for glance
|
"""
|
||||||
|
Unittest runner for glance
|
||||||
|
|
||||||
To run all test::
|
To run all test::
|
||||||
python run_tests.py
|
python run_tests.py
|
||||||
|
@ -212,7 +213,8 @@ class GlanceTestResult(result.TextTestResult):
|
||||||
|
|
||||||
# NOTE(vish, tfukushima): copied from unittest with edit to add color
|
# NOTE(vish, tfukushima): copied from unittest with edit to add color
|
||||||
def addError(self, test, err):
|
def addError(self, test, err):
|
||||||
"""Overrides normal addError to add support for errorClasses.
|
"""
|
||||||
|
Overrides normal addError to add support for errorClasses.
|
||||||
If the exception is a registered class, the error will be added
|
If the exception is a registered class, the error will be added
|
||||||
to the list for that class, not errors.
|
to the list for that class, not errors.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -40,7 +40,6 @@ class TestBinGlance(functional.FunctionalTest):
|
||||||
3. Delete the image
|
3. Delete the image
|
||||||
4. Verify no longer in index
|
4. Verify no longer in index
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
self.start_servers()
|
self.start_servers()
|
||||||
|
|
||||||
|
@ -106,7 +105,6 @@ class TestBinGlance(functional.FunctionalTest):
|
||||||
5. Update the image's Name attribute
|
5. Update the image's Name attribute
|
||||||
6. Verify the updated name is shown
|
6. Verify the updated name is shown
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
self.start_servers()
|
self.start_servers()
|
||||||
|
|
||||||
|
@ -192,7 +190,6 @@ class TestBinGlance(functional.FunctionalTest):
|
||||||
3. Verify the status of the image is displayed in the show output
|
3. Verify the status of the image is displayed in the show output
|
||||||
and is in status 'killed'
|
and is in status 'killed'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
|
|
||||||
# Start servers with a Swift backend and a bad auth URL
|
# Start servers with a Swift backend and a bad auth URL
|
||||||
|
@ -253,7 +250,6 @@ class TestBinGlance(functional.FunctionalTest):
|
||||||
3. Verify no public images found
|
3. Verify no public images found
|
||||||
4. Run SQL against DB to verify no undeleted properties
|
4. Run SQL against DB to verify no undeleted properties
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
self.start_servers()
|
self.start_servers()
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,6 @@ class TestCurlApi(functional.FunctionalTest):
|
||||||
11. PUT /images/1
|
11. PUT /images/1
|
||||||
- Add a previously deleted property.
|
- Add a previously deleted property.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
self.start_servers()
|
self.start_servers()
|
||||||
|
|
||||||
|
@ -382,7 +381,6 @@ class TestCurlApi(functional.FunctionalTest):
|
||||||
6. GET /images
|
6. GET /images
|
||||||
- Verify one public image
|
- Verify one public image
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
self.start_servers()
|
self.start_servers()
|
||||||
|
|
||||||
|
@ -502,7 +500,6 @@ class TestCurlApi(functional.FunctionalTest):
|
||||||
handled properly, and that usage of the Accept: header does
|
handled properly, and that usage of the Accept: header does
|
||||||
content negotiation properly.
|
content negotiation properly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
self.start_servers()
|
self.start_servers()
|
||||||
|
|
||||||
|
@ -570,7 +567,7 @@ class TestCurlApi(functional.FunctionalTest):
|
||||||
self.assertTrue('Unknown accept header'
|
self.assertTrue('Unknown accept header'
|
||||||
in open(self.api_server.log_file).read())
|
in open(self.api_server.log_file).read())
|
||||||
|
|
||||||
# 5. GET / with an Accept: application/vnd.openstack.images-v1
|
# 4. GET / with an Accept: application/vnd.openstack.images-v1
|
||||||
# Verify empty image list returned
|
# Verify empty image list returned
|
||||||
cmd = ("curl -H 'Accept: application/vnd.openstack.images-v1' "
|
cmd = ("curl -H 'Accept: application/vnd.openstack.images-v1' "
|
||||||
"http://0.0.0.0:%d/images") % api_port
|
"http://0.0.0.0:%d/images") % api_port
|
||||||
|
@ -693,7 +690,6 @@ class TestCurlApi(functional.FunctionalTest):
|
||||||
|
|
||||||
:see https://bugs.launchpad.net/glance/+bug/739433
|
:see https://bugs.launchpad.net/glance/+bug/739433
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
self.start_servers()
|
self.start_servers()
|
||||||
|
|
||||||
|
@ -760,7 +756,6 @@ class TestCurlApi(functional.FunctionalTest):
|
||||||
|
|
||||||
:see https://bugs.launchpad.net/glance/+bug/755912
|
:see https://bugs.launchpad.net/glance/+bug/755912
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
self.start_servers()
|
self.start_servers()
|
||||||
|
|
||||||
|
@ -1117,3 +1112,118 @@ class TestCurlApi(functional.FunctionalTest):
|
||||||
self.assertEqual(int(images[0]['id']), 2)
|
self.assertEqual(int(images[0]['id']), 2)
|
||||||
|
|
||||||
self.stop_servers()
|
self.stop_servers()
|
||||||
|
|
||||||
|
def test_ordered_images(self):
|
||||||
|
"""
|
||||||
|
Set up three test images and ensure each query param filter works
|
||||||
|
"""
|
||||||
|
self.cleanup()
|
||||||
|
self.start_servers()
|
||||||
|
|
||||||
|
api_port = self.api_port
|
||||||
|
registry_port = self.registry_port
|
||||||
|
|
||||||
|
# 0. GET /images
|
||||||
|
# Verify no public images
|
||||||
|
cmd = "curl http://0.0.0.0:%d/v1/images" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual('{"images": []}', out.strip())
|
||||||
|
|
||||||
|
# 1. POST /images with three public images with various attributes
|
||||||
|
cmd = ("curl -i -X POST "
|
||||||
|
"-H 'Expect: ' " # Necessary otherwise sends 100 Continue
|
||||||
|
"-H 'X-Image-Meta-Name: Image1' "
|
||||||
|
"-H 'X-Image-Meta-Status: active' "
|
||||||
|
"-H 'X-Image-Meta-Container-Format: ovf' "
|
||||||
|
"-H 'X-Image-Meta-Disk-Format: vdi' "
|
||||||
|
"-H 'X-Image-Meta-Size: 19' "
|
||||||
|
"-H 'X-Image-Meta-Is-Public: True' "
|
||||||
|
"http://0.0.0.0:%d/v1/images") % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
|
||||||
|
lines = out.split("\r\n")
|
||||||
|
status_line = lines[0]
|
||||||
|
|
||||||
|
self.assertEqual("HTTP/1.1 201 Created", status_line)
|
||||||
|
|
||||||
|
cmd = ("curl -i -X POST "
|
||||||
|
"-H 'Expect: ' " # Necessary otherwise sends 100 Continue
|
||||||
|
"-H 'X-Image-Meta-Name: ASDF' "
|
||||||
|
"-H 'X-Image-Meta-Status: active' "
|
||||||
|
"-H 'X-Image-Meta-Container-Format: bare' "
|
||||||
|
"-H 'X-Image-Meta-Disk-Format: iso' "
|
||||||
|
"-H 'X-Image-Meta-Size: 2' "
|
||||||
|
"-H 'X-Image-Meta-Is-Public: True' "
|
||||||
|
"http://0.0.0.0:%d/v1/images") % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
|
||||||
|
lines = out.split("\r\n")
|
||||||
|
status_line = lines[0]
|
||||||
|
|
||||||
|
self.assertEqual("HTTP/1.1 201 Created", status_line)
|
||||||
|
cmd = ("curl -i -X POST "
|
||||||
|
"-H 'Expect: ' " # Necessary otherwise sends 100 Continue
|
||||||
|
"-H 'X-Image-Meta-Name: XYZ' "
|
||||||
|
"-H 'X-Image-Meta-Status: saving' "
|
||||||
|
"-H 'X-Image-Meta-Container-Format: ami' "
|
||||||
|
"-H 'X-Image-Meta-Disk-Format: ami' "
|
||||||
|
"-H 'X-Image-Meta-Size: 5' "
|
||||||
|
"-H 'X-Image-Meta-Is-Public: True' "
|
||||||
|
"http://0.0.0.0:%d/v1/images") % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
|
||||||
|
lines = out.split("\r\n")
|
||||||
|
status_line = lines[0]
|
||||||
|
|
||||||
|
self.assertEqual("HTTP/1.1 201 Created", status_line)
|
||||||
|
|
||||||
|
# 2. GET /images with no query params
|
||||||
|
# Verify three public images sorted by created_at desc
|
||||||
|
cmd = "curl http://0.0.0.0:%d/v1/images" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
images = json.loads(out.strip())['images']
|
||||||
|
|
||||||
|
self.assertEqual(len(images), 3)
|
||||||
|
self.assertEqual(images[0]['id'], 3)
|
||||||
|
self.assertEqual(images[1]['id'], 2)
|
||||||
|
self.assertEqual(images[2]['id'], 1)
|
||||||
|
|
||||||
|
# 3. GET /images sorted by name asc
|
||||||
|
params = 'sort_key=name&sort_dir=asc'
|
||||||
|
cmd = "curl 'http://0.0.0.0:%d/v1/images?%s'" % (api_port, params)
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
images = json.loads(out.strip())['images']
|
||||||
|
|
||||||
|
self.assertEqual(len(images), 3)
|
||||||
|
self.assertEqual(images[0]['id'], 2)
|
||||||
|
self.assertEqual(images[1]['id'], 1)
|
||||||
|
self.assertEqual(images[2]['id'], 3)
|
||||||
|
|
||||||
|
# 4. GET /images sorted by size desc
|
||||||
|
params = 'sort_key=size&sort_dir=desc'
|
||||||
|
cmd = "curl 'http://0.0.0.0:%d/v1/images?%s'" % (api_port, params)
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
images = json.loads(out.strip())['images']
|
||||||
|
|
||||||
|
self.assertEqual(len(images), 3)
|
||||||
|
self.assertEqual(images[0]['id'], 1)
|
||||||
|
self.assertEqual(images[1]['id'], 3)
|
||||||
|
self.assertEqual(images[2]['id'], 2)
|
||||||
|
|
|
@ -33,7 +33,7 @@ class TestApiHttplib2(functional.FunctionalTest):
|
||||||
|
|
||||||
"""Functional tests using httplib2 against the API server"""
|
"""Functional tests using httplib2 against the API server"""
|
||||||
|
|
||||||
def test_001_get_head_simple_post(self):
|
def test_get_head_simple_post(self):
|
||||||
"""
|
"""
|
||||||
We test the following sequential series of actions:
|
We test the following sequential series of actions:
|
||||||
|
|
||||||
|
@ -63,7 +63,6 @@ class TestApiHttplib2(functional.FunctionalTest):
|
||||||
11. PUT /images/1
|
11. PUT /images/1
|
||||||
- Add a previously deleted property.
|
- Add a previously deleted property.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
self.start_servers()
|
self.start_servers()
|
||||||
|
|
||||||
|
@ -272,3 +271,322 @@ class TestApiHttplib2(functional.FunctionalTest):
|
||||||
self.assertEqual(data['properties']['distro'], "Ubuntu")
|
self.assertEqual(data['properties']['distro'], "Ubuntu")
|
||||||
|
|
||||||
self.stop_servers()
|
self.stop_servers()
|
||||||
|
|
||||||
|
def test_queued_process_flow(self):
|
||||||
|
"""
|
||||||
|
We test the process flow where a user registers an image
|
||||||
|
with Glance but does not immediately upload an image file.
|
||||||
|
Later, the user uploads an image file using a PUT operation.
|
||||||
|
We track the changing of image status throughout this process.
|
||||||
|
|
||||||
|
0. GET /images
|
||||||
|
- Verify no public images
|
||||||
|
1. POST /images with public image named Image1 with no location
|
||||||
|
attribute and no image data.
|
||||||
|
- Verify 201 returned
|
||||||
|
2. GET /images
|
||||||
|
- Verify one public image
|
||||||
|
3. HEAD /images/1
|
||||||
|
- Verify image now in queued status
|
||||||
|
4. PUT /images/1 with image data
|
||||||
|
- Verify 200 returned
|
||||||
|
5. HEAD /images/1
|
||||||
|
- Verify image now in active status
|
||||||
|
6. GET /images
|
||||||
|
- Verify one public image
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.cleanup()
|
||||||
|
self.start_servers()
|
||||||
|
|
||||||
|
# 0. GET /images
|
||||||
|
# Verify no public images
|
||||||
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'GET')
|
||||||
|
self.assertEqual(response.status, 200)
|
||||||
|
self.assertEqual(content, '{"images": []}')
|
||||||
|
|
||||||
|
# 1. POST /images with public image named Image1
|
||||||
|
# with no location or image data
|
||||||
|
headers = {'Content-Type': 'application/octet-stream',
|
||||||
|
'X-Image-Meta-Name': 'Image1',
|
||||||
|
'X-Image-Meta-Is-Public': 'True'}
|
||||||
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'POST', headers=headers)
|
||||||
|
self.assertEqual(response.status, 201)
|
||||||
|
data = json.loads(content)
|
||||||
|
self.assertEqual(data['image']['checksum'], None)
|
||||||
|
self.assertEqual(data['image']['size'], 0)
|
||||||
|
self.assertEqual(data['image']['container_format'], None)
|
||||||
|
self.assertEqual(data['image']['disk_format'], None)
|
||||||
|
self.assertEqual(data['image']['name'], "Image1")
|
||||||
|
self.assertEqual(data['image']['is_public'], True)
|
||||||
|
|
||||||
|
# 2. GET /images
|
||||||
|
# Verify 1 public image
|
||||||
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'GET')
|
||||||
|
self.assertEqual(response.status, 200)
|
||||||
|
data = json.loads(content)
|
||||||
|
self.assertEqual(data['images'][0]['id'], 1)
|
||||||
|
self.assertEqual(data['images'][0]['checksum'], None)
|
||||||
|
self.assertEqual(data['images'][0]['size'], 0)
|
||||||
|
self.assertEqual(data['images'][0]['container_format'], None)
|
||||||
|
self.assertEqual(data['images'][0]['disk_format'], None)
|
||||||
|
self.assertEqual(data['images'][0]['name'], "Image1")
|
||||||
|
|
||||||
|
# 3. HEAD /images
|
||||||
|
# Verify status is in queued
|
||||||
|
path = "http://%s:%d/v1/images/1" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'HEAD')
|
||||||
|
self.assertEqual(response.status, 200)
|
||||||
|
self.assertEqual(response['x-image-meta-name'], "Image1")
|
||||||
|
self.assertEqual(response['x-image-meta-status'], "queued")
|
||||||
|
self.assertEqual(response['x-image-meta-size'], '0')
|
||||||
|
self.assertEqual(response['x-image-meta-id'], '1')
|
||||||
|
|
||||||
|
# 4. PUT /images/1 with image data, verify 200 returned
|
||||||
|
image_data = "*" * FIVE_KB
|
||||||
|
headers = {'Content-Type': 'application/octet-stream'}
|
||||||
|
path = "http://%s:%d/v1/images/1" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'PUT', headers=headers,
|
||||||
|
body=image_data)
|
||||||
|
self.assertEqual(response.status, 200)
|
||||||
|
data = json.loads(content)
|
||||||
|
self.assertEqual(data['image']['checksum'],
|
||||||
|
hashlib.md5(image_data).hexdigest())
|
||||||
|
self.assertEqual(data['image']['size'], FIVE_KB)
|
||||||
|
self.assertEqual(data['image']['name'], "Image1")
|
||||||
|
self.assertEqual(data['image']['is_public'], True)
|
||||||
|
|
||||||
|
# 5. HEAD /images
|
||||||
|
# Verify status is in active
|
||||||
|
path = "http://%s:%d/v1/images/1" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'HEAD')
|
||||||
|
self.assertEqual(response.status, 200)
|
||||||
|
self.assertEqual(response['x-image-meta-name'], "Image1")
|
||||||
|
self.assertEqual(response['x-image-meta-status'], "active")
|
||||||
|
|
||||||
|
# 6. GET /images
|
||||||
|
# Verify 1 public image still...
|
||||||
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'GET')
|
||||||
|
self.assertEqual(response.status, 200)
|
||||||
|
data = json.loads(content)
|
||||||
|
self.assertEqual(data['images'][0]['checksum'],
|
||||||
|
hashlib.md5(image_data).hexdigest())
|
||||||
|
self.assertEqual(data['images'][0]['id'], 1)
|
||||||
|
self.assertEqual(data['images'][0]['size'], FIVE_KB)
|
||||||
|
self.assertEqual(data['images'][0]['container_format'], None)
|
||||||
|
self.assertEqual(data['images'][0]['disk_format'], None)
|
||||||
|
self.assertEqual(data['images'][0]['name'], "Image1")
|
||||||
|
|
||||||
|
self.stop_servers()
|
||||||
|
|
||||||
|
def test_version_variations(self):
|
||||||
|
"""
|
||||||
|
We test that various calls to the images and root endpoints are
|
||||||
|
handled properly, and that usage of the Accept: header does
|
||||||
|
content negotiation properly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.cleanup()
|
||||||
|
self.start_servers()
|
||||||
|
|
||||||
|
versions = {'versions': [{
|
||||||
|
"id": "v1.0",
|
||||||
|
"status": "CURRENT",
|
||||||
|
"links": [{
|
||||||
|
"rel": "self",
|
||||||
|
"href": "http://0.0.0.0:%d/v1/" % self.api_port}]}]}
|
||||||
|
versions_json = json.dumps(versions)
|
||||||
|
images = {'images': []}
|
||||||
|
images_json = json.dumps(images)
|
||||||
|
|
||||||
|
# 0. GET / with no Accept: header
|
||||||
|
# Verify version choices returned.
|
||||||
|
# Bug lp:803260 no Accept header causes a 500 in glance-api
|
||||||
|
path = "http://%s:%d/" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'GET')
|
||||||
|
self.assertEqual(response.status, 300)
|
||||||
|
self.assertEqual(content, versions_json)
|
||||||
|
|
||||||
|
# 1. GET /images with no Accept: header
|
||||||
|
# Verify version choices returned.
|
||||||
|
path = "http://%s:%d/images" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'GET')
|
||||||
|
self.assertEqual(response.status, 300)
|
||||||
|
self.assertEqual(content, versions_json)
|
||||||
|
|
||||||
|
# 2. GET /v1/images with no Accept: header
|
||||||
|
# Verify empty images list returned.
|
||||||
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'GET')
|
||||||
|
self.assertEqual(response.status, 200)
|
||||||
|
self.assertEqual(content, images_json)
|
||||||
|
|
||||||
|
# 3. GET / with Accept: unknown header
|
||||||
|
# Verify version choices returned. Verify message in API log about
|
||||||
|
# unknown accept header.
|
||||||
|
path = "http://%s:%d/" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
headers = {'Accept': 'unknown'}
|
||||||
|
response, content = http.request(path, 'GET', headers=headers)
|
||||||
|
self.assertEqual(response.status, 300)
|
||||||
|
self.assertEqual(content, versions_json)
|
||||||
|
self.assertTrue('Unknown accept header'
|
||||||
|
in open(self.api_server.log_file).read())
|
||||||
|
|
||||||
|
# 4. GET / with an Accept: application/vnd.openstack.images-v1
|
||||||
|
# Verify empty image list returned
|
||||||
|
path = "http://%s:%d/images" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
headers = {'Accept': 'application/vnd.openstack.images-v1'}
|
||||||
|
response, content = http.request(path, 'GET', headers=headers)
|
||||||
|
self.assertEqual(response.status, 200)
|
||||||
|
self.assertEqual(content, images_json)
|
||||||
|
|
||||||
|
# 5. 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" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
headers = {'Accept': 'application/vnd.openstack.compute-v1'}
|
||||||
|
response, content = http.request(path, 'GET', headers=headers)
|
||||||
|
self.assertEqual(response.status, 300)
|
||||||
|
self.assertEqual(content, versions_json)
|
||||||
|
self.assertTrue('Unknown accept header'
|
||||||
|
in open(self.api_server.log_file).read())
|
||||||
|
|
||||||
|
# 6. GET /v1.0/images with no Accept: header
|
||||||
|
# Verify empty image list returned
|
||||||
|
path = "http://%s:%d/v1.0/images" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'GET')
|
||||||
|
self.assertEqual(response.status, 200)
|
||||||
|
self.assertEqual(content, images_json)
|
||||||
|
|
||||||
|
# 7. GET /v1.a/images with no Accept: header
|
||||||
|
# Verify empty image list returned
|
||||||
|
path = "http://%s:%d/v1.a/images" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'GET')
|
||||||
|
self.assertEqual(response.status, 200)
|
||||||
|
self.assertEqual(content, images_json)
|
||||||
|
|
||||||
|
# 8. GET /va.1/images with no Accept: header
|
||||||
|
# Verify version choices returned
|
||||||
|
path = "http://%s:%d/va.1/images" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'GET')
|
||||||
|
self.assertEqual(response.status, 300)
|
||||||
|
self.assertEqual(content, versions_json)
|
||||||
|
|
||||||
|
# 9. GET /versions with no Accept: header
|
||||||
|
# Verify version choices returned
|
||||||
|
path = "http://%s:%d/versions" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'GET')
|
||||||
|
self.assertEqual(response.status, 300)
|
||||||
|
self.assertEqual(content, versions_json)
|
||||||
|
|
||||||
|
# 10. GET /versions with a Accept: application/vnd.openstack.images-v1
|
||||||
|
# header. Verify version choices returned.
|
||||||
|
path = "http://%s:%d/versions" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
headers = {'Accept': 'application/vnd.openstack.images-v1'}
|
||||||
|
response, content = http.request(path, 'GET', headers=headers)
|
||||||
|
self.assertEqual(response.status, 300)
|
||||||
|
self.assertEqual(content, versions_json)
|
||||||
|
|
||||||
|
# 11. GET /v1/versions with no Accept: header
|
||||||
|
# Verify 404 returned
|
||||||
|
path = "http://%s:%d/v1/versions" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'GET')
|
||||||
|
self.assertEqual(response.status, 404)
|
||||||
|
|
||||||
|
# 12. GET /v2/versions with no Accept: header
|
||||||
|
# Verify version choices returned
|
||||||
|
path = "http://%s:%d/v2/versions" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'GET')
|
||||||
|
self.assertEqual(response.status, 300)
|
||||||
|
self.assertEqual(content, versions_json)
|
||||||
|
|
||||||
|
# 13. 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" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
headers = {'Accept': 'application/vnd.openstack.images-v2'}
|
||||||
|
response, content = http.request(path, 'GET', headers=headers)
|
||||||
|
self.assertEqual(response.status, 300)
|
||||||
|
self.assertEqual(content, versions_json)
|
||||||
|
self.assertTrue('Unknown accept header'
|
||||||
|
in open(self.api_server.log_file).read())
|
||||||
|
|
||||||
|
# 14. GET /v1.2/images with no Accept: header
|
||||||
|
# Verify version choices returned
|
||||||
|
path = "http://%s:%d/v1.2/images" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'GET')
|
||||||
|
self.assertEqual(response.status, 300)
|
||||||
|
self.assertEqual(content, versions_json)
|
||||||
|
self.assertTrue('Unknown version in versioned URI'
|
||||||
|
in open(self.api_server.log_file).read())
|
||||||
|
|
||||||
|
self.stop_servers()
|
||||||
|
|
||||||
|
def test_size_greater_2G_mysql(self):
|
||||||
|
"""
|
||||||
|
A test against the actual datastore backend for the registry
|
||||||
|
to ensure that the image size property is not truncated.
|
||||||
|
|
||||||
|
:see https://bugs.launchpad.net/glance/+bug/739433
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.cleanup()
|
||||||
|
self.start_servers()
|
||||||
|
|
||||||
|
# 1. POST /images with public image named Image1
|
||||||
|
# attribute and a size of 5G. Use the HTTP engine with an
|
||||||
|
# X-Image-Meta-Location attribute to make Glance forego
|
||||||
|
# "adding" the image data.
|
||||||
|
# Verify a 201 OK is returned
|
||||||
|
headers = {'Content-Type': 'application/octet-stream',
|
||||||
|
'X-Image-Meta-Location': 'http://example.com/fakeimage',
|
||||||
|
'X-Image-Meta-Size': str(FIVE_GB),
|
||||||
|
'X-Image-Meta-Name': 'Image1',
|
||||||
|
'X-Image-Meta-Is-Public': 'True'}
|
||||||
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'POST', headers=headers)
|
||||||
|
self.assertEqual(response.status, 201)
|
||||||
|
new_image_url = response['location']
|
||||||
|
self.assertTrue(new_image_url is not None,
|
||||||
|
"Could not find a new image URI!")
|
||||||
|
self.assertTrue("v1/images" in new_image_url,
|
||||||
|
"v1/images no in %s" % new_image_url)
|
||||||
|
|
||||||
|
# 2. HEAD /images
|
||||||
|
# Verify image size is what was passed in, and not truncated
|
||||||
|
path = new_image_url
|
||||||
|
http = httplib2.Http()
|
||||||
|
response, content = http.request(path, 'HEAD')
|
||||||
|
self.assertEqual(response.status, 200)
|
||||||
|
self.assertEqual(response['x-image-meta-size'], str(FIVE_GB))
|
||||||
|
self.assertEqual(response['x-image-meta-name'], 'Image1')
|
||||||
|
self.assertEqual(response['x-image-meta-is_public'], 'True')
|
||||||
|
|
||||||
|
self.stop_servers()
|
||||||
|
|
|
@ -33,7 +33,6 @@ class TestLogging(functional.FunctionalTest):
|
||||||
Test logging output proper when verbose and debug
|
Test logging output proper when verbose and debug
|
||||||
is on.
|
is on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
self.start_servers()
|
self.start_servers()
|
||||||
|
|
||||||
|
@ -60,7 +59,6 @@ class TestLogging(functional.FunctionalTest):
|
||||||
Test logging output proper when verbose and debug
|
Test logging output proper when verbose and debug
|
||||||
is off.
|
is off.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
self.start_servers(debug=False, verbose=False)
|
self.start_servers(debug=False, verbose=False)
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,6 @@ class TestMiscellaneous(functional.FunctionalTest):
|
||||||
We also fire the glance-upload tool against the API server
|
We also fire the glance-upload tool against the API server
|
||||||
and verify that glance-upload doesn't eat the exception either...
|
and verify that glance-upload doesn't eat the exception either...
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
self.start_servers()
|
self.start_servers()
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import sys
|
||||||
import stubout
|
import stubout
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
|
import glance.common.client
|
||||||
from glance.common import exception
|
from glance.common import exception
|
||||||
from glance.registry import server as rserver
|
from glance.registry import server as rserver
|
||||||
from glance.api import v1 as server
|
from glance.api import v1 as server
|
||||||
|
@ -43,14 +44,14 @@ DEBUG = False
|
||||||
|
|
||||||
|
|
||||||
def stub_out_http_backend(stubs):
|
def stub_out_http_backend(stubs):
|
||||||
"""Stubs out the httplib.HTTPRequest.getresponse to return
|
"""
|
||||||
|
Stubs out the httplib.HTTPRequest.getresponse to return
|
||||||
faked-out data instead of grabbing actual contents of a resource
|
faked-out data instead of grabbing actual contents of a resource
|
||||||
|
|
||||||
The stubbed getresponse() returns an iterator over
|
The stubbed getresponse() returns an iterator over
|
||||||
the data "I am a teapot, short and stout\n"
|
the data "I am a teapot, short and stout\n"
|
||||||
|
|
||||||
:param stubs: Set of stubout stubs
|
:param stubs: Set of stubout stubs
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class FakeHTTPConnection(object):
|
class FakeHTTPConnection(object):
|
||||||
|
@ -94,7 +95,6 @@ def stub_out_filesystem_backend():
|
||||||
//tmp/glance-tests/2 <-- file containing "chunk00000remainder"
|
//tmp/glance-tests/2 <-- file containing "chunk00000remainder"
|
||||||
|
|
||||||
The stubbed service yields the data in the above files.
|
The stubbed service yields the data in the above files.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Establish a clean faked filesystem with dummy images
|
# Establish a clean faked filesystem with dummy images
|
||||||
|
@ -108,13 +108,13 @@ def stub_out_filesystem_backend():
|
||||||
|
|
||||||
|
|
||||||
def stub_out_s3_backend(stubs):
|
def stub_out_s3_backend(stubs):
|
||||||
""" Stubs out the S3 Backend with fake data and calls.
|
"""
|
||||||
|
Stubs out the S3 Backend with fake data and calls.
|
||||||
|
|
||||||
The stubbed s3 backend provides back an iterator over
|
The stubbed s3 backend provides back an iterator over
|
||||||
the data ""
|
the data ""
|
||||||
|
|
||||||
:param stubs: Set of stubout stubs
|
:param stubs: Set of stubout stubs
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class FakeSwiftAuth(object):
|
class FakeSwiftAuth(object):
|
||||||
|
@ -254,14 +254,15 @@ def stub_out_registry_and_store_server(stubs):
|
||||||
for i in self.response.app_iter:
|
for i in self.response.app_iter:
|
||||||
yield i
|
yield i
|
||||||
|
|
||||||
stubs.Set(glance.client.BaseClient, 'get_connection_type',
|
stubs.Set(glance.common.client.BaseClient, 'get_connection_type',
|
||||||
fake_get_connection_type)
|
fake_get_connection_type)
|
||||||
stubs.Set(glance.client.ImageBodyIterator, '__iter__',
|
stubs.Set(glance.common.client.ImageBodyIterator, '__iter__',
|
||||||
fake_image_iter)
|
fake_image_iter)
|
||||||
|
|
||||||
|
|
||||||
def stub_out_registry_db_image_api(stubs):
|
def stub_out_registry_db_image_api(stubs):
|
||||||
"""Stubs out the database set/fetch API calls for Registry
|
"""
|
||||||
|
Stubs out the database set/fetch API calls for Registry
|
||||||
so the calls are routed to an in-memory dict. This helps us
|
so the calls are routed to an in-memory dict. This helps us
|
||||||
avoid having to manually clear or flush the SQLite database.
|
avoid having to manually clear or flush the SQLite database.
|
||||||
|
|
||||||
|
@ -269,6 +270,7 @@ def stub_out_registry_db_image_api(stubs):
|
||||||
|
|
||||||
:param stubs: Set of stubout stubs
|
:param stubs: Set of stubout stubs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class FakeDatastore(object):
|
class FakeDatastore(object):
|
||||||
|
|
||||||
FIXTURES = [
|
FIXTURES = [
|
||||||
|
@ -388,8 +390,8 @@ def stub_out_registry_db_image_api(stubs):
|
||||||
else:
|
else:
|
||||||
return images[0]
|
return images[0]
|
||||||
|
|
||||||
def image_get_all_public(self, _context, filters=None,
|
def image_get_all_public(self, _context, filters=None, marker=None,
|
||||||
marker=None, limit=1000):
|
limit=1000, sort_key=None, sort_dir=None):
|
||||||
images = [f for f in self.images if f['is_public'] == True]
|
images = [f for f in self.images if f['is_public'] == True]
|
||||||
|
|
||||||
if 'size_min' in filters:
|
if 'size_min' in filters:
|
||||||
|
@ -414,16 +416,24 @@ def stub_out_registry_db_image_api(stubs):
|
||||||
for k, v in filters.items():
|
for k, v in filters.items():
|
||||||
images = [f for f in images if f[k] == v]
|
images = [f for f in images if f[k] == v]
|
||||||
|
|
||||||
|
# sorted func expects func that compares in descending order
|
||||||
def image_cmp(x, y):
|
def image_cmp(x, y):
|
||||||
if x['created_at'] > y['created_at']:
|
_sort_dir = sort_dir or 'desc'
|
||||||
return 1
|
multiplier = {
|
||||||
elif x['created_at'] == y['created_at']:
|
'asc': -1,
|
||||||
|
'desc': 1,
|
||||||
|
}[_sort_dir]
|
||||||
|
|
||||||
|
_sort_key = sort_key or 'created_at'
|
||||||
|
if x[_sort_key] > y[_sort_key]:
|
||||||
|
return 1 * multiplier
|
||||||
|
elif x[_sort_key] == y[_sort_key]:
|
||||||
if x['id'] > y['id']:
|
if x['id'] > y['id']:
|
||||||
return 1
|
return 1 * multiplier
|
||||||
else:
|
else:
|
||||||
return -1
|
return -1 * multiplier
|
||||||
else:
|
else:
|
||||||
return -1
|
return -1 * multiplier
|
||||||
|
|
||||||
images = sorted(images, cmp=image_cmp)
|
images = sorted(images, cmp=image_cmp)
|
||||||
images.reverse()
|
images.reverse()
|
||||||
|
@ -437,6 +447,9 @@ def stub_out_registry_db_image_api(stubs):
|
||||||
start_index = i + 1
|
start_index = i + 1
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if start_index == -1:
|
||||||
|
raise exception.NotFound(marker)
|
||||||
|
|
||||||
return images[start_index:start_index + limit]
|
return images[start_index:start_index + limit]
|
||||||
|
|
||||||
fake_datastore = FakeDatastore()
|
fake_datastore = FakeDatastore()
|
||||||
|
|
|
@ -50,9 +50,9 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.stubs.UnsetAll()
|
self.stubs.UnsetAll()
|
||||||
|
|
||||||
def test_get_root(self):
|
def test_get_root(self):
|
||||||
"""Tests that the root registry API returns "index",
|
"""
|
||||||
|
Tests that the root registry API returns "index",
|
||||||
which is a list of public images
|
which is a list of public images
|
||||||
|
|
||||||
"""
|
"""
|
||||||
fixture = {'id': 2,
|
fixture = {'id': 2,
|
||||||
'name': 'fake image #2',
|
'name': 'fake image #2',
|
||||||
|
@ -70,9 +70,9 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.assertEquals(v, images[0][k])
|
self.assertEquals(v, images[0][k])
|
||||||
|
|
||||||
def test_get_index(self):
|
def test_get_index(self):
|
||||||
"""Tests that the /images registry API returns list of
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
public images
|
public images
|
||||||
|
|
||||||
"""
|
"""
|
||||||
fixture = {'id': 2,
|
fixture = {'id': 2,
|
||||||
'name': 'fake image #2',
|
'name': 'fake image #2',
|
||||||
|
@ -90,11 +90,10 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.assertEquals(v, images[0][k])
|
self.assertEquals(v, images[0][k])
|
||||||
|
|
||||||
def test_get_index_marker(self):
|
def test_get_index_marker(self):
|
||||||
"""Tests that the /images registry API returns list of
|
|
||||||
public images that conforms to a marker query param
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images that conforms to a marker query param
|
||||||
|
"""
|
||||||
time1 = datetime.datetime.utcnow() + datetime.timedelta(seconds=5)
|
time1 = datetime.datetime.utcnow() + datetime.timedelta(seconds=5)
|
||||||
time2 = datetime.datetime.utcnow()
|
time2 = datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
@ -147,10 +146,19 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.assertEquals(int(images[1]['id']), 5)
|
self.assertEquals(int(images[1]['id']), 5)
|
||||||
self.assertEquals(int(images[2]['id']), 2)
|
self.assertEquals(int(images[2]['id']), 2)
|
||||||
|
|
||||||
def test_get_index_limit(self):
|
def test_get_index_invalid_marker(self):
|
||||||
"""Tests that the /images registry API returns list of
|
"""
|
||||||
public images that conforms to a limit query param
|
Tests that the /images registry API returns a 400
|
||||||
|
when an invalid marker is provided
|
||||||
|
"""
|
||||||
|
req = webob.Request.blank('/images?marker=10')
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
self.assertEquals(res.status_int, 400)
|
||||||
|
|
||||||
|
def test_get_index_limit(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images that conforms to a limit query param
|
||||||
"""
|
"""
|
||||||
extra_fixture = {'id': 3,
|
extra_fixture = {'id': 3,
|
||||||
'status': 'active',
|
'status': 'active',
|
||||||
|
@ -186,27 +194,27 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.assertTrue(int(images[0]['id']), 4)
|
self.assertTrue(int(images[0]['id']), 4)
|
||||||
|
|
||||||
def test_get_index_limit_negative(self):
|
def test_get_index_limit_negative(self):
|
||||||
"""Tests that the /images registry API returns list of
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
public images that conforms to a limit query param
|
public images that conforms to a limit query param
|
||||||
|
|
||||||
"""
|
"""
|
||||||
req = webob.Request.blank('/images?limit=-1')
|
req = webob.Request.blank('/images?limit=-1')
|
||||||
res = req.get_response(self.api)
|
res = req.get_response(self.api)
|
||||||
self.assertEquals(res.status_int, 400)
|
self.assertEquals(res.status_int, 400)
|
||||||
|
|
||||||
def test_get_index_limit_non_int(self):
|
def test_get_index_limit_non_int(self):
|
||||||
"""Tests that the /images registry API returns list of
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
public images that conforms to a limit query param
|
public images that conforms to a limit query param
|
||||||
|
|
||||||
"""
|
"""
|
||||||
req = webob.Request.blank('/images?limit=a')
|
req = webob.Request.blank('/images?limit=a')
|
||||||
res = req.get_response(self.api)
|
res = req.get_response(self.api)
|
||||||
self.assertEquals(res.status_int, 400)
|
self.assertEquals(res.status_int, 400)
|
||||||
|
|
||||||
def test_get_index_limit_marker(self):
|
def test_get_index_limit_marker(self):
|
||||||
"""Tests that the /images registry API returns list of
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
public images that conforms to limit and marker query params
|
public images that conforms to limit and marker query params
|
||||||
|
|
||||||
"""
|
"""
|
||||||
extra_fixture = {'id': 3,
|
extra_fixture = {'id': 3,
|
||||||
'status': 'active',
|
'status': 'active',
|
||||||
|
@ -242,10 +250,10 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.assertEqual(int(images[0]['id']), 2)
|
self.assertEqual(int(images[0]['id']), 2)
|
||||||
|
|
||||||
def test_get_index_filter_name(self):
|
def test_get_index_filter_name(self):
|
||||||
"""Tests that the /images registry API returns list of
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
public images that have a specific name. This is really a sanity
|
public images that have a specific name. This is really a sanity
|
||||||
check, filtering is tested more in-depth using /images/detail
|
check, filtering is tested more in-depth using /images/detail
|
||||||
|
|
||||||
"""
|
"""
|
||||||
fixture = {'id': 2,
|
fixture = {'id': 2,
|
||||||
'name': 'fake image #2',
|
'name': 'fake image #2',
|
||||||
|
@ -285,10 +293,402 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
for image in images:
|
for image in images:
|
||||||
self.assertEqual('new name! #123', image['name'])
|
self.assertEqual('new name! #123', image['name'])
|
||||||
|
|
||||||
def test_get_details(self):
|
def test_get_index_sort_default_created_at_desc(self):
|
||||||
"""Tests that the /images/detail registry API returns
|
"""
|
||||||
a mapping containing a list of detailed image information
|
Tests that the /images registry API returns list of
|
||||||
|
public images that conforms to a default sort key/dir
|
||||||
|
"""
|
||||||
|
time1 = datetime.datetime.utcnow() + datetime.timedelta(seconds=5)
|
||||||
|
time2 = datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'new name! #123',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None,
|
||||||
|
'created_at': time1}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'new name! #123',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None,
|
||||||
|
'created_at': time1}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 5,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'new name! #123',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None,
|
||||||
|
'created_at': time2}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
req = webob.Request.blank('/images')
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
res_dict = json.loads(res.body)
|
||||||
|
self.assertEquals(res.status_int, 200)
|
||||||
|
|
||||||
|
images = res_dict['images']
|
||||||
|
self.assertEquals(len(images), 4)
|
||||||
|
self.assertEquals(int(images[0]['id']), 4)
|
||||||
|
self.assertEquals(int(images[1]['id']), 3)
|
||||||
|
self.assertEquals(int(images[2]['id']), 5)
|
||||||
|
self.assertEquals(int(images[3]['id']), 2)
|
||||||
|
|
||||||
|
def test_get_index_bad_sort_key(self):
|
||||||
|
"""Ensure a 400 is returned when a bad sort_key is provided."""
|
||||||
|
req = webob.Request.blank('/images?sort_key=asdf')
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
self.assertEqual(400, res.status_int)
|
||||||
|
|
||||||
|
def test_get_index_bad_sort_dir(self):
|
||||||
|
"""Ensure a 400 is returned when a bad sort_dir is provided."""
|
||||||
|
req = webob.Request.blank('/images?sort_dir=asdf')
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
self.assertEqual(400, res.status_int)
|
||||||
|
|
||||||
|
def test_get_index_sort_id_desc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images sorted by id in descending order.
|
||||||
|
"""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'asdf',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'xyz',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
req = webob.Request.blank('/images?sort_key=id&sort_dir=desc')
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
self.assertEquals(res.status_int, 200)
|
||||||
|
res_dict = json.loads(res.body)
|
||||||
|
|
||||||
|
images = res_dict['images']
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 4)
|
||||||
|
self.assertEquals(int(images[1]['id']), 3)
|
||||||
|
self.assertEquals(int(images[2]['id']), 2)
|
||||||
|
|
||||||
|
def test_get_index_sort_name_asc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images sorted alphabetically by name in
|
||||||
|
ascending order.
|
||||||
|
"""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'asdf',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'xyz',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
req = webob.Request.blank('/images?sort_key=name&sort_dir=asc')
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
self.assertEquals(res.status_int, 200)
|
||||||
|
res_dict = json.loads(res.body)
|
||||||
|
|
||||||
|
images = res_dict['images']
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 3)
|
||||||
|
self.assertEquals(int(images[1]['id']), 2)
|
||||||
|
self.assertEquals(int(images[2]['id']), 4)
|
||||||
|
|
||||||
|
def test_get_index_sort_status_desc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images sorted alphabetically by status in
|
||||||
|
descending order.
|
||||||
|
"""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'killed',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'asdf',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'xyz',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
req = webob.Request.blank('/images?sort_key=status&sort_dir=desc')
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
self.assertEquals(res.status_int, 200)
|
||||||
|
res_dict = json.loads(res.body)
|
||||||
|
|
||||||
|
images = res_dict['images']
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 3)
|
||||||
|
self.assertEquals(int(images[1]['id']), 4)
|
||||||
|
self.assertEquals(int(images[2]['id']), 2)
|
||||||
|
|
||||||
|
def test_get_index_sort_disk_format_asc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images sorted alphabetically by disk_format in
|
||||||
|
ascending order.
|
||||||
|
"""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'ami',
|
||||||
|
'container_format': 'ami',
|
||||||
|
'name': 'asdf',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vdi',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'xyz',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
req = webob.Request.blank('/images?sort_key=disk_format&sort_dir=asc')
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
self.assertEquals(res.status_int, 200)
|
||||||
|
res_dict = json.loads(res.body)
|
||||||
|
|
||||||
|
images = res_dict['images']
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 3)
|
||||||
|
self.assertEquals(int(images[1]['id']), 4)
|
||||||
|
self.assertEquals(int(images[2]['id']), 2)
|
||||||
|
|
||||||
|
def test_get_index_sort_container_format_desc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images sorted alphabetically by container_format in
|
||||||
|
descending order.
|
||||||
|
"""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'ami',
|
||||||
|
'container_format': 'ami',
|
||||||
|
'name': 'asdf',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'iso',
|
||||||
|
'container_format': 'bare',
|
||||||
|
'name': 'xyz',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
url = '/images?sort_key=container_format&sort_dir=desc'
|
||||||
|
req = webob.Request.blank(url)
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
self.assertEquals(res.status_int, 200)
|
||||||
|
res_dict = json.loads(res.body)
|
||||||
|
|
||||||
|
images = res_dict['images']
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 2)
|
||||||
|
self.assertEquals(int(images[1]['id']), 4)
|
||||||
|
self.assertEquals(int(images[2]['id']), 3)
|
||||||
|
|
||||||
|
def test_get_index_sort_size_asc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images sorted by size in ascending order.
|
||||||
|
"""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'ami',
|
||||||
|
'container_format': 'ami',
|
||||||
|
'name': 'asdf',
|
||||||
|
'size': 100,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'iso',
|
||||||
|
'container_format': 'bare',
|
||||||
|
'name': 'xyz',
|
||||||
|
'size': 2,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
url = '/images?sort_key=size&sort_dir=asc'
|
||||||
|
req = webob.Request.blank(url)
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
self.assertEquals(res.status_int, 200)
|
||||||
|
res_dict = json.loads(res.body)
|
||||||
|
|
||||||
|
images = res_dict['images']
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 4)
|
||||||
|
self.assertEquals(int(images[1]['id']), 2)
|
||||||
|
self.assertEquals(int(images[2]['id']), 3)
|
||||||
|
|
||||||
|
def test_get_index_sort_created_at_asc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images sorted by created_at in ascending order.
|
||||||
|
"""
|
||||||
|
now = datetime.datetime.utcnow()
|
||||||
|
time1 = now + datetime.timedelta(seconds=5)
|
||||||
|
time2 = now
|
||||||
|
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'new name! #123',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None,
|
||||||
|
'created_at': time1}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'new name! #123',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None,
|
||||||
|
'created_at': time2}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
req = webob.Request.blank('/images?sort_key=created_at&sort_dir=asc')
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
self.assertEquals(res.status_int, 200)
|
||||||
|
res_dict = json.loads(res.body)
|
||||||
|
|
||||||
|
images = res_dict['images']
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 2)
|
||||||
|
self.assertEquals(int(images[1]['id']), 4)
|
||||||
|
self.assertEquals(int(images[2]['id']), 3)
|
||||||
|
|
||||||
|
def test_get_index_sort_updated_at_desc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images sorted by updated_at in descending order.
|
||||||
|
"""
|
||||||
|
now = datetime.datetime.utcnow()
|
||||||
|
time1 = now + datetime.timedelta(seconds=5)
|
||||||
|
time2 = now
|
||||||
|
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'new name! #123',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None,
|
||||||
|
'created_at': None,
|
||||||
|
'created_at': time1}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'new name! #123',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None,
|
||||||
|
'created_at': None,
|
||||||
|
'updated_at': time2}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
req = webob.Request.blank('/images?sort_key=updated_at&sort_dir=desc')
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
self.assertEquals(res.status_int, 200)
|
||||||
|
res_dict = json.loads(res.body)
|
||||||
|
|
||||||
|
images = res_dict['images']
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 3)
|
||||||
|
self.assertEquals(int(images[1]['id']), 4)
|
||||||
|
self.assertEquals(int(images[2]['id']), 2)
|
||||||
|
|
||||||
|
def test_get_details(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images/detail registry API returns
|
||||||
|
a mapping containing a list of detailed image information
|
||||||
"""
|
"""
|
||||||
fixture = {'id': 2,
|
fixture = {'id': 2,
|
||||||
'name': 'fake image #2',
|
'name': 'fake image #2',
|
||||||
|
@ -311,11 +711,11 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.assertEquals(v, images[0][k])
|
self.assertEquals(v, images[0][k])
|
||||||
|
|
||||||
def test_get_details_limit_marker(self):
|
def test_get_details_limit_marker(self):
|
||||||
"""Tests that the /images/details registry API returns list of
|
"""
|
||||||
|
Tests that the /images/details registry API returns list of
|
||||||
public images that conforms to limit and marker query params.
|
public images that conforms to limit and marker query params.
|
||||||
This functionality is tested more thoroughly on /images, this is
|
This functionality is tested more thoroughly on /images, this is
|
||||||
just a sanity check
|
just a sanity check
|
||||||
|
|
||||||
"""
|
"""
|
||||||
extra_fixture = {'id': 3,
|
extra_fixture = {'id': 3,
|
||||||
'status': 'active',
|
'status': 'active',
|
||||||
|
@ -350,10 +750,19 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
# expect list to be sorted by created_at desc
|
# expect list to be sorted by created_at desc
|
||||||
self.assertEqual(int(images[0]['id']), 2)
|
self.assertEqual(int(images[0]['id']), 2)
|
||||||
|
|
||||||
def test_get_details_filter_name(self):
|
def test_get_details_invalid_marker(self):
|
||||||
"""Tests that the /images/detail registry API returns list of
|
"""
|
||||||
public images that have a specific name
|
Tests that the /images/detail registry API returns a 400
|
||||||
|
when an invalid marker is provided
|
||||||
|
"""
|
||||||
|
req = webob.Request.blank('/images/detail?marker=10')
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
self.assertEquals(res.status_int, 400)
|
||||||
|
|
||||||
|
def test_get_details_filter_name(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images/detail registry API returns list of
|
||||||
|
public images that have a specific name
|
||||||
"""
|
"""
|
||||||
extra_fixture = {'id': 3,
|
extra_fixture = {'id': 3,
|
||||||
'status': 'active',
|
'status': 'active',
|
||||||
|
@ -389,9 +798,9 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.assertEqual('new name! #123', image['name'])
|
self.assertEqual('new name! #123', image['name'])
|
||||||
|
|
||||||
def test_get_details_filter_status(self):
|
def test_get_details_filter_status(self):
|
||||||
"""Tests that the /images/detail registry API returns list of
|
"""
|
||||||
|
Tests that the /images/detail registry API returns list of
|
||||||
public images that have a specific status
|
public images that have a specific status
|
||||||
|
|
||||||
"""
|
"""
|
||||||
extra_fixture = {'id': 3,
|
extra_fixture = {'id': 3,
|
||||||
'status': 'saving',
|
'status': 'saving',
|
||||||
|
@ -427,9 +836,9 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.assertEqual('saving', image['status'])
|
self.assertEqual('saving', image['status'])
|
||||||
|
|
||||||
def test_get_details_filter_container_format(self):
|
def test_get_details_filter_container_format(self):
|
||||||
"""Tests that the /images/detail registry API returns list of
|
"""
|
||||||
|
Tests that the /images/detail registry API returns list of
|
||||||
public images that have a specific container_format
|
public images that have a specific container_format
|
||||||
|
|
||||||
"""
|
"""
|
||||||
extra_fixture = {'id': 3,
|
extra_fixture = {'id': 3,
|
||||||
'status': 'active',
|
'status': 'active',
|
||||||
|
@ -465,9 +874,9 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.assertEqual('ovf', image['container_format'])
|
self.assertEqual('ovf', image['container_format'])
|
||||||
|
|
||||||
def test_get_details_filter_disk_format(self):
|
def test_get_details_filter_disk_format(self):
|
||||||
"""Tests that the /images/detail registry API returns list of
|
"""
|
||||||
|
Tests that the /images/detail registry API returns list of
|
||||||
public images that have a specific disk_format
|
public images that have a specific disk_format
|
||||||
|
|
||||||
"""
|
"""
|
||||||
extra_fixture = {'id': 3,
|
extra_fixture = {'id': 3,
|
||||||
'status': 'active',
|
'status': 'active',
|
||||||
|
@ -503,9 +912,9 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.assertEqual('vhd', image['disk_format'])
|
self.assertEqual('vhd', image['disk_format'])
|
||||||
|
|
||||||
def test_get_details_filter_size_min(self):
|
def test_get_details_filter_size_min(self):
|
||||||
"""Tests that the /images/detail registry API returns list of
|
"""
|
||||||
|
Tests that the /images/detail registry API returns list of
|
||||||
public images that have a size greater than or equal to size_min
|
public images that have a size greater than or equal to size_min
|
||||||
|
|
||||||
"""
|
"""
|
||||||
extra_fixture = {'id': 3,
|
extra_fixture = {'id': 3,
|
||||||
'status': 'active',
|
'status': 'active',
|
||||||
|
@ -541,9 +950,9 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.assertTrue(image['size'] >= 19)
|
self.assertTrue(image['size'] >= 19)
|
||||||
|
|
||||||
def test_get_details_filter_size_max(self):
|
def test_get_details_filter_size_max(self):
|
||||||
"""Tests that the /images/detail registry API returns list of
|
"""
|
||||||
|
Tests that the /images/detail registry API returns list of
|
||||||
public images that have a size less than or equal to size_max
|
public images that have a size less than or equal to size_max
|
||||||
|
|
||||||
"""
|
"""
|
||||||
extra_fixture = {'id': 3,
|
extra_fixture = {'id': 3,
|
||||||
'status': 'active',
|
'status': 'active',
|
||||||
|
@ -579,10 +988,10 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.assertTrue(image['size'] <= 19)
|
self.assertTrue(image['size'] <= 19)
|
||||||
|
|
||||||
def test_get_details_filter_size_min_max(self):
|
def test_get_details_filter_size_min_max(self):
|
||||||
"""Tests that the /images/detail registry API returns list of
|
"""
|
||||||
|
Tests that the /images/detail registry API returns list of
|
||||||
public images that have a size less than or equal to size_max
|
public images that have a size less than or equal to size_max
|
||||||
and greater than or equal to size_min
|
and greater than or equal to size_min
|
||||||
|
|
||||||
"""
|
"""
|
||||||
extra_fixture = {'id': 3,
|
extra_fixture = {'id': 3,
|
||||||
'status': 'active',
|
'status': 'active',
|
||||||
|
@ -629,9 +1038,9 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.assertTrue(image['size'] <= 19 and image['size'] >= 18)
|
self.assertTrue(image['size'] <= 19 and image['size'] >= 18)
|
||||||
|
|
||||||
def test_get_details_filter_property(self):
|
def test_get_details_filter_property(self):
|
||||||
"""Tests that the /images/detail registry API returns list of
|
"""
|
||||||
|
Tests that the /images/detail registry API returns list of
|
||||||
public images that have a specific custom property
|
public images that have a specific custom property
|
||||||
|
|
||||||
"""
|
"""
|
||||||
extra_fixture = {'id': 3,
|
extra_fixture = {'id': 3,
|
||||||
'status': 'active',
|
'status': 'active',
|
||||||
|
@ -668,6 +1077,45 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
for image in images:
|
for image in images:
|
||||||
self.assertEqual('v a', image['properties']['prop_123'])
|
self.assertEqual('v a', image['properties']['prop_123'])
|
||||||
|
|
||||||
|
def test_get_details_sort_name_asc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images/details registry API returns list of
|
||||||
|
public images sorted alphabetically by name in
|
||||||
|
ascending order.
|
||||||
|
"""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'asdf',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'xyz',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
req = webob.Request.blank('/images/detail?sort_key=name&sort_dir=asc')
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
self.assertEquals(res.status_int, 200)
|
||||||
|
res_dict = json.loads(res.body)
|
||||||
|
|
||||||
|
images = res_dict['images']
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 3)
|
||||||
|
self.assertEquals(int(images[1]['id']), 2)
|
||||||
|
self.assertEquals(int(images[2]['id']), 4)
|
||||||
|
|
||||||
def test_create_image(self):
|
def test_create_image(self):
|
||||||
"""Tests that the /images POST registry API creates the image"""
|
"""Tests that the /images POST registry API creates the image"""
|
||||||
fixture = {'name': 'fake public image',
|
fixture = {'name': 'fake public image',
|
||||||
|
@ -733,8 +1181,10 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.assertTrue('Invalid disk format' in res.body)
|
self.assertTrue('Invalid disk format' in res.body)
|
||||||
|
|
||||||
def test_create_image_with_mismatched_formats(self):
|
def test_create_image_with_mismatched_formats(self):
|
||||||
"""Tests that exception raised for bad matching disk and container
|
"""
|
||||||
formats"""
|
Tests that exception raised for bad matching disk and
|
||||||
|
container formats
|
||||||
|
"""
|
||||||
fixture = {'name': 'fake public image #3',
|
fixture = {'name': 'fake public image #3',
|
||||||
'container_format': 'aki',
|
'container_format': 'aki',
|
||||||
'disk_format': 'ari'}
|
'disk_format': 'ari'}
|
||||||
|
@ -790,8 +1240,10 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.assertEquals(v, res_dict['image'][k])
|
self.assertEquals(v, res_dict['image'][k])
|
||||||
|
|
||||||
def test_update_image_not_existing(self):
|
def test_update_image_not_existing(self):
|
||||||
"""Tests proper exception is raised if attempt to update non-existing
|
"""
|
||||||
image"""
|
Tests proper exception is raised if attempt to update
|
||||||
|
non-existing image
|
||||||
|
"""
|
||||||
fixture = {'status': 'killed'}
|
fixture = {'status': 'killed'}
|
||||||
|
|
||||||
req = webob.Request.blank('/images/3')
|
req = webob.Request.blank('/images/3')
|
||||||
|
@ -847,8 +1299,10 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.assertTrue('Invalid container format' in res.body)
|
self.assertTrue('Invalid container format' in res.body)
|
||||||
|
|
||||||
def test_update_image_with_mismatched_formats(self):
|
def test_update_image_with_mismatched_formats(self):
|
||||||
"""Tests that exception raised for bad matching disk and container
|
"""
|
||||||
formats"""
|
Tests that exception raised for bad matching disk and
|
||||||
|
container formats
|
||||||
|
"""
|
||||||
fixture = {'container_format': 'ari'}
|
fixture = {'container_format': 'ari'}
|
||||||
|
|
||||||
req = webob.Request.blank('/images/2') # Image 2 has disk format 'vhd'
|
req = webob.Request.blank('/images/2') # Image 2 has disk format 'vhd'
|
||||||
|
@ -892,9 +1346,10 @@ class TestRegistryAPI(unittest.TestCase):
|
||||||
self.assertEquals(new_num_images, orig_num_images - 1)
|
self.assertEquals(new_num_images, orig_num_images - 1)
|
||||||
|
|
||||||
def test_delete_image_not_existing(self):
|
def test_delete_image_not_existing(self):
|
||||||
"""Tests proper exception is raised if attempt to delete non-existing
|
"""
|
||||||
image"""
|
Tests proper exception is raised if attempt to delete
|
||||||
|
non-existing image
|
||||||
|
"""
|
||||||
req = webob.Request.blank('/images/3')
|
req = webob.Request.blank('/images/3')
|
||||||
|
|
||||||
req.method = 'DELETE'
|
req.method = 'DELETE'
|
||||||
|
@ -1042,6 +1497,45 @@ class TestGlanceAPI(unittest.TestCase):
|
||||||
"res.headerlist = %r" % res.headerlist)
|
"res.headerlist = %r" % res.headerlist)
|
||||||
self.assertTrue('/images/3' in res.headers['location'])
|
self.assertTrue('/images/3' in res.headers['location'])
|
||||||
|
|
||||||
|
def test_get_index_sort_name_asc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images sorted alphabetically by name in
|
||||||
|
ascending order.
|
||||||
|
"""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'asdf',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'xyz',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
req = webob.Request.blank('/images?sort_key=name&sort_dir=asc')
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
self.assertEquals(res.status_int, 200)
|
||||||
|
res_dict = json.loads(res.body)
|
||||||
|
|
||||||
|
images = res_dict['images']
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 3)
|
||||||
|
self.assertEquals(int(images[1]['id']), 2)
|
||||||
|
self.assertEquals(int(images[2]['id']), 4)
|
||||||
|
|
||||||
def test_image_is_checksummed(self):
|
def test_image_is_checksummed(self):
|
||||||
"""Test that the image contents are checksummed properly"""
|
"""Test that the image contents are checksummed properly"""
|
||||||
fixture_headers = {'x-image-meta-store': 'file',
|
fixture_headers = {'x-image-meta-store': 'file',
|
||||||
|
@ -1206,3 +1700,12 @@ class TestGlanceAPI(unittest.TestCase):
|
||||||
req.method = 'DELETE'
|
req.method = 'DELETE'
|
||||||
res = req.get_response(self.api)
|
res = req.get_response(self.api)
|
||||||
self.assertEquals(res.status_int, 200)
|
self.assertEquals(res.status_int, 200)
|
||||||
|
|
||||||
|
def test_get_details_invalid_marker(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images/detail registry API returns a 400
|
||||||
|
when an invalid marker is provided
|
||||||
|
"""
|
||||||
|
req = webob.Request.blank('/images/detail?marker=10')
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
self.assertEquals(res.status_int, 400)
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import datetime
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import stubout
|
import stubout
|
||||||
|
@ -37,7 +38,7 @@ class TestBadClients(unittest.TestCase):
|
||||||
def test_bad_address(self):
|
def test_bad_address(self):
|
||||||
"""Test ClientConnectionError raised"""
|
"""Test ClientConnectionError raised"""
|
||||||
c = client.Client("127.999.1.1")
|
c = client.Client("127.999.1.1")
|
||||||
self.assertRaises(client.ClientConnectionError,
|
self.assertRaises(exception.ClientConnectionError,
|
||||||
c.get_image,
|
c.get_image,
|
||||||
1)
|
1)
|
||||||
|
|
||||||
|
@ -70,6 +71,298 @@ class TestRegistryClient(unittest.TestCase):
|
||||||
for k, v in fixture.items():
|
for k, v in fixture.items():
|
||||||
self.assertEquals(v, images[0][k])
|
self.assertEquals(v, images[0][k])
|
||||||
|
|
||||||
|
def test_get_index_sort_id_desc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images sorted by id in descending order.
|
||||||
|
"""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'asdf',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'xyz',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
images = self.client.get_images(sort_key='id', sort_dir='desc')
|
||||||
|
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 4)
|
||||||
|
self.assertEquals(int(images[1]['id']), 3)
|
||||||
|
self.assertEquals(int(images[2]['id']), 2)
|
||||||
|
|
||||||
|
def test_get_index_sort_name_asc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images sorted alphabetically by name in
|
||||||
|
ascending order.
|
||||||
|
"""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'asdf',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'xyz',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
images = self.client.get_images(sort_key='name', sort_dir='asc')
|
||||||
|
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 3)
|
||||||
|
self.assertEquals(int(images[1]['id']), 2)
|
||||||
|
self.assertEquals(int(images[2]['id']), 4)
|
||||||
|
|
||||||
|
def test_get_index_sort_status_desc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images sorted alphabetically by status in
|
||||||
|
descending order.
|
||||||
|
"""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'killed',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'asdf',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'xyz',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
images = self.client.get_images(sort_key='status', sort_dir='desc')
|
||||||
|
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 3)
|
||||||
|
self.assertEquals(int(images[1]['id']), 4)
|
||||||
|
self.assertEquals(int(images[2]['id']), 2)
|
||||||
|
|
||||||
|
def test_get_index_sort_disk_format_asc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images sorted alphabetically by disk_format in
|
||||||
|
ascending order.
|
||||||
|
"""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'ami',
|
||||||
|
'container_format': 'ami',
|
||||||
|
'name': 'asdf',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vdi',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'xyz',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
images = self.client.get_images(sort_key='disk_format',
|
||||||
|
sort_dir='asc')
|
||||||
|
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 3)
|
||||||
|
self.assertEquals(int(images[1]['id']), 4)
|
||||||
|
self.assertEquals(int(images[2]['id']), 2)
|
||||||
|
|
||||||
|
def test_get_index_sort_container_format_desc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images sorted alphabetically by container_format in
|
||||||
|
descending order.
|
||||||
|
"""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'ami',
|
||||||
|
'container_format': 'ami',
|
||||||
|
'name': 'asdf',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'iso',
|
||||||
|
'container_format': 'bare',
|
||||||
|
'name': 'xyz',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
images = self.client.get_images(sort_key='container_format',
|
||||||
|
sort_dir='desc')
|
||||||
|
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 2)
|
||||||
|
self.assertEquals(int(images[1]['id']), 4)
|
||||||
|
self.assertEquals(int(images[2]['id']), 3)
|
||||||
|
|
||||||
|
def test_get_index_sort_size_asc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images sorted by size in ascending order.
|
||||||
|
"""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'ami',
|
||||||
|
'container_format': 'ami',
|
||||||
|
'name': 'asdf',
|
||||||
|
'size': 100,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'iso',
|
||||||
|
'container_format': 'bare',
|
||||||
|
'name': 'xyz',
|
||||||
|
'size': 2,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
images = self.client.get_images(sort_key='size', sort_dir='asc')
|
||||||
|
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 4)
|
||||||
|
self.assertEquals(int(images[1]['id']), 2)
|
||||||
|
self.assertEquals(int(images[2]['id']), 3)
|
||||||
|
|
||||||
|
def test_get_index_sort_created_at_asc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images sorted by created_at in ascending order.
|
||||||
|
"""
|
||||||
|
now = datetime.datetime.utcnow()
|
||||||
|
time1 = now + datetime.timedelta(seconds=5)
|
||||||
|
time2 = now
|
||||||
|
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'new name! #123',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None,
|
||||||
|
'created_at': time1}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'new name! #123',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None,
|
||||||
|
'created_at': time2}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
images = self.client.get_images(sort_key='created_at', sort_dir='asc')
|
||||||
|
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 2)
|
||||||
|
self.assertEquals(int(images[1]['id']), 4)
|
||||||
|
self.assertEquals(int(images[2]['id']), 3)
|
||||||
|
|
||||||
|
def test_get_index_sort_updated_at_desc(self):
|
||||||
|
"""
|
||||||
|
Tests that the /images registry API returns list of
|
||||||
|
public images sorted by updated_at in descending order.
|
||||||
|
"""
|
||||||
|
now = datetime.datetime.utcnow()
|
||||||
|
time1 = now + datetime.timedelta(seconds=5)
|
||||||
|
time2 = now
|
||||||
|
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'new name! #123',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None,
|
||||||
|
'created_at': None,
|
||||||
|
'created_at': time1}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'new name! #123',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None,
|
||||||
|
'created_at': None,
|
||||||
|
'updated_at': time2}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
images = self.client.get_images(sort_key='updated_at', sort_dir='desc')
|
||||||
|
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 3)
|
||||||
|
self.assertEquals(int(images[1]['id']), 4)
|
||||||
|
self.assertEquals(int(images[2]['id']), 2)
|
||||||
|
|
||||||
def test_get_image_index_marker(self):
|
def test_get_image_index_marker(self):
|
||||||
"""Test correct set of images returned with marker param."""
|
"""Test correct set of images returned with marker param."""
|
||||||
extra_fixture = {'id': 3,
|
extra_fixture = {'id': 3,
|
||||||
|
@ -100,6 +393,12 @@ class TestRegistryClient(unittest.TestCase):
|
||||||
for image in images:
|
for image in images:
|
||||||
self.assertTrue(image['id'] < 4)
|
self.assertTrue(image['id'] < 4)
|
||||||
|
|
||||||
|
def test_get_image_index_invalid_marker(self):
|
||||||
|
"""Test exception is raised when marker is invalid"""
|
||||||
|
self.assertRaises(exception.Invalid,
|
||||||
|
self.client.get_images,
|
||||||
|
marker=4)
|
||||||
|
|
||||||
def test_get_image_index_limit(self):
|
def test_get_image_index_limit(self):
|
||||||
"""Test correct number of images returned with limit param."""
|
"""Test correct number of images returned with limit param."""
|
||||||
extra_fixture = {'id': 3,
|
extra_fixture = {'id': 3,
|
||||||
|
@ -156,9 +455,38 @@ class TestRegistryClient(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEquals(images[0]['id'], 2)
|
self.assertEquals(images[0]['id'], 2)
|
||||||
|
|
||||||
|
def test_get_image_index_limit_None(self):
|
||||||
|
"""Test correct set of images returned with limit param == None."""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'saving',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'new name! #123',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'saving',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'new name! #125',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
images = self.client.get_images(limit=None)
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
|
||||||
def test_get_image_index_by_name(self):
|
def test_get_image_index_by_name(self):
|
||||||
"""Test correct set of public, name-filtered image returned. This
|
"""
|
||||||
is just a sanity check, we test the details call more in-depth."""
|
Test correct set of public, name-filtered image returned. This
|
||||||
|
is just a sanity check, we test the details call more in-depth.
|
||||||
|
"""
|
||||||
extra_fixture = {'id': 3,
|
extra_fixture = {'id': 3,
|
||||||
'status': 'active',
|
'status': 'active',
|
||||||
'is_public': True,
|
'is_public': True,
|
||||||
|
@ -170,7 +498,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||||
|
|
||||||
glance.registry.db.api.image_create(None, extra_fixture)
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
images = self.client.get_images({'name': 'new name! #123'})
|
images = self.client.get_images(filters={'name': 'new name! #123'})
|
||||||
self.assertEquals(len(images), 1)
|
self.assertEquals(len(images), 1)
|
||||||
|
|
||||||
for image in images:
|
for image in images:
|
||||||
|
@ -223,6 +551,12 @@ class TestRegistryClient(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEquals(images[0]['id'], 2)
|
self.assertEquals(images[0]['id'], 2)
|
||||||
|
|
||||||
|
def test_get_image_details_invalid_marker(self):
|
||||||
|
"""Test exception is raised when marker is invalid"""
|
||||||
|
self.assertRaises(exception.Invalid,
|
||||||
|
self.client.get_images_detailed,
|
||||||
|
marker=4)
|
||||||
|
|
||||||
def test_get_image_details_by_name(self):
|
def test_get_image_details_by_name(self):
|
||||||
"""Tests that a detailed call can be filtered by name"""
|
"""Tests that a detailed call can be filtered by name"""
|
||||||
extra_fixture = {'id': 3,
|
extra_fixture = {'id': 3,
|
||||||
|
@ -236,7 +570,8 @@ class TestRegistryClient(unittest.TestCase):
|
||||||
|
|
||||||
glance.registry.db.api.image_create(None, extra_fixture)
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
images = self.client.get_images_detailed({'name': 'new name! #123'})
|
filters = {'name': 'new name! #123'}
|
||||||
|
images = self.client.get_images_detailed(filters=filters)
|
||||||
self.assertEquals(len(images), 1)
|
self.assertEquals(len(images), 1)
|
||||||
|
|
||||||
for image in images:
|
for image in images:
|
||||||
|
@ -255,7 +590,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||||
|
|
||||||
glance.registry.db.api.image_create(None, extra_fixture)
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
images = self.client.get_images_detailed({'status': 'saving'})
|
images = self.client.get_images_detailed(filters={'status': 'saving'})
|
||||||
self.assertEquals(len(images), 1)
|
self.assertEquals(len(images), 1)
|
||||||
|
|
||||||
for image in images:
|
for image in images:
|
||||||
|
@ -274,7 +609,8 @@ class TestRegistryClient(unittest.TestCase):
|
||||||
|
|
||||||
glance.registry.db.api.image_create(None, extra_fixture)
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
images = self.client.get_images_detailed({'container_format': 'ovf'})
|
filters = {'container_format': 'ovf'}
|
||||||
|
images = self.client.get_images_detailed(filters=filters)
|
||||||
self.assertEquals(len(images), 2)
|
self.assertEquals(len(images), 2)
|
||||||
|
|
||||||
for image in images:
|
for image in images:
|
||||||
|
@ -293,7 +629,8 @@ class TestRegistryClient(unittest.TestCase):
|
||||||
|
|
||||||
glance.registry.db.api.image_create(None, extra_fixture)
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
images = self.client.get_images_detailed({'disk_format': 'vhd'})
|
filters = {'disk_format': 'vhd'}
|
||||||
|
images = self.client.get_images_detailed(filters=filters)
|
||||||
self.assertEquals(len(images), 2)
|
self.assertEquals(len(images), 2)
|
||||||
|
|
||||||
for image in images:
|
for image in images:
|
||||||
|
@ -312,7 +649,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||||
|
|
||||||
glance.registry.db.api.image_create(None, extra_fixture)
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
images = self.client.get_images_detailed({'size_max': 20})
|
images = self.client.get_images_detailed(filters={'size_max': 20})
|
||||||
self.assertEquals(len(images), 1)
|
self.assertEquals(len(images), 1)
|
||||||
|
|
||||||
for image in images:
|
for image in images:
|
||||||
|
@ -331,7 +668,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||||
|
|
||||||
glance.registry.db.api.image_create(None, extra_fixture)
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
images = self.client.get_images_detailed({'size_min': 20})
|
images = self.client.get_images_detailed(filters={'size_min': 20})
|
||||||
self.assertEquals(len(images), 1)
|
self.assertEquals(len(images), 1)
|
||||||
|
|
||||||
for image in images:
|
for image in images:
|
||||||
|
@ -351,12 +688,49 @@ class TestRegistryClient(unittest.TestCase):
|
||||||
|
|
||||||
glance.registry.db.api.image_create(None, extra_fixture)
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
images = self.client.get_images_detailed({'property-p a': 'v a'})
|
filters = {'property-p a': 'v a'}
|
||||||
|
images = self.client.get_images_detailed(filters=filters)
|
||||||
self.assertEquals(len(images), 1)
|
self.assertEquals(len(images), 1)
|
||||||
|
|
||||||
for image in images:
|
for image in images:
|
||||||
self.assertEquals('v a', image['properties']['p a'])
|
self.assertEquals('v a', image['properties']['p a'])
|
||||||
|
|
||||||
|
def test_get_image_details_sort_disk_format_asc(self):
|
||||||
|
"""
|
||||||
|
Tests that a detailed call returns list of
|
||||||
|
public images sorted alphabetically by disk_format in
|
||||||
|
ascending order.
|
||||||
|
"""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'ami',
|
||||||
|
'container_format': 'ami',
|
||||||
|
'name': 'asdf',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vdi',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'xyz',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
images = self.client.get_images_detailed(sort_key='disk_format',
|
||||||
|
sort_dir='asc')
|
||||||
|
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 3)
|
||||||
|
self.assertEquals(int(images[1]['id']), 4)
|
||||||
|
self.assertEquals(int(images[2]['id']), 2)
|
||||||
|
|
||||||
def test_get_image(self):
|
def test_get_image(self):
|
||||||
"""Tests that the detailed info about an image returned"""
|
"""Tests that the detailed info about an image returned"""
|
||||||
fixture = {'id': 1,
|
fixture = {'id': 1,
|
||||||
|
@ -379,7 +753,6 @@ class TestRegistryClient(unittest.TestCase):
|
||||||
|
|
||||||
def test_get_image_non_existing(self):
|
def test_get_image_non_existing(self):
|
||||||
"""Tests that NotFound is raised when getting a non-existing image"""
|
"""Tests that NotFound is raised when getting a non-existing image"""
|
||||||
|
|
||||||
self.assertRaises(exception.NotFound,
|
self.assertRaises(exception.NotFound,
|
||||||
self.client.get_image,
|
self.client.get_image,
|
||||||
42)
|
42)
|
||||||
|
@ -493,7 +866,6 @@ class TestRegistryClient(unittest.TestCase):
|
||||||
|
|
||||||
def test_delete_image(self):
|
def test_delete_image(self):
|
||||||
"""Tests that image metadata is deleted properly"""
|
"""Tests that image metadata is deleted properly"""
|
||||||
|
|
||||||
# Grab the original number of images
|
# Grab the original number of images
|
||||||
orig_num_images = len(self.client.get_images())
|
orig_num_images = len(self.client.get_images())
|
||||||
|
|
||||||
|
@ -507,7 +879,6 @@ class TestRegistryClient(unittest.TestCase):
|
||||||
|
|
||||||
def test_delete_image_not_existing(self):
|
def test_delete_image_not_existing(self):
|
||||||
"""Tests cannot delete non-existing image"""
|
"""Tests cannot delete non-existing image"""
|
||||||
|
|
||||||
self.assertRaises(exception.NotFound,
|
self.assertRaises(exception.NotFound,
|
||||||
self.client.delete_image,
|
self.client.delete_image,
|
||||||
3)
|
3)
|
||||||
|
@ -557,11 +928,46 @@ class TestClient(unittest.TestCase):
|
||||||
|
|
||||||
def test_get_image_not_existing(self):
|
def test_get_image_not_existing(self):
|
||||||
"""Test retrieval of a non-existing image returns a 404"""
|
"""Test retrieval of a non-existing image returns a 404"""
|
||||||
|
|
||||||
self.assertRaises(exception.NotFound,
|
self.assertRaises(exception.NotFound,
|
||||||
self.client.get_image,
|
self.client.get_image,
|
||||||
3)
|
3)
|
||||||
|
|
||||||
|
def test_get_image_index_sort_container_format_desc(self):
|
||||||
|
"""
|
||||||
|
Tests that the client returns list of public images
|
||||||
|
sorted alphabetically by container_format in
|
||||||
|
descending order.
|
||||||
|
"""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'ami',
|
||||||
|
'container_format': 'ami',
|
||||||
|
'name': 'asdf',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
extra_fixture = {'id': 4,
|
||||||
|
'status': 'active',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'iso',
|
||||||
|
'container_format': 'bare',
|
||||||
|
'name': 'xyz',
|
||||||
|
'size': 20,
|
||||||
|
'checksum': None}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
images = self.client.get_images(sort_key='container_format',
|
||||||
|
sort_dir='desc')
|
||||||
|
|
||||||
|
self.assertEquals(len(images), 3)
|
||||||
|
self.assertEquals(int(images[0]['id']), 2)
|
||||||
|
self.assertEquals(int(images[1]['id']), 4)
|
||||||
|
self.assertEquals(int(images[2]['id']), 3)
|
||||||
|
|
||||||
def test_get_image_index(self):
|
def test_get_image_index(self):
|
||||||
"""Test correct set of public image returned"""
|
"""Test correct set of public image returned"""
|
||||||
fixture = {'id': 2,
|
fixture = {'id': 2,
|
||||||
|
@ -602,6 +1008,12 @@ class TestClient(unittest.TestCase):
|
||||||
for image in images:
|
for image in images:
|
||||||
self.assertTrue(image['id'] < 4)
|
self.assertTrue(image['id'] < 4)
|
||||||
|
|
||||||
|
def test_get_image_index_invalid_marker(self):
|
||||||
|
"""Test exception is raised when marker is invalid"""
|
||||||
|
self.assertRaises(exception.Invalid,
|
||||||
|
self.client.get_images,
|
||||||
|
marker=4)
|
||||||
|
|
||||||
def test_get_image_index_limit(self):
|
def test_get_image_index_limit(self):
|
||||||
"""Test correct number of public images returned with limit param."""
|
"""Test correct number of public images returned with limit param."""
|
||||||
extra_fixture = {'id': 3,
|
extra_fixture = {'id': 3,
|
||||||
|
@ -671,7 +1083,8 @@ class TestClient(unittest.TestCase):
|
||||||
|
|
||||||
glance.registry.db.api.image_create(None, extra_fixture)
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
images = self.client.get_images({'name': 'new name! #123'})
|
filters = {'name': 'new name! #123'}
|
||||||
|
images = self.client.get_images(filters=filters)
|
||||||
|
|
||||||
self.assertEquals(len(images), 1)
|
self.assertEquals(len(images), 1)
|
||||||
self.assertEquals('new name! #123', images[0]['name'])
|
self.assertEquals('new name! #123', images[0]['name'])
|
||||||
|
@ -690,7 +1103,8 @@ class TestClient(unittest.TestCase):
|
||||||
|
|
||||||
glance.registry.db.api.image_create(None, extra_fixture)
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
images = self.client.get_images({'property-p a': 'v a'})
|
filters = {'property-p a': 'v a'}
|
||||||
|
images = self.client.get_images(filters=filters)
|
||||||
|
|
||||||
self.assertEquals(len(images), 1)
|
self.assertEquals(len(images), 1)
|
||||||
self.assertEquals(3, images[0]['id'])
|
self.assertEquals(3, images[0]['id'])
|
||||||
|
@ -752,6 +1166,12 @@ class TestClient(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEquals(images[0]['id'], 2)
|
self.assertEquals(images[0]['id'], 2)
|
||||||
|
|
||||||
|
def test_get_image_details_invalid_marker(self):
|
||||||
|
"""Test exception is raised when marker is invalid"""
|
||||||
|
self.assertRaises(exception.Invalid,
|
||||||
|
self.client.get_images_detailed,
|
||||||
|
marker=4)
|
||||||
|
|
||||||
def test_get_image_details_by_base_attribute(self):
|
def test_get_image_details_by_base_attribute(self):
|
||||||
"""Tests that a detailed call can be filtered by a base attribute"""
|
"""Tests that a detailed call can be filtered by a base attribute"""
|
||||||
extra_fixture = {'id': 3,
|
extra_fixture = {'id': 3,
|
||||||
|
@ -765,7 +1185,8 @@ class TestClient(unittest.TestCase):
|
||||||
|
|
||||||
glance.registry.db.api.image_create(None, extra_fixture)
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
images = self.client.get_images_detailed({'name': 'new name! #123'})
|
filters = {'name': 'new name! #123'}
|
||||||
|
images = self.client.get_images_detailed(filters=filters)
|
||||||
self.assertEquals(len(images), 1)
|
self.assertEquals(len(images), 1)
|
||||||
|
|
||||||
for image in images:
|
for image in images:
|
||||||
|
@ -785,12 +1206,30 @@ class TestClient(unittest.TestCase):
|
||||||
|
|
||||||
glance.registry.db.api.image_create(None, extra_fixture)
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
images = self.client.get_images_detailed({'property-p a': 'v a'})
|
filters = {'property-p a': 'v a'}
|
||||||
|
images = self.client.get_images_detailed(filters=filters)
|
||||||
self.assertEquals(len(images), 1)
|
self.assertEquals(len(images), 1)
|
||||||
|
|
||||||
for image in images:
|
for image in images:
|
||||||
self.assertEquals('v a', image['properties']['p a'])
|
self.assertEquals('v a', image['properties']['p a'])
|
||||||
|
|
||||||
|
def test_get_image_bad_filters_with_other_params(self):
|
||||||
|
"""Tests that a detailed call can be filtered by a property"""
|
||||||
|
extra_fixture = {'id': 3,
|
||||||
|
'status': 'saving',
|
||||||
|
'is_public': True,
|
||||||
|
'disk_format': 'vhd',
|
||||||
|
'container_format': 'ovf',
|
||||||
|
'name': 'new name! #123',
|
||||||
|
'size': 19,
|
||||||
|
'checksum': None,
|
||||||
|
'properties': {'p a': 'v a'}}
|
||||||
|
|
||||||
|
glance.registry.db.api.image_create(None, extra_fixture)
|
||||||
|
|
||||||
|
images = self.client.get_images_detailed(filters=None, limit=1)
|
||||||
|
self.assertEquals(len(images), 1)
|
||||||
|
|
||||||
def test_get_image_meta(self):
|
def test_get_image_meta(self):
|
||||||
"""Tests that the detailed info about an image returned"""
|
"""Tests that the detailed info about an image returned"""
|
||||||
fixture = {'id': 2,
|
fixture = {'id': 2,
|
||||||
|
@ -834,7 +1273,6 @@ class TestClient(unittest.TestCase):
|
||||||
|
|
||||||
def test_get_image_non_existing(self):
|
def test_get_image_non_existing(self):
|
||||||
"""Tests that NotFound is raised when getting a non-existing image"""
|
"""Tests that NotFound is raised when getting a non-existing image"""
|
||||||
|
|
||||||
self.assertRaises(exception.NotFound,
|
self.assertRaises(exception.NotFound,
|
||||||
self.client.get_image,
|
self.client.get_image,
|
||||||
42)
|
42)
|
||||||
|
@ -928,9 +1366,11 @@ class TestClient(unittest.TestCase):
|
||||||
self.assertEquals('active', data['status'])
|
self.assertEquals('active', data['status'])
|
||||||
|
|
||||||
def test_add_image_with_bad_iso_properties(self):
|
def test_add_image_with_bad_iso_properties(self):
|
||||||
"""Verify that ISO with invalid container format is rejected.
|
"""
|
||||||
|
Verify that ISO with invalid container format is rejected.
|
||||||
Intended to exercise error path once rather than be exhaustive
|
Intended to exercise error path once rather than be exhaustive
|
||||||
set of mismatches"""
|
set of mismatches
|
||||||
|
"""
|
||||||
fixture = {'name': 'fake public iso',
|
fixture = {'name': 'fake public iso',
|
||||||
'is_public': True,
|
'is_public': True,
|
||||||
'disk_format': 'iso',
|
'disk_format': 'iso',
|
||||||
|
@ -1113,7 +1553,6 @@ class TestClient(unittest.TestCase):
|
||||||
|
|
||||||
def test_delete_image(self):
|
def test_delete_image(self):
|
||||||
"""Tests that image metadata is deleted properly"""
|
"""Tests that image metadata is deleted properly"""
|
||||||
|
|
||||||
# Grab the original number of images
|
# Grab the original number of images
|
||||||
orig_num_images = len(self.client.get_images())
|
orig_num_images = len(self.client.get_images())
|
||||||
|
|
||||||
|
@ -1127,7 +1566,6 @@ class TestClient(unittest.TestCase):
|
||||||
|
|
||||||
def test_delete_image_not_existing(self):
|
def test_delete_image_not_existing(self):
|
||||||
"""Tests cannot delete non-existing image"""
|
"""Tests cannot delete non-existing image"""
|
||||||
|
|
||||||
self.assertRaises(exception.NotFound,
|
self.assertRaises(exception.NotFound,
|
||||||
self.client.delete_image,
|
self.client.delete_image,
|
||||||
3)
|
3)
|
||||||
|
|
|
@ -77,7 +77,8 @@ def check_dependencies():
|
||||||
|
|
||||||
|
|
||||||
def create_virtualenv(venv=VENV):
|
def create_virtualenv(venv=VENV):
|
||||||
"""Creates the virtual environment and installs PIP only into the
|
"""
|
||||||
|
Creates the virtual environment and installs PIP only into the
|
||||||
virtual environment
|
virtual environment
|
||||||
"""
|
"""
|
||||||
print 'Creating venv...',
|
print 'Creating venv...',
|
||||||
|
|
Loading…
Reference in New Issue