Add kapacitor datasource

Enabling Kapacitor alerts and clusters in Vitrage Entity Graph.
Goal is integrate TICK monitor system with vitrage

Change-Id: I275135378a63b70c6859e5dfe36bdef9cb1b9c4b
Story: 2005428
Task: 30461
This commit is contained in:
hoa ngo 2019-04-17 19:38:26 +07:00
parent 1a789c12f0
commit bc54094285
20 changed files with 1080 additions and 0 deletions

View File

@ -28,6 +28,7 @@ Datasources
k8s_datasource
nova-config
prometheus-datasource
kapacitor-datasource
Notifiers
---------

View File

@ -0,0 +1,112 @@
Kapacitor-Vitrage
=================
Kapacitor will send alert to vitrage by using [ exec-handle ], send to message queue topic of vitrage.
https://docs.influxdata.com/kapacitor/v1.5/working/alerts/
Installation
------------
Copy the 'https://github.com/openstack/vitrage/tree/master/vitrage/datasources/kapacitor/auxliary/kapacitor_vitrage.py' script into the Kapacitor servers.
.. code-block:: bash
$ cp kapacitor_vitrage.py /etc/kapacitor/kapacitor_vitrage.py
$ chmod 755 /etc/kapacitor/kapacitor_vitrage.py
Configuration
-------------
1. Define topic , which use for alert publish to. Create file ``forward_to_vitrage.yaml``:
| topic: forward_to_vitrage
| id: forward_to_vitrage
| kind: exec
| options:
| prog: '/usr/bin/python'
| args: ['/etc/kapacitor/kapacitor_vitrage.py','rabbit://<rabbit_user>:<rabbit_pass>@controller']
**Note:** rabbit://<rabbit_user>:<rabbit_pass>@controller is Vitrage message bus url, ``rabbit_user:rabbit_pass`` for devstack rabbitmq is ``stackrabbit/secret``
Run command to define topic
.. code-block:: bash
$ kapacitor define-topic-handler ./forward_to_vitrage.yaml
2. Assign your Task to topic, in Tick script define that alert, add in "alert()" step:
| ...
| alert()
| ...
| .topic('forward_to_vitrage')
In case your Task already in topic and you don't want to add another, you only need to do: append 'exec handler' to TICK script which define it.
| ...
| alert()
| ...
| .exec('/usr/bin/python', '/etc/kapacitor/kapacitor_vitrage.py', 'rabbit://<rabbit_user>:<rabbit_pass>@controller')
Run command define your task:
.. code::
$ kapacitor define <task_name> -tick <tick_script>
Vitrage configuration:
1. Add kapacitor to list of datasources in ``/etc/vitrage/vitrage.conf``
.. code::
[datasources]
types = kapacitor,zabbix,nova.host,nova.instance,nova.zone,static_physical,aodh,cinder.volume,neutron.network,neutron.port,heat.stack
2. Add section to ``/etc/vitrage/vitrage.conf``
.. code::
[kapacitor]
config_file = /etc/vitrage/kapacitor_conf.yaml
3. Create ``/etc/vitrage/kapacitor_conf.yaml`` with this content
.. code ::
kapacitor:
- alert:
host: cloud.compute1 # hostname of host been raised alarm
vitrage_resource:
type: nova.host # resource type of enity vitrage
name: compute-1 # resource name of enity vitrage
- alert:
host: compute-(.*)
vitrage_resource:
type: nova.host
name: ${kapacitor_host}
- alert:
host: (.*)
vitrage_resource:
type: nova.instance
name: ${kapacitor_host}
In example:
alarm on host have hostname `cloud.compute1` will map to resource name `compute-1`,
alarm on host have hostname `compute-99` will map to resource name `compute-99`
Another alarm, like alarm on instance will map with resource type ``nova.instance`` and name equal with hostname of instance
4. Restart vitrage service in devstack/openstack
DONE
----

View File

@ -0,0 +1,18 @@
category: ALARM
values:
- aggregated values:
priority: 40
original values:
- name: critical
operational_value: CRITICAL
- aggregated values:
priority: 20
original values:
- name: warning
operational_value: WARNING
- aggregated values:
priority: 10
original values:
- name: OK
operational_value: OK

View File

@ -0,0 +1,6 @@
---
features:
- A new ``Kapacitor Datasource`` was added, to handle alerts coming
from Kapacitor. Kapacitor is an alarming engine in the TICK Stack.
It is build on an Open Source core, processing metric of host or
instance store in InfluxDB to export alerts.

