summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStuart McLaren <stuart.mclaren@hp.com>2013-07-16 10:58:57 +0000
committerStuart McLaren <stuart.mclaren@hp.com>2013-07-26 11:00:32 +0000
commit12feedb2cfe087f7126f250850f35ebb1cf3de7a (patch)
treefcc5a00304cbd17c526e4facffcd2ab8c6bd8709
parent8d7411d78b87020a7c5765dc294118f24449db0c (diff)
downloadpython-glanceclient-12feedb2cfe087f7126f250850f35ebb1cf3de7a.tar.gz
Add v1 client side owner based filtering
Add the --owner option to the v1 client's image-list command to support filtering images based on the owner (tenant id). Allows administrators to more easily list a particular user's images. Note that this is far less efficient than v2 server-side owner based filtering. Addresses bug 1201765. Change-Id: I4ffa522b96c91e659c87f5452f2f1f44e47e806b
-rw-r--r--glanceclient/v1/images.py33
-rw-r--r--glanceclient/v1/shell.py8
-rw-r--r--tests/v1/test_images.py122
3 files changed, 157 insertions, 6 deletions
diff --git a/glanceclient/v1/images.py b/glanceclient/v1/images.py
index c96d5fa..abde32f 100644
--- a/glanceclient/v1/images.py
+++ b/glanceclient/v1/images.py
@@ -142,24 +142,42 @@ class ImageManager(base.Manager):
list than that represented by this image id
:param filters: dict of direct comparison filters that mimics the
structure of an image object
+ :param owner: If provided, only images with this owner (tenant id)
+ will be listed. An empty string ('') matches ownerless
+ images.
:rtype: list of :class:`Image`
"""
absolute_limit = kwargs.get('limit')
def paginate(qp, seen=0):
- # Note(flaper87) Url encoding should
- # be moved inside http utils, at least
- # shouldn't be here.
- #
- # Making sure all params are str before
- # trying to encode them
+ def filter_owner(owner, image):
+ # If client side owner 'filter' is specified
+ # only return images that match 'owner'.
+ if owner is None:
+ # Do not filter based on owner
+ return False
+ if (not hasattr(image, 'owner')) or image.owner is None:
+ # ownerless image
+ return not (owner == '')
+ else:
+ return not (image.owner == owner)
+
+ owner = qp.pop('owner', None)
for param, value in qp.iteritems():
if isinstance(value, basestring):
+ # Note(flaper87) Url encoding should
+ # be moved inside http utils, at least
+ # shouldn't be here.
+ #
+ # Making sure all params are str before
+ # trying to encode them
qp[param] = strutils.safe_encode(value)
url = '/v1/images/detail?%s' % urllib.urlencode(qp)
images = self._list(url, "images")
for image in images:
+ if filter_owner(owner, image):
+ continue
seen += 1
if absolute_limit is not None and seen > absolute_limit:
return
@@ -198,6 +216,9 @@ class ImageManager(base.Manager):
for key, value in properties.items():
params['property-%s' % key] = value
params.update(filters)
+ if kwargs.get('owner') is not None:
+ params['owner'] = kwargs['owner']
+ params['is_public'] = None
return paginate(params)
diff --git a/glanceclient/v1/shell.py b/glanceclient/v1/shell.py
index 71882aa..946bb80 100644
--- a/glanceclient/v1/shell.py
+++ b/glanceclient/v1/shell.py
@@ -66,6 +66,13 @@ DISK_FORMATS = ('Acceptable formats: ami, ari, aki, vhd, vmdk, raw, '
@utils.arg('--is-public', type=utils.string_to_bool, metavar='{True|False}',
help=('Allows the user to select a listing of public or non '
'public images.'))
+@utils.arg('--owner', default=None, metavar='<TENANT_ID>',
+ help='Display only images owned by this tenant id. Filtering '
+ 'occurs on the client side so may be inefficient. This option '
+ 'is mainly intended for admin use. Use an empty string (\'\') '
+ 'to list images with no owner. Note: This option overrides '
+ 'the --is-public argument if present. Note: the v2 API '
+ 'supports more efficient server-side owner based filtering.')
def do_image_list(gc, args):
"""List images you can access."""
filter_keys = ['name', 'status', 'container_format', 'disk_format',
@@ -83,6 +90,7 @@ def do_image_list(gc, args):
kwargs['sort_key'] = args.sort_key
kwargs['sort_dir'] = args.sort_dir
+ kwargs['owner'] = args.owner
images = gc.images.list(**kwargs)
diff --git a/tests/v1/test_images.py b/tests/v1/test_images.py
index 9b93639..7758f13 100644
--- a/tests/v1/test_images.py
+++ b/tests/v1/test_images.py
@@ -68,6 +68,90 @@ fixtures = {
]},
),
},
+ '/v1/images/detail?is_public=None&limit=20': {
+ 'GET': (
+ {},
+ {'images': [
+ {
+ 'id': 'a',
+ 'owner': 'A',
+ 'name': 'image-1',
+ 'properties': {'arch': 'x86_64'},
+ },
+ {
+ 'id': 'b',
+ 'owner': 'B',
+ 'name': 'image-2',
+ 'properties': {'arch': 'x86_64'},
+ },
+ {
+ 'id': 'c',
+ 'name': 'image-3',
+ 'properties': {'arch': 'x86_64'},
+ },
+ ]},
+ ),
+ },
+ '/v1/images/detail?is_public=None&limit=5': {
+ 'GET': (
+ {},
+ {'images': [
+ {
+ 'id': 'a',
+ 'owner': 'A',
+ 'name': 'image-1',
+ 'properties': {'arch': 'x86_64'},
+ },
+ {
+ 'id': 'b',
+ 'owner': 'B',
+ 'name': 'image-2',
+ 'properties': {'arch': 'x86_64'},
+ },
+ {
+ 'id': 'b2',
+ 'owner': 'B',
+ 'name': 'image-3',
+ 'properties': {'arch': 'x86_64'},
+ },
+ {
+ 'id': 'c',
+ 'name': 'image-3',
+ 'properties': {'arch': 'x86_64'},
+ },
+ ]},
+ ),
+ },
+ '/v1/images/detail?limit=5': {
+ 'GET': (
+ {},
+ {'images': [
+ {
+ 'id': 'a',
+ 'owner': 'A',
+ 'name': 'image-1',
+ 'properties': {'arch': 'x86_64'},
+ },
+ {
+ 'id': 'b',
+ 'owner': 'B',
+ 'name': 'image-2',
+ 'properties': {'arch': 'x86_64'},
+ },
+ {
+ 'id': 'b2',
+ 'owner': 'B',
+ 'name': 'image-3',
+ 'properties': {'arch': 'x86_64'},
+ },
+ {
+ 'id': 'c',
+ 'name': 'image-3',
+ 'properties': {'arch': 'x86_64'},
+ },
+ ]},
+ ),
+ },
'/v1/images/detail?marker=a&limit=%d' % images.DEFAULT_PAGE_SIZE: {
'GET': (
{},
@@ -501,6 +585,44 @@ class ImageManagerTest(testtools.TestCase):
headers = self.mgr._image_meta_from_headers(fields)
self.assertEqual(headers["name"], u"ni\xf1o")
+ def test_image_list_with_owner(self):
+ images = self.mgr.list(owner='A', page_size=20)
+ image_list = list(images)
+ self.assertEqual(image_list[0].owner, 'A')
+ self.assertEqual(image_list[0].id, 'a')
+ self.assertEqual(len(image_list), 1)
+
+ def test_image_list_with_notfound_owner(self):
+ images = self.mgr.list(owner='X', page_size=20)
+ self.assertEqual(len(list(images)), 0)
+
+ def test_image_list_with_empty_string_owner(self):
+ images = self.mgr.list(owner='', page_size=20)
+ image_list = list(images)
+ self.assertRaises(AttributeError, lambda: image_list[0].owner)
+ self.assertEqual(image_list[0].id, 'c')
+ self.assertEqual(len(image_list), 1)
+
+ def test_image_list_with_unspecified_owner(self):
+ images = self.mgr.list(owner=None, page_size=5)
+ image_list = list(images)
+ self.assertEqual(image_list[0].owner, 'A')
+ self.assertEqual(image_list[0].id, 'a')
+ self.assertEqual(image_list[1].owner, 'B')
+ self.assertEqual(image_list[1].id, 'b')
+ self.assertEqual(image_list[2].owner, 'B')
+ self.assertEqual(image_list[2].id, 'b2')
+ self.assertRaises(AttributeError, lambda: image_list[3].owner)
+ self.assertEqual(image_list[3].id, 'c')
+ self.assertEqual(len(image_list), 4)
+
+ def test_image_list_with_owner_and_limit(self):
+ images = self.mgr.list(owner='B', page_size=5, limit=1)
+ image_list = list(images)
+ self.assertEqual(image_list[0].owner, 'B')
+ self.assertEqual(image_list[0].id, 'b')
+ self.assertEqual(len(image_list), 1)
+
class ImageTest(testtools.TestCase):
def setUp(self):