Implement Doctor datasource for handling alarms coming from OPNFV Doctor monitor

Implements: blueprint doctor-datasource

Change-Id: If41b8b5ef1d7a62d374e15bc3cc5739afecc06ca
This commit is contained in:
Ifat Afek 2016-12-26 13:25:09 +00:00
parent 9844aa077e
commit 2c1432c222
21 changed files with 896 additions and 77 deletions

View File

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

View File

@ -0,0 +1,70 @@
metadata:
name: host_down_scenarios
description: scenarios triggered by Doctor monitor 'compute.host.down' alarm
definitions:
entities:
- entity:
category: ALARM
name: compute.host.down
template_id: host_down_alarm
- entity:
category: ALARM
type: vitrage
name: Instance Down
template_id: instance_alarm
- entity:
category: RESOURCE
type: nova.instance
template_id: instance
- entity:
category: RESOURCE
type: nova.host
template_id: host
relationships:
- relationship:
source: host_down_alarm
relationship_type: on
target: host
template_id : host_down_alarm_on_host
- relationship:
source: host
relationship_type: contains
target: instance
template_id : host_contains_instance
- relationship:
source: instance_alarm
relationship_type: on
target: instance
template_id : alarm_on_instance
scenarios:
- scenario:
condition: host_down_alarm_on_host
actions:
- action:
action_type: set_state
action_target:
target: host
properties:
state: ERROR
- action:
action_type: mark_down
action_target:
target: host
- scenario:
condition: host_down_alarm_on_host and host_contains_instance
actions:
- action:
action_type: raise_alarm
action_target:
target: instance
properties:
alarm_name: Instance Down
severity: critical
- scenario:
condition: host_down_alarm_on_host and host_contains_instance and alarm_on_instance
actions:
- action:
action_type: add_causal_relationship
action_target:
source: host_down_alarm
target: instance_alarm

View File

@ -141,3 +141,7 @@ class AlarmDriverBase(DriverBase):
ret = alarm if filter_(alarm, old_alarm) else None
self.cache[self._alarm_key(alarm)] = alarm, time
return ret
def _old_alarm(self, event):
alarm_key = self._alarm_key(event)
return self.cache.get(alarm_key, (None, None))[0]

View File

@ -16,11 +16,16 @@ from oslo_log import log as logging
from vitrage.common.constants import DatasourceAction
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 GraphAction
from vitrage.common.constants import VertexProperties as VProps
from vitrage.common.exception import VitrageTransformerError
from vitrage.datasources.alarm_properties import AlarmProperties as AlarmProps
from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE
from vitrage.datasources import transformer_base as tbase
from vitrage.datasources.transformer_base import Neighbor
import vitrage.graph.utils as graph_utils
LOG = logging.getLogger(__name__)
@ -63,11 +68,36 @@ class AlarmTransformerBase(tbase.TransformerBase):
def _get_alarm_state(self, entity_event):
event_type = entity_event.get(DSProps.EVENT_TYPE, None)
if event_type is not None:
return AlarmProps.INACTIVE_STATE if \
GraphAction.DELETE_ENTITY == event_type else \
AlarmProps.ACTIVE_STATE
if event_type and event_type == GraphAction.DELETE_ENTITY:
return AlarmProps.INACTIVE_STATE
else:
return AlarmProps.INACTIVE_STATE if \
self._ok_status(entity_event) else \
AlarmProps.ACTIVE_STATE
def _create_neighbor(self,
vitrage_id,
sample_timestamp,
resource_type,
resource_name):
# Any resource transformer will do (nova for example)
transformer = self.transformers[NOVA_HOST_DATASOURCE]
if transformer:
properties = {
VProps.TYPE: resource_type,
VProps.ID: resource_name,
VProps.SAMPLE_TIMESTAMP: sample_timestamp
}
resource_vertex = transformer.create_placeholder_vertex(
**properties)
relationship_edge = graph_utils.create_edge(
source_id=vitrage_id,
target_id=resource_vertex.vertex_id,
relationship_type=EdgeLabel.ON)
return Neighbor(resource_vertex, relationship_edge)
LOG.warning('Cannot create neighbour, host transformer does not exist')
return None