View File

@ -0,0 +1,42 @@
# Copyright 2019 - Viettel
#
# 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
KAPACITOR_DATASOURCE = 'kapacitor'
OPTS = [
cfg.StrOpt(DSOpts.TRANSFORMER,
default='vitrage.datasources.kapacitor.transformer.'
'KapacitorTransformer',
help='Kapacitor transformer class path',
required=True),
cfg.StrOpt(DSOpts.DRIVER,
default='vitrage.datasources.kapacitor.driver.'
'KapacitorDriver',
help='Kapacitor driver class path',
required=True),
cfg.StrOpt(DSOpts.UPDATE_METHOD,
default=UpdateMethod.PUSH,
help='None: updates only via Vitrage periodic snapshots.'
'Pull: updates periodically.'
'Push: updates by getting notifications from the'
' datasource itself.',
required=True),
cfg.StrOpt(DSOpts.CONFIG_FILE, default='/etc/vitrage/kapacitor_conf.yaml',
help='Kapacitor configuration file')
]

View File

@ -0,0 +1,109 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright 2019 - Viettel
#
# 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 argparse
import json
import logging
from logging.handlers import RotatingFileHandler
from oslo_config import cfg
import oslo_messaging as messaging
from oslo_utils import uuidutils
import socket
import sys
'''
Expected input:
Send To: rabbit://userrabbit:passrabbit@rabbit_host:5672/
EVENT_TYPE: {ALARM.STATUS} || kapacitor.alarm.critical warning info or ok
Alarm:
id: mem high-host=controller
message: mem high
details: {{ .Level }} {{alarm_name}}...
times: 2019-04-10T12:18:00Z
duration: 0
priority: CRITICAL
previousLevel: OK
host: host1
'''
LOG_FILE = '/var/log/kapacitor/kapacitor_vitrage.log'
LOG_MAX_SIZE = 10000000
LOG_FORMAT = '%(asctime)s.%(msecs).03d %(name)s[%(process)d] %(threadName)s %' \
'(levelname)s - %(message)s'
LOG_DATE_FMT = '%Y.%m.%d %H:%M:%S'
KAPACITOR_EVENT_TYPE = 'kapacitor.alarm'
debug = False
def main():
parser = argparse.ArgumentParser()
parser.add_argument('sendto', help='url')
args = parser.parse_args()
data = sys.stdin.readlines()[0]
transport_url = args.sendto
transport = messaging.get_notification_transport(cfg.CONF, transport_url)
driver = 'messagingv2'
publisher = 'kapacitor_%s' % socket.gethostname()
notifier = messaging.Notifier(transport,
driver=driver,
publisher_id=publisher,
topics=['vitrage_notifications'])
alarm = json.loads(data)
host = alarm['data']['series'][0]['tags']['host']
priority = alarm['level'].lower()
alarm.update({'host': host,
'priority': priority})
alarm.pop('data', None)
alarm_status = alarm['level'].lower()
event_type = '%s.%s' % (KAPACITOR_EVENT_TYPE, alarm_status)
logging.info('Send to: %s', transport_url)
logging.info('BODY:\n----\n%s\n', data)
logging.info('PUBLISHER: %s', publisher)
logging.info('EVENT_TYPE: %s', event_type)
logging.info('\nALARM:\n%s', alarm)
notifier.info(ctxt={'message_id': uuidutils.generate_uuid(),
'publisher_id': publisher},
event_type=event_type,
payload=alarm)
logging.info('MESSAGE SENT..')
if __name__ == '__main__':
log = logging.getLogger()
if debug:
log.setLevel(logging.DEBUG)
else:
log.setLevel(logging.INFO)
handler = RotatingFileHandler(filename=LOG_FILE,
maxBytes=LOG_MAX_SIZE,
backupCount=3)
fmt = logging.Formatter(LOG_FORMAT, LOG_DATE_FMT)
handler.setFormatter(fmt)
log.addHandler(handler)
logging.info('***----------Script start-----------***')
try:
main()
except Exception as e:
logging.exception('MESSAGE WAS NOT SENT - %s' % e)

View File

