Prometheus datasource

The datasource currently supports notifications from
Prometheus.

There are a few related tasks that will be handled in
future commits:

1. Prometheus may send the ip of the instance instead
   of its name. We need to find a way (in the
   transformer)to identify the correct instance in
   the graph.

2. The 'vitrage event post' API should be changed so
   it can handle events from Prometheus.

3. Need to implement get_all in the datasource

Change-Id: Id1cba3e060b70c232891a34bf6793c185a074a7b
Depends-On: Ide6906ee477aa7df9ab0918d3b45a7001afdcf74
Implements: blueprint prometheus-datasource
This commit is contained in:
Ifat Afek 2018-05-03 14:44:07 +00:00
parent feb76c1eb1
commit 2382591f9f
12 changed files with 640 additions and 50 deletions

View File

@ -0,0 +1,39 @@
# Copyright 2018 - 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 oslo_config import cfg
from vitrage.common.constants import DatasourceOpts as DSOpts
from vitrage.common.constants import UpdateMethod
PROMETHEUS_DATASOURCE = 'prometheus'
OPTS = [
cfg.StrOpt(DSOpts.TRANSFORMER,
default='vitrage.datasources.prometheus.transformer.'
'PrometheusTransformer',
help='Prometheus transformer class path',
required=True),
cfg.StrOpt(DSOpts.DRIVER,
default='vitrage.datasources.prometheus.driver.'
'PrometheusDriver',
help='Prometheus driver class path',
required=True),
cfg.StrOpt(DSOpts.UPDATE_METHOD,
default=UpdateMethod.PUSH,
help='None: updates only via Vitrage periodic snapshots.'
'Pull: updates every [changes_interval] seconds.'
'Push: updates by getting notifications from the'
' datasource itself.',
required=True),
]

View File

@ -0,0 +1,151 @@
# Copyright 2018 - 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 collections import namedtuple
from oslo_log import log
from vitrage.common.constants import DatasourceAction
from vitrage.common.constants import DatasourceProperties as DSProps
from vitrage.datasources.alarm_driver_base import AlarmDriverBase
from vitrage.datasources.prometheus import PROMETHEUS_DATASOURCE
from vitrage.datasources.prometheus.properties import get_alarm_update_time
from vitrage.datasources.prometheus.properties import get_label
from vitrage.datasources.prometheus.properties import PrometheusAlertStatus \
as PAlertStatus
from vitrage.datasources.prometheus.properties import PrometheusLabels \
as PLabels
from vitrage.datasources.prometheus.properties import PrometheusProperties \
as PProps
LOG = log.getLogger(__name__)
PROMETHEUS_EVENT_TYPE = 'prometheus.alarm'
class PrometheusDriver(AlarmDriverBase):
AlarmKey = namedtuple('AlarmKey', ['alert_name', 'instance'])
def __init__(self, conf):
super(PrometheusDriver, self).__init__()
self.conf = conf
self._client = None
def _vitrage_type(self):
return PROMETHEUS_DATASOURCE
def _alarm_key(self, alarm):
return self.AlarmKey(alert_name=get_label(alarm, PLabels.ALERT_NAME),
instance=str(get_label(alarm, PLabels.INSTANCE)))
def _is_erroneous(self, alarm):
return alarm and PAlertStatus.FIRING == alarm.get(PProps.STATUS)
def _is_valid(self, alarm):
if not alarm or PProps.STATUS not in alarm:
return False
return True
def _status_changed(self, new_alarm, old_alarm):
return new_alarm.get(PProps.STATUS) != old_alarm.get(PProps.STATUS)
def _get_alarms(self):
# TODO(iafek): should be implemented
return []
def enrich_event(self, event, event_type):
"""Get an event from Prometheus and create a list of alarm events
:param event: dictionary of this form:
{
"status": "firing",
"groupLabels": {
"alertname": "HighInodeUsage"
},
"groupKey": "{}:{alertname=\"HighInodeUsage\"}",
"commonAnnotations": {
"mount_point": "/%",
"description": "\"Consider ssh\"ing into the instance \"\n",
"title": "High number of inode usage",
"value": "96.81%",
"device": "/dev/vda1%",
"runbook": "troubleshooting/filesystem_alerts_inodes.md"
},
"alerts": [
{
"status": "firing",
"labels": {
"severity": "critical",
"fstype": "ext4",
"instance": "localhost:9100",
"job": "node",
"alertname": "HighInodeUsage",
"device": "/dev/vda1",
"mountpoint": "/"
},
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "http://devstack-rocky-4:9090/graph?g0.htm1",
"startsAt": "2018-05-03T12:25:38.231388525Z",
"annotations": {
"mount_point": "/%",
"description": "\"Consider ssh\"ing into the instance\"\n",
"title": "High number of inode usage",
"value": "96.81%",
"device": "/dev/vda1%",
"runbook": "troubleshooting/filesystem_alerts_inodes.md"
}
}
],
"version": "4",
"receiver": "vitrage",
"externalURL": "http://devstack-rocky-4:9093",
"commonLabels": {
"severity": "critical",
"fstype": "ext4",
"instance": "localhost:9100",
"job": "node",
"alertname": "HighInodeUsage",
"device": "/dev/vda1",
"mountpoint": "/"
}
}
:param event_type: The type of the event. Always 'prometheus.alarm'.
:return: a list of events, one per Prometheus alert
"""
LOG.debug('Going to enrich event: %s', str(event))
alarms = []
for alarm in event.get(PProps.ALERTS, []):
alarm[DSProps.EVENT_TYPE] = event_type
alarm[PProps.STATUS] = event[PProps.STATUS]
old_alarm = self._old_alarm(alarm)
alarm = self._filter_and_cache_alarm(alarm, old_alarm,
self._filter_get_erroneous,
get_alarm_update_time(alarm))
if alarm:
alarms.append(alarm)
LOG.debug('Enriched event. Created alarm events: %s', str(alarms))
return self.make_pickleable(alarms, PROMETHEUS_DATASOURCE,
DatasourceAction.UPDATE)
@staticmethod
def get_event_types():
return [PROMETHEUS_EVENT_TYPE]