View File

@ -254,8 +254,7 @@ class AodhDriver(AlarmDriverBase):
"event_type": "instance.update"}}
"""
alarm_key = self._alarm_key(event)
old_alarm = self.cache.get(alarm_key, (None, None))[0]
old_alarm = self._old_alarm(event)
entity = old_alarm.copy()
changed_rule = event[AodhProps.DETAIL]
@ -273,8 +272,7 @@ class AodhDriver(AlarmDriverBase):
datetime_utils.utcnow(False))
def _convert_alarm_state_transition_event(self, event):
alarm_key = self._alarm_key(event)
old_alarm = self.cache.get(alarm_key, (None, None))[0]
old_alarm = self._old_alarm(event)
entity = old_alarm.copy()
entity[AodhProps.STATE] = event[AodhProps.DETAIL][AodhProps.STATE]

View File

@ -0,0 +1,37 @@
# Copyright 2016 - 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 UpdateMethod
DOCTOR_DATASOURCE = 'doctor'
OPTS = [
cfg.StrOpt('transformer',
default='vitrage.datasources.doctor.transformer.'
'DoctorTransformer',
help='Doctor transformer class path',
required=True),
cfg.StrOpt('driver',
default='vitrage.datasources.doctor.driver.DoctorDriver',
help='Doctor driver class path',
required=True),
cfg.StrOpt('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,112 @@
# Copyright 2016 - 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.doctor import DOCTOR_DATASOURCE
from vitrage.datasources.doctor.properties import DoctorDetails
from vitrage.datasources.doctor.properties import DoctorProperties \
as DoctorProps
from vitrage.datasources.doctor.properties import DoctorStatus
from vitrage.datasources.doctor.properties import get_detail
LOG = log.getLogger(__name__)
class DoctorDriver(AlarmDriverBase):
AlarmKey = namedtuple('AlarmKey', ['alarm_name', 'hostname'])
def __init__(self, conf):
super(DoctorDriver, self).__init__()
self.conf = conf
self._client = None
def _entity_type(self):
return DOCTOR_DATASOURCE
def _alarm_key(self, alarm):
return self.AlarmKey(alarm_name=alarm[DoctorProps.TYPE],
hostname=get_detail(alarm,
DoctorDetails.HOSTNAME))
def _is_erroneous(self, alarm):
return alarm and \
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:
return False
details = alarm[DoctorProps.DETAILS]
return DoctorDetails.STATUS in details and \
DoctorDetails.SEVERITY in details and \
DoctorDetails.HOSTNAME in details
def _status_changed(self, new_alarm, old_alarm):
return get_detail(old_alarm, DoctorDetails.STATUS) != \
get_detail(new_alarm, DoctorDetails.STATUS)
def _get_alarms(self):
# pulling alarms is not supported in Doctor monitor
return []
def enrich_event(self, event, event_type):
"""Enrich the given event
:param event: dictionary of this form:
{
'time': '2016-04-12T08:00:00.12345',
'type': 'compute.host.down',
'details': {
'hostname': 'compute-1',
'source': 'sample_monitor',
'cause': 'link-down',
'severity': 'critical',
'status': 'down',
'monitor_id': 'monitor-1',
'monitor_event_id': '123',
}
}
:param event_type: always 'compute.host.down'
:return: the same event, with the following changes:
- DoctorProps.UPDATE_TIME - the event 'time' if it is new, or the
update time of the same event if it is already cached
"""
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 = self._filter_and_cache_alarm(event, old_alarm,
self._filter_get_erroneous,
event[DoctorProps.TIME])
if event:
return DoctorDriver.make_pickleable([event], DOCTOR_DATASOURCE,
DatasourceAction.UPDATE)[0]
@staticmethod
def get_event_types():
return [DoctorProps.HOST_DOWN]

View File

@ -0,0 +1,42 @@
# Copyright 2016 - 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 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):
"""The details appear inside a details section in the event """
HOSTNAME = 'hostname'
STATUS = 'status'
SEVERITY = 'severity'
class DoctorStatus(object):
DOWN = 'down'
UP = 'up'
def get_detail(alarm, detail):
return alarm[DoctorProperties.DETAILS][detail] if \
alarm and DoctorProperties.DETAILS in alarm and \
detail in alarm[DoctorProperties.DETAILS] \
else None

View File

@ -0,0 +1,97 @@
# Copyright 2016 - 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 EntityCategory
from vitrage.common.constants import VertexProperties as VProps
from vitrage.datasources.alarm_transformer_base import AlarmTransformerBase
from vitrage.datasources.doctor import DOCTOR_DATASOURCE
from vitrage.datasources.doctor.properties import DoctorDetails
from vitrage.datasources.doctor.properties import DoctorProperties \
as DoctorProps
from vitrage.datasources.doctor.properties import DoctorStatus
from vitrage.datasources.doctor.properties import get_detail
from vitrage.datasources import transformer_base as tbase
import vitrage.graph.utils as graph_utils
from vitrage.utils.datetime import change_time_str_format
LOG = logging.getLogger(__name__)
class DoctorTransformer(AlarmTransformerBase):
def __init__(self, transformers, conf):
super(DoctorTransformer, self).__init__(transformers, conf)
def _create_snapshot_entity_vertex(self, entity_event):
# The Doctor monitor does not support snapshot mode
return None
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]
return graph_utils.create_vertex(
self._create_entity_key(entity_event),
entity_category=EntityCategory.ALARM,
entity_type=entity_event[DSProps.ENTITY_TYPE],
entity_state=self._get_alarm_state(entity_event),
sample_timestamp=entity_event[DoctorProps.TIME],
update_timestamp=entity_event[DoctorProps.UPDATE_TIME],
metadata=details)
def _create_update_neighbors(self, entity_event):
return [self._create_neighbor(
self._create_entity_key(entity_event),
entity_event[DoctorProps.TIME],
DoctorProps.HOST_TYPE,
get_detail(entity_event, DoctorDetails.HOSTNAME))]
def _create_entity_key(self, entity_event):
return tbase.build_key(self._key_values(
entity_event[DSProps.ENTITY_TYPE],
entity_event[DoctorProps.TYPE],
get_detail(entity_event, DoctorDetails.HOSTNAME)))
def get_type(self):
return DOCTOR_DATASOURCE
def _ok_status(self, entity_event):
return entity_event and \
get_detail(entity_event, DoctorDetails.STATUS) == DoctorStatus.UP
@staticmethod
def get_enrich_query(event):
hostname = get_detail(event, DoctorDetails.HOSTNAME)
if not hostname:
return None
return {VProps.ID: hostname}
@staticmethod
def _unify_time_format(entity_event):
DoctorTransformer._unify_prop_time_format(entity_event,
DoctorProps.TIME)
DoctorTransformer._unify_prop_time_format(entity_event,
DoctorProps.UPDATE_TIME)
@staticmethod
def _unify_prop_time_format(entity_event, prop):
entity_event[prop] = change_time_str_format(
entity_event[prop],
DoctorProps.TIME_FORMAT,
tbase.TIMESTAMP_FORMAT)

View File

@ -15,16 +15,13 @@
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.nagios import NAGIOS_DATASOURCE
from vitrage.datasources.nagios.properties import NagiosProperties
from vitrage.datasources.nagios.properties import NagiosTestStatus
from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE
from vitrage.datasources import transformer_base as tbase
from vitrage.datasources.transformer_base import Neighbor
import vitrage.graph.utils as graph_utils
from vitrage.utils import datetime as datetime_utils
@ -92,33 +89,6 @@ class NagiosTransformer(AlarmTransformerBase):
return []
def _create_neighbor(self,
vitrage_id,
sample_timestamp,
resource_type,
resource_name):
# Any resource transformer will do (nova for example)
transformer = self.transformers[NOVA_HOST_DATASOURCE]
if transformer:
properties = {
VProps.TYPE: resource_type,
VProps.ID: resource_name,
VProps.SAMPLE_TIMESTAMP: sample_timestamp
}
resource_vertex = transformer.create_placeholder_vertex(
**properties)
relationship_edge = graph_utils.create_edge(
source_id=vitrage_id,
target_id=resource_vertex.vertex_id,
relationship_type=EdgeLabel.ON)
return Neighbor(resource_vertex, relationship_edge)
LOG.warning('Cannot transform host, host transformer does not exist')
return None
def _ok_status(self, entity_event):
return entity_event[NagiosProperties.STATUS] == NagiosTestStatus.OK

View File

@ -15,14 +15,11 @@
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_properties import AlarmProperties as AlarmProps
from vitrage.datasources.alarm_transformer_base import AlarmTransformerBase
from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE
from vitrage.datasources import transformer_base as tbase
from vitrage.datasources.transformer_base import Neighbor
from vitrage.datasources.zabbix.properties import ZabbixProperties as ZProps
from vitrage.datasources.zabbix.properties import ZabbixTriggerSeverity \
as TriggerSeverity
@ -104,33 +101,6 @@ class ZabbixTransformer(AlarmTransformerBase):
return []
def _create_neighbor(self,
vitrage_id,
sample_timestamp,
resource_type,
resource_name):
# Any resource transformer will do (nova for example)
transformer = self.transformers[NOVA_HOST_DATASOURCE]
if transformer:
properties = {
VProps.TYPE: resource_type,
VProps.ID: resource_name,
VProps.SAMPLE_TIMESTAMP: sample_timestamp
}
resource_vertex = transformer.create_placeholder_vertex(
**properties)
relationship_edge = graph_utils.create_edge(
source_id=vitrage_id,
target_id=resource_vertex.vertex_id,
relationship_type=EdgeLabel.ON)
return Neighbor(resource_vertex, relationship_edge)
LOG.warning('Cannot transform host, host transformer does not exist')
return None
def _ok_status(self, entity_event):
return entity_event[ZProps.VALUE] == TriggerValue.OK

View File

@ -39,13 +39,19 @@ class TransformerManager(object):
transformers = {}
for datasource_type in conf.datasources.types:
transformers[datasource_type] = importutils.import_object(
conf[datasource_type].transformer,
transformers, conf)
if opt_exists(conf[datasource_type], ENTITIES):
for entity in conf[datasource_type].entities:
transformers[entity] = importutils.import_object(
conf[datasource_type].transformer, transformers, conf)
try:
transformers[datasource_type] = importutils.import_object(
conf[datasource_type].transformer,
transformers, conf)
if opt_exists(conf[datasource_type], ENTITIES):
for entity in conf[datasource_type].entities:
transformers[entity] = importutils.import_object(
conf[datasource_type].transformer,
transformers, conf)
except Exception as e:
LOG.exception('Failed to register transformer %s. '
'Exception: %s', datasource_type, e)
transformers[VITRAGE_TYPE] = importutils.import_object(
"%s.%s" % (EvaluatorEventTransformer.__module__,

View File

@ -437,6 +437,27 @@ def simple_zabbix_alarm_generators(host_num,
return tg.get_trace_generators(test_entity_spec_list)
def simple_doctor_alarm_generators(update_vals=None):
"""A function for returning Doctor alarm event generators.
Returns generators for a given number of Doctor 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_DOCTOR_UPDATE_D,
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)
def simple_aodh_alarm_notification_generators(alarm_num,
update_events=0,
update_vals=None):

