Support post event API

Change-Id: I985774ee17fa9c6c7b12e8daff5ec1481ebdfa7b
Implements: blueprint support-inspector-sb-api
This commit is contained in:
Ifat Afek 2017-01-18 17:47:42 +00:00
parent c771ad1a1b
commit 329fe51977
14 changed files with 262 additions and 31 deletions

View File

@ -24,7 +24,7 @@ VITRAGE_USE_MOD_WSGI=${VITRAGE_USE_MOD_WSGI:-${ENABLE_HTTPD_MOD_WSGI_SERVICES}}
# Toggle for deploying Vitrage with/without nagios
VITRAGE_USE_NAGIOS=$(trueorfalse False VITRAGE_USE_NAGIOS)
VITRAGE_DEFAULT_DATASOURCES=${VITRAGE_DEFAULT_DATASOURCES:-nova.host,nova.instance,nova.zone,nagios,static_physical,aodh,cinder.volume,neutron.network,neutron.port,heat.stack}
VITRAGE_DEFAULT_DATASOURCES=${VITRAGE_DEFAULT_DATASOURCES:-nova.host,nova.instance,nova.zone,nagios,static_physical,aodh,cinder.volume,neutron.network,neutron.port,heat.stack,doctor}
# Tell Tempest this project is present

View File

@ -9,5 +9,6 @@
"get rca:all_tenants": "role:admin",
"template validate": "",
"template list": "",
"template show": ""
"template show": "",
"event post": ""
}

View File

@ -0,0 +1,53 @@
# Copyright 2017 - Nokia Corporation
#
# 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 pecan
from oslo_log import log
from pecan.core import abort
from vitrage.api.controllers.rest import RootRestController
from vitrage.api.policy import enforce
from vitrage.i18n import _LI
LOG = log.getLogger(__name__)
class EventController(RootRestController):
@pecan.expose('json')
def post(self, **kwargs):
LOG.info(_LI('Post event called with args: %s') % kwargs)
enforce("event post", pecan.request.headers,
pecan.request.enforcer, {})
event_time = kwargs['time']
event_type = kwargs['type']
details = kwargs['details']
self.post_event(event_time, event_type, details)
@staticmethod
def post_event(event_time, event_type, details):
try:
pecan.request.client.call(pecan.request.context,
'post',
event_time=event_time,
event_type=event_type,
details=details)
except Exception as e:
LOG.exception('Failed to post an event', e)
abort(404, str(e))

View File

@ -11,6 +11,7 @@
# under the License.
from vitrage.api.controllers.v1 import alarm
from vitrage.api.controllers.v1 import event
from vitrage.api.controllers.v1 import rca
from vitrage.api.controllers.v1 import resource
from vitrage.api.controllers.v1 import template
@ -23,3 +24,4 @@ class V1Controller(object):
alarm = alarm.AlarmsController()
rca = rca.RCAController()
template = template.TemplateController()
event = event.EventController()

View File

@ -0,0 +1,62 @@
# Copyright 2017 - Nokia Corporation
#
# 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 datetime import datetime
import json
from oslo_log import log
import oslo_messaging
from oslo_utils import uuidutils
import socket
from vitrage.api_handler.apis.base import EntityGraphApisBase
from vitrage.common.constants import EventProperties
from vitrage.messaging import get_transport
LOG = log.getLogger(__name__)
class EventApis(EntityGraphApisBase):
def __init__(self, conf):
self.conf = conf
self._init_oslo_notifier()
def post(self, ctx, event_time, event_type, details):
try:
event = {EventProperties.TYPE: event_type,
EventProperties.TIME: event_time,
EventProperties.DETAILS: json.loads(details)}
self.oslo_notifier.info(
ctxt={'message_id': uuidutils.generate_uuid(),
'publisher_id': self.publisher,
'timestamp': datetime.utcnow()},
event_type=event_type,
payload=event)
except Exception as e:
LOG.warn('Failed to post event %s. Exception: %s',
event_type, e)
def _init_oslo_notifier(self):
self.oslo_notifier = None
try:
self.publisher = 'api_%s' % socket.gethostname()
self.oslo_notifier = oslo_messaging.Notifier(
get_transport(self.conf),
driver='messagingv2',
publisher_id=self.publisher,
topic='vitrage_notifications')
except Exception as e:
LOG.info('Failed to initialize oslo notifier %s', str(e))