@ -0,0 +1,91 @@
# Copyright 2019 - Viettel
#
# 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 re
from oslo_log import log
from vitrage.common.constants import DatasourceOpts as DSOpts
from vitrage.utils import file as file_utils
LOG = log.getLogger(__name__)
KAPACITOR_HOST = 'kapacitor_host'
KAPACITOR = 'kapacitor'
HOST = 'host'
TYPE = 'type'
NAME = 'name'
ALERT = 'alert'
VITRAGE_RESOURCE = 'vitrage_resource'
class KapacitorConfig(object):
def __init__(self, conf):
try:
kapacitor_config_file = conf.kapacitor[DSOpts.CONFIG_FILE]
kapacitor_config = file_utils.load_yaml_file(kapacitor_config_file)
kapacitor = kapacitor_config[KAPACITOR]
self.mappings = [self._create_mapping(config)
for config in kapacitor]
except Exception:
LOG.exception('Failed in init.')
self.mappings = []
@staticmethod
def _create_mapping(config):
return KapacitorHostMapping(config[ALERT][HOST],
config[VITRAGE_RESOURCE][TYPE],
config[VITRAGE_RESOURCE][NAME])
def get_vitrage_resource(self, kapacitor_host):
"""Get Resource type and name for the given kapacitor host name
Go over the configuration mappings one by one, and return the resource
by the first mapping that applies to kapacitor host name.
:param kapacitor_host: kapacitor host name
:return: Vitrage (resource type, resource name)
"""
for mapping in self.mappings:
mapped_resource = mapping.map(kapacitor_host)
if mapped_resource:
return mapped_resource
return None
class KapacitorHostMapping(object):
KAPACITOR_HOST_NAME = '${' + KAPACITOR_HOST + '}'
def __init__(self, kapacitor_host_regexp, resource_type, resource_name):
self.kapacitor_host_regexp = re.compile(kapacitor_host_regexp)
self.resource_type = resource_type
self.resource_name = resource_name
def map(self, kapacitor_host):
"""Check if the mapping applies to this service
:param kapacitor_host: kapacitor host name
:return: a tuple of (resource type, resource name)
In case kapacitor_host_regexp is ${kapacitor_host},
return kapacitor host name as the resource name
"""
if kapacitor_host and self.kapacitor_host_regexp.match(kapacitor_host):
resource_name = \
kapacitor_host if self.resource_name == self.KAPACITOR_HOST_NAME \
else self.resource_name
return self.resource_type, resource_name
else:
return None

View File

@ -0,0 +1,94 @@
# Copyright 2019 - Viettel
#
# 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.kapacitor.config import KapacitorConfig
from vitrage.datasources.kapacitor import KAPACITOR_DATASOURCE
from vitrage.datasources.kapacitor.properties import KapacitorProperties \
as KProps
from vitrage.datasources.kapacitor.properties import KapacitorState
LOG = log.getLogger(__name__)
class KapacitorDriver(AlarmDriverBase):
ServiceKey = namedtuple('ServiceKey', ['hostname', 'alarmid'])
conf_map = None
def __init__(self, conf):
super(KapacitorDriver, self).__init__()
self.cfg = conf
if not KapacitorDriver.conf_map:
self.conf_map = KapacitorConfig(conf)
self._client = None
@staticmethod
def get_event_types():
return ['kapacitor.alarm.ok',
'kapacitor.alarm.info',
'kapacitor.alarm.warning',
'kapacitor.alarm.critical']
def _vitrage_type(self):
return KAPACITOR_DATASOURCE
def _alarm_key(self, alarm):
return self.ServiceKey(hostname=alarm[KProps.RESOURCE_NAME],
alarmid=alarm[KProps.ID])
def _enrich_alarms(self, alarms):
"""Enrich kapacitor alarm using kapacitor configuration file
Converting Kapacitor host name to Vitrage resource type and name
It is function of get_all for pulling method
Not implement yet
"""
pass
def enrich_event(self, event, event_type):
event[DSProps.EVENT_TYPE] = event_type
kapacitor_host = event[KProps.HOST]
vitrage_resource = self.conf_map.get_vitrage_resource(kapacitor_host)
event[KProps.RESOURCE_TYPE] = \
vitrage_resource[0] if vitrage_resource else None
event[KProps.RESOURCE_NAME] = \
vitrage_resource[1] if vitrage_resource else None
return KapacitorDriver.make_pickleable([event], KAPACITOR_DATASOURCE,
DatasourceAction.UPDATE)[0]
def _is_erroneous(self, alarm):
return alarm and alarm[KProps.PRIORITY] != KapacitorState.OK
def _status_changed(self, new_alarm, old_alarm):
return new_alarm and old_alarm and \
not new_alarm[KProps.PRIORITY] == old_alarm[KProps.PRIORITY]
def _is_valid(self, alarm):
return alarm[KProps.RESOURCE_TYPE] is not None and \
alarm[KProps.RESOURCE_NAME] is not None
def _get_alarms(self):
"""Query all alarm and send to vitrage
Not implement yet
"""
return []