View File

@ -188,3 +188,25 @@ def simple_aodh_update_alarm_generators(alarm_num,
}
]
return tg.get_trace_generators(test_entity_spec_list)
def simple_doctor_alarm_generators(update_vals=None):
"""A function for returning Doctor alarm event generators.
Returns generators for a given number of Doctor 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_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)

View File

@ -45,6 +45,7 @@ GENERATOR = 'generator'
MOCK_DRIVER_PATH = '%s/mock_configurations/driver' % \
utils.get_resources_dir()
DRIVER_AODH_UPDATE_D = 'driver_aodh_update_dynamic.json'
DRIVER_DOCTOR_UPDATE_D = 'driver_doctor_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'
@ -66,6 +67,7 @@ MOCK_TRANSFORMER_PATH = '%s/mock_configurations/transformer' % \
utils.get_resources_dir()
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_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'
@ -108,6 +110,7 @@ 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_INST_SNAPSHOT_D: _get_vm_snapshot_driver_values,
DRIVER_INST_UPDATE_D: _get_vm_update_driver_values,
DRIVER_HOST_SNAPSHOT_D: _get_host_snapshot_driver_values,
@ -124,6 +127,7 @@ class EventTraceGenerator(object):
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_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}
@ -243,6 +247,17 @@ def _get_host_snapshot_driver_values(spec):
return static_values
def _get_doctor_update_driver_values(spec):
"""Generates the static driver values for Doctor 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.
@ -584,11 +599,11 @@ def _get_trans_zone_snapshot_values(spec):
def _get_trans_aodh_alarm_snapshot_values(spec):
"""Generates the static transformer values for each vm.
"""Generates the dynamic transformer values for Aodh datasource.
:param spec: specification of event generation.
:type spec: dict
:return: list of static transformer values for each vm.
:return: list of dynamic transformer values for Aodh datasource.
:rtype: list
"""
@ -621,6 +636,23 @@ 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
:param spec: specification of event generation.
:type spec: dict
:return: list of dynamic transformer values for a Doctor alarm
:rtype: list with one alarm
"""
static_info_re = None
if spec[STATIC_INFO_FKEY] is not None:
static_info_re = utils.load_specs(spec[STATIC_INFO_FKEY])
return [combine_data(static_info_re,
None, spec.get(EXTERNAL_INFO_KEY, None))]
def combine_data(static_info_re, mapping_info, external_info):
if external_info:
mapping_info = utils.merge_vals(mapping_info, external_info)

