Support post event API
Change-Id: I985774ee17fa9c6c7b12e8daff5ec1481ebdfa7b Implements: blueprint support-inspector-sb-api
This commit is contained in:
parent
c771ad1a1b
commit
329fe51977
@ -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
|
||||
|
@ -9,5 +9,6 @@
|
||||
"get rca:all_tenants": "role:admin",
|
||||
"template validate": "",
|
||||
"template list": "",
|
||||
"template show": ""
|
||||
"template show": "",
|
||||
"event post": ""
|
||||
}
|
53
vitrage/api/controllers/v1/event.py
Normal file
53
vitrage/api/controllers/v1/event.py
Normal 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))
|
@ -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()
|
||||
|
62
vitrage/api_handler/apis/event.py
Normal file
62
vitrage/api_handler/apis/event.py
Normal 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))
|
@ -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)
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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])
|
||||
|
@ -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(
|
||||
|
15
vitrage_tempest_tests/tests/api/event/__init__.py
Normal file
15
vitrage_tempest_tests/tests/api/event/__init__.py
Normal 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'
|
83
vitrage_tempest_tests/tests/api/event/test_events.py
Normal file
83
vitrage_tempest_tests/tests/api/event/test_events.py
Normal 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])
|
Loading…
Reference in New Issue
Block a user