Added sort_key and sort_dir query params to apis and clients.

This commit is contained in:
Brian Waldon 2011-06-28 14:03:10 +00:00 committed by Tarmac
commit 1a7ff12a45
15 changed files with 1333 additions and 256 deletions

View File

@ -194,7 +194,7 @@ EXAMPLES
print "Returned the following metadata for the new image:"
for k, v in sorted(image_meta.items()):
print " %(k)30s => %(v)s" % locals()
except client.ClientConnectionError, e:
except exception.ClientConnectionError, e:
host = options.host
port = options.port
print ("Failed to connect to the Glance API server "

View File

@ -113,7 +113,36 @@ in size and in the `saving` status.
c = Client("glance.example.com", 9292)
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
------------------------------------------------

View File

@ -129,6 +129,20 @@ list details these query parameters.
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
------------------------------------------------

View File

@ -83,6 +83,20 @@ list details these query parameters.
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``
----------------

View File

@ -45,6 +45,8 @@ logger = logging.getLogger('glance.api.v1.images')
SUPPORTED_FILTERS = ['name', 'status', 'container_format', 'disk_format',
'size_min', 'size_max']
SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir')
class Controller(object):
@ -92,14 +94,7 @@ class Controller(object):
'size': <SIZE>}, ...
]}
"""
params = {'filters': self._get_filters(req)}
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')
params = self._get_query_params(req)
images = registry.get_images_list(self.options, **params)
return dict(images=images)
@ -125,17 +120,23 @@ class Controller(object):
'properties': {'distro': 'Ubuntu 10.04 LTS', ...}}, ...
]}
"""
params = {'filters': self._get_filters(req)}
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')
params = self._get_query_params(req)
images = registry.get_images_detail(self.options, **params)
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):
"""
Return a dictionary of query param filters from the request

View File

@ -19,172 +19,17 @@
Client classes for callers of a Glance system
"""
import httplib
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 import utils
#TODO(jaypipes) Allow a logger param for client classes
class ClientConnectionError(Exception):
"""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):
class V1Client(base_client.BaseClient):
"""Main client class for accessing Glance resources"""
@ -209,7 +54,7 @@ class V1Client(BaseClient):
return super(V1Client, self).do_request(method, action, body,
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
@ -217,18 +62,15 @@ class V1Client(BaseClient):
collection of images should be filtered
:param marker: id after which to start the page of images
:param limit: maximum number of items to return
:param sort_key: results will be ordered by this image attribute
:param sort_dir: direction in which to to order results (asc, desc)
"""
params = filters or {}
if marker:
params['marker'] = marker
if limit:
params['limit'] = limit
params = self._extract_params(kwargs, v1_images.SUPPORTED_PARAMS)
res = self.do_request("GET", "/images", params=params)
data = json.loads(res.read())['images']
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
@ -236,13 +78,11 @@ class V1Client(BaseClient):
collection of images should be filtered
:param marker: id after which to start the page of images
:param limit: maximum number of items to return
:param sort_key: results will be ordered by this image attribute
:param sort_dir: direction in which to to order results (asc, desc)
"""
params = filters or {}
if marker:
params['marker'] = marker
if limit:
params['limit'] = limit
params = self._extract_params(kwargs, v1_images.SUPPORTED_PARAMS)
res = self.do_request("GET", "/images/detail", params=params)
data = json.loads(res.read())['images']
return data
@ -260,7 +100,7 @@ class V1Client(BaseClient):
res = self.do_request("GET", "/images/%s" % image_id)
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):
"""

169
glance/common/client.py Normal file
View File

