diff --git a/doc/source/client.rst b/doc/source/client.rst index 9a4182cb94..247840bf02 100644 --- a/doc/source/client.rst +++ b/doc/source/client.rst @@ -63,6 +63,57 @@ Using Glance's Client, we can do this using the following code print c.get_images_detailed() +Filtering Images Returned via ``get_images()`` and ``get_images_detailed()`` +---------------------------------------------------------------------------- + +Both the ``get_images()`` and ``get_images_detailed()`` methods take query +parameters that serve to filter the returned list of images. + +When calling, simply pass an optional dictionary to the method containing +the filters by which you wish to limit results, with the filter keys being one +or more of the below: + +* ``name: NAME`` + + Filters images having a ``name`` attribute matching ``NAME``. + +* ``container_format: FORMAT`` + + Filters images having a ``container_format`` attribute matching ``FORMAT`` + + For more information, see :doc:`About Disk and Container Formats ` + +* ``disk_format: FORMAT`` + + Filters images having a ``disk_format`` attribute matching ``FORMAT`` + + For more information, see :doc:`About Disk and Container Formats ` + +* ``status: STATUS`` + + Filters images having a ``status`` attribute matching ``STATUS`` + + For more information, see :doc:`About Image Statuses ` + +* ``size_min: BYTES`` + + Filters images having a ``size`` attribute greater than or equal to ``BYTES`` + +* ``size_max: BYTES`` + + Filters images having a ``size`` attribute less than or equal to ``BYTES`` + +Here's a quick example that will return all images less than or equal to 5G +in size and in the `saving` status. + +.. code-block:: python + + from glance.client import Client + + c = Client("glance.example.com", 9292) + + filters = {'status': 'saving', 'size_max': (5 * 1024 * 1024 * 1024)} + print c.get_images_detailed(filters) Requesting Detailed Metadata on a Specific Image ------------------------------------------------ @@ -86,7 +137,6 @@ first public image returned, we can use the following code print c.get_image_meta("http://glance.example.com/images/1") - Retrieving a Virtual Machine Image ---------------------------------- diff --git a/doc/source/glanceapi.rst b/doc/source/glanceapi.rst index 57ac588ed4..b978e9832d 100644 --- a/doc/source/glanceapi.rst +++ b/doc/source/glanceapi.rst @@ -92,6 +92,42 @@ JSON-encoded mapping in the following format:: The `checksum` field is an MD5 checksum of the image file data +Filtering Images Returned via ``GET /images`` and ``GET /images/detail`` +------------------------------------------------------------------------ + +Both the ``GET /images`` and ``GET /images/detail`` requests take query +parameters that serve to filter the returned list of images. The following +list details these query parameters. + +* ``name=NAME`` + + Filters images having a ``name`` attribute matching ``NAME``. + +* ``container_format=FORMAT`` + + Filters images having a ``container_format`` attribute matching ``FORMAT`` + + For more information, see :doc:`About Disk and Container Formats ` + +* ``disk_format=FORMAT`` + + Filters images having a ``disk_format`` attribute matching ``FORMAT`` + + For more information, see :doc:`About Disk and Container Formats ` + +* ``status=STATUS`` + + Filters images having a ``status`` attribute matching ``STATUS`` + + For more information, see :doc:`About Image Statuses ` + +* ``size_min=BYTES`` + + Filters images having a ``size`` attribute greater than or equal to ``BYTES`` + +* ``size_max=BYTES`` + + Filters images having a ``size`` attribute less than or equal to ``BYTES`` Requesting Detailed Metadata on a Specific Image ------------------------------------------------ diff --git a/doc/source/registries.rst b/doc/source/registries.rst index 7d392b96dc..a949bbe253 100644 --- a/doc/source/registries.rst +++ b/doc/source/registries.rst @@ -46,6 +46,43 @@ The following is a brief description of the Glance API:: PUT /images/ Update metadata about an existing image DELETE /images/ Remove an image's metadata from the registry +Filtering Images Returned via ``GET /images`` and ``GET /images/detail`` +------------------------------------------------------------------------ + +Both the ``GET /images`` and ``GET /images/detail`` requests take query +parameters that serve to filter the returned list of images. The following +list details these query parameters. + +* ``name=NAME`` + + Filters images having a ``name`` attribute matching ``NAME``. + +* ``container_format=FORMAT`` + + Filters images having a ``container_format`` attribute matching ``FORMAT`` + + For more information, see :doc:`About Disk and Container Formats ` + +* ``disk_format=FORMAT`` + + Filters images having a ``disk_format`` attribute matching ``FORMAT`` + + For more information, see :doc:`About Disk and Container Formats ` + +* ``status=STATUS`` + + Filters images having a ``status`` attribute matching ``STATUS`` + + For more information, see :doc:`About Image Statuses ` + +* ``size_min=BYTES`` + + Filters images having a ``size`` attribute greater than or equal to ``BYTES`` + +* ``size_max=BYTES`` + + Filters images having a ``size`` attribute less than or equal to ``BYTES`` + ``POST /images`` ---------------- diff --git a/glance/client.py b/glance/client.py index 4eb6faca0a..920eed0036 100644 --- a/glance/client.py +++ b/glance/client.py @@ -25,6 +25,7 @@ import logging import urlparse import socket import sys +import urllib from glance import utils from glance.common import exception @@ -94,7 +95,8 @@ class BaseClient(object): else: return httplib.HTTPConnection - def do_request(self, method, action, body=None, headers=None): + 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 @@ -105,6 +107,8 @@ class BaseClient(object): :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 @@ -115,6 +119,9 @@ class BaseClient(object): 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 {} @@ -197,24 +204,24 @@ class V1Client(BaseClient): self.doc_root = doc_root super(Client, self).__init__(host, port, use_ssl) - def do_request(self, method, action, body=None, headers=None): + def do_request(self, method, action, body=None, headers=None, params=None): action = "%s/%s" % (self.doc_root, action.lstrip("/")) - return super(V1Client, self).do_request(method, action, - body, headers) + return super(V1Client, self).do_request(method, action, body, + headers, params) - def get_images(self): + def get_images(self, filters=None): """ Returns a list of image id/name mappings from Registry """ - res = self.do_request("GET", "/images") + res = self.do_request("GET", "/images", params=filters) data = json.loads(res.read())['images'] return data - def get_images_detailed(self): + def get_images_detailed(self, filters=None): """ Returns a list of detailed image data mappings from Registry """ - res = self.do_request("GET", "/images/detail") + res = self.do_request("GET", "/images/detail", params=filters) data = json.loads(res.read())['images'] return data diff --git a/glance/registry/client.py b/glance/registry/client.py index 7de4cdf375..6f750f17a2 100644 --- a/glance/registry/client.py +++ b/glance/registry/client.py @@ -48,12 +48,7 @@ class RegistryClient(BaseClient): """ Returns a list of image id/name mappings from Registry """ - if filters != None: - action = "/images?%s" % urllib.urlencode(filters) - else: - action = "/images" - - res = self.do_request("GET", action) + res = self.do_request("GET", "/images", params=filters) data = json.loads(res.read())['images'] return data @@ -61,12 +56,7 @@ class RegistryClient(BaseClient): """ Returns a list of detailed image data mappings from Registry """ - if filters != None: - action = "/images/detail?%s" % urllib.urlencode(filters) - else: - action = "/images/detail" - - res = self.do_request("GET", action) + res = self.do_request("GET", "/images/detail", params=filters) data = json.loads(res.read())['images'] return data diff --git a/tests/unit/test_clients.py b/tests/unit/test_clients.py index 3aecf1cbdd..91e98d23b1 100644 --- a/tests/unit/test_clients.py +++ b/tests/unit/test_clients.py @@ -457,6 +457,43 @@ class TestClient(unittest.TestCase): for k, v in fixture.items(): self.assertEquals(v, images[0][k]) + def test_get_image_index_by_base_attribute(self): + """Tests that an index call can be filtered by a base attribute""" + extra_fixture = {'id': 3, + 'status': 'active', + '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) + + images = self.client.get_images({'name': 'new name! #123'}) + + self.assertEquals(len(images), 1) + self.assertEquals('new name! #123', images[0]['name']) + + def test_get_image_index_by_property(self): + """Tests that an index 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({'property-p a': 'v a'}) + + self.assertEquals(len(images), 1) + self.assertEquals(3, images[0]['id']) + def test_get_image_details(self): """Tests that the detailed info about public images returned""" fixture = {'id': 2, @@ -485,6 +522,45 @@ class TestClient(unittest.TestCase): for k, v in expected.items(): self.assertEquals(v, images[0][k]) + 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, + 'status': 'active', + '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) + + images = self.client.get_images_detailed({'name': 'new name! #123'}) + self.assertEquals(len(images), 1) + + for image in images: + self.assertEquals('new name! #123', image['name']) + + def test_get_image_details_by_property(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({'property-p a': 'v a'}) + self.assertEquals(len(images), 1) + + for image in images: + self.assertEquals('v a', image['properties']['p a']) + def test_get_image_meta(self): """Tests that the detailed info about an image returned""" fixture = {'id': 2,