Merge "Add service status notification"

This commit is contained in:
Jenkins 2016-01-23 00:14:50 +00:00 committed by Gerrit Code Review
commit df83122250
6 changed files with 151 additions and 0 deletions

View File

@ -22,6 +22,7 @@ from nova.i18n import _LW
from nova import objects
from nova.objects import base
from nova.objects import fields
from nova.objects import notification
LOG = logging.getLogger(__name__)
@ -282,6 +283,24 @@ class Service(base.NovaPersistentObject, base.NovaObject,
db_service = db.service_update(self._context, self.id, updates)
self._from_db_object(self._context, self, db_service)
self._send_status_update_notification(updates)
def _send_status_update_notification(self, updates):
# Note(gibi): We do not trigger notification on version as that field
# is always dirty, which would cause that nova sends notification on
# every other field change. See the comment in save() too.
if set(updates.keys()).intersection(
{'disabled', 'disabled_reason', 'forced_down'}):
payload = ServiceStatusPayload(self)
ServiceStatusNotification(
publisher=notification.NotificationPublisher.from_service_obj(
self),
event_type=notification.EventType(
object='service',
action=fields.NotificationAction.UPDATE),
priority=fields.NotificationPriority.INFO,
payload=payload).emit(self._context)
@base.remotable
def destroy(self):
db.service_destroy(self._context, self.id)
@ -372,3 +391,47 @@ class ServiceList(base.ObjectListBase, base.NovaObject):
context, db_services)
return base.obj_make_list(context, cls(context), objects.Service,
db_services)
@base.NovaObjectRegistry.register
class ServiceStatusNotification(notification.NotificationBase):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'payload': fields.ObjectField('ServiceStatusPayload')
}
@base.NovaObjectRegistry.register
class ServiceStatusPayload(notification.NotificationPayloadBase):
SCHEMA = {
'host': ('service', 'host'),
'binary': ('service', 'binary'),
'topic': ('service', 'topic'),
'report_count': ('service', 'report_count'),
'disabled': ('service', 'disabled'),
'disabled_reason': ('service', 'disabled_reason'),
'availability_zone': ('service', 'availability_zone'),
'last_seen_up': ('service', 'last_seen_up'),
'forced_down': ('service', 'forced_down'),
'version': ('service', 'version')
}
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'host': fields.StringField(nullable=True),
'binary': fields.StringField(nullable=True),
'topic': fields.StringField(nullable=True),
'report_count': fields.IntegerField(),
'disabled': fields.BooleanField(),
'disabled_reason': fields.StringField(nullable=True),
'availability_zone': fields.StringField(nullable=True),
'last_seen_up': fields.DateTimeField(nullable=True),
'forced_down': fields.BooleanField(),
'version': fields.IntegerField(),
}
def __init__(self, service):
super(ServiceStatusPayload, self).__init__()
self.populate_schema(service=service)

View File

@ -18,6 +18,8 @@ from oslo_utils import fixture as utils_fixture
from nova.tests.functional.api_sample_tests import api_sample_base
from nova.tests.unit.api.openstack.compute import test_services
from nova.tests.unit import fake_notifier
CONF = cfg.CONF
CONF.import_opt('osapi_compute_extension',
@ -50,6 +52,18 @@ class ServicesJsonTest(api_sample_base.ApiSampleTestBaseV21):
self.stub_out("nova.db.service_update",
test_services.fake_service_update)
self.useFixture(utils_fixture.TimeFixture(test_services.fake_utcnow()))
fake_notifier.stub_notifier(self.stubs)
self.addCleanup(fake_notifier.reset)
def _verify_notification(self, **kwargs):
# TODO(gibi): store notification sample and start using that for
# verification instead
self.assertEqual(1, len(fake_notifier.VERSIONED_NOTIFICATIONS))
payload = fake_notifier.VERSIONED_NOTIFICATIONS[0]['payload']
fields = payload['nova_object.data']
for key, value in kwargs.items():
self.assertEqual(value, fields[key],
'Mismatch in key %s' % key)
def test_services_list(self):
"""Return a list of all agent builds."""
@ -61,6 +75,7 @@ class ServicesJsonTest(api_sample_base.ApiSampleTestBaseV21):
'status': 'disabled',
'state': 'up'}
self._verify_response('services-list-get-resp', subs, response, 200)
self.assertEqual(0, len(fake_notifier.VERSIONED_NOTIFICATIONS))
def test_service_enable(self):
"""Enable an existing agent build."""
@ -70,6 +85,7 @@ class ServicesJsonTest(api_sample_base.ApiSampleTestBaseV21):
'service-enable-put-req', subs,
api_version=self.microversion)
self._verify_response('service-enable-put-resp', subs, response, 200)
self._verify_notification(disabled=False, disabled_reason=None)
def test_service_disable(self):
"""Disable an existing agent build."""
@ -79,6 +95,7 @@ class ServicesJsonTest(api_sample_base.ApiSampleTestBaseV21):
'service-disable-put-req', subs,
api_version=self.microversion)
self._verify_response('service-disable-put-resp', subs, response, 200)
self._verify_notification(disabled=True, disabled_reason=None)
def test_service_disable_log_reason(self):
"""Disable an existing service and log the reason."""
@ -90,6 +107,7 @@ class ServicesJsonTest(api_sample_base.ApiSampleTestBaseV21):
api_version=self.microversion)
self._verify_response('service-disable-log-put-resp',
subs, response, 200)
self._verify_notification(disabled=True, disabled_reason='test2')
def test_service_delete(self):
"""Delete an existing service."""
@ -97,6 +115,7 @@ class ServicesJsonTest(api_sample_base.ApiSampleTestBaseV21):
api_version=self.microversion)
self.assertEqual(204, response.status_code)
self.assertEqual("", response.content)
self.assertEqual(0, len(fake_notifier.VERSIONED_NOTIFICATIONS))
class ServicesV211JsonTest(ServicesJsonTest):
@ -116,6 +135,7 @@ class ServicesV211JsonTest(ServicesJsonTest):
'status': 'disabled',
'state': 'up'}
self._verify_response('services-list-get-resp', subs, response, 200)
self.assertEqual(0, len(fake_notifier.VERSIONED_NOTIFICATIONS))
def test_force_down(self):
"""Set forced_down flag"""
@ -127,3 +147,4 @@ class ServicesV211JsonTest(ServicesJsonTest):
api_version=self.microversion)
self._verify_response('service-force-down-put-resp', subs,
response, 200)
self._verify_notification(forced_down=True)

