merging trunk
This commit is contained in:
commit
116ef8202b
@ -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 "
|
||||||
|
@ -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``
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
@ -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,14 +94,7 @@ class Controller(object):
|
|||||||
'size': <SIZE>}, ...
|
'size': <SIZE>}, ...
|
||||||
]}
|
]}
|
||||||
"""
|
"""
|
||||||
params = {'filters': self._get_filters(req)}
|
params = self._get_query_params(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')
|
|
||||||
|
|
||||||
images = registry.get_images_list(self.options, **params)
|
images = registry.get_images_list(self.options, **params)
|
||||||
return dict(images=images)
|
return dict(images=images)
|
||||||
|
|
||||||
@ -125,17 +120,23 @@ 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)
|
||||||
|
|
||||||
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_detail(self.options, **params)
|
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
|
||||||
|
186
glance/client.py
186
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"""
|
||||||
|
|
||||||
@ -209,7 +54,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 +62,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 +78,11 @@ 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 = filters or {}
|
params = self._extract_params(kwargs, v1_images.SUPPORTED_PARAMS)
|
||||||
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 +100,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):
|
||||||
"""
|
"""
|
||||||
|
169
glance/common/client.py
Normal file
169
glance/common/client.py
Normal 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
|
@ -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):
|
||||||
@ -44,42 +45,32 @@ class RegistryClient(BaseClient):
|
|||||||
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
|
||||||
@ -129,7 +129,8 @@ 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,
|
||||||
|
sort_key='created_at', sort_dir='desc'):
|
||||||
"""Get all public images that match zero or more filters.
|
"""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'
|
||||||
@ -137,7 +138,8 @@ def image_get_all_public(context, filters=None, marker=None, limit=None):
|
|||||||
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 +148,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'])
|
||||||
|
@ -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"""
|
||||||
@ -69,14 +76,7 @@ class Controller(object):
|
|||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
params = {
|
params = self._get_query_params(req)
|
||||||
'filters': self._get_filters(req),
|
|
||||||
'limit': self._get_limit(req),
|
|
||||||
}
|
|
||||||
|
|
||||||
if 'marker' in req.str_params:
|
|
||||||
params['marker'] = self._get_marker(req)
|
|
||||||
|
|
||||||
images = db_api.image_get_all_public(None, **params)
|
images = db_api.image_get_all_public(None, **params)
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
@ -99,19 +99,33 @@ class Controller(object):
|
|||||||
all image model fields.
|
all image model fields.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
params = {
|
params = self._get_query_params(req)
|
||||||
'filters': self._get_filters(req),
|
|
||||||
'limit': self._get_limit(req),
|
|
||||||
}
|
|
||||||
|
|
||||||
if 'marker' in req.str_params:
|
|
||||||
params['marker'] = self._get_marker(req)
|
|
||||||
|
|
||||||
images = db_api.image_get_all_public(None, **params)
|
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
|
||||||
|
|
||||||
@ -148,12 +162,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:
|
||||||
|
@ -1117,3 +1117,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)
|
||||||
|
@ -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
|
||||||
@ -254,9 +255,9 @@ 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)
|
||||||
|
|
||||||
|
|
||||||
@ -388,8 +389,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 +415,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()
|
||||||
|
@ -285,6 +285,398 @@ 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_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):
|
def test_get_details(self):
|
||||||
"""Tests that the /images/detail registry API returns
|
"""Tests that the /images/detail registry API returns
|
||||||
a mapping containing a list of detailed image information
|
a mapping containing a list of detailed image information
|
||||||
@ -668,6 +1060,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',
|
||||||
@ -1042,6 +1473,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',
|
||||||
|
@ -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,
|
||||||
@ -170,7 +463,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:
|
||||||
@ -236,7 +529,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 +549,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 +568,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 +588,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 +608,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 +627,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 +647,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,
|
||||||
@ -562,6 +895,42 @@ class TestClient(unittest.TestCase):
|
|||||||
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,
|
||||||
@ -671,7 +1040,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 +1060,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'])
|
||||||
@ -765,7 +1136,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,7 +1157,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({'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:
|
||||||
|
Loading…
Reference in New Issue
Block a user