Versioned notification: Add instance action notification base

This change added the base of instance action notifications.

Partially-Implements: bp support-versioned-notification
Change-Id: Ia96cc378c0e2f0d50178bc4665c4ca4905b15563
This commit is contained in:
liusheng 2017-01-06 18:05:11 +08:00 committed by Zhenguo Niu
parent b28f7390fe
commit b9b2da6be6
5 changed files with 239 additions and 1 deletions

View File

@ -0,0 +1,63 @@
# Copyright 2016 Huawei Technologies Co.,LTD.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Functionality related to notifications common to multiple layers of
the system.
"""
from mogan.notifications.objects import base as notification_base
from mogan.notifications.objects import exception as notification_exception
from mogan.notifications.objects import instance as instance_notification
from mogan.objects import fields
def _get_fault_and_priority_from_exc(exception):
fault = None
priority = fields.NotificationPriority.INFO
if exception:
priority = fields.NotificationPriority.ERROR
fault = notification_exception.ExceptionPayload.from_exception(
exception)
return fault, priority
def notify_about_instance_action(context, instance, host, action, phase=None,
binary='mogan-engine', exception=None):
"""Send versioned notification about the action made on the instance
:param instance: the instance which the action performed on
:param host: the host emitting the notification
:param action: the name of the action
:param phase: the phase of the action
:param binary: the binary emitting the notification
:param exception: the thrown exception (used in error notifications)
"""
fault, priority = _get_fault_and_priority_from_exc(exception)
payload = instance_notification.InstanceActionPayload(
instance=instance,
fault=fault)
notification = instance_notification.InstanceActionNotification(
context=context,
priority=priority,
publisher=notification_base.NotificationPublisher(
context=context, host=host, binary=binary),
event_type=notification_base.EventType(
object='instance',
action=action,
phase=phase),
payload=payload)
notification.emit(context)

View File

@ -0,0 +1,53 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import inspect
import six
from mogan.notifications.objects import base
from mogan.objects import base as moban_base
from mogan.objects import fields
@moban_base.MoganObjectRegistry.register_notification
class ExceptionPayload(base.NotificationPayloadBase):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'module_name': fields.StringField(),
'function_name': fields.StringField(),
'exception': fields.StringField(),
'exception_message': fields.StringField()
}
@classmethod
def from_exception(cls, fault):
trace = inspect.trace()[-1]
# TODO(gibi): apply strutils.mask_password on exception_message and
# consider emitting the exception_message only if the safe flag is
# true in the exception like in the REST API
module = inspect.getmodule(trace[0])
module_name = module.__name__ if module else 'unknown'
return cls(
function_name=trace[3],
module_name=module_name,
exception=fault.__class__.__name__,
exception_message=six.text_type(fault))
@moban_base.MoganObjectRegistry.register_notification
class ExceptionNotification(base.NotificationBase):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'payload': fields.ObjectField('ExceptionPayload')
}

View File

@ -0,0 +1,84 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from mogan.notifications.objects import base
from mogan.objects import base as mogan_base
from mogan.objects import fields
@mogan_base.MoganObjectRegistry.register_notification
class InstancePayload(base.NotificationPayloadBase):
SCHEMA = {
'name': ('instance', 'name'),
'uuid': ('instance', 'uuid'),
'user_id': ('instance', 'user_id'),
'project_id': ('instance', 'project_id'),
'availability_zone': ('instance', 'availability_zone'),
'image_uuid': ('instance', 'image_uuid'),
'created_at': ('instance', 'created_at'),
'launched_at': ('instance', 'launched_at'),
'updated_at': ('instance', 'updated_at'),
'status': ('instance', 'status'),
# TODO(liusheng) the instance object hasn't power_state attribute
# 'power_state': ('instance', 'power_state'),
'instance_type_uuid': ('instance', 'instance_type_uuid'),
'description': ('instance', 'description')
}
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'name': fields.StringField(nullable=False),
'uuid': fields.UUIDField(nullable=False),
'user_id': fields.StringField(nullable=True),
'project_id': fields.StringField(nullable=True),
'description': fields.StringField(nullable=True),
'instance_type_uuid': fields.UUIDField(nullable=False),
'image_uuid': fields.UUIDField(nullable=True),
'availability_zone': fields.StringField(nullable=True),
# 'power_state': fields.StringField(nullable=True),
'created_at': fields.DateTimeField(nullable=True),
'launched_at': fields.DateTimeField(nullable=True),
'updated_at': fields.DateTimeField(nullable=True),
'status': fields.StringField(nullable=True),
# 'network_info'
# 'extra'
}
def __init__(self, instance, **kwargs):
super(InstancePayload, self).__init__(**kwargs)
self.populate_schema(instance=instance)
@mogan_base.MoganObjectRegistry.register_notification
class InstanceActionPayload(InstancePayload):
# No SCHEMA as all the additional fields are calculated
VERSION = '1.0'
fields = {
'fault': fields.ObjectField('ExceptionPayload', nullable=True),
}
def __init__(self, instance, fault, **kwargs):
super(InstanceActionPayload, self).__init__(
instance=instance,
fault=fault,
**kwargs)
@mogan_base.MoganObjectRegistry.register_notification
class InstanceActionNotification(base.NotificationBase):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'payload': fields.ObjectField('InstanceActionPayload')
}

View File

@ -146,7 +146,7 @@ class NotificationAction(BaseMoganEnum):
SHUTDOWN = 'shutdown'
CREATE = 'create'
ALL = (UPDATE, EXCEPTION, DELETE, POWER_OFF)
ALL = (UPDATE, EXCEPTION, DELETE, CREATE, POWER_OFF)
class NotificationPhaseField(object_fields.BaseEnumField):

View File

@ -14,10 +14,13 @@
import mock
from mogan.notifications import base as notification_base
from mogan.notifications.objects import base as notification
from mogan.objects import base
from mogan.objects import fields
from mogan.objects import instance as inst_obj
from mogan.tests import base as test_base
from mogan.tests.unit.db import utils as db_utils
class TestNotificationBase(test_base.TestCase):
@ -218,3 +221,38 @@ class TestNotificationBase(test_base.TestCase):
self.assertEqual(2, len(self.TestNotification.samples))
self.assertIn('test-update-1.json', self.TestNotification.samples)
self.assertIn('test-update-2.json', self.TestNotification.samples)
class TestInstanceActionNotification(test_base.TestCase):
@mock.patch('mogan.notifications.objects.instance.'
'InstanceActionNotification._emit')
def test_send_version_instance_action(self, mock_emit):
# Make sure that the notification payload chooses the values in
# instance.flavor.$value instead of instance.$value
fake_inst_values = db_utils.get_test_instance()
instance = inst_obj.Instance(**fake_inst_values)
notification_base.notify_about_instance_action(
mock.MagicMock(),
instance,
'test-host',
fields.NotificationAction.CREATE,
fields.NotificationPhase.START,
'mogan-compute')
self.assertEqual('instance.create.start',
mock_emit.call_args_list[0][1]['event_type'])
self.assertEqual('mogan-compute:test-host',
mock_emit.call_args_list[0][1]['publisher_id'])
payload = mock_emit.call_args_list[0][1]['payload'][
'mogan_object.data']
self.assertEqual(fake_inst_values['uuid'], payload['uuid'])
self.assertEqual(fake_inst_values['instance_type_uuid'],
payload['instance_type_uuid'])
self.assertEqual(fake_inst_values['status'], payload['status'])
self.assertEqual(fake_inst_values['user_id'], payload['user_id'])
self.assertEqual(fake_inst_values['availability_zone'],
payload['availability_zone'])
self.assertEqual(fake_inst_values['name'], payload['name'])
self.assertEqual(fake_inst_values['image_uuid'], payload['image_uuid'])
self.assertEqual(fake_inst_values['project_id'], payload['project_id'])
self.assertEqual(fake_inst_values['description'],
payload['description'])