Notifications for metadefinition resources

Metadefinition resources - namespaces, objects, properties, tags and
resource types - don't provide any notification events when certain
operations are performed on them. This patch includes following events
that will be triggered when necessary:

* metadef_namespace.create - namespace has been created
* metadef_namespace.update - namespace has been updated
* metadef_namespace.delete - namespace has been deleted
* metadef_namespace.delete_properties - all properties have been removed
from namespace
* metadef_namespace.delete_objects - all objects have been removed from
namespace
* metadef_namespace.delete_tags - all tags have been removed from
namespace

* metadef_object.create - object has been created
* metadef_object.update - object has been updated
* metadef_object.delete - object has been deleted

* metadef_property.create - property has been created
* metadef_property.update - property has been updated
* metadef_property.delete - property has been deleted

* metadef_tag.create - tag has been created
* metadef_tag.update - tag has been updated
* metadef_tag.delete - tag has been deleted

* metadef_resource_type.create - resource type has been added to
namespace
* metadef_resource_type.delete - resource type has been removed from
namespace

Additionally new configuration option has been added to allow for
disabling either individual or group of notifications.

DocImpact
UpgradeImpact
Depends-On: Iaa771ead0114e3941667b1e07ff32472d2f77afd

Change-Id: Ie1635793d80188f8f7a07aea91b9f0842900ffa6
Implements: blueprint metadefs-notifications
This commit is contained in:
Kamil Rykowski 2015-01-20 14:16:00 +01:00
parent 030250ed64
commit fd547e3717
14 changed files with 1213 additions and 177 deletions

View File

@ -1303,6 +1303,18 @@ Sets the notification driver used by oslo.messaging. Options include
For more information see :doc:`Glance notifications <notifications>` and
`oslo.messaging <http://docs.openstack.org/developer/oslo.messaging/>`_.
* ``disabled_notifications``
Optional. Default: ``[]``
List of disabled notifications. A notification can be given either as a
notification type to disable a single event, or as a notification group prefix
to disable all events within a group.
Example: if this config option is set to ["image.create", "metadef_namespace"],
then "image.create" notification will not be sent after image is created and
none of the notifications for metadefinition namespaces will be sent.
Configuring Glance Property Protections
---------------------------------------

View File

@ -220,6 +220,15 @@ registry_client_protocol = http
# Default publisher_id for outgoing notifications.
# default_publisher_id = image.localhost
# List of disabled notifications. A notification can be given either as a
# notification type to disable a single event, or as a notification group
# prefix to disable all events within a group.
# Example: if this config option is set to
# ["image.create", "metadef_namespace"], then "image.create" notification will
# not be sent after image is created and none of the notifications for
# metadefinition namespaces will be sent.
# disabled_notifications = []
# Messaging driver used for 'messaging' notifications driver
# rpc_backend = 'rabbit'

View File

@ -48,10 +48,12 @@ CONF = cfg.CONF
class NamespaceController(object):
def __init__(self, db_api=None, policy_enforcer=None):
def __init__(self, db_api=None, policy_enforcer=None, notifier=None):
self.db_api = db_api or glance.db.get_api()
self.policy = policy_enforcer or policy.Enforcer()
self.notifier = notifier or glance.notifier.Notifier()
self.gateway = glance.gateway.Gateway(db_api=self.db_api,
notifier=self.notifier,
policy_enforcer=self.policy)
self.ns_schema_link = '/v2/schemas/metadefs/namespace'
self.obj_schema_link = '/v2/schemas/metadefs/object'
@ -269,6 +271,7 @@ class NamespaceController(object):
namespace_repo = self.gateway.get_metadef_namespace_repo(req.context)
try:
ns_obj = namespace_repo.get(namespace)
ns_obj._old_namespace = ns_obj.namespace
ns_obj.namespace = wsme_utils._get_value(user_ns.namespace)
ns_obj.display_name = wsme_utils._get_value(user_ns.display_name)
ns_obj.description = wsme_utils._get_value(user_ns.description)

View File

