Versioned notifications for services

https://blueprints.launchpad.net/watcher/+spec/service-versioned-notifications-api

Following the implementation of the watcher-notifications-ovo blueprint, Watcher now has all the necessary prerequisites in order to provide versioned notifications throughout its codebase. This blueprint will focus on describing the notifications to implement in Watcher concerning the Service object.

Problem description

As of now, there is no way for any service (Watcher included) to know when a service of Watcher has been failed or whether it has been reactivated afterwards. This prevents any form of event-based reaction which may be useful for 3rd party services or plugins.

Use Cases

As an OpenStack developer, I want to be able to listen to notifications coming from Watcher about service.

As an OpenStack developer, I want to know what the format of the service notifications are.

As an OpenStack developer, I want to be notified whenever a service has been failed or whether it has been reactivated afterwards.

Proposed change

In order to implement the above use cases, one notification will be needed:

  • service.update whenever a service has been failed.

  • service.update whenever a service has been reactivated afterwards.

Monitoring the status of the services will be performed through the background job, which will be performed periodicaly (60 seconds) by the watcher-api service.

Here below is suggestion of background job structure:

scheduling

class APISchedulingService(scheduling.BackgroundSchedulerService):

    def __init__(self, gconfig=None, **options):
        self.services_status = {}
        gconfig = None or {}
        super(APISchedulingService, self).__init__(gconfig, **options)

    def get_services_status(self, context):
        services = objects.service.Service.list(context)
        for service in services:
            result = self.get_service_status(context, service.name)
            if service.id not in self.services_status.keys():
                self.services_status[service.id] = result
                continue
            if self.services_status[service.id] != result:
                self.services_status[service.id] = result
                notifications.service.send_service_update(context, service,
                                                          state=result)

    def get_service_status(self, context, name):
        service = objects.Service.get(context, id)
        last_heartbeat = (service.last_seen_up or service.updated_at
                          or service.created_at)
        if isinstance(last_heartbeat, six.string_types):
            last_heartbeat = timeutils.parse_strtime(last_heartbeat)
        else:
            last_heartbeat = last_heartbeat.replace(tzinfo=None)
        elapsed = timeutils.delta_seconds(last_heartbeat, timeutils.utcnow())
        is_up = abs(elapsed) <= CONF.service_down_time
        if not is_up:
            LOG.warning(_LW('Seems service %(name)s on host %(host)s is down. '
                            'Last heartbeat was %(lhb)s.'
                            'Elapsed time is %(el)s'),
                        {'name': service.name,
                         'host': service.host,
                         'lhb': str(last_heartbeat), 'el': str(elapsed)})
            return objects.service.ServiceStatus.FAILED

        return objects.service.ServiceStatus.ACTIVE

    def start(self):
        """Start service."""
        context = watcher_context.make_context(is_admin=True)
        self.add_job(self.get_services_status, name='service_status',
                     trigger='interval', jobstore='default', args=[context],
                     next_run_time=datetime.datetime.now(), seconds=60)
        super(APISchedulingService, self).start()

    def stop(self):
        """Stop service."""
        self.shutdown()

    def wait(self):
        """Wait for service to complete."""

    def reset(self):
        """Reset service."""

Moreover, we will rely on oslo.versionedobjects to version the payloads of service-related notifications.

Here below is suggestion of notification structure of the aforementioned events:

service.update

{
  "payload": {
    "watcher_object.name": "ServiceUpdatePayload",
    "watcher_object.namespace": "watcher",
    "watcher_object.data": {
      "status_update": {
        "watcher_object.name": "ServiceStatusUpdatePayload",
        "watcher_object.namespace": "watcher",
        "watcher_object.data": {
          "old_state" :"ACTIVE",
          "state": "FAILED"
        },
        "watcher_object.version": "1.0"
      },
      "last_seen_up": "2016-09-22T08:32:06Z",
      "name": "watcher-service",
      "sevice_host": "controller"
    },
    "watcher_object.version": "1.0"
  },
  "event_type": "service.update",
  "priority": "INFO",
    "message_id": "3984dc2b-8aef-462b-a220-8ae04237a56e",
    "timestamp": "2016-10-18 09:52:05.219414",
    "publisher_id": "infra-optim:node0"
}

Alternatives

Instead of using versioned objects, we can define the payload of our service notifications without any support for versioning.

Data model impact

New versioned objects will be created although none of them are to be persisted as they will be used to structure the content of the notifications.

Here are some of the payloads to be declared:

@base.WatcherObjectRegistry.register_notification
class ServicePayload(notificationbase.NotificationPayloadBase):

    VERSION = '1.0'
    fields = {
        'sevice_host': wfields.StringField(),
        'name': wfields.StringField(),
        'last_seen_up': wfields.DateTimeField(),
    }


@base.WatcherObjectRegistry.register_notification
class ServiceStatusUpdatePayload(notificationbase.NotificationPayloadBase):

   VERSION = '1.0'
    fields = {
        'old_state': wfields.StringField(nullable=True)
        'state': wfields.StringField(nullable=True)
    }


@base.WatcherObjectRegistry.register_notification
class ServiceUpdatePayload(ServicePayload):

    VERSION = '1.0'
    fields = {
        'status_update': wfields.ObjectField('ServiceStatusUpdatePayload'),
    }

REST API impact

None.

Security impact

None.

Notifications impact

This blueprint will implement the following notifications:

  • service.update

Other end user impact

None.

Performance Impact

When enabled, code to send the notification will be called each time an event occurs that triggers a notification. This shouldn’t be much of a problem for Watcher itself, but the load on whatever message bus is used should be considered.

Other deployer impact

In order for the notifications to be emitted, the deployer will have to configure the notification topics using oslo.messaging. Other configuration options exposed via oslo.messaging may also be tuned.

Developer impact

Developers should add here proper versioning guidelines and use the notification base classes when creating/updating notifications.

Implementation

Assignee(s)

Primary assignee:

Vladimir Ostroverkhov <ostroverkhov@servionica.ru>

Work Items

  • Implement service.update

Testing

These notifications will have to be tested mainly via unit testing.

Documentation Impact

A notification sample should be provided and made dynamically available in the online documentation.

The sequence diagrams in the Watcher architecture.

References

None.