View File

@ -0,0 +1,13 @@
{
"time": "2016-04-12T08:00:00.12345",
"type": "compute.host.down",
"details": {
"hostname": "compute-1",
"source": "sample_monitor",
"cause": "link-down",
"severity": "critical",
"status": "down",
"monitor_id": "monitor-1",
"monitor_event_id": "123"
}
}

View File

@ -0,0 +1,15 @@
{
"time": "2016-04-12T08:00:00.12345",
"type": "compute.host.down",
"vitrage_entity_type" : "doctor",
"vitrage_datasource_action" : "update",
"details": {
"hostname": "compute-1",
"source": "sample_monitor",
"cause": "link-down",
"severity": "critical",
"status": "down",
"monitor_id": "monitor-1",
"monitor_event_id": "123"
}
}

View File

@ -0,0 +1,134 @@
# Copyright 2016 - 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_config import cfg
from vitrage.common.constants import DatasourceProperties as DSProps
from vitrage.datasources.doctor.driver import DoctorDriver
from vitrage.datasources.doctor.properties import DoctorDetails
from vitrage.datasources.doctor.properties import DoctorProperties \
as DoctorProps
from vitrage.datasources.doctor.properties import DoctorStatus
from vitrage.tests import base
from vitrage.tests.mocks import mock_driver
# noinspection PyProtectedMember
class DoctorDriverTest(base.BaseTest):
OPTS = []
# noinspection PyPep8Naming
@classmethod
def setUpClass(cls):
cls.conf = cfg.ConfigOpts()
cls.conf.register_opts(cls.OPTS, group='doctor')
def test_enrich_event(self):
# Test setup
driver = DoctorDriver(self.conf)
event_type = DoctorProps.HOST_DOWN
time1 = datetime.now().isoformat()
host1 = 'host1'
event = self._generate_event(time1, host1, DoctorStatus.DOWN)
# Enrich event
event = driver.enrich_event(event, event_type)
# Test assertions
self._assert_event_equal(event, event_type, host1,
DoctorStatus.DOWN, time1, time1)
# Add another event
time2 = datetime.now().isoformat()
host2 = 'host2'
event = self._generate_event(time2, host2, DoctorStatus.DOWN)
# Enrich event
event = driver.enrich_event(event, event_type)
# Test assertions
self._assert_event_equal(event, event_type, host2,
DoctorStatus.DOWN, time2, time2)
# Change the first event to 'up' - should be marked as deleted
time3 = datetime.now().isoformat()
event = self._generate_event(time3, host1, DoctorStatus.UP)
# Enrich event
event = driver.enrich_event(event, event_type)
# Test assertions
self._assert_event_equal(event, event_type, host1,
DoctorStatus.UP, time3, time3)
# self._assert_marked_as_deleted(driver, event, True)
# Send again the second event. The sample time should be new, but the
# update time should remain with its old value (since the state has
# not changed)
time4 = datetime.now().isoformat()
event = self._generate_event(time4, host2, DoctorStatus.DOWN)
# Enrich event
event = driver.enrich_event(event, event_type)
# Test assertions
self._assert_event_equal(event, event_type, host2,
DoctorStatus.DOWN, time4, time2)
# Send again the first event, after it was deleted. Make sure it is
# raised again
time5 = datetime.now().isoformat()
event = self._generate_event(time5, host1, DoctorStatus.DOWN)
# Enrich event
event = driver.enrich_event(event, event_type)
# Test assertions
self._assert_event_equal(event, event_type, host1,
DoctorStatus.DOWN, time5, time5)
@staticmethod
def _generate_event(time, hostname, status):
details = {}
if hostname:
details[DoctorDetails.HOSTNAME] = hostname
if status:
details[DoctorDetails.STATUS] = status
update_vals = {DoctorProps.DETAILS: details}
if time:
update_vals[DoctorProps.TIME] = time
generators = mock_driver.simple_doctor_alarm_generators(
update_vals=update_vals)
return mock_driver.generate_sequential_events_list(generators)[0]
def _assert_event_equal(self,
event,
expected_event_type,
expected_hostname,
expected_status,
expected_sample_date,
expected_update_date):
self.assertIsNotNone(event, 'No event returned')
self.assertEqual(expected_hostname,
event[DoctorProps.DETAILS][DoctorDetails.HOSTNAME])
self.assertEqual(expected_status,
event[DoctorProps.DETAILS][DoctorDetails.STATUS])
self.assertEqual(expected_sample_date, event[DoctorProps.TIME])
self.assertEqual(expected_update_date, event[DoctorProps.UPDATE_TIME])
self.assertEqual(expected_event_type, event[DSProps.EVENT_TYPE])

