Skip to content

Commit 705e785

Browse files
authored
Merge pull request #14 from ARGOeu/devel
Preparing for production
2 parents 2d9f8a6 + 1e3673f commit 705e785

24 files changed

+1023
-1
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
*.pyc
2+
*.pyo
3+
*.egg-info
4+
*.egg
5+
*.EGG
6+
*.EGG-INFO
7+
buld
8+
dist

.travis.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
language: python
2+
# Target py version 2.7
3+
python:
4+
- "2.7"
5+
6+
script: pytest

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include README.md
2+
include requirements.txt

README.md

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,125 @@
1-
# argo-alert
1+
argo-alert
2+
===============================
3+
4+
version number: 0.0.1
5+
author: GRNET
6+
7+
Overview
8+
--------
9+
10+
Streaming Publisher of argo-streaming status events as alerts to an alerta service endpoint.
11+
Alerta mail configuration rules from gocdb contact data.
12+
13+
Argo streaming-service produces status events with the following schema:
14+
```
15+
json
16+
{
17+
"endpoint_group":"SITE-A"
18+
"service":"SERVICE-A",
19+
"hostname":"host-A.site-A.foo"
20+
"metric":"metric-A"
21+
"status":"warning"
22+
"summary":"a warning message"
23+
"type":"metric"
24+
}
25+
```
26+
27+
There are four types of status events: `[ "endpoint_group", "service", "endpoint", "metric"]`
28+
29+
Argo-alert connects to a kafka-topic and receives such status events. Each event is
30+
then transformed to a compatible alerta schema and gets published to an alerta service endpoint.
31+
32+
Alerta-mailer rule generation
33+
---------------------------
34+
Argo-alert provides the ability to create alerta-mailer rules from gocdb contact data. Argo-alert
35+
connects to a defined gocdb api endpoint and transforms the xml contact information to alerta-mailer
36+
rule json format. The rules are saved to a defined output file. The parameter `use_notifications_flag`
37+
(by default set to `True`) is used to select only the gocdb contacts that contain
38+
`<NOTIFICATIONS>Y</NOTIFICATIONS>` xml element. If the gocdb instance doesn't support notifications flag
39+
please set `use_notifications_flag=False`.
40+
41+
The configuration file for argo-alert provides a `[gocdb]` section to configure the gocdb endpoint
42+
and the required certificate parameters for access. The `mail-rules` parameter in `[alerta]` section
43+
specifies the alerta-mailer rule filename for output.
44+
45+
46+
Installation / Usage
47+
--------------------
48+
49+
To install use pip:
50+
51+
$ pip install argoalert
52+
53+
54+
Or clone the repo:
55+
56+
$ git clone https://github.com/ARGOeu/argoalert.git
57+
$ python setup.py install
58+
59+
60+
argoalert requires a configuration file with the following options:
61+
```
62+
[gocdb]
63+
# Path to godcb endpoint
64+
api=https://gocdb-url.example.foo
65+
# Path to ca bundle folder
66+
cabundle=/path/to/cabundle
67+
# Path to host certificate file
68+
hostcert=/path/to/hostcert
69+
# Path to host key file
70+
hostkey=/path/to/hostkey
71+
# Verify https requests
72+
verify=False
73+
# Examine notification flag when selecting contacts
74+
use_notifications_flag=True
75+
# request path for gocdb to retrieve top-level items
76+
top_request=/gocdbpi/public/?method=get_site
77+
# request path for gocdb to retrieve sub-level items
78+
sub_request=/gocdbpi/public/?method=get_service
79+
80+
[kafka]
81+
# kafka endpoint
82+
endpoint=localhost:9092
83+
# kafka topic
84+
topic=metrics
85+
86+
[alerta]
87+
# alerta service endpoint
88+
endpoint=http://localhost:8080
89+
# alerta enviroment
90+
environment=devel
91+
# alerta token
92+
token=s3cr3tt0ke3n
93+
# path to store the generated mail rules
94+
mail-rules=/home/root/alerta-rules-101
95+
96+
[logging]
97+
# loggin level
98+
level = INFO
99+
100+
```
101+
102+
To run argo-alert publisher:
103+
104+
$ argo-alert-publisher -c /path/to/argo-alert.conf
105+
106+
To run argo-alert mail rule generator:
107+
108+
$ argo-alert-rulegen -c /path/to/argo-alert.conf
109+
110+
Using test-emails
111+
-----------------
112+
For testing purposes if you want to distribute notifications using a list
113+
of your own email aliases (before using client contact emails) issue:
114+
115+
$ argo-alert-rulegen -c /path/to/argo-alert.conf --test-emails test01@email.foo,test02@email.foo,test03@email.foo
116+
117+
The rule list will be generated using the configured client sources but each client
118+
email will be replaced (using round-robin method) by a test-email (as defined in the cli arg list)
119+
120+
Run tests
121+
---------
122+
123+
To run argo-alert tests:
124+
125+
$ pytest

