Fix for Image members not generating notifications
Image members CRUD doesn't generate notifications which is impacting searchlight service by not having latest changes to Image memberships. If you create an image and later change its members, the members are not updated via notifications. You have to run the index sync again to get the updated member list. See: https://bugs.launchpad.net/searchlight/+bug/1490697 Membership information is critical for horizon filtering. Typically, a person is allowed to view an image under the following conditions: 1) The image is owned by the project I am currently logged into. 2) The image is public 3) The image is owned by another project which has added me as a member and I have accepted membership to it. Without current membership information, 3) above is not possible. See: https://bugs.launchpad.net/searchlight/+bug/1491085 Change-Id: Ia56e42d3d8da36cfa419d5c3c7d69c9ccf8974fd Closes-Bug: #1441453
This commit is contained in:
parent
f4d46d1c34
commit
4b0ce57c73
@ -112,13 +112,24 @@ class ImageRepoProxy(glance.domain.proxy.Repo):
|
||||
return [proxy_image(self.context, i) for i in images]
|
||||
|
||||
|
||||
class ImageMemberRepoProxy(glance.domain.proxy.Repo):
|
||||
class ImageMemberRepoProxy(glance.domain.proxy.MemberRepo):
|
||||
|
||||
def __init__(self, member_repo, image, context):
|
||||
self.member_repo = member_repo
|
||||
self.image = image
|
||||
self.context = context
|
||||
super(ImageMemberRepoProxy, self).__init__(member_repo)
|
||||
proxy_kwargs = {'context': self.context}
|
||||
super(ImageMemberRepoProxy, self).__init__(
|
||||
image,
|
||||
member_repo,
|
||||
member_proxy_class=ImageMemberProxy,
|
||||
member_proxy_kwargs=proxy_kwargs)
|
||||
self._check_image_visibility()
|
||||
|
||||
def _check_image_visibility(self):
|
||||
if self.image.visibility == 'public':
|
||||
message = _("Public images do not have members.")
|
||||
raise exception.Forbidden(message)
|
||||
|
||||
def get(self, member_id):
|
||||
if (self.context.is_admin or
|
||||
@ -189,11 +200,16 @@ class ImageFactoryProxy(glance.domain.proxy.ImageFactory):
|
||||
return super(ImageFactoryProxy, self).new_image(owner=owner, **kwargs)
|
||||
|
||||
|
||||
class ImageMemberFactoryProxy(object):
|
||||
class ImageMemberFactoryProxy(glance.domain.proxy.ImageMembershipFactory):
|
||||
|
||||
def __init__(self, image_member_factory, context):
|
||||
self.image_member_factory = image_member_factory
|
||||
self.context = context
|
||||
kwargs = {'context': self.context}
|
||||
super(ImageMemberFactoryProxy, self).__init__(
|
||||
image_member_factory,
|
||||
proxy_class=ImageMemberProxy,
|
||||
proxy_kwargs=kwargs)
|
||||
|
||||
def new_image_member(self, image, member_id):
|
||||
owner = image.owner
|
||||
@ -315,10 +331,6 @@ class ImmutableImageProxy(object):
|
||||
message = _("You are not permitted to delete this image.")
|
||||
raise exception.Forbidden(message)
|
||||
|
||||
def get_member_repo(self):
|
||||
member_repo = self.base.get_member_repo()
|
||||
return ImageMemberRepoProxy(member_repo, self, self.context)
|
||||
|
||||
def get_data(self, *args, **kwargs):
|
||||
return self.base.get_data(*args, **kwargs)
|
||||
|
||||
@ -401,13 +413,13 @@ class ImageProxy(glance.domain.proxy.Image):
|
||||
self.context = context
|
||||
super(ImageProxy, self).__init__(image)
|
||||
|
||||
def get_member_repo(self, **kwargs):
|
||||
if self.image.visibility == 'public':
|
||||
message = _("Public images do not have members.")
|
||||
raise exception.Forbidden(message)
|
||||
else:
|
||||
member_repo = self.image.get_member_repo(**kwargs)
|
||||
return ImageMemberRepoProxy(member_repo, self, self.context)
|
||||
|
||||
class ImageMemberProxy(glance.domain.proxy.ImageMember):
|
||||
|
||||
def __init__(self, image_member, context):
|
||||
self.image_member = image_member
|
||||
self.context = context
|
||||
super(ImageMemberProxy, self).__init__(image_member)
|
||||
|
||||
|
||||
class TaskProxy(glance.domain.proxy.Task):
|
||||
|
@ -191,9 +191,14 @@ class ImageProxy(glance.domain.proxy.Image):
|
||||
self.policy.enforce(self.context, 'upload_image', self.target)
|
||||
return self.image.set_data(*args, **kwargs)
|
||||
|
||||
def get_member_repo(self, **kwargs):
|
||||
member_repo = self.image.get_member_repo(**kwargs)
|
||||
return ImageMemberRepoProxy(member_repo, self.context, self.policy)
|
||||
|
||||
class ImageMemberProxy(glance.domain.proxy.ImageMember):
|
||||
|
||||
def __init__(self, image_member, context, policy):
|
||||
super(ImageMemberProxy, self).__init__(image_member)
|
||||
self.image_member = image_member
|
||||
self.context = context
|
||||
self.policy = policy
|
||||
|
||||
|
||||
class ImageFactoryProxy(glance.domain.proxy.ImageFactory):
|
||||
@ -218,15 +223,16 @@ class ImageMemberFactoryProxy(glance.domain.proxy.ImageMembershipFactory):
|
||||
def __init__(self, member_factory, context, policy):
|
||||
super(ImageMemberFactoryProxy, self).__init__(
|
||||
member_factory,
|
||||
image_proxy_class=ImageProxy,
|
||||
image_proxy_kwargs={'context': context, 'policy': policy})
|
||||
proxy_class=ImageMemberProxy,
|
||||
proxy_kwargs={'context': context, 'policy': policy})
|
||||
|
||||
|
||||
class ImageMemberRepoProxy(glance.domain.proxy.Repo):
|
||||
|
||||
def __init__(self, member_repo, context, policy):
|
||||
def __init__(self, member_repo, image, context, policy):
|
||||
self.member_repo = member_repo
|
||||
self.target = ImageTarget(self.member_repo.image)
|
||||
self.image = image
|
||||
self.target = ImageTarget(image)
|
||||
self.context = context
|
||||
self.policy = policy
|
||||
|
||||
|
@ -47,6 +47,18 @@ class ImageMembersController(object):
|
||||
self.gateway = glance.gateway.Gateway(self.db_api, self.store_api,
|
||||
self.notifier, self.policy)
|
||||
|
||||
def _get_member_repo(self, req, image):
|
||||
try:
|
||||
# For public images, a forbidden exception with message
|
||||
# "Public images do not have members" is thrown.
|
||||
return self.gateway.get_member_repo(image, req.context)
|
||||
except exception.Forbidden as e:
|
||||
msg = (_("Error fetching members of image %(image_id)s: "
|
||||
"%(inner_msg)s") % {"image_id": image.image_id,
|
||||
"inner_msg": e.msg})
|
||||
LOG.warning(msg)
|
||||
raise webob.exc.HTTPForbidden(explanation=msg)
|
||||
|
||||
def _lookup_image(self, req, image_id):
|
||||
image_repo = self.gateway.get_repo(req.context)
|
||||
try:
|
||||
@ -60,21 +72,8 @@ class ImageMembersController(object):
|
||||
LOG.warning(msg)
|
||||
raise webob.exc.HTTPForbidden(explanation=msg)
|
||||
|
||||
@staticmethod
|
||||
def _get_member_repo(image):
|
||||
try:
|
||||
# For public images, a forbidden exception with message
|
||||
# "Public images do not have members" is thrown.
|
||||
return image.get_member_repo()
|
||||
except exception.Forbidden as e:
|
||||
msg = (_("Error fetching members of image %(image_id)s: "
|
||||
"%(inner_msg)s") % {"image_id": image.image_id,
|
||||
"inner_msg": e.msg})
|
||||
LOG.warning(msg)
|
||||
raise webob.exc.HTTPForbidden(explanation=msg)
|
||||
|
||||
def _lookup_member(self, image, member_id):
|
||||
member_repo = self._get_member_repo(image)
|
||||
def _lookup_member(self, req, image, member_id):
|
||||
member_repo = self._get_member_repo(req, image)
|
||||
try:
|
||||
return member_repo.get(member_id)
|
||||
except (exception.NotFound):
|
||||
@ -106,7 +105,7 @@ class ImageMembersController(object):
|
||||
|
||||
"""
|
||||
image = self._lookup_image(req, image_id)
|
||||
member_repo = self._get_member_repo(image)
|
||||
member_repo = self._get_member_repo(req, image)
|
||||
image_member_factory = self.gateway.get_image_member_factory(
|
||||
req.context)
|
||||
try:
|
||||
@ -148,8 +147,8 @@ class ImageMembersController(object):
|
||||
|
||||
"""
|
||||
image = self._lookup_image(req, image_id)
|
||||
member_repo = self._get_member_repo(image)
|
||||
member = self._lookup_member(image, member_id)
|
||||
member_repo = self._get_member_repo(req, image)
|
||||
member = self._lookup_member(req, image, member_id)
|
||||
try:
|
||||
member.status = status
|
||||
member_repo.save(member)
|
||||
@ -182,7 +181,7 @@ class ImageMembersController(object):
|
||||
]}
|
||||
"""
|
||||
image = self._lookup_image(req, image_id)
|
||||
member_repo = self._get_member_repo(image)
|
||||
member_repo = self._get_member_repo(req, image)
|
||||
members = []
|
||||
try:
|
||||
for member in member_repo.list():
|
||||
@ -209,7 +208,7 @@ class ImageMembersController(object):
|
||||
"""
|
||||
try:
|
||||
image = self._lookup_image(req, image_id)
|
||||
return self._lookup_member(image, member_id)
|
||||
return self._lookup_member(req, image, member_id)
|
||||
except webob.exc.HTTPForbidden as e:
|
||||
# Convert Forbidden to NotFound to prevent information
|
||||
# leakage.
|
||||
@ -221,8 +220,8 @@ class ImageMembersController(object):
|
||||
Removes a membership from the image.
|
||||
"""
|
||||
image = self._lookup_image(req, image_id)
|
||||
member_repo = self._get_member_repo(image)
|
||||
member = self._lookup_member(image, member_id)
|
||||
member_repo = self._get_member_repo(req, image)
|
||||
member = self._lookup_member(req, image, member_id)
|
||||
try:
|
||||
member_repo.remove(member)
|
||||
return webob.Response(body='', status=204)
|
||||
|
@ -302,11 +302,6 @@ class ImageProxy(glance.domain.proxy.Image):
|
||||
self.image = image
|
||||
super(ImageProxy, self).__init__(image)
|
||||
|
||||
def get_member_repo(self):
|
||||
member_repo = ImageMemberRepo(self.context, self.db_api,
|
||||
self.image)
|
||||
return member_repo
|
||||
|
||||
|
||||
class ImageMemberRepo(object):
|
||||
|
||||
|
@ -105,6 +105,37 @@ class Repo(object):
|
||||
return self.helper.proxy(result)
|
||||
|
||||
|
||||
class MemberRepo(object):
|
||||
def __init__(self, image, base,
|
||||
member_proxy_class=None, member_proxy_kwargs=None):
|
||||
self.image = image
|
||||
self.base = base
|
||||
self.member_proxy_helper = Helper(member_proxy_class,
|
||||
member_proxy_kwargs)
|
||||
|
||||
def get(self, member_id):
|
||||
member = self.base.get(member_id)
|
||||
return self.member_proxy_helper.proxy(member)
|
||||
|
||||
def add(self, member):
|
||||
self.base.add(self.member_proxy_helper.unproxy(member))
|
||||
|
||||
def list(self, *args, **kwargs):
|
||||
members = self.base.list(*args, **kwargs)
|
||||
return [self.member_proxy_helper.proxy(member) for member
|
||||
in members]
|
||||
|
||||
def remove(self, member):
|
||||
base_item = self.member_proxy_helper.unproxy(member)
|
||||
result = self.base.remove(base_item)
|
||||
return self.member_proxy_helper.proxy(result)
|
||||
|
||||
def save(self, member, from_state=None):
|
||||
base_item = self.member_proxy_helper.unproxy(member)
|
||||
result = self.base.save(base_item, from_state=from_state)
|
||||
return self.member_proxy_helper.proxy(result)
|
||||
|
||||
|
||||
class ImageFactory(object):
|
||||
def __init__(self, base, proxy_class=None, proxy_kwargs=None):
|
||||
self.helper = Helper(proxy_class, proxy_kwargs)
|
||||
@ -115,16 +146,14 @@ class ImageFactory(object):
|
||||
|
||||
|
||||
class ImageMembershipFactory(object):
|
||||
def __init__(self, base, image_proxy_class=None, image_proxy_kwargs=None,
|
||||
member_proxy_class=None, member_proxy_kwargs=None):
|
||||
def __init__(self, base, proxy_class=None, proxy_kwargs=None):
|
||||
self.helper = Helper(proxy_class, proxy_kwargs)
|
||||
self.base = base
|
||||
self.image_helper = Helper(image_proxy_class, image_proxy_kwargs)
|
||||
self.member_helper = Helper(member_proxy_class, member_proxy_kwargs)
|
||||
|
||||
def new_image_member(self, image, member_id):
|
||||
base_image = self.image_helper.unproxy(image)
|
||||
member = self.base.new_image_member(base_image, member_id)
|
||||
return self.member_helper.proxy(member)
|
||||
def new_image_member(self, image, member, **kwargs):
|
||||
return self.helper.proxy(self.base.new_image_member(image,
|
||||
member,
|
||||
**kwargs))
|
||||
|
||||
|
||||
class Image(object):
|
||||
@ -168,8 +197,17 @@ class Image(object):
|
||||
def get_data(self, *args, **kwargs):
|
||||
return self.base.get_data(*args, **kwargs)
|
||||
|
||||
def get_member_repo(self):
|
||||
return self.helper.proxy(self.base.get_member_repo())
|
||||
|
||||
class ImageMember(object):
|
||||
def __init__(self, base):
|
||||
self.base = base
|
||||
|
||||
id = _proxy('base', 'id')
|
||||
image_id = _proxy('base', 'image_id')
|
||||
member_id = _proxy('base', 'member_id')
|
||||
status = _proxy('base', 'status')
|
||||
created_at = _proxy('base', 'created_at')
|
||||
updated_at = _proxy('base', 'updated_at')
|
||||
|
||||
|
||||
class Task(object):
|
||||
|
@ -89,6 +89,20 @@ class Gateway(object):
|
||||
|
||||
return authorized_image_repo
|
||||
|
||||
def get_member_repo(self, image, context):
|
||||
image_member_repo = glance.db.ImageMemberRepo(
|
||||
context, self.db_api, image)
|
||||
store_image_repo = glance.location.ImageMemberRepoProxy(
|
||||
image_member_repo, image, context, self.store_api)
|
||||
policy_member_repo = policy.ImageMemberRepoProxy(
|
||||
store_image_repo, image, context, self.policy)
|
||||
notifier_member_repo = glance.notifier.ImageMemberRepoProxy(
|
||||
policy_member_repo, image, context, self.notifier)
|
||||
authorized_member_repo = authorization.ImageMemberRepoProxy(
|
||||
notifier_member_repo, image, context)
|
||||
|
||||
return authorized_member_repo
|
||||
|
||||
def get_task_factory(self, context):
|
||||
task_factory = glance.domain.TaskFactory()
|
||||
policy_task_factory = policy.TaskFactoryProxy(
|
||||
|
@ -45,11 +45,16 @@ class ImageRepoProxy(glance.domain.proxy.Repo):
|
||||
item_proxy_class=ImageProxy,
|
||||
item_proxy_kwargs=proxy_kwargs)
|
||||
|
||||
self.db_api = glance.db.get_api()
|
||||
|
||||
def _set_acls(self, image):
|
||||
public = image.visibility == 'public'
|
||||
member_ids = []
|
||||
if image.locations and not public:
|
||||
member_repo = image.get_member_repo()
|
||||
member_repo = _get_member_repo_for_store(image,
|
||||
self.context,
|
||||
self.db_api,
|
||||
self.store_api)
|
||||
member_ids = [m.member_id for m in member_repo.list()]
|
||||
for location in image.locations:
|
||||
self.store_api.set_acls(location['url'], public=public,
|
||||
@ -67,6 +72,15 @@ class ImageRepoProxy(glance.domain.proxy.Repo):
|
||||
return result
|
||||
|
||||
|
||||
def _get_member_repo_for_store(image, context, db_api, store_api):
|
||||
image_member_repo = glance.db.ImageMemberRepo(
|
||||
context, db_api, image)
|
||||
store_image_repo = glance.location.ImageMemberRepoProxy(
|
||||
image_member_repo, image, context, store_api)
|
||||
|
||||
return store_image_repo
|
||||
|
||||
|
||||
def _check_location_uri(context, store_api, store_utils, uri):
|
||||
"""Check if an image location is valid.
|
||||
|
||||
|
@ -133,6 +133,21 @@ def format_image_notification(image):
|
||||
}
|
||||
|
||||
|
||||
def format_image_member_notification(image_member):
|
||||
"""Given a glance.domain.ImageMember object, return a dictionary of relevant
|
||||
notification information.
|
||||
"""
|
||||
return {
|
||||
'image_id': image_member.image_id,
|
||||
'member_id': image_member.member_id,
|
||||
'status': image_member.status,
|
||||
'created_at': timeutils.isotime(image_member.created_at),
|
||||
'updated_at': timeutils.isotime(image_member.updated_at),
|
||||
'deleted': False,
|
||||
'deleted_at': None,
|
||||
}
|
||||
|
||||
|
||||
def format_task_notification(task):
|
||||
# NOTE(nikhil): input is not passed to the notifier payload as it may
|
||||
# contain sensitive info.
|
||||
@ -436,6 +451,11 @@ class ImageProxy(NotificationProxy, domain_proxy.Image):
|
||||
self.send_notification('image.activate', self.repo)
|
||||
|
||||
|
||||
class ImageMemberProxy(NotificationProxy, domain_proxy.ImageMember):
|
||||
def get_super_class(self):
|
||||
return domain_proxy.ImageMember
|
||||
|
||||
|
||||
class ImageFactoryProxy(NotificationFactoryProxy, domain_proxy.ImageFactory):
|
||||
def get_super_class(self):
|
||||
return domain_proxy.ImageFactory
|
||||
@ -469,6 +489,43 @@ class ImageRepoProxy(NotificationRepoProxy, domain_proxy.Repo):
|
||||
})
|
||||
|
||||
|
||||
class ImageMemberRepoProxy(NotificationBase, domain_proxy.MemberRepo):
|
||||
|
||||
def __init__(self, repo, image, context, notifier):
|
||||
self.repo = repo
|
||||
self.image = image
|
||||
self.context = context
|
||||
self.notifier = notifier
|
||||
proxy_kwargs = {'context': self.context, 'notifier': self.notifier}
|
||||
|
||||
proxy_class = self.get_proxy_class()
|
||||
super_class = self.get_super_class()
|
||||
super_class.__init__(self, image, repo, proxy_class, proxy_kwargs)
|
||||
|
||||
def get_super_class(self):
|
||||
return domain_proxy.MemberRepo
|
||||
|
||||
def get_proxy_class(self):
|
||||
return ImageMemberProxy
|
||||
|
||||
def get_payload(self, obj):
|
||||
return format_image_member_notification(obj)
|
||||
|
||||
def save(self, member, from_state=None):
|
||||
super(ImageMemberRepoProxy, self).save(member, from_state=from_state)
|
||||
self.send_notification('image.member.update', member)
|
||||
|
||||
def add(self, member):
|
||||
super(ImageMemberRepoProxy, self).add(member)
|
||||
self.send_notification('image.member.create', member)
|
||||
|
||||
def remove(self, member):
|
||||
super(ImageMemberRepoProxy, self).remove(member)
|
||||
self.send_notification('image.member.delete', member, extra_payload={
|
||||
'deleted': True, 'deleted_at': timeutils.isotime()
|
||||
})
|
||||
|
||||
|
||||
class TaskProxy(NotificationProxy, domain_proxy.Task):
|
||||
def get_super_class(self):
|
||||
return domain_proxy.Task
|
||||
|
@ -169,8 +169,8 @@ class ImageMemberFactoryProxy(glance.domain.proxy.ImageMembershipFactory):
|
||||
'store_utils': store_utils}
|
||||
super(ImageMemberFactoryProxy, self).__init__(
|
||||
member_factory,
|
||||
image_proxy_class=ImageProxy,
|
||||
image_proxy_kwargs=proxy_kwargs)
|
||||
proxy_class=ImageMemberProxy,
|
||||
proxy_kwargs=proxy_kwargs)
|
||||
|
||||
def _enforce_image_member_quota(self, image):
|
||||
if CONF.image_member_quota < 0:
|
||||
@ -368,3 +368,13 @@ class ImageProxy(glance.domain.proxy.Image):
|
||||
def added_new_properties(self):
|
||||
current_props = set(self.image.extra_properties.keys())
|
||||
return bool(current_props.difference(self.orig_props))
|
||||
|
||||
|
||||
class ImageMemberProxy(glance.domain.proxy.ImageMember):
|
||||
|
||||
def __init__(self, image_member, context, db_api, store_utils):
|
||||
self.image_member = image_member
|
||||
self.context = context
|
||||
self.db_api = db_api
|
||||
self.store_utils = store_utils
|
||||
super(ImageMemberProxy, self).__init__(image_member)
|
||||
|
@ -220,8 +220,7 @@ class TestImageMembershipFactory(test_utils.BaseTestCase):
|
||||
|
||||
def test_proxy_wrapped_membership(self):
|
||||
proxy_factory = proxy.ImageMembershipFactory(
|
||||
self.factory, member_proxy_class=FakeProxy,
|
||||
member_proxy_kwargs={'a': 1})
|
||||
self.factory, proxy_class=FakeProxy, proxy_kwargs={'a': 1})
|
||||
self.factory.result = 'tyrion'
|
||||
membership = proxy_factory.new_image_member('jaime', 'cersei')
|
||||
self.assertIsInstance(membership, FakeProxy)
|
||||
@ -232,12 +231,12 @@ class TestImageMembershipFactory(test_utils.BaseTestCase):
|
||||
|
||||
def test_proxy_wrapped_image(self):
|
||||
proxy_factory = proxy.ImageMembershipFactory(
|
||||
self.factory, image_proxy_class=FakeProxy)
|
||||
self.factory, proxy_class=FakeProxy)
|
||||
self.factory.result = 'tyrion'
|
||||
image = FakeProxy('jaime')
|
||||
membership = proxy_factory.new_image_member(image, 'cersei')
|
||||
self.assertEqual('tyrion', membership)
|
||||
self.assertEqual('jaime', self.factory.image)
|
||||
self.assertIsInstance(membership, FakeProxy)
|
||||
self.assertIsInstance(self.factory.image, FakeProxy)
|
||||
self.assertEqual('cersei', self.factory.member_id)
|
||||
|
||||
def test_proxy_both_wrapped(self):
|
||||
@ -246,9 +245,8 @@ class TestImageMembershipFactory(test_utils.BaseTestCase):
|
||||
|
||||
proxy_factory = proxy.ImageMembershipFactory(
|
||||
self.factory,
|
||||
member_proxy_class=FakeProxy,
|
||||
member_proxy_kwargs={'b': 2},
|
||||
image_proxy_class=FakeProxy2)
|
||||
proxy_class=FakeProxy,
|
||||
proxy_kwargs={'b': 2})
|
||||
|
||||
self.factory.result = 'tyrion'
|
||||
image = FakeProxy2('jaime')
|
||||
@ -256,7 +254,7 @@ class TestImageMembershipFactory(test_utils.BaseTestCase):
|
||||
self.assertIsInstance(membership, FakeProxy)
|
||||
self.assertEqual('tyrion', membership.base)
|
||||
self.assertEqual({'b': 2}, membership.kwargs)
|
||||
self.assertEqual('jaime', self.factory.image)
|
||||
self.assertIsInstance(self.factory.image, FakeProxy2)
|
||||
self.assertEqual('cersei', self.factory.member_id)
|
||||
|
||||
|
||||
@ -264,29 +262,6 @@ class FakeImage(object):
|
||||
def __init__(self, result=None):
|
||||
self.result = result
|
||||
|
||||
def get_member_repo(self):
|
||||
return self.result
|
||||
|
||||
|
||||
class TestImage(test_utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestImage, self).setUp()
|
||||
self.image = FakeImage()
|
||||
|
||||
def test_normal_member_repo(self):
|
||||
proxy_image = proxy.Image(self.image)
|
||||
self.image.result = 'mormont'
|
||||
self.assertEqual('mormont', proxy_image.get_member_repo())
|
||||
|
||||
def test_proxied_member_repo(self):
|
||||
proxy_image = proxy.Image(self.image,
|
||||
member_repo_proxy_class=FakeProxy,
|
||||
member_repo_proxy_kwargs={'a': 10})
|
||||
self.image.result = 'corn'
|
||||
member_repo = proxy_image.get_member_repo()
|
||||
self.assertIsInstance(member_repo, FakeProxy)
|
||||
self.assertEqual('corn', member_repo.base)
|
||||
|
||||
|
||||
class TestTaskFactory(test_utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
|
@ -66,6 +66,23 @@ class ImageRepoStub(object):
|
||||
return ['images_from_list']
|
||||
|
||||
|
||||
class ImageMemberRepoStub(object):
|
||||
def remove(self, *args, **kwargs):
|
||||
return 'image_member_from_remove'
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
return 'image_member_from_save'
|
||||
|
||||
def add(self, *args, **kwargs):
|
||||
return 'image_member_from_add'
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return 'image_member_from_get'
|
||||
|
||||
def list(self, *args, **kwargs):
|
||||
return ['image_members_from_list']
|
||||
|
||||
|
||||
class TaskStub(glance.domain.TaskStub):
|
||||
def run(self, executor):
|
||||
pass
|
||||
@ -394,6 +411,91 @@ class TestImageNotifications(utils.BaseTestCase):
|
||||
self.assertIn('Failed', output_log['payload'])
|
||||
|
||||
|
||||
class TestImageMemberNotifications(utils.BaseTestCase):
|
||||
"""Test Image Member Notifications work"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestImageMemberNotifications, self).setUp()
|
||||
self.context = glance.context.RequestContext(tenant=TENANT2,
|
||||
user=USER1)
|
||||
self.notifier = unit_test_utils.FakeNotifier()
|
||||
|
||||
self.image = ImageStub(
|
||||
image_id=UUID1, name='image-1', status='active', size=1024,
|
||||
created_at=DATETIME, updated_at=DATETIME, owner=TENANT1,
|
||||
visibility='public', container_format='ami',
|
||||
tags=['one', 'two'], disk_format='ami', min_ram=128,
|
||||
min_disk=10, checksum='ca425b88f047ce8ec45ee90e813ada91',
|
||||
locations=['http://127.0.0.1'])
|
||||
self.image_member = glance.domain.ImageMembership(
|
||||
id=1, image_id=UUID1, member_id=TENANT1, created_at=DATETIME,
|
||||
updated_at=DATETIME, status='accepted')
|
||||
|
||||
self.image_member_repo_stub = ImageMemberRepoStub()
|
||||
self.image_member_repo_proxy = glance.notifier.ImageMemberRepoProxy(
|
||||
self.image_member_repo_stub, self.image,
|
||||
self.context, self.notifier)
|
||||
self.image_member_proxy = glance.notifier.ImageMemberProxy(
|
||||
self.image_member, self.context, self.notifier)
|
||||
|
||||
def _assert_image_member_with_notifier(self, output_log, deleted=False):
|
||||
self.assertEqual(self.image_member.member_id,
|
||||
output_log['payload']['member_id'])
|
||||
self.assertEqual(self.image_member.image_id,
|
||||
output_log['payload']['image_id'])
|
||||
self.assertEqual(self.image_member.status,
|
||||
output_log['payload']['status'])
|
||||
self.assertEqual(timeutils.isotime(self.image_member.created_at),
|
||||
output_log['payload']['created_at'])
|
||||
self.assertEqual(timeutils.isotime(self.image_member.updated_at),
|
||||
output_log['payload']['updated_at'])
|
||||
|
||||
if deleted:
|
||||
self.assertTrue(output_log['payload']['deleted'])
|
||||
self.assertIsNotNone(output_log['payload']['deleted_at'])
|
||||
else:
|
||||
self.assertFalse(output_log['payload']['deleted'])
|
||||
self.assertIsNone(output_log['payload']['deleted_at'])
|
||||
|
||||
def test_image_member_add_notification(self):
|
||||
self.image_member_repo_proxy.add(self.image_member_proxy)
|
||||
output_logs = self.notifier.get_logs()
|
||||
self.assertEqual(1, len(output_logs))
|
||||
output_log = output_logs[0]
|
||||
self.assertEqual('INFO', output_log['notification_type'])
|
||||
self.assertEqual('image.member.create', output_log['event_type'])
|
||||
self._assert_image_member_with_notifier(output_log)
|
||||
|
||||
def test_image_member_save_notification(self):
|
||||
self.image_member_repo_proxy.save(self.image_member_proxy)
|
||||
output_logs = self.notifier.get_logs()
|
||||
self.assertEqual(1, len(output_logs))
|
||||
output_log = output_logs[0]
|
||||
self.assertEqual('INFO', output_log['notification_type'])
|
||||
self.assertEqual('image.member.update', output_log['event_type'])
|
||||
self._assert_image_member_with_notifier(output_log)
|
||||
|
||||
def test_image_member_delete_notification(self):
|
||||
self.image_member_repo_proxy.remove(self.image_member_proxy)
|
||||
output_logs = self.notifier.get_logs()
|
||||
self.assertEqual(1, len(output_logs))
|
||||
output_log = output_logs[0]
|
||||
self.assertEqual('INFO', output_log['notification_type'])
|
||||
self.assertEqual('image.member.delete', output_log['event_type'])
|
||||
self._assert_image_member_with_notifier(output_log, deleted=True)
|
||||
|
||||
def test_image_member_get(self):
|
||||
image_member = self.image_member_repo_proxy.get(TENANT1)
|
||||
self.assertIsInstance(image_member, glance.notifier.ImageMemberProxy)
|
||||
self.assertEqual('image_member_from_get', image_member.repo)
|
||||
|
||||
def test_image_member_list(self):
|
||||
image_members = self.image_member_repo_proxy.list()
|
||||
self.assertIsInstance(image_members[0],
|
||||
glance.notifier.ImageMemberProxy)
|
||||
self.assertEqual('image_members_from_list', image_members[0].repo)
|
||||
|
||||
|
||||
class TestTaskNotifications(utils.BaseTestCase):
|
||||
"""Test Task Notifications work"""
|
||||
|
||||
|
@ -349,8 +349,10 @@ class TestMemberPolicy(test_utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
self.policy = mock.Mock()
|
||||
self.policy.enforce = mock.Mock()
|
||||
self.image_stub = ImageStub(UUID1)
|
||||
image = glance.api.policy.ImageProxy(self.image_stub, {}, self.policy)
|
||||
self.member_repo = glance.api.policy.ImageMemberRepoProxy(
|
||||
MemberRepoStub(), {}, self.policy)
|
||||
MemberRepoStub(), image, {}, self.policy)
|
||||
self.target = self.member_repo.target
|
||||
super(TestMemberPolicy, self).setUp()
|
||||
|
||||
|
@ -605,7 +605,7 @@ class TestImageMemberQuotas(test_utils.BaseTestCase):
|
||||
self.image_member_factory.new_image_member(self.image,
|
||||
'fake_id')
|
||||
nim = self.base_image_member_factory.new_image_member
|
||||
nim .assert_called_once_with(self.image.base, 'fake_id')
|
||||
nim.assert_called_once_with(self.image, 'fake_id')
|
||||
|
||||
def test_new_image_member_unlimited_members(self):
|
||||
self.config(image_member_quota=-1)
|
||||
@ -613,7 +613,7 @@ class TestImageMemberQuotas(test_utils.BaseTestCase):
|
||||
self.image_member_factory.new_image_member(self.image,
|
||||
'fake_id')
|
||||
nim = self.base_image_member_factory.new_image_member
|
||||
nim.assert_called_once_with(self.image.base, 'fake_id')
|
||||
nim.assert_called_once_with(self.image, 'fake_id')
|
||||
|
||||
def test_new_image_member_too_many_members(self):
|
||||
self.config(image_member_quota=0)
|
||||
|
@ -741,6 +741,18 @@ class TestStoreImageRepo(utils.BaseTestCase):
|
||||
self.image_repo = glance.location.ImageRepoProxy(self.image_repo_stub,
|
||||
{}, self.store_api,
|
||||
store_utils)
|
||||
patcher = mock.patch("glance.location._get_member_repo_for_store",
|
||||
self.get_fake_member_repo)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.fake_member_repo = FakeMemberRepo(self.image, [TENANT1, TENANT2])
|
||||
self.image_member_repo = glance.location.ImageMemberRepoProxy(
|
||||
self.fake_member_repo,
|
||||
self.image,
|
||||
{}, self.store_api)
|
||||
|
||||
def get_fake_member_repo(self, image, context, db_api, store_api):
|
||||
return FakeMemberRepo(self.image, [TENANT1, TENANT2])
|
||||
|
||||
def test_add_updates_acls(self):
|
||||
self.image_stub.locations = [{'url': 'foo', 'metadata': {},
|
||||
@ -794,10 +806,9 @@ class TestStoreImageRepo(utils.BaseTestCase):
|
||||
self.image_stub.locations = [{'url': 'glug', 'metadata': {},
|
||||
'status': 'active'}]
|
||||
self.image_stub.visibility = 'private'
|
||||
member_repo = self.image.get_member_repo()
|
||||
membership = glance.domain.ImageMembership(
|
||||
UUID1, TENANT3, None, None, status='accepted')
|
||||
member_repo.add(membership)
|
||||
self.image_member_repo.add(membership)
|
||||
self.assertIn('glug', self.store_api.acls)
|
||||
acls = self.store_api.acls['glug']
|
||||
self.assertFalse(acls['public'])
|
||||
@ -808,10 +819,9 @@ class TestStoreImageRepo(utils.BaseTestCase):
|
||||
self.image_stub.locations = [{'url': 'glug', 'metadata': {},
|
||||
'status': 'active'}]
|
||||
self.image_stub.visibility = 'private'
|
||||
member_repo = self.image.get_member_repo()
|
||||
membership = glance.domain.ImageMembership(
|
||||
UUID1, TENANT1, None, None, status='accepted')
|
||||
member_repo.remove(membership)
|
||||
self.image_member_repo.remove(membership)
|
||||
self.assertIn('glug', self.store_api.acls)
|
||||
acls = self.store_api.acls['glug']
|
||||
self.assertFalse(acls['public'])
|
||||
|
Loading…
x
Reference in New Issue
Block a user