@ -42,10 +42,12 @@ CONF = cfg.CONF
class MetadefObjectsController(object):
def __init__(self, db_api=None, policy_enforcer=None):
def __init__(self, db_api=None, policy_enforcer=None, notifier=None):
self.db_api = db_api or glance.db.get_api()
self.policy = policy_enforcer or policy.Enforcer()
self.notifier = notifier or glance.notifier.Notifier()
self.gateway = glance.gateway.Gateway(db_api=self.db_api,
notifier=self.notifier,
policy_enforcer=self.policy)
self.obj_schema_link = '/v2/schemas/metadefs/object'
@ -117,6 +119,7 @@ class MetadefObjectsController(object):
meta_repo = self.gateway.get_metadef_object_repo(req.context)
try:
metadef_object = meta_repo.get(namespace, object_name)
metadef_object._old_name = metadef_object.name
metadef_object.name = wsme_utils._get_value(
metadata_object.name)
metadef_object.description = wsme_utils._get_value(

View File

@ -40,10 +40,12 @@ _LI = i18n._LI
class NamespacePropertiesController(object):
def __init__(self, db_api=None, policy_enforcer=None):
def __init__(self, db_api=None, policy_enforcer=None, notifier=None):
self.db_api = db_api or glance.db.get_api()
self.policy = policy_enforcer or policy.Enforcer()
self.notifier = notifier or glance.notifier.Notifier()
self.gateway = glance.gateway.Gateway(db_api=self.db_api,
notifier=self.notifier,
policy_enforcer=self.policy)
def _to_dict(self, model_property_type):
@ -131,6 +133,7 @@ class NamespacePropertiesController(object):
prop_repo = self.gateway.get_metadef_property_repo(req.context)
try:
db_property_type = prop_repo.get(namespace, property_name)
db_property_type._old_name = db_property_type.name
db_property_type.name = property_type.name
db_property_type.schema = (self._to_dict(property_type))['schema']
updated_property_type = prop_repo.save(db_property_type)

View File

@ -40,10 +40,12 @@ _LI = i18n._LI
class ResourceTypeController(object):
def __init__(self, db_api=None, policy_enforcer=None):
def __init__(self, db_api=None, policy_enforcer=None, notifier=None):
self.db_api = db_api or glance.db.get_api()
self.policy = policy_enforcer or policy.Enforcer()
self.notifier = notifier or glance.notifier.Notifier()
self.gateway = glance.gateway.Gateway(db_api=self.db_api,
notifier=self.notifier,
policy_enforcer=self.policy)
def index(self, req):

View File

@ -41,10 +41,12 @@ CONF = cfg.CONF
class TagsController(object):
def __init__(self, db_api=None, policy_enforcer=None):
def __init__(self, db_api=None, policy_enforcer=None, notifier=None):
self.db_api = db_api or glance.db.get_api()
self.policy = policy_enforcer or policy.Enforcer()
self.notifier = notifier or glance.notifier.Notifier()
self.gateway = glance.gateway.Gateway(db_api=self.db_api,
notifier=self.notifier,
policy_enforcer=self.policy)
self.tag_schema_link = '/v2/schemas/metadefs/tag'
@ -141,6 +143,7 @@ class TagsController(object):
meta_repo = self.gateway.get_metadef_tag_repo(req.context)
try:
metadef_tag = meta_repo.get(namespace, tag_name)
metadef_tag._old_name = metadef_tag.name
metadef_tag.name = wsme_utils._get_value(
metadata_tag.name)
updated_metadata_tag = meta_repo.save(metadef_tag)

View File

@ -1060,6 +1060,15 @@ def _task_info_get(task_id):
return task_info
def _metadef_delete_namespace_content(get_func, key, context, namespace_name):
global DATA
metadefs = get_func(context, namespace_name)
data = DATA[key]
for metadef in metadefs:
data.remove(metadef)
return metadefs
@log_call
def metadef_namespace_create(context, values):
"""Create a namespace object"""
@ -1374,6 +1383,13 @@ def metadef_object_delete(context, namespace_name, object_name):
return object
def metadef_object_delete_namespace_content(context, namespace_name,
session=None):
"""Delete an object or raise if namespace or object doesn't exist."""
return _metadef_delete_namespace_content(
metadef_object_get_all, 'metadef_objects', context, namespace_name)
@log_call
def metadef_object_count(context, namespace_name):
"""Get metadef object count in a namespace"""
@ -1550,6 +1566,14 @@ def metadef_property_delete(context, namespace_name, property_name):
return property
def metadef_property_delete_namespace_content(context, namespace_name,
session=None):
"""Delete a property or raise if it or namespace doesn't exist."""
return _metadef_delete_namespace_content(
metadef_property_get_all, 'metadef_properties', context,
namespace_name)
@log_call
def metadef_resource_type_create(context, values):
"""Create a metadef resource type"""
@ -1863,6 +1887,13 @@ def metadef_tag_delete(context, namespace_name, name):
return tags
def metadef_tag_delete_namespace_content(context, namespace_name,
session=None):
"""Delete an tag or raise if namespace or tag doesn't exist."""
return _metadef_delete_namespace_content(
metadef_tag_get_all, 'metadef_tags', context, namespace_name)
@log_call
def metadef_tag_count(context, namespace_name):
"""Get metadef tag count in a namespace"""

View File

@ -131,77 +131,103 @@ class Gateway(object):
ns_factory = glance.domain.MetadefNamespaceFactory()
policy_ns_factory = policy.MetadefNamespaceFactoryProxy(
ns_factory, context, self.policy)
notifier_ns_factory = glance.notifier.MetadefNamespaceFactoryProxy(
policy_ns_factory, context, self.notifier)
authorized_ns_factory = authorization.MetadefNamespaceFactoryProxy(
policy_ns_factory, context)
notifier_ns_factory, context)
return authorized_ns_factory
def get_metadef_namespace_repo(self, context):
ns_repo = glance.db.MetadefNamespaceRepo(context, self.db_api)
policy_ns_repo = policy.MetadefNamespaceRepoProxy(
ns_repo, context, self.policy)
notifier_ns_repo = glance.notifier.MetadefNamespaceRepoProxy(
policy_ns_repo, context, self.notifier)
authorized_ns_repo = authorization.MetadefNamespaceRepoProxy(
policy_ns_repo, context)
notifier_ns_repo, context)
return authorized_ns_repo
def get_metadef_object_factory(self, context):
object_factory = glance.domain.MetadefObjectFactory()
policy_object_factory = policy.MetadefObjectFactoryProxy(
object_factory, context, self.policy)
notifier_object_factory = glance.notifier.MetadefObjectFactoryProxy(
policy_object_factory, context, self.notifier)
authorized_object_factory = authorization.MetadefObjectFactoryProxy(
policy_object_factory, context)
notifier_object_factory, context)
return authorized_object_factory
def get_metadef_object_repo(self, context):
object_repo = glance.db.MetadefObjectRepo(context, self.db_api)
policy_object_repo = policy.MetadefObjectRepoProxy(
object_repo, context, self.policy)
notifier_object_repo = glance.notifier.MetadefObjectRepoProxy(
policy_object_repo, context, self.notifier)
authorized_object_repo = authorization.MetadefObjectRepoProxy(
policy_object_repo, context)
notifier_object_repo, context)
return authorized_object_repo
def get_metadef_resource_type_factory(self, context):
resource_type_factory = glance.domain.MetadefResourceTypeFactory()
policy_resource_type_factory = policy.MetadefResourceTypeFactoryProxy(
resource_type_factory, context, self.policy)
return authorization.MetadefResourceTypeFactoryProxy(
policy_resource_type_factory, context)
notifier_resource_type_factory = (
glance.notifier.MetadefResourceTypeFactoryProxy(
policy_resource_type_factory, context, self.notifier)
)
authorized_resource_type_factory = (
authorization.MetadefResourceTypeFactoryProxy(
notifier_resource_type_factory, context)
)
return authorized_resource_type_factory
def get_metadef_resource_type_repo(self, context):
resource_type_repo = glance.db.MetadefResourceTypeRepo(
context, self.db_api)
policy_object_repo = policy.MetadefResourceTypeRepoProxy(
resource_type_repo, context, self.policy)
return authorization.MetadefResourceTypeRepoProxy(policy_object_repo,
context)
notifier_object_repo = glance.notifier.MetadefResourceTypeRepoProxy(
policy_object_repo, context, self.notifier)
authorized_object_repo = authorization.MetadefResourceTypeRepoProxy(
notifier_object_repo, context)
return authorized_object_repo
def get_metadef_property_factory(self, context):
prop_factory = glance.domain.MetadefPropertyFactory()
policy_prop_factory = policy.MetadefPropertyFactoryProxy(
prop_factory, context, self.policy)
notifier_prop_factory = glance.notifier.MetadefPropertyFactoryProxy(
policy_prop_factory, context, self.notifier)
authorized_prop_factory = authorization.MetadefPropertyFactoryProxy(
policy_prop_factory, context)
notifier_prop_factory, context)
return authorized_prop_factory
def get_metadef_property_repo(self, context):
prop_repo = glance.db.MetadefPropertyRepo(context, self.db_api)
policy_prop_repo = policy.MetadefPropertyRepoProxy(
prop_repo, context, self.policy)
notifier_prop_repo = glance.notifier.MetadefPropertyRepoProxy(
policy_prop_repo, context, self.notifier)
authorized_prop_repo = authorization.MetadefPropertyRepoProxy(
policy_prop_repo, context)
notifier_prop_repo, context)
return authorized_prop_repo
def get_metadef_tag_factory(self, context):
tag_factory = glance.domain.MetadefTagFactory()
policy_tag_factory = policy.MetadefTagFactoryProxy(
tag_factory, context, self.policy)
notifier_tag_factory = glance.notifier.MetadefTagFactoryProxy(
policy_tag_factory, context, self.notifier)
authorized_tag_factory = authorization.MetadefTagFactoryProxy(
policy_tag_factory, context)
notifier_tag_factory, context)
return authorized_tag_factory
def get_metadef_tag_repo(self, context):
tag_repo = glance.db.MetadefTagRepo(context, self.db_api)
policy_tag_repo = policy.MetadefTagRepoProxy(
tag_repo, context, self.policy)
notifier_tag_repo = glance.notifier.MetadefTagRepoProxy(
policy_tag_repo, context, self.notifier)
authorized_tag_repo = authorization.MetadefTagRepoProxy(
policy_tag_repo, context)
notifier_tag_repo, context)
return authorized_tag_repo