View File

@ -0,0 +1,32 @@
# Copyright 2019 - Viettel
#
# 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 KapacitorProperties(object):
ID = 'id'
RESOURCE_TYPE = 'resource_type'
RESOURCE_NAME = 'resource_name'
DETAILS = 'details'
STATUS = 'status'
HOST = 'host'
PRIORITY = 'priority'
TIME = 'time'
MESSAGE = 'message'
class KapacitorState(object):
OK = 'ok'
INFO = 'info'
WARNING = 'warning'
CRITICAL = 'critical'

View File

@ -0,0 +1,110 @@
# Copyright 2019 - Viettel
#
# 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 EdgeLabel
from vitrage.common.constants import EntityCategory
from vitrage.common.constants import VertexProperties as VProps
from vitrage.datasources.alarm_transformer_base import \
AlarmTransformerBase
from vitrage.datasources.kapacitor import KAPACITOR_DATASOURCE
from vitrage.datasources.kapacitor.properties import KapacitorProperties \
as KProps
from vitrage.datasources.kapacitor.properties import KapacitorState
from vitrage.datasources import transformer_base as tbase
import vitrage.graph.utils as graph_utils
LOG = logging.getLogger(__name__)
class KapacitorTransformer(AlarmTransformerBase):
def __init__(self, transformers, conf):
super(KapacitorTransformer, self).__init__(transformers, conf)
def _create_snapshot_entity_vertex(self, entity_event):
return self._create_vertex(entity_event)
def _create_update_entity_vertex(self, entity_event):
return self._create_vertex(entity_event)
def _create_snapshot_neighbors(self, entity_event):
return self._create_kapacitor_neighbors(entity_event)
def _create_update_neighbors(self, entity_event):
return self._create_kapacitor_neighbors(entity_event)
def _create_entity_key(self, entity_event):
"""the unique key of this entity"""
entity_type = entity_event[DSProps.ENTITY_TYPE]
alarm_id = entity_event[KProps.ID]
resource_name = entity_event[KProps.RESOURCE_NAME]
return tbase.build_key((EntityCategory.ALARM,
entity_type,
resource_name,
alarm_id))
@staticmethod
def get_vitrage_type():
return KAPACITOR_DATASOURCE
def _create_vertex(self, entity_event):
update_timestamp = str(datetime.strptime(
entity_event[KProps.TIME], tbase.TIMESTAMP_FORMAT))
vitrage_sample_timestamp = entity_event[DSProps.SAMPLE_DATE]
metadata = {
VProps.NAME: entity_event[KProps.MESSAGE],
VProps.SEVERITY: entity_event[KProps.PRIORITY],
VProps.RAWTEXT: entity_event[KProps.DETAILS],
VProps.RESOURCE_NAME: entity_event[KProps.RESOURCE_NAME],
}
return graph_utils.create_vertex(
self._create_entity_key(entity_event),
vitrage_category=EntityCategory.ALARM,
vitrage_type=entity_event[DSProps.ENTITY_TYPE],
vitrage_sample_timestamp=vitrage_sample_timestamp,
update_timestamp=update_timestamp,
entity_state=self._get_alarm_state(entity_event),
metadata=metadata)
def _ok_status(self, entity_event):
return entity_event[KProps.PRIORITY] == KapacitorState.OK
def _create_kapacitor_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=EntityCategory.RESOURCE)
for graph_neighbor in graph_neighbors]
@staticmethod
def get_enrich_query(event):
resource_type = event.get(KProps.RESOURCE_TYPE)
resource_name = event.get(KProps.RESOURCE_NAME)
if resource_type and resource_name:
return {VProps.NAME: resource_name,
VProps.VITRAGE_TYPE: resource_type}
return None

View File