argoalert/__init__.py

Whitespace-only changes.

argoalert/argoalert.py

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
from kafka import KafkaConsumer
2+
import json
3+
import requests
4+
import logging
5+
from defusedxml.minidom import parseString
6+
7+
8+
def transform(argo_event, environment):
9+
"""Transform an argo status event to an alerta alert
10+
11+
Args:
12+
argo_event: obj. Json representation of an argo status event
13+
environment: str. Alerta enviroment parameter to build the alert
14+
15+
Return:
16+
obj: Json representation of an alerta alert
17+
"""
18+
status = argo_event["status"].lower()
19+
hostname = argo_event["hostname"]
20+
metric = argo_event["metric"]
21+
group = argo_event["endpoint_group"]
22+
etype = argo_event["type"]
23+
service = argo_event["service"]
24+
25+
# alerta vars
26+
resource = ""
27+
event = "status"
28+
alerta_service = []
29+
30+
if etype == "endpoint_group":
31+
alerta_service.append("endpoint_group")
32+
resource = group
33+
elif etype == "service":
34+
alerta_service.append("service")
35+
resource = group + "/" + service
36+
elif etype == "endpoint":
37+
alerta_service.append("endpoint")
38+
resource = group + "/" + service + "/" + hostname
39+
elif etype == "metric":
40+
alerta_service.append("metric")
41+
resource = group + "/" + service + "/" + hostname + "/" + metric
42+
# prepare alerta json
43+
alerta = {"environment": environment, "event": event, "resource": resource,
44+
"service": alerta_service, "severity": status}
45+
46+
return alerta
47+
48+
49+
def read_and_send(message, environment, alerta_url, alerta_token):
50+
"""Read an argo status event from kafka and send it to alerta
51+
52+
Args:
53+
message: str. Current message from kafka queue
54+
environment: str. Alerta environment to be used (e.g. 'Devel')
55+
alerta_url: str. Alerta api endpoint
56+
alerta_token: str. Alerta api access token
57+
58+
"""
59+
try:
60+
argo_event = json.loads(message.value)
61+
except ValueError:
62+
logging.warning("NOT JSON: " + message.value)
63+
return
64+
65+
try:
66+
alerta = transform(argo_event, environment)
67+
except KeyError:
68+
logging.warning("WRONG JSON SCHEMA: " + message.value)
69+
return
70+
71+
logging.info("Attempting to send alert:" + json.dumps(alerta))
72+
headers = {'Authorization': 'Key ' + alerta_token,
73+
'Content-Type': 'application/json'}
74+
75+
r = requests.post(alerta_url + "/alert", headers=headers,
76+
data=json.dumps(alerta))
77+
78+
if r.status_code == 201:
79+
logging.info("Alert send to alerta successfully")
80+
else:
81+
logging.warning("Alert wasn't send to alerta")
82+
logging.warning(r.text)
83+
84+
85+
def start_listening(environment, kafka_endpoints, kafka_topic,
86+
alerta_endpoint, alerta_token):
87+
"""Start listening to a kafka topic and send alerts to an alerta endpoint
88+
89+
Args:
90+
environment: str. Alerta environment to be used (e.g. 'Devel')
91+
kafka_endpoints: str. kafka broker endpoint
92+
kafka_topic: str. kafka topic to listen t
93+
alerta_endpoint: str. Alerta api endpoint
94+
alerta_token: str. Alerta api access token
95+
96+
"""
97+
98+
# Initialize kafka
99+
kafka_list = kafka_endpoints.split(',')
100+
101+
consumer = KafkaConsumer(kafka_topic,
102+
group_id='argo-alerta',
103+
bootstrap_servers=kafka_list)
104+
for message in consumer:
105+
read_and_send(message, environment, alerta_endpoint, alerta_token)
106+
107+
108+
def gocdb_to_contacts(gocdb_xml, use_notif_flag, test_emails):
109+
"""Transform gocdb xml schema info on generic contacts json information
110+
111+
Args:
112+
gocdb_xml: str. Data in gocdb xml format
113+
use_notif_flag: boolean. Examine or not notifications flag when gathering contacts
114+
115+
Return:
116+
obj: Json representation of contact information
117+
"""
118+
xmldoc = parseString(gocdb_xml)
119+
contacts = []
120+
clist = xmldoc.getElementsByTagName("CONTACT_EMAIL")
121+
122+
indx = 0
123+
for item in clist:
124+
125+
# By default accept all contacts
126+
notify_val = 'Y'
127+
# If flag on accept only contacts with notification flag
128+
if use_notif_flag:
129+
notify = item.parentNode.getElementsByTagName('NOTIFICATIONS')[0]
130+
# if notification flag is set to false skip
131+
notify_val = notify.firstChild.nodeValue
132+
133+
if notify_val == 'TRUE' or notify_val == 'Y':
134+
c = dict()
135+
c["type"] = item.parentNode.tagName
136+
137+
# Check if name tag exists
138+
name_tags = item.parentNode.getElementsByTagName("NAME")
139+
# if not check short name tag
140+
if len(name_tags) == 0:
141+
name_tags = item.parentNode.getElementsByTagName("SHORT_NAME")
142+
143+
# if still no name related tag skip
144+
if len(name_tags) == 0:
145+
continue
146+
147+
c["name"] = name_tags[0].firstChild.nodeValue
148+
149+
if test_emails is None:
150+
c["email"] = item.firstChild.nodeValue
151+
else:
152+
c["email"] = test_emails[indx % len(test_emails)]
153+
indx = indx + 1
154+
155+
contacts.append(c)
156+
157+
return contacts
158+
159+
160+
def contacts_to_alerta(contacts):
161+
"""Transform a contacts json object to alerta's rule json object
162+
163+
Args:
164+
contacts: obj. Json representation of contact information
165+
166+
Return:
167+
obj: Json representation of alerta mailer rules
168+
"""
169+
rules = []
170+
for c in contacts:
171+
172+
rule_name = "rule_" + c["name"]
173+
rule_fields = [{u"field": u"resource", u"regex": c["name"]}]
174+
rule_contacts = [c["email"]]
175+
rule_exlude = True
176+
rule = {u"name": rule_name, u"fields": rule_fields, u"contacts": rule_contacts, u"exclude": rule_exlude}
177+
rules.append(rule)
178+
179+
logging.info("Generated " + str(len(rules)) + " alerta rules from contact information")
180+
return rules
181+
182+
183+
def get_gocdb(api_url, ca_bundle, hostcert, hostkey, verify):
184+
"""Http Rest call to gocdb-api to get xml contact information
185+
186+
Args:
187+
api_url: str. Gocdb url call
188+
ca_bundle: str. CA bundle file
189+
hostcert: str. Host certificate file
190+
hostkey: str. Host key file
191+
verify: str. path to a ca_bundle for verification - if available
192+
193+
Return:
194+
str: gocdb-api xml response
195+
"""
196+
197+
# If verify is true replace it with ca_bundle path
198+
if verify:
199+
verify = ca_bundle
200+
201+
logging.info("Requesting data from gocdb api: " + api_url)
202+
r = requests.get(api_url, cert=(hostcert, hostkey), verify=verify)
203+
204+
if r.status_code == 200:
205+
logging.info("Gocdb data retrieval successful")
206+
return r.text.encode('utf-8').strip()
207+
208+
return ""
209+
210+
211+
def write_rules(rules, outfile):
212+
"""Writes alerta email rules to a specific output file
213+
214+
Args:
215+
rules: obj. json representation of alerta rules
216+
outfile: str. output filename path
217+
"""
218+
219+
json_str = json.dumps(rules, indent=4)
220+
logging.info("Saving rule to file: " + outfile)
221+
with open(outfile, "w") as output_file:
222+
output_file.write(json_str)

0 commit comments

Comments
 (0)