View File

@ -0,0 +1,56 @@
# Copyright 2018 - 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.
class PrometheusProperties(object):
STATUS = 'status'
ALERTS = 'alerts'
ANNOTATIONS = 'annotations'
LABELS = 'labels'
class PrometheusAlertStatus(object):
FIRING = 'firing'
RESOLVED = 'resolved'
class PrometheusAlertProperties(object):
STARTS_AT = 'startsAt'
ENDS_AT = 'endsAt'
class PrometheusAnnotations(object):
TITLE = 'title' # A human friendly name of the alert
class PrometheusLabels(object):
SEVERITY = 'severity'
INSTANCE = 'instance'
ALERT_NAME = 'alertname' # A (unique?) name of the alert
def get_alarm_update_time(alarm):
return alarm.get(PrometheusAlertProperties.ENDS_AT) if \
PrometheusAlertProperties.ENDS_AT in alarm else \
alarm.get(PrometheusAlertProperties.STARTS_AT)
def get_annotation(alarm, annotation):
annotations = alarm.get(PrometheusProperties.ANNOTATIONS)
return annotations.get(annotation) if annotations else None
def get_label(alarm, label):
labels = alarm.get(PrometheusProperties.LABELS)
return labels.get(label) if labels else None

View File

@ -0,0 +1,93 @@
# Copyright 2018 - 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 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 as ECategory
from vitrage.common.constants import VertexProperties as VProps
from vitrage.datasources.alarm_transformer_base import AlarmTransformerBase
from vitrage.datasources.prometheus import PROMETHEUS_DATASOURCE
from vitrage.datasources.prometheus.properties import get_alarm_update_time
from vitrage.datasources.prometheus.properties import get_label
from vitrage.datasources.prometheus.properties import PrometheusAlertStatus \
as PAlertStatus
from vitrage.datasources.prometheus.properties import PrometheusLabels \
as PLabels
from vitrage.datasources.prometheus.properties import PrometheusProperties \
as PProps
from vitrage.datasources import transformer_base as tbase
import vitrage.graph.utils as graph_utils
LOG = logging.getLogger(__name__)
class PrometheusTransformer(AlarmTransformerBase):
def __init__(self, transformers, conf):
super(PrometheusTransformer, self).__init__(transformers, conf)
def _create_snapshot_entity_vertex(self, entity_event):
# TODO(iafek): should be implemented
return None
def _create_update_entity_vertex(self, entity_event):
metadata = {
VProps.NAME: get_label(entity_event, PLabels.ALERT_NAME),
VProps.SEVERITY: get_label(entity_event, PLabels.SEVERITY),
PProps.STATUS: entity_event.get(PProps.STATUS),
}
return graph_utils.create_vertex(
self._create_entity_key(entity_event),
vitrage_category=ECategory.ALARM,
vitrage_type=entity_event[DSProps.ENTITY_TYPE],
vitrage_sample_timestamp=entity_event[DSProps.SAMPLE_DATE],
entity_state=self._get_alarm_state(entity_event),
update_timestamp=get_alarm_update_time(entity_event),
metadata=metadata
)
def _create_update_neighbors(self, entity_event):
graph_neighbors = entity_event.get(self.QUERY_RESULT, [])
return [self._create_neighbor(entity_event,
graph_neighbor[VProps.ID],
graph_neighbor[VProps.VITRAGE_TYPE],
EdgeLabel.ON,
neighbor_category=ECategory.RESOURCE)
for graph_neighbor in graph_neighbors]
def _create_entity_key(self, entity_event):
return tbase.build_key((ECategory.ALARM,
entity_event[DSProps.ENTITY_TYPE],
get_label(entity_event, PLabels.ALERT_NAME),
get_label(entity_event, PLabels.INSTANCE)))
def get_vitrage_type(self):
return PROMETHEUS_DATASOURCE
def _ok_status(self, entity_event):
return entity_event and \
PAlertStatus.RESOLVED == entity_event.get(PProps.STATUS)
@staticmethod
def get_enrich_query(event):
LOG.debug('event for enrich query: %s', str(event))
hostname = get_label(event, PLabels.INSTANCE)
if not hostname:
return None
hostname = hostname[:hostname.index(':')]
return {VProps.ID: hostname}