View File

@ -14,16 +14,19 @@
# License for the specific language governing permissions and limitations
# under the License.
import abc
import glance_store
from oslo import messaging
from oslo_config import cfg
from oslo_utils import excutils
from oslo_utils import timeutils
import six
import webob
from glance.common import exception
from glance.common import utils
import glance.domain.proxy
from glance.domain import proxy as domain_proxy
from glance import i18n
import glance.openstack.common.log as logging
@ -32,6 +35,15 @@ _ = i18n._
notifier_opts = [
cfg.StrOpt('default_publisher_id', default="image.localhost",
help='Default publisher_id for outgoing notifications.'),
cfg.ListOpt('disabled_notifications', default=[],
help='List of disabled notifications. A notification can be '
'given either as a notification type to disable a single '
'event, or as a notification group prefix to disable all '
'events within a group. Example: if this config option '
'is set to ["image.create", "metadef_namespace"], then '
'"image.create" notification will not be sent after '
'image is created and none of the notifications for '
'metadefinition namespaces will be sent.'),
]
CONF = cfg.CONF
@ -69,6 +81,27 @@ class Notifier(object):
self._notifier.error({}, event_type, payload)
def _get_notification_group(notification):
return notification.split('.', 1)[0]
def _is_notification_enabled(notification):
disabled_notifications = CONF.disabled_notifications
notification_group = _get_notification_group(notification)
notifications = (notification, notification_group)
for disabled_notification in disabled_notifications:
if disabled_notification in notifications:
return False
return True
def _send_notification(notify, notification_type, payload):
if _is_notification_enabled(notification_type):
notify(notification_type, payload)
def format_image_notification(image):
"""
Given a glance.domain.Image object, return a dictionary of relevant
@ -100,70 +133,211 @@ def format_image_notification(image):
def format_task_notification(task):
# NOTE(nikhil): input is not passed to the notifier payload as it may
# contain sensitive info.
return {'id': task.task_id,
'type': task.type,
'status': task.status,
'result': None,
'owner': task.owner,
'message': None,
'expires_at': timeutils.isotime(task.expires_at),
'created_at': timeutils.isotime(task.created_at),
'updated_at': timeutils.isotime(task.updated_at),
'deleted': False,
'deleted_at': None,
}
return {
'id': task.task_id,
'type': task.type,
'status': task.status,
'result': None,
'owner': task.owner,
'message': None,
'expires_at': timeutils.isotime(task.expires_at),
'created_at': timeutils.isotime(task.created_at),
'updated_at': timeutils.isotime(task.updated_at),
'deleted': False,
'deleted_at': None,
}
class ImageRepoProxy(glance.domain.proxy.Repo):
def format_metadef_namespace_notification(metadef_namespace):
return {
'namespace': metadef_namespace.namespace,
'namespace_old': metadef_namespace.namespace,
'display_name': metadef_namespace.display_name,
'protected': metadef_namespace.protected,
'visibility': metadef_namespace.visibility,
'owner': metadef_namespace.owner,
'description': metadef_namespace.description,
'created_at': timeutils.isotime(metadef_namespace.created_at),
'updated_at': timeutils.isotime(metadef_namespace.updated_at),
'deleted': False,
'deleted_at': None,
}
def __init__(self, image_repo, context, notifier):
self.image_repo = image_repo
def format_metadef_object_notification(metadef_object):
properties = []
for name, prop in six.iteritems(metadef_object.properties):
object_property = _format_metadef_object_property(name, prop)
properties.append(object_property)
return {
'namespace': metadef_object.namespace,
'name': metadef_object.name,
'name_old': metadef_object.name,
'properties': properties,
'required': metadef_object.required,
'description': metadef_object.description,
'created_at': timeutils.isotime(metadef_object.created_at),
'updated_at': timeutils.isotime(metadef_object.updated_at),
'deleted': False,
'deleted_at': None,
}
def _format_metadef_object_property(name, metadef_property):
return {
'name': name,
'type': metadef_property.type or None,
'title': metadef_property.title or None,
'description': metadef_property.description or None,
'default': metadef_property.default or None,
'minimum': metadef_property.minimum or None,
'maximum': metadef_property.maximum or None,
'enum': metadef_property.enum or None,
'pattern': metadef_property.pattern or None,
'minLength': metadef_property.minLength or None,
'maxLength': metadef_property.maxLength or None,
'confidential': metadef_property.confidential or None,
'items': metadef_property.items or None,
'uniqueItems': metadef_property.uniqueItems or None,
'minItems': metadef_property.minItems or None,
'maxItems': metadef_property.maxItems or None,
'additionalItems': metadef_property.additionalItems or None,
}
def format_metadef_property_notification(metadef_property):
schema = metadef_property.schema
return {
'namespace': metadef_property.namespace,
'name': metadef_property.name,
'name_old': metadef_property.name,
'type': schema.get('type'),
'title': schema.get('title'),
'description': schema.get('description'),
'default': schema.get('default'),
'minimum': schema.get('minimum'),
'maximum': schema.get('maximum'),
'enum': schema.get('enum'),
'pattern': schema.get('pattern'),
'minLength': schema.get('minLength'),
'maxLength': schema.get('maxLength'),
'confidential': schema.get('confidential'),
'items': schema.get('items'),
'uniqueItems': schema.get('uniqueItems'),
'minItems': schema.get('minItems'),
'maxItems': schema.get('maxItems'),
'additionalItems': schema.get('additionalItems'),
'deleted': False,
'deleted_at': None,
}
def format_metadef_resource_type_notification(metadef_resource_type):
return {
'namespace': metadef_resource_type.namespace,
'name': metadef_resource_type.name,
'name_old': metadef_resource_type.name,
'prefix': metadef_resource_type.prefix,
'properties_target': metadef_resource_type.properties_target,
'created_at': timeutils.isotime(metadef_resource_type.created_at),
'updated_at': timeutils.isotime(metadef_resource_type.updated_at),
'deleted': False,
'deleted_at': None,
}
def format_metadef_tag_notification(metadef_tag):
return {
'namespace': metadef_tag.namespace,
'name': metadef_tag.name,
'name_old': metadef_tag.name,
'created_at': timeutils.isotime(metadef_tag.created_at),
'updated_at': timeutils.isotime(metadef_tag.updated_at),
'deleted': False,
'deleted_at': None,
}
class NotificationBase(object):
def get_payload(self, obj):
return {}
def send_notification(self, notification_id, obj, extra_payload=None):
payload = self.get_payload(obj)
if extra_payload is not None:
payload.update(extra_payload)
_send_notification(self.notifier.info, notification_id, payload)
@six.add_metaclass(abc.ABCMeta)
class NotificationProxy(NotificationBase):
def __init__(self, repo, context, notifier):
self.repo = repo
self.context = context
self.notifier = notifier
super_class = self.get_super_class()
super_class.__init__(self, repo)
@abc.abstractmethod
def get_super_class(self):
pass
@six.add_metaclass(abc.ABCMeta)
class NotificationRepoProxy(NotificationBase):
def __init__(self, repo, context, notifier):
self.repo = repo
self.context = context
self.notifier = notifier
proxy_kwargs = {'context': self.context, 'notifier': self.notifier}
super(ImageRepoProxy, self).__init__(image_repo,
item_proxy_class=ImageProxy,
item_proxy_kwargs=proxy_kwargs)
def save(self, image, from_state=None):
super(ImageRepoProxy, self).save(image, from_state=from_state)
self.notifier.info('image.update',
format_image_notification(image))
proxy_class = self.get_proxy_class()
super_class = self.get_super_class()
super_class.__init__(self, repo, proxy_class, proxy_kwargs)
def add(self, image):
super(ImageRepoProxy, self).add(image)
self.notifier.info('image.create',
format_image_notification(image))
@abc.abstractmethod
def get_super_class(self):
pass
def remove(self, image):
super(ImageRepoProxy, self).remove(image)
payload = format_image_notification(image)
payload['deleted'] = True
payload['deleted_at'] = timeutils.isotime()
self.notifier.info('image.delete', payload)
@abc.abstractmethod
def get_proxy_class(self):
pass
class ImageFactoryProxy(glance.domain.proxy.ImageFactory):
@six.add_metaclass(abc.ABCMeta)
class NotificationFactoryProxy(object):
def __init__(self, factory, context, notifier):
kwargs = {'context': context, 'notifier': notifier}
super(ImageFactoryProxy, self).__init__(factory,
proxy_class=ImageProxy,
proxy_kwargs=kwargs)
proxy_class = self.get_proxy_class()
super_class = self.get_super_class()
super_class.__init__(self, factory, proxy_class, kwargs)
@abc.abstractmethod
def get_super_class(self):
pass
@abc.abstractmethod
def get_proxy_class(self):
pass
class ImageProxy(glance.domain.proxy.Image):
class ImageProxy(NotificationProxy, domain_proxy.Image):
def get_super_class(self):
return domain_proxy.Image
def __init__(self, image, context, notifier):
self.image = image
self.context = context
self.notifier = notifier
super(ImageProxy, self).__init__(image)
def get_payload(self, obj):
return format_image_notification(obj)
def _format_image_send(self, bytes_sent):
return {
'bytes_sent': bytes_sent,
'image_id': self.image.image_id,
'owner_id': self.image.owner,
'image_id': self.repo.image_id,
'owner_id': self.repo.owner,
'receiver_tenant_id': self.context.tenant,
'receiver_user_id': self.context.user,
}
@ -174,14 +348,14 @@ class ImageProxy(glance.domain.proxy.Image):
yield chunk
sent += len(chunk)
if sent != (chunk_size or self.image.size):
if sent != (chunk_size or self.repo.size):
notify = self.notifier.error
else:
notify = self.notifier.info
try:
notify('image.send',
self._format_image_send(sent))
_send_notification(notify, 'image.send',
self._format_image_send(sent))
except Exception as err:
msg = (_("An error occurred during image.send"
" notification: %(err)s") % {'err': err})
@ -191,152 +365,454 @@ class ImageProxy(glance.domain.proxy.Image):
# Due to the need of evaluating subsequent proxies, this one
# should return a generator, the call should be done before
# generator creation
data = self.image.get_data(offset=offset, chunk_size=chunk_size)
data = self.repo.get_data(offset=offset, chunk_size=chunk_size)
return self._get_chunk_data_iterator(data, chunk_size=chunk_size)
def set_data(self, data, size=None):
payload = format_image_notification(self.image)
self.notifier.info('image.prepare', payload)
self.send_notification('image.prepare', self.repo)
notify_error = self.notifier.error
try:
self.image.set_data(data, size)
self.repo.set_data(data, size)
except glance_store.StorageFull as e:
msg = (_("Image storage media is full: %s") %
utils.exception_to_str(e))
self.notifier.error('image.upload', msg)
_send_notification(notify_error, 'image.upload', msg)
raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg)
except glance_store.StorageWriteDenied as e:
msg = (_("Insufficient permissions on image storage media: %s")
% utils.exception_to_str(e))
self.notifier.error('image.upload', msg)
_send_notification(notify_error, 'image.upload', msg)
raise webob.exc.HTTPServiceUnavailable(explanation=msg)
except ValueError as e:
msg = (_("Cannot save data for image %(image_id)s: %(error)s") %
{'image_id': self.image.image_id,
{'image_id': self.repo.image_id,
'error': utils.exception_to_str(e)})
self.notifier.error('image.upload', msg)
_send_notification(notify_error, 'image.upload', msg)
raise webob.exc.HTTPBadRequest(
explanation=utils.exception_to_str(e))
except exception.Duplicate as e:
msg = (_("Unable to upload duplicate image data for image"
"%(image_id)s: %(error)s") %
{'image_id': self.image.image_id,
{'image_id': self.repo.image_id,
'error': utils.exception_to_str(e)})
self.notifier.error('image.upload', msg)
_send_notification(notify_error, 'image.upload', msg)
raise webob.exc.HTTPConflict(explanation=msg)
except exception.Forbidden as e:
msg = (_("Not allowed to upload image data for image %(image_id)s:"
" %(error)s") % {'image_id': self.image.image_id,
" %(error)s") % {'image_id': self.repo.image_id,
'error': utils.exception_to_str(e)})
self.notifier.error('image.upload', msg)
_send_notification(notify_error, 'image.upload', msg)
raise webob.exc.HTTPForbidden(explanation=msg)
except exception.NotFound as e:
msg = (_("Image %(image_id)s could not be found after upload."
" The image may have been deleted during the upload:"
" %(error)s") % {'image_id': self.image.image_id,
" %(error)s") % {'image_id': self.repo.image_id,
'error': utils.exception_to_str(e)})
self.notifier.error('image.upload', msg)
_send_notification(notify_error, 'image.upload', msg)
raise webob.exc.HTTPNotFound(explanation=utils.exception_to_str(e))
except webob.exc.HTTPError as e:
with excutils.save_and_reraise_exception():
msg = (_("Failed to upload image data for image %(image_id)s"
" due to HTTP error: %(error)s") %
{'image_id': self.image.image_id,
{'image_id': self.repo.image_id,
'error': utils.exception_to_str(e)})
self.notifier.error('image.upload', msg)
_send_notification(notify_error, 'image.upload', msg)
except Exception as e:
with excutils.save_and_reraise_exception():
msg = (_("Failed to upload image data for image %(image_id)s "
"due to internal error: %(error)s") %
{'image_id': self.image.image_id,
{'image_id': self.repo.image_id,
'error': utils.exception_to_str(e)})
self.notifier.error('image.upload', msg)
_send_notification(notify_error, 'image.upload', msg)
else:
payload = format_image_notification(self.image)
self.notifier.info('image.upload', payload)
self.notifier.info('image.activate', payload)
self.send_notification('image.upload', self.repo)
self.send_notification('image.activate', self.repo)
class TaskRepoProxy(glance.domain.proxy.TaskRepo):
class ImageFactoryProxy(NotificationFactoryProxy, domain_proxy.ImageFactory):
def get_super_class(self):
return domain_proxy.ImageFactory
def __init__(self, task_repo, context, notifier):
self.task_repo = task_repo
self.context = context
self.notifier = notifier
proxy_kwargs = {'context': self.context, 'notifier': self.notifier}
super(TaskRepoProxy, self).__init__(task_repo,
task_proxy_class=TaskProxy,
task_proxy_kwargs=proxy_kwargs)
def add(self, task):
self.notifier.info('task.create',
format_task_notification(task))
super(TaskRepoProxy, self).add(task)
def remove(self, task):
payload = format_task_notification(task)
payload['deleted'] = True
payload['deleted_at'] = timeutils.isotime()
self.notifier.info('task.delete', payload)
super(TaskRepoProxy, self).remove(task)
def get_proxy_class(self):
return ImageProxy
class TaskStubRepoProxy(glance.domain.proxy.TaskStubRepo):
class ImageRepoProxy(NotificationRepoProxy, domain_proxy.Repo):
def get_super_class(self):
return domain_proxy.Repo
def __init__(self, task_stub_repo, context, notifier):
self.task_stub_repo = task_stub_repo
self.context = context
self.notifier = notifier
proxy_kwargs = {'context': self.context, 'notifier': self.notifier}
super(TaskStubRepoProxy, self).__init__(
task_stub_repo,
task_stub_proxy_class=TaskStubProxy,
task_stub_proxy_kwargs=proxy_kwargs)
def get_proxy_class(self):
return ImageProxy
def get_payload(self, obj):
return format_image_notification(obj)
def save(self, image, from_state=None):
super(ImageRepoProxy, self).save(image, from_state=from_state)
self.send_notification('image.update', image)
def add(self, image):
super(ImageRepoProxy, self).add(image)
self.send_notification('image.create', image)
def remove(self, image):
super(ImageRepoProxy, self).remove(image)
self.send_notification('image.delete', image, extra_payload={
'deleted': True, 'deleted_at': timeutils.isotime()
})
class TaskFactoryProxy(glance.domain.proxy.TaskFactory):
def __init__(self, task_factory, context, notifier):
kwargs = {'context': context, 'notifier': notifier}
super(TaskFactoryProxy, self).__init__(
task_factory,
task_proxy_class=TaskProxy,
task_proxy_kwargs=kwargs)
class TaskProxy(NotificationProxy, domain_proxy.Task):
def get_super_class(self):
return domain_proxy.Task
class TaskProxy(glance.domain.proxy.Task):
def __init__(self, task, context, notifier):
self.task = task
self.context = context
self.notifier = notifier
super(TaskProxy, self).__init__(task)
def get_payload(self, obj):
return format_task_notification(obj)
def begin_processing(self):
self.notifier.info(
'task.processing',
format_task_notification(self.task)
)
return super(TaskProxy, self).begin_processing()
super(TaskProxy, self).begin_processing()
self.send_notification('task.processing', self.repo)
def succeed(self, result):
self.notifier.info('task.success',
format_task_notification(self.task))
return super(TaskProxy, self).succeed(result)
super(TaskProxy, self).succeed(result)
self.send_notification('task.success', self.repo)
def fail(self, message):
self.notifier.info('task.failure',
format_task_notification(self.task))
return super(TaskProxy, self).fail(message)
super(TaskProxy, self).fail(message)
self.send_notification('task.failure', self.repo)
def run(self, executor):
self.notifier.info('task.run',
format_task_notification(self.task))
return super(TaskProxy, self).run(executor)
super(TaskProxy, self).run(executor)
self.send_notification('task.run', self.repo)
class TaskStubProxy(glance.domain.proxy.TaskStub):
class TaskFactoryProxy(NotificationFactoryProxy, domain_proxy.TaskFactory):
def get_super_class(self):
return domain_proxy.TaskFactory
def __init__(self, task, context, notifier):
self.task = task
self.context = context
self.notifier = notifier
super(TaskStubProxy, self).__init__(task)
def get_proxy_class(self):
return TaskProxy
class TaskRepoProxy(NotificationRepoProxy, domain_proxy.TaskRepo):
def get_super_class(self):
return domain_proxy.TaskRepo
def get_proxy_class(self):
return TaskProxy
def get_payload(self, obj):
return format_task_notification(obj)
def add(self, task):
result = super(TaskRepoProxy, self).add(task)
self.send_notification('task.create', task)
return result
def remove(self, task):
result = super(TaskRepoProxy, self).remove(task)
self.send_notification('task.delete', task, extra_payload={
'deleted': True, 'deleted_at': timeutils.isotime()
})
return result
class TaskStubProxy(NotificationProxy, domain_proxy.TaskStub):
def get_super_class(self):
return domain_proxy.TaskStub
class TaskStubRepoProxy(NotificationRepoProxy, domain_proxy.TaskStubRepo):
def get_super_class(self):
return domain_proxy.TaskStubRepo
def get_proxy_class(self):
return TaskStubProxy
class MetadefNamespaceProxy(NotificationProxy, domain_proxy.MetadefNamespace):
def get_super_class(self):
return domain_proxy.MetadefNamespace
class MetadefNamespaceFactoryProxy(NotificationFactoryProxy,
domain_proxy.MetadefNamespaceFactory):
def get_super_class(self):
return domain_proxy.MetadefNamespaceFactory
def get_proxy_class(self):
return MetadefNamespaceProxy
class MetadefNamespaceRepoProxy(NotificationRepoProxy,
domain_proxy.MetadefNamespaceRepo):
def get_super_class(self):
return domain_proxy.MetadefNamespaceRepo
def get_proxy_class(self):
return MetadefNamespaceProxy
def get_payload(self, obj):
return format_metadef_namespace_notification(obj)
def save(self, metadef_namespace):
name = getattr(metadef_namespace, '_old_namespace',
metadef_namespace.namespace)
result = super(MetadefNamespaceRepoProxy, self).save(metadef_namespace)
self.send_notification(
'metadef_namespace.update', metadef_namespace,
extra_payload={
'namespace_old': name,
})
return result
def add(self, metadef_namespace):
result = super(MetadefNamespaceRepoProxy, self).add(metadef_namespace)
self.send_notification('metadef_namespace.create', metadef_namespace)
return result
def remove(self, metadef_namespace):
result = super(MetadefNamespaceRepoProxy, self).remove(
metadef_namespace)
self.send_notification(
'metadef_namespace.delete', metadef_namespace,
extra_payload={'deleted': True, 'deleted_at': timeutils.isotime()}
)
return result
def remove_objects(self, metadef_namespace):
result = super(MetadefNamespaceRepoProxy, self).remove_objects(
metadef_namespace)
self.send_notification('metadef_namespace.delete_objects',
metadef_namespace)
return result
def remove_properties(self, metadef_namespace):
result = super(MetadefNamespaceRepoProxy, self).remove_properties(
metadef_namespace)
self.send_notification('metadef_namespace.delete_properties',
metadef_namespace)
return result
def remove_tags(self, metadef_namespace):
result = super(MetadefNamespaceRepoProxy, self).remove_tags(
metadef_namespace)
self.send_notification('metadef_namespace.delete_tags',
metadef_namespace)
return result
class MetadefObjectProxy(NotificationProxy, domain_proxy.MetadefObject):
def get_super_class(self):
return domain_proxy.MetadefObject
class MetadefObjectFactoryProxy(NotificationFactoryProxy,
domain_proxy.MetadefObjectFactory):
def get_super_class(self):
return domain_proxy.MetadefObjectFactory
def get_proxy_class(self):
return MetadefObjectProxy
class MetadefObjectRepoProxy(NotificationRepoProxy,
domain_proxy.MetadefObjectRepo):
def get_super_class(self):
return domain_proxy.MetadefObjectRepo
def get_proxy_class(self):
return MetadefObjectProxy
def get_payload(self, obj):
return format_metadef_object_notification(obj)
def save(self, metadef_object):
name = getattr(metadef_object, '_old_name', metadef_object.name)
result = super(MetadefObjectRepoProxy, self).save(metadef_object)
self.send_notification(
'metadef_object.update', metadef_object,
extra_payload={
'namespace': metadef_object.namespace.namespace,
'name_old': name,
})
return result
def add(self, metadef_object):
result = super(MetadefObjectRepoProxy, self).add(metadef_object)
self.send_notification('metadef_object.create', metadef_object)
return result
def remove(self, metadef_object):
result = super(MetadefObjectRepoProxy, self).remove(metadef_object)
self.send_notification(
'metadef_object.delete', metadef_object,
extra_payload={
'deleted': True,
'deleted_at': timeutils.isotime(),
'namespace': metadef_object.namespace.namespace
}
)
return result
class MetadefPropertyProxy(NotificationProxy, domain_proxy.MetadefProperty):
def get_super_class(self):
return domain_proxy.MetadefProperty
class MetadefPropertyFactoryProxy(NotificationFactoryProxy,
domain_proxy.MetadefPropertyFactory):
def get_super_class(self):
return domain_proxy.MetadefPropertyFactory
def get_proxy_class(self):
return MetadefPropertyProxy
class MetadefPropertyRepoProxy(NotificationRepoProxy,
domain_proxy.MetadefPropertyRepo):
def get_super_class(self):
return domain_proxy.MetadefPropertyRepo
def get_proxy_class(self):
return MetadefPropertyProxy
def get_payload(self, obj):
return format_metadef_property_notification(obj)
def save(self, metadef_property):
name = getattr(metadef_property, '_old_name', metadef_property.name)
result = super(MetadefPropertyRepoProxy, self).save(metadef_property)
self.send_notification(
'metadef_property.update', metadef_property,
extra_payload={
'namespace': metadef_property.namespace.namespace,
'name_old': name,
})
return result
def add(self, metadef_property):
result = super(MetadefPropertyRepoProxy, self).add(metadef_property)
self.send_notification('metadef_property.create', metadef_property)
return result
def remove(self, metadef_property):
result = super(MetadefPropertyRepoProxy, self).remove(metadef_property)
self.send_notification(
'metadef_property.delete', metadef_property,
extra_payload={
'deleted': True,
'deleted_at': timeutils.isotime(),
'namespace': metadef_property.namespace.namespace
}
)
return result
class MetadefResourceTypeProxy(NotificationProxy,
domain_proxy.MetadefResourceType):
def get_super_class(self):
return domain_proxy.MetadefResourceType
class MetadefResourceTypeFactoryProxy(NotificationFactoryProxy,
domain_proxy.MetadefResourceTypeFactory):
def get_super_class(self):
return domain_proxy.MetadefResourceTypeFactory
def get_proxy_class(self):
return MetadefResourceTypeProxy
class MetadefResourceTypeRepoProxy(NotificationRepoProxy,
domain_proxy.MetadefResourceTypeRepo):
def get_super_class(self):
return domain_proxy.MetadefResourceTypeRepo
def get_proxy_class(self):
return MetadefResourceTypeProxy
def get_payload(self, obj):
return format_metadef_resource_type_notification(obj)
def add(self, md_resource_type):
result = super(MetadefResourceTypeRepoProxy, self).add(
md_resource_type)
self.send_notification('metadef_resource_type.create',
md_resource_type)
return result
def remove(self, md_resource_type):
result = super(MetadefResourceTypeRepoProxy, self).remove(
md_resource_type)
self.send_notification(
'metadef_resource_type.delete', md_resource_type,
extra_payload={
'deleted': True,
'deleted_at': timeutils.isotime(),
'namespace': md_resource_type.namespace.namespace
}
)
return result
class MetadefTagProxy(NotificationProxy, domain_proxy.MetadefTag):
def get_super_class(self):
return domain_proxy.MetadefTag
class MetadefTagFactoryProxy(NotificationFactoryProxy,
domain_proxy.MetadefTagFactory):
def get_super_class(self):
return domain_proxy.MetadefTagFactory
def get_proxy_class(self):
return MetadefTagProxy
class MetadefTagRepoProxy(NotificationRepoProxy, domain_proxy.MetadefTagRepo):
def get_super_class(self):
return domain_proxy.MetadefTagRepo
def get_proxy_class(self):
return MetadefTagProxy
def get_payload(self, obj):
return format_metadef_tag_notification(obj)
def save(self, metadef_tag):
name = getattr(metadef_tag, '_old_name', metadef_tag.name)
result = super(MetadefTagRepoProxy, self).save(metadef_tag)
self.send_notification(
'metadef_tag.update', metadef_tag,
extra_payload={
'namespace': metadef_tag.namespace.namespace,
'name_old': name,
})
return result
def add(self, metadef_tag):
result = super(MetadefTagRepoProxy, self).add(metadef_tag)
self.send_notification('metadef_tag.create', metadef_tag)
return result
def add_tags(self, metadef_tags):
result = super(MetadefTagRepoProxy, self).add_tags(metadef_tags)
for metadef_tag in metadef_tags:
self.send_notification('metadef_tag.create', metadef_tag)
return result
def remove(self, metadef_tag):
result = super(MetadefTagRepoProxy, self).remove(metadef_tag)
self.send_notification(
'metadef_tag.delete', metadef_tag,
extra_payload={
'deleted': True,
'deleted_at': timeutils.isotime(),
'namespace': metadef_tag.namespace.namespace
}
)
return result

View File

@ -173,12 +173,12 @@ class TestImageNotifications(utils.BaseTestCase):
def test_image_get(self):
image = self.image_repo_proxy.get(UUID1)
self.assertIsInstance(image, glance.notifier.ImageProxy)
self.assertEqual('image_from_get', image.image)
self.assertEqual('image_from_get', image.repo)
def test_image_list(self):
images = self.image_repo_proxy.list()
self.assertIsInstance(images[0], glance.notifier.ImageProxy)
self.assertEqual('images_from_list', images[0].image)
self.assertEqual('images_from_list', images[0].repo)
def test_image_get_data_should_call_next_image_get_data(self):
with mock.patch.object(self.image, 'get_data') as get_data_mock:

View File

@ -146,6 +146,7 @@ class OptsTestCase(utils.BaseTestCase):
'public_endpoint',
'digest_algorithm',
'http_keepalive',
'disabled_notifications',
]
self._check_opt_groups(opt_list, expected_opt_groups)

View File

@ -600,6 +600,20 @@ class TestImagesController(base.IsolatedUnitTest):
self.assertEqual('image.create', output_log['event_type'])
self.assertEqual('image-1', output_log['payload']['name'])
def test_create_disabled_notification(self):
self.config(disabled_notifications=["image.create"])
request = unit_test_utils.get_fake_request()
image = {'name': 'image-1'}
output = self.controller.create(request, image=image,
extra_properties={},
tags=[])
self.assertEqual('image-1', output.name)
self.assertEqual({}, output.extra_properties)
self.assertEqual(set([]), output.tags)
self.assertEqual('private', output.visibility)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
def test_create_with_properties(self):
request = unit_test_utils.get_fake_request()
image_properties = {'foo': 'bar'}
@ -1781,6 +1795,17 @@ class TestImagesController(base.IsolatedUnitTest):
self.assertEqual('image.update', output_log['event_type'])
self.assertEqual(UUID1, output_log['payload']['id'])
def test_update_disabled_notification(self):
self.config(disabled_notifications=["image.update"])
request = unit_test_utils.get_fake_request()
changes = [
{'op': 'replace', 'path': ['name'], 'value': 'Ping Pong'},
]
output = self.controller.update(request, UUID1, changes)
self.assertEqual('Ping Pong', output.name)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
def test_delete(self):
request = unit_test_utils.get_fake_request()
self.assertIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
@ -1800,6 +1825,23 @@ class TestImagesController(base.IsolatedUnitTest):
self.assertEqual('deleted', deleted_img['status'])
self.assertNotIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
def test_delete_disabled_notification(self):
self.config(disabled_notifications=["image.delete"])
request = unit_test_utils.get_fake_request()
self.assertIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
try:
self.controller.delete(request, UUID1)
output_logs = self.notifier.get_logs()
self.assertEqual(0, len(output_logs))
except Exception as e:
self.fail("Delete raised exception: %s" % e)
deleted_img = self.db.image_get(request.context, UUID1,
force_show_deleted=True)
self.assertTrue(deleted_img['deleted'])
self.assertEqual('deleted', deleted_img['status'])
self.assertNotIn('%s/%s' % (BASE_URI, UUID1), self.store.data)
def test_delete_queued_updates_status(self):
"""Ensure status of queued image is updated (LP bug #1048851)"""
request = unit_test_utils.get_fake_request(is_admin=True)

File diff suppressed because it is too large Load Diff