@ -0,0 +1,169 @@
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:
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
"""
result = actual_params.get('filters', {})
for allowed_param in allowed_params:
if allowed_param in actual_params:
result[allowed_param] = actual_params[allowed_param]
return result

View File

@ -83,6 +83,11 @@ class DatabaseMigrationError(Error):
pass
class ClientConnectionError(Exception):
"""Error resulting from a client connecting to a server"""
pass
def wrap_exception(f):
def _wrap(*args, **kw):
try:

View File

@ -23,7 +23,8 @@ the Glance Registry API
import json
import urllib
from glance.client import BaseClient
from glance.common.client import BaseClient
from glance.registry import server
class RegistryClient(BaseClient):
@ -44,42 +45,32 @@ class RegistryClient(BaseClient):
port = port or self.DEFAULT_PORT
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
:param filters: dict of keys & expected values to filter results
:param marker: image id after which to start page
: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 {}
if marker != None:
params['marker'] = marker
if limit != None:
params['limit'] = limit
params = self._extract_params(kwargs, server.SUPPORTED_PARAMS)
res = self.do_request("GET", "/images", params=params)
data = json.loads(res.read())['images']
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
:param filters: dict of keys & expected values to filter results
:param marker: image id after which to start page
: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 {}
if marker != None:
params['marker'] = marker
if limit != None:
params['limit'] = limit
params = self._extract_params(kwargs, server.SUPPORTED_PARAMS)
res = self.do_request("GET", "/images/detail", params=params)
data = json.loads(res.read())['images']
return data

View File

@ -23,7 +23,7 @@ Defines interface for DB access
import logging
from sqlalchemy import create_engine, desc
from sqlalchemy import asc, create_engine, desc
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import exc
from sqlalchemy.orm import joinedload
@ -129,7 +129,8 @@ def image_get(context, image_id, session=None):
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,
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'
@ -137,7 +138,8 @@ def image_get_all_public(context, filters=None, marker=None, limit=None):
filters on the image properties attribute
:param marker: image id after which to start page
: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 {}
@ -146,9 +148,17 @@ def image_get_all_public(context, filters=None, marker=None, limit=None):
options(joinedload(models.Image.properties)).\
filter_by(deleted=_deleted(context)).\
filter_by(is_public=True).\
filter(models.Image.status != 'killed').\
order_by(desc(models.Image.created_at)).\
order_by(desc(models.Image.id))
filter(models.Image.status != 'killed')
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:
query = query.filter(models.Image.size >= filters['size_min'])

View File

@ -39,8 +39,15 @@ DISPLAY_FIELDS_IN_INDEX = ['id', 'name', 'size',
SUPPORTED_FILTERS = ['name', 'status', 'container_format', 'disk_format',
'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
SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir')
class Controller(object):
"""Controller for the reference implementation registry server"""
@ -69,14 +76,7 @@ class Controller(object):
}
"""
params = {
'filters': self._get_filters(req),
'limit': self._get_limit(req),
}
if 'marker' in req.str_params:
params['marker'] = self._get_marker(req)
params = self._get_query_params(req)
images = db_api.image_get_all_public(None, **params)
results = []
@ -99,19 +99,33 @@ class Controller(object):
all image model fields.
"""
params = {
'filters': self._get_filters(req),
'limit': self._get_limit(req),
}
if 'marker' in req.str_params:
params['marker'] = self._get_marker(req)
params = self._get_query_params(req)
images = db_api.image_get_all_public(None, **params)
image_dicts = [make_image_dict(i) for i in images]
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):
"""Return a dictionary of query param filters from the request
@ -148,12 +162,35 @@ class Controller(object):
def _get_marker(self, req):
"""Parse a marker query param into something usable."""
marker = req.str_params.get('marker', None)
if marker is None:
return None
try:
marker = int(req.str_params.get('marker', None))
marker = int(marker)
except ValueError:
raise exc.HTTPBadRequest("marker param must be an integer")
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):
"""Return data about the given image id."""
try:

View File

@ -1117,3 +1117,118 @@ class TestCurlApi(functional.FunctionalTest):
self.assertEqual(int(images[0]['id']), 2)
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)

View File