View File

@ -571,6 +571,27 @@ def simple_aodh_alarm_notification_generators(alarm_num,
return tg.get_trace_generators(test_entity_spec_list)
def simple_prometheus_alarm_generators(update_vals=None):
"""A function for returning Prometheus alarm event generators.
Returns generators for a given number of Prometheus alarms.
:param update_vals: preset values for ALL update events
:return: generators for alarms as specified
"""
test_entity_spec_list = [({
tg.DYNAMIC_INFO_FKEY: tg.DRIVER_PROMETHEUS_UPDATE_D,
tg.STATIC_INFO_FKEY: None,
tg.EXTERNAL_INFO_KEY: update_vals,
tg.MAPPING_KEY: None,
tg.NAME_KEY: 'Prometheus alarm generator',
tg.NUM_EVENTS: 1
})]
return tg.get_trace_generators(test_entity_spec_list)
def simple_k8s_nodes_generators(nodes_num, snapshot_events=0):
mapping = ['vm-{0}'.format(index) for index in range(nodes_num)]

View File

@ -198,18 +198,8 @@ def simple_doctor_alarm_generators(update_vals=None):
:param update_vals: preset values for ALL update events
:return: generators for alarms as specified
"""
test_entity_spec_list = [({
tg.DYNAMIC_INFO_FKEY: tg.TRANS_DOCTOR_UPDATE_D,
tg.DYNAMIC_INFO_FPATH: tg.MOCK_TRANSFORMER_PATH,
tg.STATIC_INFO_FKEY: None,
tg.EXTERNAL_INFO_KEY: update_vals,
tg.MAPPING_KEY: None,
tg.NAME_KEY: 'Doctor alarm generator',
tg.NUM_EVENTS: 1
})]
return tg.get_trace_generators(test_entity_spec_list)
return _simple_alarm_generators('Doctor',
tg.TRANS_DOCTOR_UPDATE_D, update_vals)
def simple_collectd_alarm_generators(update_vals=None):
@ -217,17 +207,41 @@ def simple_collectd_alarm_generators(update_vals=None):
Returns generators for a given number of Collectd alarms.
:param update_vals: preset values for ALL update events
:return: generators for alarms as specified
"""
return _simple_alarm_generators('Collectd',
tg.TRANS_COLLECTD_UPDATE_D, update_vals)
def simple_prometheus_alarm_generators(update_vals=None):
"""A function for returning Prometheus alarm event generators.
Returns generators for a given number of Prometheus alarms.
:param update_vals: preset values for ALL update events
:return: generators for alarms as specified
"""
return _simple_alarm_generators('Prometheus',
tg.TRANS_PROMETHEUS_UPDATE_D, update_vals)
def _simple_alarm_generators(datasource, sample_file, update_vals):
"""A function for returning alarm event generators.
Returns generators for a given number of alarms.
:param update_vals: preset values for ALL update events
:return: generators for alarms as specified
"""
test_entity_spec_list = [({
tg.DYNAMIC_INFO_FKEY: tg.TRANS_COLLECTD_UPDATE_D,
tg.DYNAMIC_INFO_FKEY: sample_file,
tg.DYNAMIC_INFO_FPATH: tg.MOCK_TRANSFORMER_PATH,
tg.STATIC_INFO_FKEY: None,
tg.EXTERNAL_INFO_KEY: update_vals,
tg.MAPPING_KEY: None,
tg.NAME_KEY: 'Collectd alarm generator',
tg.NAME_KEY: datasource + ' alarm generator',
tg.NUM_EVENTS: 1
})]

View File

@ -56,6 +56,7 @@ DRIVER_INST_SNAPSHOT_S = 'driver_inst_snapshot_static.json'
DRIVER_INST_UPDATE_D = 'driver_inst_update_dynamic.json'
DRIVER_NAGIOS_SNAPSHOT_D = 'driver_nagios_snapshot_dynamic.json'
DRIVER_NAGIOS_SNAPSHOT_S = 'driver_nagios_snapshot_static.json'
DRIVER_PROMETHEUS_UPDATE_D = 'driver_prometheus_update_dynamic.json'
DRIVER_ZABBIX_SNAPSHOT_D = 'driver_zabbix_snapshot_dynamic.json'
DRIVER_SWITCH_SNAPSHOT_D = 'driver_switch_snapshot_dynamic.json'
DRIVER_STATIC_SNAPSHOT_D = 'driver_static_snapshot_dynamic.json'
@ -76,6 +77,7 @@ TRANS_AODH_SNAPSHOT_D = 'transformer_aodh_snapshot_dynamic.json'
TRANS_AODH_UPDATE_D = 'transformer_aodh_update_dynamic.json'
TRANS_DOCTOR_UPDATE_D = 'transformer_doctor_update_dynamic.json'
TRANS_COLLECTD_UPDATE_D = 'transformer_collectd_update_dynamic.json'
TRANS_PROMETHEUS_UPDATE_D = 'transformer_prometheus_update_dynamic.json'
TRANS_INST_SNAPSHOT_D = 'transformer_inst_snapshot_dynamic.json'
TRANS_INST_SNAPSHOT_S = 'transformer_inst_snapshot_static.json'
TRANS_HOST_SNAPSHOT_D = 'transformer_host_snapshot_dynamic.json'
@ -118,8 +120,8 @@ class EventTraceGenerator(object):
static_info_parsers = \
{DRIVER_AODH_UPDATE_D: _get_aodh_alarm_update_driver_values,
DRIVER_DOCTOR_UPDATE_D: _get_doctor_update_driver_values,
DRIVER_COLLECTD_UPDATE_D: _get_collectd_update_driver_values,
DRIVER_DOCTOR_UPDATE_D: _get_simple_update_driver_values,
DRIVER_COLLECTD_UPDATE_D: _get_simple_update_driver_values,
DRIVER_KUBE_SNAPSHOT_D: _get_k8s_node_snapshot_driver_values,
DRIVER_INST_SNAPSHOT_D: _get_vm_snapshot_driver_values,
DRIVER_INST_UPDATE_D: _get_vm_update_driver_values,
@ -135,11 +137,13 @@ class EventTraceGenerator(object):
DRIVER_ZABBIX_SNAPSHOT_D: _get_zabbix_alarm_driver_values,
DRIVER_CONSISTENCY_UPDATE_D:
_get_consistency_update_driver_values,
DRIVER_PROMETHEUS_UPDATE_D: _get_simple_update_driver_values,
TRANS_AODH_SNAPSHOT_D: _get_trans_aodh_alarm_snapshot_values,
TRANS_AODH_UPDATE_D: _get_trans_aodh_alarm_snapshot_values,
TRANS_DOCTOR_UPDATE_D: _get_trans_doctor_alarm_update_values,
TRANS_COLLECTD_UPDATE_D: _get_trans_collectd_alarm_update_values,
TRANS_DOCTOR_UPDATE_D: _get_simple_trans_alarm_update_values,
TRANS_COLLECTD_UPDATE_D: _get_simple_trans_alarm_update_values,
TRANS_PROMETHEUS_UPDATE_D: _get_simple_trans_alarm_update_values,
TRANS_INST_SNAPSHOT_D: _get_trans_vm_snapshot_values,
TRANS_HOST_SNAPSHOT_D: _get_trans_host_snapshot_values,
TRANS_ZONE_SNAPSHOT_D: _get_trans_zone_snapshot_values}
@ -277,7 +281,7 @@ def _get_k8s_node_snapshot_driver_values(spec):
return static_values
def _get_doctor_update_driver_values(spec):
def _get_simple_update_driver_values(spec):
"""Generates the static driver values for Doctor monitor notification.
:param spec: specification of event generation.
@ -288,17 +292,6 @@ def _get_doctor_update_driver_values(spec):
return [combine_data(None, None, spec.get(EXTERNAL_INFO_KEY, None))]
def _get_collectd_update_driver_values(spec):
"""Generates the static driver values for Collectd monitor notification.
:param spec: specification of event generation.
:type spec: dict
:return: list of notifications of Doctor monitor
:rtype: list
"""
return [combine_data(None, None, spec.get(EXTERNAL_INFO_KEY, None))]
def _get_zone_snapshot_driver_values(spec):
"""Generates the static driver values for each zone.
@ -768,12 +761,12 @@ def _get_aodh_alarm_update_driver_values(spec):
return static_values
def _get_trans_doctor_alarm_update_values(spec):
"""Generates the dynamic transformer values for a Doctor alarm
def _get_simple_trans_alarm_update_values(spec):
"""Generates the dynamic transformer values for a simple alarm
:param spec: specification of event generation.
:type spec: dict
:return: list of dynamic transformer values for a Doctor alarm
:return: list of dynamic transformer values for a simple alarm
:rtype: list with one alarm
"""
@ -785,23 +778,6 @@ def _get_trans_doctor_alarm_update_values(spec):
None, spec.get(EXTERNAL_INFO_KEY, None))]
def _get_trans_collectd_alarm_update_values(spec):
"""Generates the dynamic transformer values for a Collectd alarm
:param spec: specification of event generation.
:type spec: dict
:return: list of dynamic transformer values for a Collectd alarm
:rtype: list with one alarm
"""
static_info = None
if spec[STATIC_INFO_FKEY] is not None:
static_info = utils.load_specs(spec[STATIC_INFO_FKEY])
return [combine_data(static_info,
None, spec.get(EXTERNAL_INFO_KEY, None))]
def combine_data(static_info, mapping_info, external_info):
if external_info:
mapping_info = utils.merge_vals(mapping_info, external_info)

View File

@ -0,0 +1,52 @@
{
"status": "firing",
"groupLabels": {
"alertname": "HighInodeUsage"
},
"groupKey": "{}:{alertname=\"HighInodeUsage\"}",
"commonAnnotations": {
"mount_point": "/%",
"description": "\"Consider ssh\"ing into the instance and removing files or clean\ntemp files\"\n",
"title": "High number of inode usage",
"value": "96.81%",
"device": "/dev/vda1%",
"runbook": "troubleshooting/filesystem_alerts_inodes.md"
},
"alerts": [
{
"status": "firing",
"labels": {
"severity": "critical",
"fstype": "ext4",
"instance": "localhost:9100",
"job": "node",
"alertname": "HighInodeUsage",
"device": "/dev/vda1",
"mountpoint": "/"
},
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "http://devstack-rocky-4:9090/graph?g0.expr=node_filesystem_files_free%7Bfstype%3D~%22%28ext.%7Cxfs%29%22%2Cjob%3D%22node%22%7D+%2F+node_filesystem_files%7Bfstype%3D~%22%28ext.%7Cxfs%29%22%2Cjob%3D%22node%22%7D+%2A+100+%3C%3D+100&g0.tab=1",
"startsAt": "2018-05-03T12:25:38.231388525Z",
"annotations": {
"mount_point": "/%",
"description": "\"Consider ssh\"ing into the instance and removing files or clean\ntemp files\"\n",
"title": "High number of inode usage",
"value": "96.81%",
"device": "/dev/vda1%",
"runbook": "troubleshooting/filesystem_alerts_inodes.md"
}
}
],
"version": "4",
"receiver": "vitrage",
"externalURL": "http://devstack-rocky-4:9093",
"commonLabels": {
"severity": "critical",
"fstype": "ext4",
"instance": "localhost:9100",
"job": "node",
"alertname": "HighInodeUsage",
"device": "/dev/vda1",
"mountpoint": "/"
}
}

View File

@ -0,0 +1,26 @@
{
"vitrage_entity_type" : "prometheus",
"vitrage_datasource_action" : "update",
"vitrage_sample_date": "2018-05-06T06:31:50.094836",
"status": "firing",
"labels": {
"severity": "critical",
"fstype": "ext4",
"instance": "localhost:9100",
"job": "node",
"alertname": "HighInodeUsage",
"device": "/dev/vda1",
"mountpoint": "/"
},
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "http://devstack-rocky-4:9090/graph?g0.expr=node_filesystem_files_free%7Bfstype%3D~%22%28ext.%7Cxfs%29%22%2Cjob%3D%22node%22%7D+%2F+node_filesystem_files%7Bfstype%3D~%22%28ext.%7Cxfs%29%22%2Cjob%3D%22node%22%7D+%2A+100+%3C%3D+100&g0.tab=1",
"startsAt": "2018-05-03T12:25:38.231388525Z",
"annotations": {
"mount_point": "/%",
"description": "\"Consider ssh\"ing into the instance and removing files or clean\ntemp files\"\n",
"title": "High number of inode usage",
"value": "96.81%",
"device": "/dev/vda1%",
"runbook": "troubleshooting/filesystem_alerts_inodes.md"
}
}

View File

@ -0,0 +1,61 @@
# Copyright 2018 - 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 oslo_config import cfg
from testtools import matchers
from vitrage.common.constants import DatasourceProperties as DSProps
from vitrage.datasources.prometheus.driver import PROMETHEUS_EVENT_TYPE
from vitrage.datasources.prometheus.driver import PrometheusDriver
from vitrage.datasources.prometheus import PROMETHEUS_DATASOURCE
from vitrage.tests import base
from vitrage.tests.mocks import mock_driver
# noinspection PyProtectedMember
class PrometheusDriverTest(base.BaseTest):
OPTS = []
# noinspection PyPep8Naming
@classmethod
def setUpClass(cls):
cls.conf = cfg.ConfigOpts()
cls.conf.register_opts(cls.OPTS, group=PROMETHEUS_DATASOURCE)
def test_enrich_event(self):
# Test setup
driver = PrometheusDriver(self.conf)
event = self._generate_event()
# Enrich event
created_events = driver.enrich_event(event, PROMETHEUS_EVENT_TYPE)
# Test assertions
self._assert_event_equal(created_events, PROMETHEUS_EVENT_TYPE)
@staticmethod
def _generate_event():
generators = mock_driver.simple_prometheus_alarm_generators(
update_vals={})
return mock_driver.generate_sequential_events_list(generators)[0]
def _assert_event_equal(self,
created_events,
expected_event_type):
self.assertIsNotNone(created_events, 'No events returned')
self.assertThat(created_events, matchers.HasLength(1),
'Expected one event')
self.assertEqual(expected_event_type,
created_events[0][DSProps.EVENT_TYPE])

View File

@ -0,0 +1,101 @@
# Copyright 2018 - 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 oslo_config import cfg
from vitrage.common.constants import DatasourceOpts as DSOpts
from vitrage.common.constants import DatasourceProperties as DSProps
from vitrage.common.constants import UpdateMethod
from vitrage.common.constants import VertexProperties as VProps
from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE
from vitrage.datasources.nova.host.transformer import HostTransformer
from vitrage.datasources.prometheus import PROMETHEUS_DATASOURCE
from vitrage.datasources.prometheus.properties import get_label
from vitrage.datasources.prometheus.properties import PrometheusAlertStatus \
as PAlertStatus
from vitrage.datasources.prometheus.properties import PrometheusLabels \
as PLabels
from vitrage.datasources.prometheus.properties import PrometheusProperties \
as PProps
from vitrage.datasources.prometheus.transformer import PrometheusTransformer
from vitrage.datasources.transformer_base import TransformerBase
from vitrage.tests.mocks import mock_transformer
from vitrage.tests.unit.datasources.test_alarm_transformer_base import \
BaseAlarmTransformerTest
# noinspection PyProtectedMember
class PrometheusTransformerTest(BaseAlarmTransformerTest):
OPTS = [
cfg.StrOpt(DSOpts.UPDATE_METHOD,
default=UpdateMethod.PUSH),
]
# noinspection PyAttributeOutsideInit,PyPep8Naming
@classmethod
def setUpClass(cls):
cls.transformers = {}
cls.conf = cfg.ConfigOpts()
cls.conf.register_opts(cls.OPTS, group=PROMETHEUS_DATASOURCE)
cls.transformers[NOVA_HOST_DATASOURCE] = \
HostTransformer(cls.transformers, cls.conf)
cls.transformers[PROMETHEUS_DATASOURCE] = \
PrometheusTransformer(cls.transformers, cls.conf)
def test_create_update_entity_vertex(self):
# Test setup
host1 = 'host1'
event = self._generate_event(host1)
self.assertIsNotNone(event)
# Test action
transformer = self.transformers[PROMETHEUS_DATASOURCE]
wrapper = transformer.transform(event)
# Test assertions
self._validate_vertex_props(wrapper.vertex, event)
# Validate the neighbors: only one valid host neighbor
entity_key1 = transformer._create_entity_key(event)
entity_uuid1 = transformer.uuid_from_deprecated_vitrage_id(entity_key1)
self._validate_host_neighbor(wrapper, entity_uuid1, host1)
# Validate the expected action on the graph - update or delete
self._validate_graph_action(wrapper)
def _validate_vertex_props(self, vertex, event):
self._validate_alarm_vertex_props(
vertex, get_label(event, PLabels.ALERT_NAME),
PROMETHEUS_DATASOURCE, event[DSProps.SAMPLE_DATE])
@staticmethod
def _generate_event(hostname):
# fake query result to be used by the transformer for determining
# the neighbor
query_result = [{VProps.VITRAGE_TYPE: NOVA_HOST_DATASOURCE,
VProps.ID: hostname}]
labels = {PLabels.SEVERITY: 'critical',
PLabels.INSTANCE: hostname}
update_vals = {TransformerBase.QUERY_RESULT: query_result,
PProps.LABELS: labels}
generators = mock_transformer.simple_prometheus_alarm_generators(
update_vals=update_vals)
return mock_transformer.generate_random_events_list(generators)[0]
def _is_erroneous(self, vertex):
return vertex[PProps.STATUS] == PAlertStatus.FIRING