diff --git a/orca/topology/alerts/elastalert/extractor.py b/orca/topology/alerts/elastalert/extractor.py index 11d87c7..728aa7b 100644 --- a/orca/topology/alerts/elastalert/extractor.py +++ b/orca/topology/alerts/elastalert/extractor.py @@ -14,6 +14,7 @@ from orca.common import str_utils from orca.topology.alerts import extractor +from orca.topology.alerts import properties as alert_props class Extractor(extractor.Extractor): @@ -36,6 +37,10 @@ class AlertExtractor(Extractor): def _extract_name(self, entity): return entity['name'] + def _extract_status(self, entity): + # TODO: Differentiate UP/DOWN status + return alert_props.AlertStatus.UP + def _extract_source_labels(self, entity): labels = entity['kubernetes'].copy() labels.pop('labels', None) diff --git a/orca/topology/alerts/extractor.py b/orca/topology/alerts/extractor.py index 19179d7..53179da 100644 --- a/orca/topology/alerts/extractor.py +++ b/orca/topology/alerts/extractor.py @@ -18,6 +18,7 @@ from orca.common import config, file_utils, logger from orca.graph import graph from orca.topology import extractor +from orca.topology.alerts import properties as alert_props CONFIG = config.CONFIG LOG = logger.get_logger(__name__) @@ -37,18 +38,25 @@ def kind(self): def extract(self, entity): name = self._extract_name(entity) + status = self._extract_status(entity) labels = self._extract_source_labels(entity) source_mapping = self._source_mapper.map(name, labels) node_id = self._build_id(name, source_mapping) properties = self._extract_properties(entity) properties['name'] = name + properties['status'] = status properties['source_mapping'] = source_mapping - return graph.Node(node_id, properties, self.origin, self.kind) + node = graph.Node(node_id, properties, self.origin, self.kind) + return Alert(node) @abc.abstractmethod def _extract_name(self, entity): """Extract name from given entity object.""" + @abc.abstractmethod + def _extract_status(self, entity): + """Extract alert status from given entity object.""" + @abc.abstractmethod def _extract_source_labels(self, entity): """Extract labels from given entity object.""" @@ -127,3 +135,18 @@ def _validate_value(self, value, mapping): if value in mapping['blacklist_values']: return False return True + + +class Alert(object): + + """Decorator for alert nodes.""" + + def __init__(self, node): + self._node = node + + def __getattr__(self, name): + return getattr(self._node, name) + + @property + def is_up(self): + return self._node.properties.status == alert_props.AlertStatus.UP diff --git a/orca/topology/alerts/falco/extractor.py b/orca/topology/alerts/falco/extractor.py index 3c55512..fc83716 100644 --- a/orca/topology/alerts/falco/extractor.py +++ b/orca/topology/alerts/falco/extractor.py @@ -14,6 +14,7 @@ from orca.common import str_utils from orca.topology.alerts import extractor +from orca.topology.alerts import properties as alert_props class Extractor(extractor.Extractor): @@ -36,6 +37,10 @@ class AlertExtractor(Extractor): def _extract_name(self, entity): return entity['rule'] + def _extract_status(self, entity): + # TODO: Differentiate UP/DOWN status + return alert_props.AlertStatus.UP + def _extract_source_labels(self, entity): return entity['output_fields'] diff --git a/orca/topology/alerts/falco/ingestor.py b/orca/topology/alerts/falco/ingestor.py index 6e3b61c..067c67a 100644 --- a/orca/topology/alerts/falco/ingestor.py +++ b/orca/topology/alerts/falco/ingestor.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from orca.topology import ingestor +from orca.topology.alerts import ingestor from orca.topology.alerts.falco import extractor diff --git a/orca/topology/alerts/ingestor.py b/orca/topology/alerts/ingestor.py new file mode 100644 index 0000000..a01f745 --- /dev/null +++ b/orca/topology/alerts/ingestor.py @@ -0,0 +1,29 @@ +# Copyright 2020 OpenRCA Authors +# +# 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 orca.topology import ingestor + + +class Ingestor(ingestor.Ingestor): + + """Base class for alert ingestors.""" + + def _ingest_event(self, event): + alert = self._extractor.extract(event) + if self._graph.get_node(alert.id): + self._graph.update_node(alert) + if not alert.is_up: + self._graph.delete_node(alert.id) + elif alert.is_up: + self._graph.add_node(alert) diff --git a/orca/topology/alerts/prometheus/extractor.py b/orca/topology/alerts/prometheus/extractor.py index 927184d..9aa038f 100644 --- a/orca/topology/alerts/prometheus/extractor.py +++ b/orca/topology/alerts/prometheus/extractor.py @@ -14,6 +14,7 @@ from orca.common import str_utils from orca.topology.alerts import extractor +from orca.topology.alerts import properties as alert_props class Extractor(extractor.Extractor): @@ -36,19 +37,20 @@ class AlertExtractor(Extractor): def _extract_name(self, entity): return entity['labels']['alertname'] + def _extract_status(self, entity): + if entity['state'] == 'firing': + return alert_props.AlertStatus.UP + return alert_props.AlertStatus.DOWN + def _extract_source_labels(self, entity): return entity['labels'] def _extract_properties(self, entity): properties = {} - properties['status'] = self._extract_status(entity) properties['severity'] = self._extract_severity(entity) properties['message'] = self._extract_message(entity) return properties - def _extract_status(self, entity): - return entity['state'] - def _extract_severity(self, entity): return entity['labels']['severity'] diff --git a/orca/topology/alerts/prometheus/ingestor.py b/orca/topology/alerts/prometheus/ingestor.py index e4a55a1..2cc9dcd 100644 --- a/orca/topology/alerts/prometheus/ingestor.py +++ b/orca/topology/alerts/prometheus/ingestor.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from orca.topology import ingestor +from orca.topology.alerts import ingestor from orca.topology.alerts.prometheus import extractor diff --git a/orca/topology/alerts/properties.py b/orca/topology/alerts/properties.py new file mode 100644 index 0000000..1c3cfff --- /dev/null +++ b/orca/topology/alerts/properties.py @@ -0,0 +1,18 @@ +# Copyright 2020 OpenRCA Authors +# +# 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 AlertStatus: + + UP = 'up' + DOWN = 'down' diff --git a/orca/topology/alerts/zabbix/extractor.py b/orca/topology/alerts/zabbix/extractor.py index 0ef6f0f..4811398 100644 --- a/orca/topology/alerts/zabbix/extractor.py +++ b/orca/topology/alerts/zabbix/extractor.py @@ -14,6 +14,7 @@ from orca.common import str_utils from orca.topology.alerts import extractor +from orca.topology.alerts import properties as alert_props class Extractor(extractor.Extractor): @@ -36,17 +37,18 @@ class AlertExtractor(Extractor): def _extract_name(self, entity): return entity['trigger'][0] + def _extract_status(self, entity): + if entity['trigger'][2] == '1': + return alert_props.AlertStatus.UP + return alert_props.AlertStatus.DOWN + def _extract_source_labels(self, entity): return {'node': entity['host']} def _extract_properties(self, entity): properties = {} - properties['status'] = self._extract_status(entity) properties['severity'] = self._extract_severity(entity) return properties - def _extract_status(self, entity): - return 'active' if entity['trigger'][2] == '1' else 'inactive' - def _extract_severity(self, entity): return entity['trigger'][1]