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