View File

@ -0,0 +1,119 @@
# 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_config import cfg
from oslo_log import log as logging
from vitrage.common.constants import UpdateMethod
from vitrage.datasources.doctor import DOCTOR_DATASOURCE
from vitrage.datasources.doctor.properties import DoctorDetails
from vitrage.datasources.doctor.properties import DoctorProperties \
as DoctorProps
from vitrage.datasources.doctor.properties import DoctorStatus
from vitrage.datasources.doctor.transformer import DoctorTransformer
from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE
from vitrage.datasources.nova.host.transformer import HostTransformer
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 DoctorTransformerTest(BaseAlarmTransformerTest):
OPTS = [
cfg.StrOpt('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=DOCTOR_DATASOURCE)
cls.conf.register_opts(cls.OPTS, group=NOVA_HOST_DATASOURCE)
cls.transformers[DOCTOR_DATASOURCE] = \
DoctorTransformer(cls.transformers, cls.conf)
cls.transformers[NOVA_HOST_DATASOURCE] = \
HostTransformer(cls.transformers, cls.conf)
def test_create_update_entity_vertex(self):
# Test setup
time1 = datetime.now().isoformat()
host1 = 'host1'
event = self._generate_event(time1, host1, DoctorStatus.DOWN)
self.assertIsNotNone(event)
# Test action
transformer = self.transformers[DOCTOR_DATASOURCE]
wrapper = transformer.transform(event)
# Test assertions
self._validate_vertex_props(wrapper.vertex, event)
# Validate the neighbors: only one valid host neighbor
self._validate_host_neighbor(wrapper,
transformer._create_entity_key(event),
host1)
# Validate the expected action on the graph - update or delete
self._validate_graph_action(wrapper)
# Create an event with status 'UP'
time2 = datetime.now().isoformat()
host2 = 'host2'
event = self._generate_event(time2, host2, DoctorStatus.UP)
self.assertIsNotNone(event)
# Test action
transformer = self.transformers[DOCTOR_DATASOURCE]
wrapper = transformer.transform(event)
# Test assertions
self._validate_vertex_props(wrapper.vertex, event)
self._validate_host_neighbor(wrapper,
transformer._create_entity_key(event),
host2)
self._validate_graph_action(wrapper)
def _validate_vertex_props(self, vertex, event):
self._validate_alarm_vertex_props(vertex,
event[DoctorProps.TYPE],
DOCTOR_DATASOURCE,
event[DoctorProps.TIME])
@staticmethod
def _generate_event(time, hostname, status):
details = {}
if hostname:
details[DoctorDetails.HOSTNAME] = hostname
if status:
details[DoctorDetails.STATUS] = status
update_vals = {DoctorProps.DETAILS: details}
if time:
update_vals[DoctorProps.TIME] = time
update_vals[DoctorProps.UPDATE_TIME] = time
generators = mock_transformer.simple_doctor_alarm_generators(
update_vals=update_vals)
return mock_transformer.generate_random_events_list(generators)[0]
def _is_erroneous(self, vertex):
return vertex[DoctorDetails.STATUS] == DoctorStatus.DOWN

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.
import abc
from oslo_log import log as logging
from vitrage.common.constants import EdgeLabel
from vitrage.common.constants import EntityCategory
from vitrage.common.constants import GraphAction
from vitrage.common.constants import VertexProperties as VProps
from vitrage.datasources.alarm_properties import AlarmProperties as AlarmProps
from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE
from vitrage.tests.unit.datasources.test_transformer_base import \
BaseTransformerTest
LOG = logging.getLogger(__name__)
# noinspection PyProtectedMember
class BaseAlarmTransformerTest(BaseTransformerTest):
def _validate_alarm_vertex_props(self,
vertex,
expected_name,
expected_datasource_name,
expected_sample_time):
self._validate_base_vertex_props(vertex,
expected_name,
expected_datasource_name)
self.assertEqual(EntityCategory.ALARM, vertex[VProps.CATEGORY])
self.assertEqual(expected_sample_time, vertex[VProps.SAMPLE_TIMESTAMP])
if self._is_erroneous(vertex):
self.assertEqual(AlarmProps.ACTIVE_STATE, vertex[VProps.STATE])
else:
self.assertEqual(AlarmProps.INACTIVE_STATE, vertex[VProps.STATE])
def _validate_host_neighbor(self,
wrapper,
alarm_id,
host_name):
self.assertEqual(1, len(wrapper.neighbors))
host_neighbor = wrapper.neighbors[0]
host_transformer = self.transformers[NOVA_HOST_DATASOURCE]
properties = {
VProps.ID: host_name,
VProps.TYPE: NOVA_HOST_DATASOURCE,
VProps.SAMPLE_TIMESTAMP: wrapper.vertex[VProps.SAMPLE_TIMESTAMP],
}
expected_neighbor = host_transformer.\
create_placeholder_vertex(**properties)
self.assertEqual(expected_neighbor, host_neighbor.vertex)
# Validate neighbor edge
edge = host_neighbor.edge
self.assertEqual(edge.source_id, alarm_id)
self.assertEqual(edge.target_id, host_neighbor.vertex.vertex_id)
self.assertEqual(edge.label, EdgeLabel.ON)
def _validate_graph_action(self, wrapper):
if self._is_erroneous(wrapper.vertex):
self.assertEqual(GraphAction.UPDATE_ENTITY, wrapper.action)
else:
self.assertEqual(GraphAction.DELETE_ENTITY, wrapper.action)
@abc.abstractmethod
def _is_erroneous(self, vertex):
pass

View File

@ -0,0 +1,32 @@
# 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 oslo_log import log as logging
from vitrage.common.constants import VertexProperties as VProps
from vitrage.tests import base
LOG = logging.getLogger(__name__)
# noinspection PyProtectedMember
class BaseTransformerTest(base.BaseTest):
def _validate_base_vertex_props(self,
vertex,
expected_name,
expected_datasource_name):
self.assertFalse(vertex[VProps.IS_PLACEHOLDER])
self.assertEqual(expected_datasource_name, vertex[VProps.TYPE])
self.assertEqual(expected_name, vertex[VProps.NAME])