View File

@ -18,6 +18,7 @@ import oslo_messaging
from oslo_service import service as os_service
from vitrage.api_handler.apis.alarm import AlarmApis
from vitrage.api_handler.apis.event import EventApis
from vitrage.api_handler.apis.rca import RcaApis
from vitrage.api_handler.apis.template import TemplateApis
from vitrage.api_handler.apis.topology import TopologyApis
@ -50,7 +51,8 @@ class VitrageApiHandlerService(os_service.Service):
endpoints = [TopologyApis(self.entity_graph, self.conf),
AlarmApis(self.entity_graph, self.conf),
RcaApis(self.entity_graph, self.conf),
TemplateApis(self.scenario_repo.templates)]
TemplateApis(self.scenario_repo.templates),
EventApis(self.conf)]
server = vitrage_rpc.get_server(target, endpoints, transport)

View File

@ -129,3 +129,10 @@ class TopologyFields(object):
RELATIONSHIP_TYPE = 'relationship_type'
SOURCE = 'source'
TARGET = 'target'
class EventProperties(object):
TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%f'
TYPE = 'type'
TIME = 'time'
DETAILS = 'details'

View File

@ -17,6 +17,7 @@ from oslo_log import log
from vitrage.common.constants import DatasourceAction
from vitrage.common.constants import DatasourceProperties as DSProps
from vitrage.common.constants import EventProperties as EventProps
from vitrage.datasources.alarm_driver_base import AlarmDriverBase
from vitrage.datasources.doctor import DOCTOR_DATASOURCE
from vitrage.datasources.doctor.properties import DoctorDetails
@ -40,7 +41,7 @@ class DoctorDriver(AlarmDriverBase):
return DOCTOR_DATASOURCE
def _alarm_key(self, alarm):
return self.AlarmKey(alarm_name=alarm[DoctorProps.TYPE],
return self.AlarmKey(alarm_name=alarm[EventProps.TYPE],
hostname=get_detail(alarm,
DoctorDetails.HOSTNAME))
@ -49,12 +50,12 @@ class DoctorDriver(AlarmDriverBase):
get_detail(alarm, DoctorDetails.STATUS) != DoctorStatus.UP
def _is_valid(self, alarm):
if not alarm or DoctorProps.TIME not in alarm or \
DoctorProps.TYPE not in alarm or \
DoctorProps.DETAILS not in alarm:
if not alarm or EventProps.TIME not in alarm or \
EventProps.TYPE not in alarm or \
EventProps.DETAILS not in alarm:
return False
details = alarm[DoctorProps.DETAILS]
details = alarm[EventProps.DETAILS]
return DoctorDetails.STATUS in details and \
DoctorDetails.SEVERITY in details and \
DoctorDetails.HOSTNAME in details
@ -91,17 +92,21 @@ class DoctorDriver(AlarmDriverBase):
"""
LOG.debug('Going to enrich event: %s', str(event))
event[DSProps.EVENT_TYPE] = event_type
old_alarm = self._old_alarm(event)
if old_alarm and not self._status_changed(old_alarm, event):
event[DoctorProps.UPDATE_TIME] = old_alarm[DoctorProps.UPDATE_TIME]
else:
event[DoctorProps.UPDATE_TIME] = event[DoctorProps.TIME]
event[DoctorProps.UPDATE_TIME] = event[EventProps.TIME]
event = self._filter_and_cache_alarm(event, old_alarm,
self._filter_get_erroneous,
event[DoctorProps.TIME])
event[EventProps.TIME])
LOG.debug('Enriched event: %s', str(event))
if event:
return DoctorDriver.make_pickleable([event], DOCTOR_DATASOURCE,

View File

@ -12,15 +12,13 @@
# License for the specific language governing permissions and limitations
# under the License.
from vitrage.common.constants import EventProperties as EventProps
class DoctorProperties(object):
TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%f'
HOST_DOWN = 'compute.host.down'
HOST_TYPE = 'nova.host'
TYPE = 'type'
TIME = 'time'
UPDATE_TIME = 'update_time'
DETAILS = 'details'
class DoctorDetails(object):
@ -36,7 +34,7 @@ class DoctorStatus(object):
def get_detail(alarm, detail):
return alarm[DoctorProperties.DETAILS][detail] if \
alarm and DoctorProperties.DETAILS in alarm and \
detail in alarm[DoctorProperties.DETAILS] \
return alarm[EventProps.DETAILS][detail] if \
alarm and EventProps.DETAILS in alarm and \
detail in alarm[EventProps.DETAILS] \
else None

View File

@ -17,6 +17,7 @@ from oslo_log import log as logging
from vitrage.common.constants import DatasourceProperties as DSProps
from vitrage.common.constants import EdgeLabel
from vitrage.common.constants import EntityCategory
from vitrage.common.constants import EventProperties as EventProps
from vitrage.common.constants import VertexProperties as VProps
from vitrage.datasources.alarm_transformer_base import AlarmTransformerBase
from vitrage.datasources.doctor import DOCTOR_DATASOURCE
@ -44,9 +45,9 @@ class DoctorTransformer(AlarmTransformerBase):
def _create_update_entity_vertex(self, entity_event):
self._unify_time_format(entity_event)
details = entity_event.get(DoctorProps.DETAILS, {})
details[VProps.NAME] = entity_event[DoctorProps.TYPE]
details[DoctorProps.TIME] = entity_event[DoctorProps.TIME]
details = entity_event.get(EventProps.DETAILS, {})
details[VProps.NAME] = entity_event[EventProps.TYPE]
details[EventProps.TIME] = entity_event[EventProps.TIME]
return graph_utils.create_vertex(
self._create_entity_key(entity_event),
@ -69,7 +70,7 @@ class DoctorTransformer(AlarmTransformerBase):
return tbase.build_key((
EntityCategory.ALARM,
entity_event[DSProps.ENTITY_TYPE],
entity_event[DoctorProps.TYPE],
entity_event[EventProps.TYPE],
get_detail(entity_event, DoctorDetails.HOSTNAME)))
def get_type(self):
@ -89,7 +90,7 @@ class DoctorTransformer(AlarmTransformerBase):
@staticmethod
def _unify_time_format(entity_event):
DoctorTransformer._unify_prop_time_format(entity_event,
DoctorProps.TIME)
EventProps.TIME)
DoctorTransformer._unify_prop_time_format(entity_event,
DoctorProps.UPDATE_TIME)
@ -97,5 +98,5 @@ class DoctorTransformer(AlarmTransformerBase):
def _unify_prop_time_format(entity_event, prop):
entity_event[prop] = change_time_str_format(
entity_event[prop],
DoctorProps.TIME_FORMAT,
EventProps.TIME_FORMAT,
tbase.TIMESTAMP_FORMAT)

View File

@ -16,6 +16,7 @@ from datetime import datetime
from oslo_config import cfg
from vitrage.common.constants import DatasourceProperties as DSProps
from vitrage.common.constants import EventProperties as EventProps
from vitrage.datasources.doctor.driver import DoctorDriver
from vitrage.datasources.doctor.properties import DoctorDetails
from vitrage.datasources.doctor.properties import DoctorProperties \
@ -107,9 +108,9 @@ class DoctorDriverTest(base.BaseTest):
if status:
details[DoctorDetails.STATUS] = status
update_vals = {DoctorProps.DETAILS: details}
update_vals = {EventProps.DETAILS: details}
if time:
update_vals[DoctorProps.TIME] = time
update_vals[EventProps.TIME] = time
generators = mock_driver.simple_doctor_alarm_generators(
update_vals=update_vals)
@ -125,9 +126,9 @@ class DoctorDriverTest(base.BaseTest):
expected_update_date):
self.assertIsNotNone(event, 'No event returned')
self.assertEqual(expected_hostname,
event[DoctorProps.DETAILS][DoctorDetails.HOSTNAME])
event[EventProps.DETAILS][DoctorDetails.HOSTNAME])
self.assertEqual(expected_status,
event[DoctorProps.DETAILS][DoctorDetails.STATUS])
self.assertEqual(expected_sample_date, event[DoctorProps.TIME])
event[EventProps.DETAILS][DoctorDetails.STATUS])
self.assertEqual(expected_sample_date, event[EventProps.TIME])
self.assertEqual(expected_update_date, event[DoctorProps.UPDATE_TIME])
self.assertEqual(expected_event_type, event[DSProps.EVENT_TYPE])

View File

@ -17,6 +17,7 @@ from oslo_config import cfg
from oslo_log import log as logging
from vitrage.common.constants import DatasourceProperties as DSProps
from vitrage.common.constants import EventProperties as EventProps
from vitrage.common.constants import UpdateMethod
from vitrage.datasources.doctor import DOCTOR_DATASOURCE
from vitrage.datasources.doctor.properties import DoctorDetails
@ -94,7 +95,7 @@ class DoctorTransformerTest(BaseAlarmTransformerTest):
def _validate_vertex_props(self, vertex, event):
self._validate_alarm_vertex_props(vertex,
event[DoctorProps.TYPE],
event[EventProps.TYPE],
DOCTOR_DATASOURCE,
event[DSProps.SAMPLE_DATE])
@ -106,9 +107,9 @@ class DoctorTransformerTest(BaseAlarmTransformerTest):
if status:
details[DoctorDetails.STATUS] = status
update_vals = {DoctorProps.DETAILS: details}
update_vals = {EventProps.DETAILS: details}
if time:
update_vals[DoctorProps.TIME] = time
update_vals[EventProps.TIME] = time
update_vals[DoctorProps.UPDATE_TIME] = time
generators = mock_transformer.simple_doctor_alarm_generators(

View File

@ -0,0 +1,15 @@
# Copyright 2017 - Nokia
#
# 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.
__author__ = 'stack'

View File

@ -0,0 +1,83 @@
# Copyright 2017 Nokia
#
# 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 datetime import datetime
from oslo_log import log as logging
from vitrage.common.constants import DatasourceProperties as DSProps
from vitrage.common.constants import EntityCategory
from vitrage.common.constants import EventProperties as EventProps
from vitrage.common.constants import VertexProperties as VProps
from vitrage_tempest_tests.tests.api.base import BaseApiTest
LOG = logging.getLogger(__name__)
class TestEvents(BaseApiTest):
"""Test class for Vitrage event API"""
@classmethod
def setUpClass(cls):
super(TestEvents, cls).setUpClass()
def test_send_doctor_event(self):
"""Sending an event in Doctor format should result in an alarm"""
try:
# post an event to the message bus
event_time = datetime.now().isoformat()
event_type = 'compute.host.down'
details = {
'hostname': 'host123',
'source': 'sample_monitor',
'cause': 'another alarm',
'severity': 'critical',
'status': 'down',
'monitor_id': 'sample monitor',
'monitor_event_id': '456',
}
self.vitrage_client.event.post(event_time, event_type, details)
# list all alarms
api_alarms = self.vitrage_client.alarms.list(vitrage_id=None)
# expect to get a 'host down alarm', generated by Doctor datasource
self.assertIsNotNone(api_alarms, 'Expected host down alarm')
self.assertEqual(1, len(api_alarms), 'Expected host down alarm')
alarm = api_alarms[0]
self._wait_for_status(2,
self._check_alarm,
alarm=alarm,
event_time=event_time,
event_type=event_type,
details=details)
except Exception as e:
LOG.exception(e)
raise
finally:
# do what?
LOG.warning('done')
def _check_alarm(self, alarm, event_time, event_type, details):
LOG.info('alarm = %s', str(alarm))
self.assertEqual(EntityCategory.ALARM, alarm[VProps.CATEGORY])
self.assertEqual(event_type, alarm[VProps.NAME])
self.assertEqual(event_time, alarm[EventProps.TIME])
self.assertEqual(event_type, alarm[DSProps.ENTITY_TYPE])
self.assertEqual(details['status'], alarm[VProps.STATE])
self.assertFalse(alarm[VProps.IS_DELETED])
self.assertFalse(alarm[VProps.IS_PLACEHOLDER])