@ -28,6 +28,7 @@ import sys
import stubout
import webob
import glance.common.client
from glance.common import exception
from glance.registry import server as rserver
from glance.api import v1 as server
@ -254,9 +255,9 @@ def stub_out_registry_and_store_server(stubs):
for i in self.response.app_iter:
yield i
stubs.Set(glance.client.BaseClient, 'get_connection_type',
stubs.Set(glance.common.client.BaseClient, 'get_connection_type',
fake_get_connection_type)
stubs.Set(glance.client.ImageBodyIterator, '__iter__',
stubs.Set(glance.common.client.ImageBodyIterator, '__iter__',
fake_image_iter)
@ -388,8 +389,8 @@ def stub_out_registry_db_image_api(stubs):
else:
return images[0]
def image_get_all_public(self, _context, filters=None,
marker=None, limit=1000):
def image_get_all_public(self, _context, filters=None, marker=None,
limit=1000, sort_key=None, sort_dir=None):
images = [f for f in self.images if f['is_public'] == True]
if 'size_min' in filters:
@ -414,16 +415,24 @@ def stub_out_registry_db_image_api(stubs):
for k, v in filters.items():
images = [f for f in images if f[k] == v]
# sorted func expects func that compares in descending order
def image_cmp(x, y):
if x['created_at'] > y['created_at']:
return 1
elif x['created_at'] == y['created_at']:
_sort_dir = sort_dir or 'desc'
multiplier = {
'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']:
return 1
return 1 * multiplier
else:
return -1
return -1 * multiplier
else:
return -1
return -1 * multiplier
images = sorted(images, cmp=image_cmp)
images.reverse()

View File

@ -285,6 +285,398 @@ class TestRegistryAPI(unittest.TestCase):
for image in images:
self.assertEqual('new name! #123', image['name'])
def test_get_index_sort_default_created_at_desc(self):
"""
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
@ -668,6 +1060,45 @@ class TestRegistryAPI(unittest.TestCase):
for image in images:
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):
"""Tests that the /images POST registry API creates the image"""
fixture = {'name': 'fake public image',
@ -1042,6 +1473,45 @@ class TestGlanceAPI(unittest.TestCase):
"res.headerlist = %r" % res.headerlist)
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):
"""Test that the image contents are checksummed properly"""
fixture_headers = {'x-image-meta-store': 'file',

View File

@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import json
import os
import stubout
@ -37,7 +38,7 @@ class TestBadClients(unittest.TestCase):
def test_bad_address(self):
"""Test ClientConnectionError raised"""
c = client.Client("127.999.1.1")
self.assertRaises(client.ClientConnectionError,
self.assertRaises(exception.ClientConnectionError,
c.get_image,
1)
@ -70,6 +71,298 @@ class TestRegistryClient(unittest.TestCase):
for k, v in fixture.items():
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):
"""Test correct set of images returned with marker param."""
extra_fixture = {'id': 3,
@ -170,7 +463,7 @@ class TestRegistryClient(unittest.TestCase):
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)
for image in images:
@ -236,7 +529,8 @@ class TestRegistryClient(unittest.TestCase):
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)
for image in images:
@ -255,7 +549,7 @@ class TestRegistryClient(unittest.TestCase):
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)
for image in images:
@ -274,7 +568,8 @@ class TestRegistryClient(unittest.TestCase):
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)
for image in images:
@ -293,7 +588,8 @@ class TestRegistryClient(unittest.TestCase):
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)
for image in images:
@ -312,7 +608,7 @@ class TestRegistryClient(unittest.TestCase):
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)
for image in images:
@ -331,7 +627,7 @@ class TestRegistryClient(unittest.TestCase):
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)
for image in images:
@ -351,12 +647,49 @@ class TestRegistryClient(unittest.TestCase):
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)
for image in images:
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):
"""Tests that the detailed info about an image returned"""
fixture = {'id': 1,
@ -562,6 +895,42 @@ class TestClient(unittest.TestCase):
self.client.get_image,
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):
"""Test correct set of public image returned"""
fixture = {'id': 2,
@ -671,7 +1040,8 @@ class TestClient(unittest.TestCase):
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('new name! #123', images[0]['name'])
@ -690,7 +1060,8 @@ class TestClient(unittest.TestCase):
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(3, images[0]['id'])
@ -765,7 +1136,8 @@ class TestClient(unittest.TestCase):
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)
for image in images:
@ -785,7 +1157,8 @@ class TestClient(unittest.TestCase):
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)
for image in images: