Merge "Add v1 client side owner based filtering"
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user