@ -618,3 +618,24 @@ def simple_k8s_nodes_generators(nodes_num, snapshot_events=0):
}
)
return tg.get_trace_generators(test_entity_spec_list)
def simple_kapacitor_alarm_generators(update_vals=None):
"""A function for returning Kapacitor alarm event generators.
Returns generators for a given number of Kapacitor 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_KAPACITOR_UPDATE_D,
tg.STATIC_INFO_FKEY: None,
tg.EXTERNAL_INFO_KEY: update_vals,
tg.MAPPING_KEY: None,
tg.NAME_KEY: 'Kapacitor alarm generator',
tg.NUM_EVENTS: 1
})]
return tg.get_trace_generators(test_entity_spec_list)

View File

@ -214,6 +214,18 @@ def simple_collectd_alarm_generators(update_vals=None):
tg.TRANS_COLLECTD_UPDATE_D, update_vals)
def simple_kapacitor_alarm_generators(update_vals=None):
"""A function for returning Kapacitor alarm event generators.
Returns generators for a given number of Kapacitor alarms.
:param update_vals: preset values for ALL update events
:return: generators for alarms as specified
"""
return _simple_alarm_generators('Kapacitor',
tg.TRANS_KAPACITOR_UPDATE_D, update_vals)
def simple_prometheus_alarm_generators(update_vals=None):
"""A function for returning Prometheus alert event generators.

View File