View File

@ -13,6 +13,7 @@
# under the License.
import copy
import datetime
import iso8601
@ -142,6 +143,8 @@ def fake_db_service_update(services):
service = _service_get_by_id(services, service_id)
if service is None:
raise exception.ServiceNotFound(service_id=service_id)
service = copy.deepcopy(service)
service.update(values)
return service
return service_update

View File

@ -1189,6 +1189,8 @@ object_data = {
'SecurityGroupRuleList': '1.2-0005c47fcd0fb78dd6d7fd32a1409f5b',
'Service': '1.19-8914320cbeb4ec29f252d72ce55d07e1',
'ServiceList': '1.17-b767102cba7cbed290e396114c3f86b3',
'ServiceStatusNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
'ServiceStatusPayload': '1.0-b13764918aaa0e29acc868cf38a0c39b',
'TaskLog': '1.0-78b0534366f29aa3eebb01860fbe18fe',
'TaskLogList': '1.0-cc8cce1af8a283b9d28b55fcd682e777',
'Tag': '1.1-8b8d7d5b48887651a0e01241672e2963',

View File

@ -22,6 +22,7 @@ from nova import db
from nova import exception
from nova import objects
from nova.objects import aggregate
from nova.objects import fields
from nova.objects import service
from nova import test
from nova.tests.unit.objects import test_compute_node
@ -400,3 +401,57 @@ class TestServiceVersion(test.TestCase):
mock.sentinel.context, fake_service['id'],
{'version': service.SERVICE_VERSION,
'host': 'foo'})
class TestServiceStatusNotification(test.TestCase):
@mock.patch('nova.objects.service.ServiceStatusNotification')
def _verify_notification(self, service_obj, mock_notification):
service_obj.save()
self.assertTrue(mock_notification.called)
event_type = mock_notification.call_args[1]['event_type']
priority = mock_notification.call_args[1]['priority']
publisher = mock_notification.call_args[1]['publisher']
payload = mock_notification.call_args[1]['payload']
self.assertEqual(service_obj.host, publisher.host)
self.assertEqual(service_obj.binary, publisher.binary)
self.assertEqual(fields.NotificationPriority.INFO, priority)
self.assertEqual('service', event_type.object)
self.assertEqual(fields.NotificationAction.UPDATE,
event_type.action)
for field in service.ServiceStatusPayload.SCHEMA:
if field in fake_service:
self.assertEqual(fake_service[field], getattr(payload, field))
mock_notification.return_value.emit.assert_called_once_with(
mock.sentinel.context)
@mock.patch('nova.db.service_update')
def test_service_update_with_notification(self, mock_db_service_update):
service_obj = objects.Service(context=mock.sentinel.context,
id=fake_service['id'])
mock_db_service_update.return_value = fake_service
for key, value in {'disabled': True,
'disabled_reason': 'my reason',
'forced_down': True}.items():
setattr(service_obj, key, value)
self._verify_notification(service_obj)
@mock.patch('nova.objects.service.ServiceStatusNotification')
@mock.patch('nova.db.service_update')
def test_service_update_without_notification(self,
mock_db_service_update,
mock_notification):
service_obj = objects.Service(context=mock.sentinel.context,
id=fake_service['id'])
mock_db_service_update.return_value = fake_service
for key, value in {'report_count': 13,
'last_seen_up': timeutils.utcnow()}.items():
setattr(service_obj, key, value)
service_obj.save()
self.assertFalse(mock_notification.called)

View File

@ -0,0 +1,7 @@
---
features:
- A new service.status versioned notification has been introduced.
When the status of the Service object is changed nova will
send a new service.update notification with versioned payload
according to bp versioned-notification-api.