@ -50,6 +50,7 @@ MOCK_DRIVER_PATH = '%s/mock_configurations/driver' % \
DRIVER_AODH_UPDATE_D = 'driver_aodh_update_dynamic.json'
DRIVER_DOCTOR_UPDATE_D = 'driver_doctor_update_dynamic.json'
DRIVER_COLLECTD_UPDATE_D = 'driver_collectd_update_dynamic.json'
DRIVER_KAPACITOR_UPDATE_D = 'driver_kapacitor_update_dynamic.json'
DRIVER_HOST_SNAPSHOT_D = 'driver_host_snapshot_dynamic.json'
DRIVER_INST_SNAPSHOT_D = 'driver_inst_snapshot_dynamic.json'
DRIVER_INST_SNAPSHOT_S = 'driver_inst_snapshot_static.json'
@ -80,6 +81,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_KAPACITOR_UPDATE_D = 'transformer_kapacitor_update_dynamic.json'
TRANS_PROMETHEUS_UPDATE_D = 'transformer_prometheus_update_dynamic.json'
TRANS_INST_SNAPSHOT_D = 'transformer_inst_snapshot_dynamic.json'
TRANS_HOST_SNAPSHOT_D = 'transformer_host_snapshot_dynamic.json'
@ -124,6 +126,7 @@ class EventTraceGenerator(object):
{DRIVER_AODH_UPDATE_D: _get_aodh_alarm_update_driver_values,
DRIVER_DOCTOR_UPDATE_D: _get_simple_update_driver_values,
DRIVER_COLLECTD_UPDATE_D: _get_simple_update_driver_values,
DRIVER_KAPACITOR_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_LEGACY_D: _get_vm_update_legacy_driver_values,
@ -149,6 +152,7 @@ class EventTraceGenerator(object):
TRANS_AODH_UPDATE_D: _get_trans_aodh_alarm_snapshot_values,
TRANS_DOCTOR_UPDATE_D: _get_simple_trans_alarm_update_values,
TRANS_COLLECTD_UPDATE_D: _get_simple_trans_alarm_update_values,
TRANS_KAPACITOR_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,

View File

@ -0,0 +1,16 @@
kapacitor:
- alert:
host: cloud.compute1
vitrage_resource:
type: nova.host
name: compute-1
- alert:
host: compute-(.*)
vitrage_resource:
type: nova.host
name: ${kapacitor_host}
- alert:
host: (.*)
vitrage_resource:
type: nova.instance
name: ${kapacitor_host}

View File

@ -0,0 +1,12 @@
{
"level": "CRITICAL",
"priority": "critical",
"host": "compute-1",
"details": "DETAIL INFO :: LEVEL: CRITICAL - ID: mem high-host=compute-1 - NAME : mem - VALUE: 9.746241910865896 - TASKNAME: chronograf-v1-2d1fad8a-1c52-4eca-8721-c7af36faf7c - GROUP: host=compute-1 - TAGS: map[host:compute-1] - TIME: 2019-04-22 08:11:00 &#43;0000 UTC",
"time": "2019-04-22T08:11:00Z",
"duration": 0,
"message": "mem high",
"id": "mem high-host=compute-1",
"recoverable": "True",
"previousLevel": "OK"
}

View File

@ -0,0 +1,18 @@
{
"level": "CRITICAL",
"priority": "critical",
"host": "compute-1",
"vitrage_entity_type" : "kapacitor",
"vitrage_datasource_name": "kapacitor",
"vitrage_datasource_action" : "update",
"vitrage_sample_date": "2019-04-22T06:31:50.094836",
"resource_type": "nova.host",
"resource_name": "compute-1",
"details": "DETAIL INFO :: LEVEL: CRITICAL - ID: mem high-host=compute-1 - NAME : mem - VALUE: 9.746241910865896 - TASKNAME: chronograf-v1-2d1fad8a-1c52-4eca-8721-c7af36faf7c - GROUP: host=compute-1 - TAGS: map[host:compute-1] - TIME: 2019-04-22 08:11:00 &#43;0000 UTC",
"time": "2019-04-22T08:11:00Z",
"duration": 0,
"message": "mem high",
"id": "mem high-host=compute-1",
"recoverable": "True",
"previousLevel": "OK"
}

View File

@ -0,0 +1,87 @@
# Copyright 2019 - Viettel
#
# 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.datasources.kapacitor.config import KapacitorConfig
from vitrage.datasources.kapacitor import KAPACITOR_DATASOURCE
from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE
from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE
from vitrage.tests import base
from vitrage.tests.mocks import utils
class TestKapacitorConfig(base.BaseTest):
OPTS = [
cfg.StrOpt(DSOpts.TRANSFORMER,
default='vitrage.datasources.kapacitor.transformer.'
'KapacitorTransformer',
help='Kapacitor data source transformer class path',
required=True),
cfg.StrOpt(DSOpts.DRIVER,
default='vitrage.datasources.kapacitor.driver.'
'KapacitorDriver',
help='Kapacitor driver class path',
required=True),
cfg.StrOpt(DSOpts.CONFIG_FILE,
help='Kapacitor configuration file',
default=utils.get_resources_dir()
+ '/kapacitor/kapacitor_conf.yaml'),
]
# noinspection PyPep8Naming
@classmethod
def setUpClass(cls):
super(TestKapacitorConfig, cls).setUpClass()
cls.conf = cfg.ConfigOpts()
cls.conf.register_opts(cls.OPTS, group=KAPACITOR_DATASOURCE)
def test_get_vitrage_resource(self):
"""Test the resource returned after processing a list of mappings
:return:
"""
# Action
kapacitor_conf = KapacitorConfig(self.conf)
# Test assertions
mapped_resource = kapacitor_conf.get_vitrage_resource(None)
self.assertIsNone(mapped_resource, 'expected None')
mapped_resource = kapacitor_conf.get_vitrage_resource('')
self.assertIsNone(mapped_resource, 'expected None')
mapped_resource = kapacitor_conf.get_vitrage_resource('cloud.compute1')
self.assertIsNotNone(mapped_resource, 'expected Not None')
self.assertEqual(NOVA_HOST_DATASOURCE, mapped_resource[0])
self.assertEqual('compute-1', mapped_resource[1])
mapped_resource = kapacitor_conf.get_vitrage_resource('compute-2')
self.assertIsNotNone(mapped_resource, 'expected Not None')
self.assertEqual(NOVA_HOST_DATASOURCE, mapped_resource[0])
self.assertEqual('compute-2', mapped_resource[1])
mapped_resource = kapacitor_conf.get_vitrage_resource('instance-1')
self.assertIsNotNone(mapped_resource, 'expected Not None')
self.assertEqual(NOVA_INSTANCE_DATASOURCE, mapped_resource[0])
self.assertEqual('instance-1', mapped_resource[1])
@staticmethod
def _assert_equals(mapping1, mapping2):
return mapping1.kapacitor_host_regexp.pattern == \
mapping2.kapacitor_host_regexp.pattern and \
mapping1.resource_type == mapping2.resource_type and \
mapping1.resource_name == mapping2.resource_name

View File

@ -0,0 +1,120 @@
# Copyright 2019 - Viettel
#
# 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.datasources.kapacitor.driver import KapacitorDriver
from vitrage.datasources.kapacitor import KAPACITOR_DATASOURCE
from vitrage.datasources.kapacitor.properties \
import KapacitorProperties as KProps
from vitrage.datasources.kapacitor.properties \
import KapacitorState as KState
from vitrage.tests import base
from vitrage.tests.mocks import mock_driver
from vitrage.tests.mocks import utils
# notification alarm input
HOST = 'compute-1'
ALARM_PRIntORIY = 'critical'
ALARM_EVENT_TYPE = 'kapacitor.alarm.critical'
# match result
EXPECTED_RESOURCE_TYPE = 'nova.host'
EXPECTED_RESOURCE_NAME = 'compute1'
EXPECTED_EVENT_PRIORIY = 'critical'
EXPECTED_EVENT_TYPE = 'kapacitor.alarm.critical'
class TestKapacitorDriver(base.BaseTest):
OPTS = [
cfg.StrOpt(DSOpts.CONFIG_FILE,
help='Kapacitor configuration file',
default=utils.get_resources_dir()
+ '/kapacitor/kapacitor_conf.yaml'),
]
# noinspection PyPep8Naming
@classmethod
def setUpClass(cls):
super(TestKapacitorDriver, cls).setUpClass()
cls.conf = cfg.ConfigOpts()
cls.conf.register_opts(cls.OPTS, group=KAPACITOR_DATASOURCE)
# noinspection PyAttributeOutsideInit
def setUp(self):
super(TestKapacitorDriver, self).setUp()
self.driver = KapacitorDriver(self.conf)
def test_enrich_event(self):
# Test event on host
# Setup
input_data = {KProps.HOST: 'compute-1',
KProps.PRIORITY: 'CPU utilization',
ALARM_EVENT_TYPE: KState.CRITICAL}
expected_data = {DSProps.EVENT_TYPE: KState.CRITICAL,
KProps.RESOURCE_NAME: 'compute-1',
KProps.RESOURCE_TYPE: 'nova.host',
KProps.PRIORITY: 'CPU utilization'}
event = self._generate_event(input_data[KProps.HOST],
input_data[KProps.PRIORITY])
# Action
event = self.driver.enrich_event(event,
input_data[ALARM_EVENT_TYPE])
# Test assertions
self._assert_event_equal(event, expected_data)
# Test event on instance
# Setup
input_data = {KProps.HOST: 'node1-vm',
KProps.PRIORITY: 'CPU utilization',
ALARM_EVENT_TYPE: KState.CRITICAL}
expected_data = {DSProps.EVENT_TYPE: KState.CRITICAL,
KProps.RESOURCE_NAME: 'node1-vm',
KProps.RESOURCE_TYPE: 'nova.instance',
KProps.PRIORITY: 'CPU utilization'}
event = self._generate_event(input_data[KProps.HOST],
input_data[KProps.PRIORITY])
# Action
event = self.driver.enrich_event(event,
input_data[ALARM_EVENT_TYPE])
# Test assertions
self._assert_event_equal(event, expected_data)
@staticmethod
def _generate_event(hostname, priority):
update_vals = {}
if hostname:
update_vals[KProps.HOST] = hostname
if priority:
update_vals[KProps.PRIORITY] = priority
generators = mock_driver.simple_kapacitor_alarm_generators(
update_vals=update_vals)
return mock_driver.generate_sequential_events_list(generators)[0]
def _assert_event_equal(self,
event1,
event2):
self.assertIsNotNone(event1, 'No event returned')
self.assertEqual(event1[DSProps.EVENT_TYPE],
event2[DSProps.EVENT_TYPE])
self.assertEqual(event1[KProps.RESOURCE_NAME],
event2[KProps.RESOURCE_NAME])
self.assertEqual(event1[KProps.RESOURCE_TYPE],
event2[KProps.RESOURCE_TYPE])
self.assertEqual(event1[KProps.PRIORITY],
event2[KProps.PRIORITY])

View File

@ -0,0 +1,175 @@
# Copyright 2019 - Viettel
#
# 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 uuid
from oslo_config import cfg
from oslo_log import log as logging
from vitrage.common.constants import DatasourceOpts as DSOpts
from vitrage.common.constants import DatasourceProperties as DSProps
from vitrage.common.constants import EntityCategory
from vitrage.common.constants import UpdateMethod
from vitrage.common.constants import VertexProperties as VProps
from vitrage.datasources.kapacitor import KAPACITOR_DATASOURCE
from vitrage.datasources.kapacitor.properties import KapacitorProperties \
as KProps
from vitrage.datasources.kapacitor.properties import KapacitorState \
as KState
from vitrage.datasources.kapacitor.transformer import KapacitorTransformer
from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE
from vitrage.datasources.nova.host.transformer import HostTransformer
from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE
from vitrage.datasources.nova.instance.transformer import InstanceTransformer
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
LOG = logging.getLogger(__name__)
# noinspection PyProtectedMember
class TestKapacitorTransformer(BaseAlarmTransformerTest):
OPTS = [
cfg.StrOpt(DSOpts.UPDATE_METHOD,
default=UpdateMethod.PUSH),
]
# noinspection PyAttributeOutsideInit,PyPep8Naming
@classmethod
def setUpClass(cls):
super(TestKapacitorTransformer, cls).setUpClass()
cls.transformers = {}
cls.conf = cfg.ConfigOpts()
cls.conf.register_opts(cls.OPTS, group=KAPACITOR_DATASOURCE)
cls.transformers[KAPACITOR_DATASOURCE] = \
KapacitorTransformer(cls.transformers, cls.conf)
cls.transformers[NOVA_INSTANCE_DATASOURCE] = \
InstanceTransformer(cls.transformers, cls.conf)
cls.transformers[NOVA_HOST_DATASOURCE] = \
HostTransformer(cls.transformers, cls.conf)
def test_create_entity_key(self):
LOG.debug('Test get key from nova host transformer')
# Test setup
host = 'compute-1'
resource_name = 'compute-1'
resource_type = 'nova.host'
update_vals = {KProps.HOST: host,
KProps.RESOURCE_TYPE: resource_type,
KProps.RESOURCE_NAME: resource_name}
event = self._generate_event(update_vals)
transformer = KapacitorTransformer(self.transformers, self.conf)
self.assertIsNotNone(event)
# Test action
observed_key = transformer._create_entity_key(event)
# Test assertions
observed_key_fields = observed_key.split(
TransformerBase.KEY_SEPARATOR)
self.assertEqual(EntityCategory.ALARM, observed_key_fields[0])
self.assertEqual(event[DSProps.ENTITY_TYPE], observed_key_fields[1])
self.assertEqual(event[KProps.RESOURCE_NAME],
observed_key_fields[2])
self.assertEqual(event[KProps.ID],
observed_key_fields[3])
def test_create_update_entity_vertex(self):
# Test setup
host1 = 'host1'
instance_id = uuid.uuid4().hex
event_on_host = self._generate_event_on_host(host1)
event_on_instance = self._generate_event_on_instance(host1,
instance_id)
self.assertIsNotNone(event_on_host)
self.assertIsNotNone(event_on_instance)
# Test action
transformer = self.transformers[KAPACITOR_DATASOURCE]
wrapper_for_host = transformer.transform(event_on_host)
wrapper_for_instance = transformer.transform(event_on_instance)
# Test assertions
self._validate_vertex_props(wrapper_for_host.vertex, event_on_host)
self._validate_vertex_props(wrapper_for_instance.vertex,
event_on_instance)
# Validate the neighbors: only one valid host neighbor
host_entity_key = transformer._create_entity_key(event_on_host)
host_entity_uuid = \
transformer.uuid_from_deprecated_vitrage_id(host_entity_key)
instance_entity_key = transformer._create_entity_key(event_on_instance)
instance_entity_uuid = \
transformer.uuid_from_deprecated_vitrage_id(instance_entity_key)
self._validate_host_neighbor(wrapper_for_host,
host_entity_uuid,
host1)
self._validate_instance_neighbor(wrapper_for_instance,
instance_entity_uuid,
instance_id)
# Validate the expected action on the graph - update or delete
self._validate_graph_action(wrapper_for_host)
self._validate_graph_action(wrapper_for_instance)
def _validate_vertex_props(self, vertex, event):
self._validate_alarm_vertex_props(
vertex, event[KProps.MESSAGE],
KAPACITOR_DATASOURCE, event[DSProps.SAMPLE_DATE])
@staticmethod
def _generate_event(update_vals):
generators = mock_transformer.simple_kapacitor_alarm_generators(
update_vals=update_vals)
return mock_transformer.generate_random_events_list(generators)[0]
def _generate_event_on_host(self, hostname):
# fake query result to be used by the transformer for determining
# the neighbor
update_vals = {}
query_result = [{VProps.VITRAGE_TYPE: NOVA_HOST_DATASOURCE,
VProps.ID: hostname}]
update_vals[KProps.HOST] = hostname
update_vals[KProps.RESOURCE_TYPE] = NOVA_HOST_DATASOURCE
update_vals[KProps.RESOURCE_NAME] = hostname
update_vals[TransformerBase.QUERY_RESULT] = query_result
return self._generate_event(update_vals)
def _generate_event_on_instance(self, hostname, instance_id):
# fake query result to be used by the transformer for determining
# the neighbor
update_vals = {}
query_result = [{VProps.VITRAGE_TYPE: NOVA_INSTANCE_DATASOURCE,
VProps.ID: instance_id}]
update_vals[KProps.HOST] = hostname
update_vals[KProps.RESOURCE_TYPE] = NOVA_INSTANCE_DATASOURCE
update_vals[KProps.RESOURCE_NAME] = hostname
update_vals[TransformerBase.QUERY_RESULT] = query_result
return self._generate_event(update_vals)
def _is_erroneous(self, vertex):
return vertex[VProps.SEVERITY] != KState.OK