omdnotificationforwarder 1.0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omdnotificationforwarder-1.0.0.1/.gitignore +5 -0
- omdnotificationforwarder-1.0.0.1/PKG-INFO +21 -0
- omdnotificationforwarder-1.0.0.1/README.md +2 -0
- omdnotificationforwarder-1.0.0.1/pyproject.toml +55 -0
- omdnotificationforwarder-1.0.0.1/src/notificationforwarder/baseclass.py +365 -0
- omdnotificationforwarder-1.0.0.1/src/notificationforwarder/email/forwarder.py +0 -0
- omdnotificationforwarder-1.0.0.1/src/notificationforwarder/example/formatter.py +18 -0
- omdnotificationforwarder-1.0.0.1/src/notificationforwarder/example/forwarder.py +36 -0
- omdnotificationforwarder-1.0.0.1/src/notificationforwarder/rabbitmq/formatter.py +29 -0
- omdnotificationforwarder-1.0.0.1/src/notificationforwarder/rabbitmq/forwarder.py +58 -0
- omdnotificationforwarder-1.0.0.1/src/notificationforwarder/syslog/formatter.py +10 -0
- omdnotificationforwarder-1.0.0.1/src/notificationforwarder/syslog/forwarder.py +42 -0
- omdnotificationforwarder-1.0.0.1/tests/pythonpath/lib/python/notificationforwarder/split1/forwarder.py +13 -0
- omdnotificationforwarder-1.0.0.1/tests/pythonpath/lib/python/notificationforwarder/split2/formatter.py +14 -0
- omdnotificationforwarder-1.0.0.1/tests/pythonpath/lib/python/notificationforwarder/split2/forwarder.py +13 -0
- omdnotificationforwarder-1.0.0.1/tests/pythonpath/lib/python/notificationforwarder/split3/formatter.py +14 -0
- omdnotificationforwarder-1.0.0.1/tests/pythonpath/lib/python/notificationforwarder/split3/forwarder.py +13 -0
- omdnotificationforwarder-1.0.0.1/tests/pythonpath/local/lib/python/notificationforwarder/split1/formatter.py +14 -0
- omdnotificationforwarder-1.0.0.1/tests/pythonpath/local/lib/python/notificationforwarder/split2/forwarder.py +13 -0
- omdnotificationforwarder-1.0.0.1/tests/pythonpath/local/lib/python/notificationforwarder/split3/formatter.py +14 -0
- omdnotificationforwarder-1.0.0.1/tests/pythonpath/local/lib/python/notificationforwarder/split3/forwarder.py +13 -0
- omdnotificationforwarder-1.0.0.1/tests/test_classes.py +185 -0
- omdnotificationforwarder-1.0.0.1/tests/test_package.py +7 -0
- omdnotificationforwarder-1.0.0.1/tests/test_paths.py +121 -0
- omdnotificationforwarder-1.0.0.1/tests/tmp/split3-flush.lock +0 -0
- omdnotificationforwarder-1.0.0.1/tests/var/log/notificationforwarder_split3.log +5 -0
- omdnotificationforwarder-1.0.0.1/tests/var/tmp/split3-notifications.db +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: omdnotificationforwarder
|
|
3
|
+
Version: 1.0.0.1
|
|
4
|
+
Summary: A framework for notification scripts for OMD
|
|
5
|
+
Project-URL: Homepage, https://github.com/lausser/noteventificationforhandlerwarder
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/lausser/noteventificationforhandlerwarder/issues
|
|
7
|
+
Author-email: Gerhard Lausser <lausser@yahoo.com>
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Requires-Dist: coshsh
|
|
13
|
+
Requires-Dist: jinja2
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
Requires-Dist: pytest-cov; extra == 'test'
|
|
16
|
+
Requires-Dist: pytest>=2.7.3; extra == 'test'
|
|
17
|
+
Requires-Dist: unittest; extra == 'test'
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# noteventificationforhandlerwarder
|
|
21
|
+
Frickel für Notifications und Eventhandler
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling", "coshsh"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[tool.hatch.build]
|
|
6
|
+
include = [
|
|
7
|
+
"src/**/*.py",
|
|
8
|
+
"/tests",
|
|
9
|
+
]
|
|
10
|
+
exclude = [
|
|
11
|
+
"__pacache__/",
|
|
12
|
+
"**/.pyc",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[tool.hatch.build.targets.wheel]
|
|
16
|
+
packages = ["src/notificationforwarder"]
|
|
17
|
+
|
|
18
|
+
[project]
|
|
19
|
+
name = "omdnotificationforwarder"
|
|
20
|
+
version = "1.0.0.1"
|
|
21
|
+
authors = [
|
|
22
|
+
{ name="Gerhard Lausser", email="lausser@yahoo.com" },
|
|
23
|
+
]
|
|
24
|
+
description = "A framework for notification scripts for OMD"
|
|
25
|
+
readme = "README.md"
|
|
26
|
+
requires-python = ">=3.7"
|
|
27
|
+
classifiers = [
|
|
28
|
+
"Programming Language :: Python :: 3",
|
|
29
|
+
"License :: OSI Approved :: MIT License",
|
|
30
|
+
"Operating System :: OS Independent",
|
|
31
|
+
]
|
|
32
|
+
dependencies = [
|
|
33
|
+
"coshsh",
|
|
34
|
+
"jinja2",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
test = [
|
|
39
|
+
"unittest",
|
|
40
|
+
"pytest >= 2.7.3",
|
|
41
|
+
"pytest-cov",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
"Homepage" = "https://github.com/lausser/noteventificationforhandlerwarder"
|
|
46
|
+
"Bug Tracker" = "https://github.com/lausser/noteventificationforhandlerwarder/issues"
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
addopts = "-ra -q --import-mode=importlib"
|
|
50
|
+
pythonpath = [
|
|
51
|
+
"src/"
|
|
52
|
+
]
|
|
53
|
+
testpaths = [
|
|
54
|
+
"tests",
|
|
55
|
+
]
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
from abc import ABCMeta, abstractmethod
|
|
2
|
+
from importlib import import_module
|
|
3
|
+
import os
|
|
4
|
+
import socket
|
|
5
|
+
import traceback
|
|
6
|
+
import signal
|
|
7
|
+
import functools
|
|
8
|
+
import errno
|
|
9
|
+
import fcntl
|
|
10
|
+
import time
|
|
11
|
+
try:
|
|
12
|
+
import simplejson as json
|
|
13
|
+
except ImportError:
|
|
14
|
+
import json
|
|
15
|
+
from importlib import import_module
|
|
16
|
+
from importlib.util import find_spec, module_from_spec
|
|
17
|
+
|
|
18
|
+
import sqlite3
|
|
19
|
+
import logging
|
|
20
|
+
from coshsh.util import setup_logging
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
MAXAGE = 5
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
logger = None
|
|
27
|
+
|
|
28
|
+
def new(target_name, tag, verbose, debug, receiveropts):
|
|
29
|
+
|
|
30
|
+
if verbose:
|
|
31
|
+
scrnloglevel = logging.INFO
|
|
32
|
+
else:
|
|
33
|
+
scrnloglevel = 100
|
|
34
|
+
if debug:
|
|
35
|
+
scrnloglevel = logging.DEBUG
|
|
36
|
+
txtloglevel = logging.DEBUG
|
|
37
|
+
else:
|
|
38
|
+
txtloglevel = logging.INFO
|
|
39
|
+
if tag:
|
|
40
|
+
logger_name = "notificationforwarder_"+target_name+"_"+tag
|
|
41
|
+
else:
|
|
42
|
+
logger_name = "notificationforwarder_"+target_name
|
|
43
|
+
|
|
44
|
+
setup_logging(logdir=os.environ["OMD_ROOT"]+"/var/log", logfile=logger_name+".log", scrnloglevel=scrnloglevel, txtloglevel=txtloglevel, format="%(asctime)s %(process)d - %(levelname)s - %(message)s")
|
|
45
|
+
logger = logging.getLogger(logger_name)
|
|
46
|
+
try:
|
|
47
|
+
if '.' in target_name:
|
|
48
|
+
module_name, class_name = target_name.rsplit('.', 1)
|
|
49
|
+
else:
|
|
50
|
+
module_name = target_name
|
|
51
|
+
class_name = target_name.capitalize()
|
|
52
|
+
forwarder_module = import_module('notificationforwarder.'+module_name+'.forwarder', package='notificationforwarder.'+module_name)
|
|
53
|
+
forwarder_class = getattr(forwarder_module, class_name)
|
|
54
|
+
|
|
55
|
+
instance = forwarder_class(receiveropts)
|
|
56
|
+
instance.__module_file__ = forwarder_module.__file__
|
|
57
|
+
instance.name = target_name
|
|
58
|
+
if tag:
|
|
59
|
+
instance.tag = tag
|
|
60
|
+
# so we can use logger.info(...) in the single modules
|
|
61
|
+
forwarder_module.logger = logging.getLogger(logger_name)
|
|
62
|
+
base_module = import_module('.baseclass', package='notificationforwarder')
|
|
63
|
+
base_module.logger = logging.getLogger(logger_name)
|
|
64
|
+
|
|
65
|
+
except Exception as e:
|
|
66
|
+
raise ImportError('{} is not part of our forwarder collection!'.format(target_name))
|
|
67
|
+
else:
|
|
68
|
+
if not issubclass(forwarder_class, NotificationForwarder):
|
|
69
|
+
raise ImportError("We currently don't have {}, but you are welcome to send in the request for it!".format(forwarder_class))
|
|
70
|
+
|
|
71
|
+
return instance
|
|
72
|
+
|
|
73
|
+
class ForwarderTimeoutError(Exception):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
def timeout(seconds, error_message="Timeout"):
|
|
77
|
+
def decorator(func):
|
|
78
|
+
@functools.wraps(func)
|
|
79
|
+
def wrapper(*args, **kwargs):
|
|
80
|
+
def handler(signum, frame):
|
|
81
|
+
raise ForwarderTimeoutError(error_message)
|
|
82
|
+
|
|
83
|
+
original_handler = signal.signal(signal.SIGALRM, handler)
|
|
84
|
+
signal.alarm(seconds)
|
|
85
|
+
try:
|
|
86
|
+
result = func(*args, **kwargs)
|
|
87
|
+
finally:
|
|
88
|
+
signal.signal(signal.SIGALRM, original_handler)
|
|
89
|
+
signal.alarm(0)
|
|
90
|
+
return result
|
|
91
|
+
return wrapper
|
|
92
|
+
return decorator
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class NotificationForwarder(object):
|
|
96
|
+
"""This is the base class where all Forwardes inherit from"""
|
|
97
|
+
__metaclass__ = ABCMeta # replace with ...BaseClass(metaclass=ABCMeta):
|
|
98
|
+
|
|
99
|
+
def __init__(self, opts):
|
|
100
|
+
self.queued_events = []
|
|
101
|
+
self.max_queue_length = 10
|
|
102
|
+
self.sleep_after_flush = 0
|
|
103
|
+
self.baseclass_logs_summary = True
|
|
104
|
+
for opt in opts:
|
|
105
|
+
setattr(self, opt, opts[opt])
|
|
106
|
+
|
|
107
|
+
def probe(self):
|
|
108
|
+
"""Checks if a forwarder is principally able to submit an event.
|
|
109
|
+
It is mostly used to contact an api and confirm that it is alive.
|
|
110
|
+
After failed attempts, when there are spooled events in the database,
|
|
111
|
+
a call to probe() can tell the forwarder that the events now can
|
|
112
|
+
be flushed.
|
|
113
|
+
"""
|
|
114
|
+
return True
|
|
115
|
+
|
|
116
|
+
def init_queue(self, maxlength=10, sleepttime=0):
|
|
117
|
+
self.max_queue_length = maxlength
|
|
118
|
+
self.sleep_after_flush = sleepttime
|
|
119
|
+
|
|
120
|
+
def flush_queue(self):
|
|
121
|
+
if not getattr(self, "can_queue", False):
|
|
122
|
+
logger.critical("forwarder {} can not flush_queue events".format(self.__class__.__name__.lower()))
|
|
123
|
+
return
|
|
124
|
+
logger.debug("flush remaining {}".format(len(self.queued_events)))
|
|
125
|
+
if self.queued_events:
|
|
126
|
+
formatted_squashed_event = self.squash_queued_events()
|
|
127
|
+
logger.debug("merge {} queued events and flush".format(len(self.queued_events)))
|
|
128
|
+
self.forward_formatted(formatted_squashed_event)
|
|
129
|
+
self.queued_events = []
|
|
130
|
+
time.sleep(self.sleep_after_flush)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def forward_queued(self, raw_event):
|
|
134
|
+
if not getattr(self, "can_queue", False):
|
|
135
|
+
logger.critical("forwarder {} can not queue events".format(self.__class__.__name__.lower()))
|
|
136
|
+
return
|
|
137
|
+
try:
|
|
138
|
+
formatted_event = self.format_event(raw_event)
|
|
139
|
+
if formatted_event:
|
|
140
|
+
self.queued_events.append(formatted_event)
|
|
141
|
+
except Exception as e:
|
|
142
|
+
logger.critical("formatter error: "+str(e))
|
|
143
|
+
if len(self.queued_events) >= self.max_queue_length:
|
|
144
|
+
formatted_squashed_event = self.squash_queued_events()
|
|
145
|
+
logger.debug("merge {} queued events and flush".format(self.max_queue_length))
|
|
146
|
+
self.forward_formatted(formatted_squashed_event)
|
|
147
|
+
self.queued_events = []
|
|
148
|
+
|
|
149
|
+
def squash_queued_events(self):
|
|
150
|
+
instance = self.formatter()
|
|
151
|
+
return instance.squash_queued_events(self.queued_events)
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
def forward(self, raw_event):
|
|
155
|
+
self.initdb()
|
|
156
|
+
try:
|
|
157
|
+
if not "omd_site" in raw_event:
|
|
158
|
+
raw_event["omd_site"] = os.environ.get("OMD_SITE", "get https://omd.consol.de/docs/omd")
|
|
159
|
+
raw_event["originating_host"] = socket.gethostname()
|
|
160
|
+
raw_event["originating_fqdn"] = socket.getfqdn()
|
|
161
|
+
formatted_event = self.format_event(raw_event)
|
|
162
|
+
if not hasattr(formatted_event, "payload") and not hasattr(formatted_event, "summary"):
|
|
163
|
+
logger.critical("a formatted event must have the attributes payload and summary")
|
|
164
|
+
formatted_event = None
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.critical("formatter error: "+str(e))
|
|
167
|
+
formatted_event = None
|
|
168
|
+
|
|
169
|
+
self.forward_formatted(formatted_event)
|
|
170
|
+
|
|
171
|
+
def forward_formatted(self, formatted_event):
|
|
172
|
+
try:
|
|
173
|
+
if self.probe():
|
|
174
|
+
self.flush()
|
|
175
|
+
except Exception as e:
|
|
176
|
+
logger.critical("flush probe failed with exception <{}>")
|
|
177
|
+
|
|
178
|
+
format_exception_msg = None
|
|
179
|
+
try:
|
|
180
|
+
if formatted_event == None:
|
|
181
|
+
success = True
|
|
182
|
+
else:
|
|
183
|
+
success = self.submit(formatted_event)
|
|
184
|
+
except Exception as e:
|
|
185
|
+
success = False
|
|
186
|
+
format_exception_msg = str(e)
|
|
187
|
+
|
|
188
|
+
if success:
|
|
189
|
+
if self.baseclass_logs_summary:
|
|
190
|
+
logger.info("forwarded {}".format(formatted_event.summary))
|
|
191
|
+
else:
|
|
192
|
+
if format_exception_msg:
|
|
193
|
+
logger.critical("forward failed with exception <{}>, spooled <{}>".format(format_exception_msg, formatted_event.summary))
|
|
194
|
+
elif self.baseclass_logs_summary:
|
|
195
|
+
logger.warning("forward failed, spooled {}".format(formatted_event.summary))
|
|
196
|
+
self.spool(formatted_event)
|
|
197
|
+
|
|
198
|
+
def formatter(self):
|
|
199
|
+
try:
|
|
200
|
+
module_name = self.__class__.__name__.lower()
|
|
201
|
+
class_name = self.__class__.__name__+"Formatter"
|
|
202
|
+
formatter_module = import_module('.formatter', package='notificationforwarder.'+module_name)
|
|
203
|
+
formatter_module.logger = logger
|
|
204
|
+
formatter_class = getattr(formatter_module, class_name)
|
|
205
|
+
instance = formatter_class()
|
|
206
|
+
instance.__module_file__ = formatter_module.__file__
|
|
207
|
+
return instance
|
|
208
|
+
except ImportError:
|
|
209
|
+
logger.debug("there is no module "+module_name)
|
|
210
|
+
return None
|
|
211
|
+
except Exception as e:
|
|
212
|
+
logger.critical("formatter error: "+str(e))
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
def format_event(self, raw_event):
|
|
216
|
+
instance = self.formatter()
|
|
217
|
+
return instance.format_event(raw_event)
|
|
218
|
+
|
|
219
|
+
def connect(self):
|
|
220
|
+
return True
|
|
221
|
+
|
|
222
|
+
def disconnect(self):
|
|
223
|
+
return True
|
|
224
|
+
|
|
225
|
+
def initdb(self):
|
|
226
|
+
db_file = os.environ["OMD_ROOT"] + '/var/tmp/' + self.name + '-notifications.db'
|
|
227
|
+
self.table_name = "events_"+self.name
|
|
228
|
+
sql_create = """CREATE TABLE IF NOT EXISTS """+self.table_name+""" (
|
|
229
|
+
id INTEGER PRIMARY KEY,
|
|
230
|
+
payload TEXT NOT NULL,
|
|
231
|
+
summary TEXT NOT NULL,
|
|
232
|
+
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
233
|
+
)"""
|
|
234
|
+
try:
|
|
235
|
+
self.dbconn = sqlite3.connect(db_file)
|
|
236
|
+
self.dbcurs = self.dbconn.cursor()
|
|
237
|
+
self.dbcurs.execute(sql_create)
|
|
238
|
+
self.dbconn.commit()
|
|
239
|
+
except Exception as e:
|
|
240
|
+
logger.info("error initializing database {}: {}".format(db_file, str(e)))
|
|
241
|
+
|
|
242
|
+
def num_spooled_events(self):
|
|
243
|
+
sql_count = "SELECT COUNT(*) FROM "+self.table_name
|
|
244
|
+
spooled_events = 999999999
|
|
245
|
+
try:
|
|
246
|
+
self.dbcurs.execute(sql_count)
|
|
247
|
+
spooled_events = self.dbcurs.fetchone()[0]
|
|
248
|
+
except Exception as e:
|
|
249
|
+
logger.critical("database error "+str(e))
|
|
250
|
+
return spooled_events
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def spool(self, event):
|
|
254
|
+
sql_insert = "INSERT INTO "+self.table_name+"(payload, summary) VALUES (?, ?)"
|
|
255
|
+
try:
|
|
256
|
+
num_spooled_events = 0
|
|
257
|
+
if type(event.payload) != list:
|
|
258
|
+
text = json.dumps(event.payload)
|
|
259
|
+
summary = event.summary
|
|
260
|
+
self.dbcurs.execute(sql_insert, (text, summary))
|
|
261
|
+
self.dbconn.commit()
|
|
262
|
+
# has already been logged in forward_formatted
|
|
263
|
+
# logger.warning("spooled "+summary)
|
|
264
|
+
num_spooled_events += 1
|
|
265
|
+
else:
|
|
266
|
+
for subevent in event.payload:
|
|
267
|
+
text = json.dumps(subevent)
|
|
268
|
+
summary = event.summary.pop(0)
|
|
269
|
+
sefl.dbcurs.execute(sql_insert, (text, summary))
|
|
270
|
+
self.dbconn.commit()
|
|
271
|
+
log.warning("spooled "+summary)
|
|
272
|
+
num_spooled_events += 1
|
|
273
|
+
spooled_events = self.num_spooled_events()
|
|
274
|
+
logger.warning("spooling queue length is {}".format(spooled_events))
|
|
275
|
+
except Exception as e:
|
|
276
|
+
logger.critical("database error "+str(e))
|
|
277
|
+
logger.info(event.__dict__)
|
|
278
|
+
|
|
279
|
+
def flush(self):
|
|
280
|
+
sql_delete = "DELETE FROM "+self.table_name+" WHERE CAST(STRFTIME('%s', timestamp) AS INTEGER) < ?"
|
|
281
|
+
sql_count = "SELECT COUNT(*) FROM "+self.table_name
|
|
282
|
+
sql_select = "SELECT id, payload, summary FROM "+self.table_name+" ORDER BY id LIMIT 10"
|
|
283
|
+
sql_delete_id = "DELETE FROM "+self.table_name+" WHERE id = ?"
|
|
284
|
+
with open(os.environ["OMD_ROOT"]+"/tmp/"+self.name+"-flush.lock", "w") as lock_file:
|
|
285
|
+
try:
|
|
286
|
+
fcntl.lockf(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
287
|
+
logger.debug("flush lock set")
|
|
288
|
+
locked = True
|
|
289
|
+
except IOError as e:
|
|
290
|
+
logger.debug("flush lock failed: "+str(e))
|
|
291
|
+
locked = False
|
|
292
|
+
if locked:
|
|
293
|
+
try:
|
|
294
|
+
outdated = int(time.time() - 60*MAXAGE)
|
|
295
|
+
self.dbcurs.execute(sql_delete, (outdated,))
|
|
296
|
+
dropped = self.dbcurs.rowcount
|
|
297
|
+
if dropped:
|
|
298
|
+
logger.info("dropped {} outdated events".format(dropped))
|
|
299
|
+
last_spooled_events = 0
|
|
300
|
+
while True:
|
|
301
|
+
self.dbcurs.execute(sql_count)
|
|
302
|
+
spooled_events = self.dbcurs.fetchone()[0]
|
|
303
|
+
if spooled_events:
|
|
304
|
+
logger.info("there are {} spooled events to be re-sent".format(spooled_events))
|
|
305
|
+
else:
|
|
306
|
+
break
|
|
307
|
+
if last_spooled_events == spooled_events:
|
|
308
|
+
if spooled_events != 0:
|
|
309
|
+
logger.critical("{} spooled events could not be submitted".format(last_spooled_events))
|
|
310
|
+
break
|
|
311
|
+
else:
|
|
312
|
+
self.dbcurs.execute(sql_select)
|
|
313
|
+
id_events = self.dbcurs.fetchall()
|
|
314
|
+
for id, payload, summary in id_events:
|
|
315
|
+
event = FormattedEvent()
|
|
316
|
+
event.is_heartbeat = False
|
|
317
|
+
event.payload = json.loads(payload)
|
|
318
|
+
event.summary = summary
|
|
319
|
+
if self.submit(event):
|
|
320
|
+
self.dbcurs.execute(sql_delete_id, (id, ))
|
|
321
|
+
logger.info("delete spooled event {}".format(id))
|
|
322
|
+
self.dbconn.commit()
|
|
323
|
+
else:
|
|
324
|
+
logger.critical("event {} spooled again".format(id))
|
|
325
|
+
last_spooled_events = spooled_events
|
|
326
|
+
self.dbconn.commit()
|
|
327
|
+
except Exception as e:
|
|
328
|
+
logger.critical("database flush failed")
|
|
329
|
+
logger.critical(e)
|
|
330
|
+
else:
|
|
331
|
+
logger.debug("missed the flush lock")
|
|
332
|
+
|
|
333
|
+
def no_more_logging(self):
|
|
334
|
+
# this is called in the forwarder. If the forwarder already wrote
|
|
335
|
+
# it's own logs and writing the summary by the baseclass is not
|
|
336
|
+
# desired.
|
|
337
|
+
self.baseclass_logs_summary = False
|
|
338
|
+
|
|
339
|
+
def __del__(self):
|
|
340
|
+
try:
|
|
341
|
+
if self.dbcursor:
|
|
342
|
+
self.dbcursor.close()
|
|
343
|
+
if self.dbconn:
|
|
344
|
+
self.dbconn.commit()
|
|
345
|
+
self.dbconn.close()
|
|
346
|
+
except Exception as a:
|
|
347
|
+
# don't care, we're finished anyway
|
|
348
|
+
pass
|
|
349
|
+
|
|
350
|
+
class NotificationFormatter(metaclass=ABCMeta):
|
|
351
|
+
@abstractmethod
|
|
352
|
+
def format_event(self):
|
|
353
|
+
pass
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class FormattedEvent(metaclass=ABCMeta):
|
|
357
|
+
def __init__(self):
|
|
358
|
+
self.payload = None
|
|
359
|
+
self.summary = "empty event"
|
|
360
|
+
|
|
361
|
+
def set_payload(self, payload):
|
|
362
|
+
self.payload = payload
|
|
363
|
+
|
|
364
|
+
def set_summary(self, summary):
|
|
365
|
+
self.summary = summary
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import os
|
|
3
|
+
from notificationforwarder.baseclass import NotificationFormatter, FormattedEvent
|
|
4
|
+
|
|
5
|
+
class ExampleFormatter(NotificationFormatter):
|
|
6
|
+
|
|
7
|
+
def format_event(self, raw_event):
|
|
8
|
+
event = FormattedEvent()
|
|
9
|
+
json_payload = {
|
|
10
|
+
'timestamp': time.time(),
|
|
11
|
+
}
|
|
12
|
+
json_payload['description'] = raw_event['description']
|
|
13
|
+
if 'signature' in raw_event:
|
|
14
|
+
json_payload['signature'] = raw_event['signature']
|
|
15
|
+
event.set_payload(json_payload)
|
|
16
|
+
event.set_summary("sum: "+json_payload['description'])
|
|
17
|
+
return event
|
|
18
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import time
|
|
3
|
+
import logging
|
|
4
|
+
from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Example(NotificationForwarder):
|
|
8
|
+
def __init__(self, opts):
|
|
9
|
+
super(self.__class__, self).__init__(opts)
|
|
10
|
+
setattr(self, "username", getattr(self, "username", "guest"))
|
|
11
|
+
setattr(self, "delay", int(getattr(self, "delay", 0)))
|
|
12
|
+
setattr(self, "fail", getattr(self, "fail", None))
|
|
13
|
+
setattr(self, "signaturefile", getattr(self, "signaturefile", "/tmp/notificationforwarder_example.txt"))
|
|
14
|
+
self.parameter = "sample"
|
|
15
|
+
|
|
16
|
+
@timeout(2, error_message="submit ran into a timeout")
|
|
17
|
+
def submit(self, event):
|
|
18
|
+
time.sleep(self.delay)
|
|
19
|
+
if True: # for example if self.connect()
|
|
20
|
+
try:
|
|
21
|
+
logger.info("{} submits {}".format(self.username, event.__dict__))
|
|
22
|
+
if self.fail:
|
|
23
|
+
logger.critical("sample api does not accept the payload")
|
|
24
|
+
return False
|
|
25
|
+
elif "signature" in event.payload:
|
|
26
|
+
with open(self.signaturefile, "a") as f:
|
|
27
|
+
print(event.payload["signature"], file=f)
|
|
28
|
+
return True
|
|
29
|
+
else:
|
|
30
|
+
return True
|
|
31
|
+
except Exception as e:
|
|
32
|
+
logger.critical("sample api post had an exception: {} with payload {}".format(str(e), str(event.payload)))
|
|
33
|
+
return False
|
|
34
|
+
else:
|
|
35
|
+
logger.critical("could not connect to the ticket system")
|
|
36
|
+
return False
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import os
|
|
3
|
+
from notificationforwarder.baseclass import NotificationFormatter, FormattedEvent
|
|
4
|
+
|
|
5
|
+
class RabbitmqFormatter(NotificationFormatter):
|
|
6
|
+
|
|
7
|
+
def format_event(self, raw_event):
|
|
8
|
+
event = FormattedEvent()
|
|
9
|
+
print("formatter ", event.__dict__)
|
|
10
|
+
print("formatter ", self.__dict__)
|
|
11
|
+
print("formatter ", raw_event)
|
|
12
|
+
json_payload = {
|
|
13
|
+
'omd_site': os.environ["OMD_SITE"],
|
|
14
|
+
'platform': 'Naemon',
|
|
15
|
+
'host_name': raw_event["HOSTNAME"],
|
|
16
|
+
'notification_type': raw_event["NOTIFICATIONTYPE"],
|
|
17
|
+
'timestamp': time.time(),
|
|
18
|
+
}
|
|
19
|
+
if "SERVICEDESC" in raw_event:
|
|
20
|
+
json_payload['service_description'] = raw_event['service_description']
|
|
21
|
+
json_payload['state'] = raw_event["state"]
|
|
22
|
+
json_payload['output'] = raw_event["output"]
|
|
23
|
+
else:
|
|
24
|
+
json_payload['state'] = raw_event["state"]
|
|
25
|
+
json_payload['output'] = raw_event["output"]
|
|
26
|
+
event.set_payload(json_payload)
|
|
27
|
+
event.set_summary(json_payload)
|
|
28
|
+
return event
|
|
29
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import pika
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Rabbitmq(NotificationForwarder):
|
|
8
|
+
def __init__(self, opts):
|
|
9
|
+
super(self.__class__, self).__init__(opts)
|
|
10
|
+
setattr(self, "port", int(getattr(self, "port", 5672)))
|
|
11
|
+
setattr(self, "server", getattr(self, "server", "localhost"))
|
|
12
|
+
setattr(self, "vhost", getattr(self, "vhost", "/"))
|
|
13
|
+
setattr(self, "queue", getattr(self, "queue", "AE"))
|
|
14
|
+
setattr(self, "username", getattr(self, "username", "guest"))
|
|
15
|
+
setattr(self, "password", getattr(self, "password", "guest"))
|
|
16
|
+
|
|
17
|
+
credentials = pika.PlainCredentials(self.username, self.password)
|
|
18
|
+
self.connectionparameters = pika.ConnectionParameters(self.server, self.port, self.vhost, credentials)
|
|
19
|
+
|
|
20
|
+
def connect(self):
|
|
21
|
+
try:
|
|
22
|
+
self.connection = pika.BlockingConnection(self.connectionparameters)
|
|
23
|
+
self.channel = self.connection.channel()
|
|
24
|
+
self.channel.queue_declare(queue=self.queue, durable=True)
|
|
25
|
+
logger.debug('Connected to {}:{}'.format(
|
|
26
|
+
self.connectionparameters.host,
|
|
27
|
+
self.connectionparameters.port))
|
|
28
|
+
return True
|
|
29
|
+
except Exception as e:
|
|
30
|
+
logger.critical("connect said: "+ str(e))
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
def disconnect():
|
|
34
|
+
try:
|
|
35
|
+
self.connection.close()
|
|
36
|
+
except Exception as e:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
@timeout(30)
|
|
40
|
+
def submit(self, payload):
|
|
41
|
+
if self.connect():
|
|
42
|
+
try:
|
|
43
|
+
logger.info("submit "+payload)
|
|
44
|
+
self.channel = self.connection.channel()
|
|
45
|
+
self.channel.queue_declare(queue=self.queue, durable=True)
|
|
46
|
+
for event in payload:
|
|
47
|
+
if "service_description" in event:
|
|
48
|
+
logger.info("host: {}, service: {}, state: {}, output: {}".format(event["host_name"], event["service_description"], event["state"], event["output"]))
|
|
49
|
+
else:
|
|
50
|
+
logger.info("host: {}, state: {}, output: {}".format(event["host_name"], event["state"], event["output"]))
|
|
51
|
+
logger.debug(json.dumps(event))
|
|
52
|
+
self.channel.basic_publish(exchange='', routing_key=MQQUEUE, body=json.dumps(event))
|
|
53
|
+
return True
|
|
54
|
+
except Exception as e:
|
|
55
|
+
logger.critical("rabbitmq post had an exception: {} wit payload {}".format(str(e), str(payload)))
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from notificationforwarder.baseclass import NotificationFormatter, FormattedEvent
|
|
2
|
+
|
|
3
|
+
class SyslogFormatter(NotificationFormatter):
|
|
4
|
+
|
|
5
|
+
def format_event(self, raw_event):
|
|
6
|
+
if "service_description" in raw_event:
|
|
7
|
+
return("host: {}, service: {}, state: {}, output: {}".format(raw_event["host_name"], raw_event["service_description"], raw_event["state"], raw_event["output"]))
|
|
8
|
+
else:
|
|
9
|
+
return("host: {}, state: {}, output: {}".format(raw_event["host_name"], raw_event["state"], raw_event["output"]))
|
|
10
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import syslog
|
|
2
|
+
import logging
|
|
3
|
+
from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Syslog(NotificationForwarder):
|
|
7
|
+
def __init__(self, opts):
|
|
8
|
+
super(self.__class__, self).__init__(opts)
|
|
9
|
+
setattr(self, "port", int(getattr(self, "port", 514)))
|
|
10
|
+
setattr(self, "server", getattr(self, "server", "localhost"))
|
|
11
|
+
setattr(self, "facility", getattr(self, "facility", "log_local0"))
|
|
12
|
+
setattr(self, "priority", getattr(self, "priority", "info"))
|
|
13
|
+
if self.facility in logging.handlers.SysLogHandler.facility_names:
|
|
14
|
+
self.facility = logging.handlers.SysLogHandler.facility_names[self.facility]
|
|
15
|
+
elif "log_"+self.facility in known_facilities:
|
|
16
|
+
self.facility = logging.handlers.SysLogHandler.facility_names["log_"+self.facility]
|
|
17
|
+
else:
|
|
18
|
+
self.facility = logging.handlers.SysLogHandler.LOG_DAEMON
|
|
19
|
+
if self.priority in logging.handlers.SysLogHandler.priority_names:
|
|
20
|
+
self.priority = logging.handlers.SysLogHandler.priority_names[self.priority]
|
|
21
|
+
elif "log_"+self.priority in logging.handlers.SysLogHandler.priority_names:
|
|
22
|
+
self.priority = logging.handlers.SysLogHandler.priority_names["log_"+self.priority]
|
|
23
|
+
else:
|
|
24
|
+
self.priority = logging.handlers.SysLogHandler.LOG_INFO
|
|
25
|
+
self.syslogger = logging.getLogger('Syslogger')
|
|
26
|
+
# do not suppress anything. (normal logger and syslog have
|
|
27
|
+
# completely different levels. setLevel(NOTSET) does not work.
|
|
28
|
+
self.syslogger.setLevel(-1)
|
|
29
|
+
handler = logging.handlers.SysLogHandler(address=(self.server, self.port), facility=self.facility)
|
|
30
|
+
self.syslogger.addHandler(handler)
|
|
31
|
+
|
|
32
|
+
@timeout(30)
|
|
33
|
+
def submit(self, payload):
|
|
34
|
+
try:
|
|
35
|
+
logger.info("submit "+payload)
|
|
36
|
+
self.syslogger.log(self.priority, payload)
|
|
37
|
+
return True
|
|
38
|
+
except Exception as e:
|
|
39
|
+
logger.critical("syslog forwarding had an error: {}".format(str(e)))
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Split1(NotificationForwarder):
|
|
5
|
+
def __init__(self, opts):
|
|
6
|
+
super(self.__class__, self).__init__(opts)
|
|
7
|
+
self.url = "https://split1.com"
|
|
8
|
+
|
|
9
|
+
@timeout(30)
|
|
10
|
+
def submit(self, payload):
|
|
11
|
+
logger.info("forwarder "+self.__module_file__)
|
|
12
|
+
return True
|
|
13
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from notificationforwarder.baseclass import NotificationFormatter, FormattedEvent
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Split2Formatter(NotificationFormatter):
|
|
5
|
+
|
|
6
|
+
def __init__(self):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
def format_event(self, raw_event):
|
|
10
|
+
logger.info("formatter "+self.__module_file__)
|
|
11
|
+
event = FormattedEvent()
|
|
12
|
+
event.payload = str(raw_event)
|
|
13
|
+
event.summary = "_".join(["{}={}".format(k, raw_event) for k in raw_event])
|
|
14
|
+
return event
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Split2(NotificationForwarder):
|
|
5
|
+
def __init__(self, opts):
|
|
6
|
+
super(self.__class__, self).__init__(opts)
|
|
7
|
+
self.url = "https://split1.com"
|
|
8
|
+
|
|
9
|
+
@timeout(30)
|
|
10
|
+
def submit(self, payload):
|
|
11
|
+
logger.info("forwarder "+self.__module_file__)
|
|
12
|
+
return True
|
|
13
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from notificationforwarder.baseclass import NotificationFormatter, FormattedEvent
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Split3Formatter(NotificationFormatter):
|
|
5
|
+
|
|
6
|
+
def __init__(self):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
def format_event(self, raw_event):
|
|
10
|
+
logger.info("formatter "+self.__module_file__)
|
|
11
|
+
event = FormattedEvent()
|
|
12
|
+
event.payload = str(raw_event)
|
|
13
|
+
event.summary = "_".join(["{}={}".format(k, raw_event) for k in raw_event])
|
|
14
|
+
return event
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Split3(NotificationForwarder):
|
|
5
|
+
def __init__(self, opts):
|
|
6
|
+
super(self.__class__, self).__init__(opts)
|
|
7
|
+
self.url = "https://split1.com"
|
|
8
|
+
|
|
9
|
+
@timeout(30)
|
|
10
|
+
def submit(self, payload):
|
|
11
|
+
logger.info("forwarder "+self.__module_file__)
|
|
12
|
+
return True
|
|
13
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from notificationforwarder.baseclass import NotificationFormatter, FormattedEvent
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Split1Formatter(NotificationFormatter):
|
|
5
|
+
|
|
6
|
+
def __init__(self):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
def format_event(self, raw_event):
|
|
10
|
+
logger.info("formatter "+self.__module_file__)
|
|
11
|
+
event = FormattedEvent()
|
|
12
|
+
event.payload = str(raw_event)
|
|
13
|
+
event.summary = "_".join(["{}={}".format(k, raw_event) for k in raw_event])
|
|
14
|
+
return event
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Split2(NotificationForwarder):
|
|
5
|
+
def __init__(self, opts):
|
|
6
|
+
super(self.__class__, self).__init__(opts)
|
|
7
|
+
self.url = "https://split1.com"
|
|
8
|
+
|
|
9
|
+
@timeout(30)
|
|
10
|
+
def submit(self, payload):
|
|
11
|
+
logger.info("forwarder "+self.__module_file__)
|
|
12
|
+
return True
|
|
13
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from notificationforwarder.baseclass import NotificationFormatter, FormattedEvent
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Split3Formatter(NotificationFormatter):
|
|
5
|
+
|
|
6
|
+
def __init__(self):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
def format_event(self, raw_event):
|
|
10
|
+
logger.info("formatter "+self.__module_file__)
|
|
11
|
+
event = FormattedEvent()
|
|
12
|
+
event.payload = str(raw_event)
|
|
13
|
+
event.summary = "_".join(["{}={}".format(k, raw_event) for k in raw_event])
|
|
14
|
+
return event
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Split3(NotificationForwarder):
|
|
5
|
+
def __init__(self, opts):
|
|
6
|
+
super(self.__class__, self).__init__(opts)
|
|
7
|
+
self.url = "https://split1.com"
|
|
8
|
+
|
|
9
|
+
@timeout(30)
|
|
10
|
+
def submit(self, payload):
|
|
11
|
+
logger.info("forwarder "+self.__module_file__)
|
|
12
|
+
return True
|
|
13
|
+
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
import hashlib, secrets
|
|
6
|
+
import notificationforwarder
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _setup():
|
|
11
|
+
omd_root = os.path.dirname(__file__)
|
|
12
|
+
os.environ["OMD_ROOT"] = omd_root
|
|
13
|
+
shutil.rmtree(omd_root+"/var", ignore_errors=True)
|
|
14
|
+
os.makedirs(omd_root+"/var/log", 0o755)
|
|
15
|
+
shutil.rmtree(omd_root+"/var", ignore_errors=True)
|
|
16
|
+
os.makedirs(omd_root+"/var/tmp", 0o755)
|
|
17
|
+
shutil.rmtree(omd_root+"/tmp", ignore_errors=True)
|
|
18
|
+
os.makedirs(omd_root+"/tmp", 0o755)
|
|
19
|
+
if os.path.exists("/tmp/notificationforwarder_example.txt"):
|
|
20
|
+
os.remove("/tmp/notificationforwarder_example.txt")
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def setup():
|
|
24
|
+
_setup()
|
|
25
|
+
yield
|
|
26
|
+
|
|
27
|
+
def get_logfile(forwarder):
|
|
28
|
+
logger_name = "notificationforwarder_"+forwarder.name
|
|
29
|
+
logger = logging.getLogger(logger_name)
|
|
30
|
+
return [h.baseFilename for h in logger.handlers if hasattr(h, "baseFilename")][0]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_example_forwarder(setup):
|
|
34
|
+
reveiveropts = {
|
|
35
|
+
"username": "i_bims",
|
|
36
|
+
"password": "dem_is_geheim"
|
|
37
|
+
}
|
|
38
|
+
example = notificationforwarder.baseclass.new("example", None, True, True, reveiveropts)
|
|
39
|
+
assert example.__class__.__name__ == "Example"
|
|
40
|
+
assert example.password == "dem_is_geheim"
|
|
41
|
+
assert example.queued_events == []
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _test_example_formatter(setup):
|
|
45
|
+
example = notificationforwarder.baseclass.new("example", None, True, True, {})
|
|
46
|
+
fexample = example.formatter()
|
|
47
|
+
assert fexample.__class__.__name__ == "ExampleFormatter"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_example_logging(setup):
|
|
51
|
+
example = notificationforwarder.baseclass.new("example", None, True, True, {})
|
|
52
|
+
logger_name = "notificationforwarder_"+example.name
|
|
53
|
+
logger = logging.getLogger(logger_name)
|
|
54
|
+
assert logger != None
|
|
55
|
+
assert logger.name == "notificationforwarder_example"
|
|
56
|
+
assert len([h for h in logger.handlers]) == 2
|
|
57
|
+
logfile = [h.baseFilename for h in logger.handlers if hasattr(h, "baseFilename")][0]
|
|
58
|
+
assert logfile.endswith("notificationforwarder_example.log")
|
|
59
|
+
|
|
60
|
+
example = notificationforwarder.baseclass.new("example", "2", True, True, {})
|
|
61
|
+
logger_name = "notificationforwarder_"+example.name+"_"+example.tag
|
|
62
|
+
logger = logging.getLogger(logger_name)
|
|
63
|
+
assert logger != None
|
|
64
|
+
assert logger.name == "notificationforwarder_example_2"
|
|
65
|
+
assert len(logger.handlers) == 2
|
|
66
|
+
logfile = [h.baseFilename for h in logger.handlers if hasattr(h, "baseFilename")][0]
|
|
67
|
+
assert logfile.endswith("notificationforwarder_example_2.log")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _test_example_formatter_format_event(setup):
|
|
71
|
+
example = notificationforwarder.baseclass.new("example", None, True, True, {})
|
|
72
|
+
fexample = example.formatter()
|
|
73
|
+
raw_event = {
|
|
74
|
+
"description": "halo i bims 1 alarm vong naemon her",
|
|
75
|
+
}
|
|
76
|
+
event = fexample.format_event(raw_event)
|
|
77
|
+
assert event.summary == "this is an example"
|
|
78
|
+
assert event.payload["description"] == "halo i bims 1 alarm vong naemon her"
|
|
79
|
+
assert event.payload["timestamp"] == pytest.approx(time.time(), abs=5)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_example_forwarder_forward(setup):
|
|
83
|
+
reveiveropts = {
|
|
84
|
+
"username": "i_bims",
|
|
85
|
+
"password": "i_bims_1_i_bims",
|
|
86
|
+
}
|
|
87
|
+
eventopts = {
|
|
88
|
+
"description": "halo i bims 1 alarm vong naemon her",
|
|
89
|
+
}
|
|
90
|
+
example = notificationforwarder.baseclass.new("example", None, True, True, reveiveropts)
|
|
91
|
+
example.forward(eventopts)
|
|
92
|
+
log = open(get_logfile(example)).read()
|
|
93
|
+
assert "INFO - i_bims submits" in log
|
|
94
|
+
assert "'description': 'halo i bims 1 alarm vong naemon her'" in log
|
|
95
|
+
# this is the global log, written by the baseclass
|
|
96
|
+
assert "INFO - forwarded sum: halo i bims 1 alarm vong naemon her" in log
|
|
97
|
+
|
|
98
|
+
_setup() # delete logfile
|
|
99
|
+
# we need to reinitialize, because the logger has the (deleted) file
|
|
100
|
+
# still open and further writes would end up in nirvana.
|
|
101
|
+
example = notificationforwarder.baseclass.new("example", None, True, True, reveiveropts)
|
|
102
|
+
eventopts = {
|
|
103
|
+
"description": "halo i bims 1 alarm vong naemon her again",
|
|
104
|
+
}
|
|
105
|
+
example.no_more_logging()
|
|
106
|
+
example.forward(eventopts)
|
|
107
|
+
log = open(get_logfile(example)).read()
|
|
108
|
+
# the formatter's logs are still there
|
|
109
|
+
assert "INFO - i_bims submits" in log
|
|
110
|
+
assert "'description': 'halo i bims 1 alarm vong naemon her again'" in log
|
|
111
|
+
# but not the baseclasse's log
|
|
112
|
+
assert "INFO - forwarded sum: halo i bims 1 alarm vong naemon her" not in log
|
|
113
|
+
|
|
114
|
+
def test_example_forwarder_forward_success(setup):
|
|
115
|
+
reveiveropts = {
|
|
116
|
+
"username": "i_bims",
|
|
117
|
+
"password": "i_bims_1_i_bims",
|
|
118
|
+
}
|
|
119
|
+
signature = hashlib.sha256(secrets.token_bytes(32)).hexdigest()
|
|
120
|
+
eventopts = {
|
|
121
|
+
"description": "halo i bims 1 alarm vong naemon her",
|
|
122
|
+
"signature": signature,
|
|
123
|
+
}
|
|
124
|
+
example = notificationforwarder.baseclass.new("example", None, True, True, reveiveropts)
|
|
125
|
+
example.forward(eventopts)
|
|
126
|
+
assert os.path.exists(example.signaturefile)
|
|
127
|
+
sig = open(example.signaturefile).read().strip()
|
|
128
|
+
assert sig == signature
|
|
129
|
+
|
|
130
|
+
def test_example_forwarder_forward_timeout(setup):
|
|
131
|
+
signatures = [
|
|
132
|
+
hashlib.sha256(secrets.token_bytes(32)).hexdigest(),
|
|
133
|
+
hashlib.sha256(secrets.token_bytes(32)).hexdigest(),
|
|
134
|
+
hashlib.sha256(secrets.token_bytes(32)).hexdigest(),
|
|
135
|
+
]
|
|
136
|
+
reveiveropts = {
|
|
137
|
+
"username": "i_bims",
|
|
138
|
+
"password": "i_bims_1_i_bims",
|
|
139
|
+
"delay": 60,
|
|
140
|
+
}
|
|
141
|
+
eventopts = {
|
|
142
|
+
"description": "halo i bims 1 alarm vong naemon her",
|
|
143
|
+
"signature": signatures[0],
|
|
144
|
+
}
|
|
145
|
+
example = notificationforwarder.baseclass.new("example", None, True, True, reveiveropts)
|
|
146
|
+
example.forward(eventopts)
|
|
147
|
+
log = open(get_logfile(example)).read()
|
|
148
|
+
# this is the global log, written by the baseclass
|
|
149
|
+
assert "submit ran into a timeout" in log
|
|
150
|
+
assert "spooled <sum: halo i bims 1 alarm vong naemon her>" in log
|
|
151
|
+
assert "WARNING - spooling queue length is 1" in log
|
|
152
|
+
eventopts = {
|
|
153
|
+
"description": "halo i bim au 1 alarm vong naemon her",
|
|
154
|
+
"signature": signatures[1],
|
|
155
|
+
}
|
|
156
|
+
example = notificationforwarder.baseclass.new("example", None, True, True, reveiveropts)
|
|
157
|
+
example.forward(eventopts)
|
|
158
|
+
log = open(get_logfile(example)).read()
|
|
159
|
+
assert "spooled <sum: halo i bim au 1 alarm vong naemon her>" in log
|
|
160
|
+
assert "WARNING - spooling queue length is 2" in log
|
|
161
|
+
|
|
162
|
+
# now the last two events were spooled and are in the database
|
|
163
|
+
reveiveropts = {
|
|
164
|
+
"username": "i_bims",
|
|
165
|
+
"password": "i_bims_1_i_bims",
|
|
166
|
+
"delay": 0,
|
|
167
|
+
}
|
|
168
|
+
eventopts = {
|
|
169
|
+
"description": "i druecke dem spuelung",
|
|
170
|
+
"signature": signatures[2],
|
|
171
|
+
}
|
|
172
|
+
example = notificationforwarder.baseclass.new("example", None, True, True, reveiveropts)
|
|
173
|
+
example.forward(eventopts)
|
|
174
|
+
log = open(get_logfile(example)).read()
|
|
175
|
+
assert re.search(r'.*i_bims submits.*i druecke dem spuelung.*', log, re.MULTILINE)
|
|
176
|
+
assert "forwarded sum: i druecke dem spuelung" in log
|
|
177
|
+
assert "DEBUG - flush lock set" in log
|
|
178
|
+
assert "INFO - there are 2 spooled events to be re-sent" in log
|
|
179
|
+
assert "INFO - delete spooled event 1" in log
|
|
180
|
+
assert "INFO - delete spooled event 2" in log
|
|
181
|
+
assert re.search(r'.*i_bims submits.*halo i bims 1 alarm vong naemon her.*', log, re.MULTILINE)
|
|
182
|
+
assert re.search(r'.*i_bims submits.*halo i bim au 1 alarm vong naemon her.*', log, re.MULTILINE)
|
|
183
|
+
sigs = [l.strip() for l in open(example.signaturefile).readlines()]
|
|
184
|
+
# flushing first, then the new event
|
|
185
|
+
assert sigs == signatures
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import inspect
|
|
3
|
+
import sys
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import shutil
|
|
7
|
+
import hashlib, secrets
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
omd_root = os.path.dirname(__file__)
|
|
11
|
+
os.environ["OMD_ROOT"] = omd_root
|
|
12
|
+
if not [p for p in sys.path if "pythonpath" in p]:
|
|
13
|
+
sys.path.append(os.environ["OMD_ROOT"]+"/pythonpath/local/lib/python")
|
|
14
|
+
sys.path.append(os.environ["OMD_ROOT"]+"/pythonpath/lib/python")
|
|
15
|
+
print("PYTHONPATH="+":".join(sys.path))
|
|
16
|
+
import notificationforwarder.baseclass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _setup():
|
|
20
|
+
omd_root = os.path.dirname(__file__)
|
|
21
|
+
os.environ["OMD_ROOT"] = omd_root
|
|
22
|
+
shutil.rmtree(omd_root+"/var", ignore_errors=True)
|
|
23
|
+
os.makedirs(omd_root+"/var/log", 0o755)
|
|
24
|
+
shutil.rmtree(omd_root+"/var", ignore_errors=True)
|
|
25
|
+
os.makedirs(omd_root+"/var/tmp", 0o755)
|
|
26
|
+
shutil.rmtree(omd_root+"/tmp", ignore_errors=True)
|
|
27
|
+
os.makedirs(omd_root+"/tmp", 0o755)
|
|
28
|
+
if os.path.exists("/tmp/notificationforwarder_example.txt"):
|
|
29
|
+
os.remove("/tmp/notificationforwarder_example.txt")
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def setup():
|
|
33
|
+
_setup()
|
|
34
|
+
yield
|
|
35
|
+
|
|
36
|
+
def get_logfile(forwarder):
|
|
37
|
+
logger_name = "notificationforwarder_"+forwarder.name
|
|
38
|
+
logger = logging.getLogger(logger_name)
|
|
39
|
+
return [h.baseFilename for h in logger.handlers if hasattr(h, "baseFilename")][0]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_split1_forwarder(setup):
|
|
43
|
+
# lib local/lib
|
|
44
|
+
# forwarder formatter
|
|
45
|
+
print(sys.path)
|
|
46
|
+
reveiveropts = {
|
|
47
|
+
"username": "i_bims",
|
|
48
|
+
"password": "dem_is_geheim"
|
|
49
|
+
}
|
|
50
|
+
eventopts = {
|
|
51
|
+
"description": "halo i bims 1 alarm vong naemon her",
|
|
52
|
+
}
|
|
53
|
+
split1 = notificationforwarder.baseclass.new("split1", None, True, True, reveiveropts)
|
|
54
|
+
assert split1.__class__.__name__ == "Split1"
|
|
55
|
+
assert split1.__module_file__.endswith("pythonpath/lib/python/notificationforwarder/split1/forwarder.py")
|
|
56
|
+
assert split1.password == "dem_is_geheim"
|
|
57
|
+
assert split1.queued_events == []
|
|
58
|
+
fsplit1 = split1.formatter()
|
|
59
|
+
assert fsplit1.__class__.__name__ == "Split1Formatter"
|
|
60
|
+
assert fsplit1.__module_file__.endswith("pythonpath/local/lib/python/notificationforwarder/split1/formatter.py")
|
|
61
|
+
split1.forward(eventopts)
|
|
62
|
+
log = open(get_logfile(split1)).read()
|
|
63
|
+
print(log)
|
|
64
|
+
assert re.search(r'forwarder '+split1.__module_file__, log)
|
|
65
|
+
assert re.search(r'formatter '+fsplit1.__module_file__, log)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_split2_forwarder(setup):
|
|
69
|
+
# lib local/lib
|
|
70
|
+
# forwarder forwarder
|
|
71
|
+
# formatter
|
|
72
|
+
print(sys.path)
|
|
73
|
+
reveiveropts = {
|
|
74
|
+
"username": "i_bims",
|
|
75
|
+
"password": "dem_is_geheim"
|
|
76
|
+
}
|
|
77
|
+
eventopts = {
|
|
78
|
+
"description": "halo i bims 1 alarm vong naemon her",
|
|
79
|
+
}
|
|
80
|
+
split2 = notificationforwarder.baseclass.new("split2", None, True, True, reveiveropts)
|
|
81
|
+
assert split2.__class__.__name__ == "Split2"
|
|
82
|
+
assert split2.__module_file__.endswith("pythonpath/local/lib/python/notificationforwarder/split2/forwarder.py")
|
|
83
|
+
assert split2.password == "dem_is_geheim"
|
|
84
|
+
assert split2.queued_events == []
|
|
85
|
+
fsplit2 = split2.formatter()
|
|
86
|
+
assert fsplit2.__class__.__name__ == "Split2Formatter"
|
|
87
|
+
assert fsplit2.__module_file__.endswith("pythonpath/lib/python/notificationforwarder/split2/formatter.py")
|
|
88
|
+
split2.forward(eventopts)
|
|
89
|
+
log = open(get_logfile(split2)).read()
|
|
90
|
+
print(log)
|
|
91
|
+
assert re.search(r'forwarder '+split2.__module_file__, log)
|
|
92
|
+
assert re.search(r'formatter '+fsplit2.__module_file__, log)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_split3_forwarder(setup):
|
|
96
|
+
# lib local/lib
|
|
97
|
+
# forwarder forwarder
|
|
98
|
+
# formatter formatter
|
|
99
|
+
print(sys.path)
|
|
100
|
+
reveiveropts = {
|
|
101
|
+
"username": "i_bims",
|
|
102
|
+
"password": "dem_is_geheim"
|
|
103
|
+
}
|
|
104
|
+
eventopts = {
|
|
105
|
+
"description": "halo i bims 1 alarm vong naemon her",
|
|
106
|
+
}
|
|
107
|
+
split3 = notificationforwarder.baseclass.new("split3", None, True, True, reveiveropts)
|
|
108
|
+
assert split3.__class__.__name__ == "Split3"
|
|
109
|
+
assert split3.__module_file__.endswith("pythonpath/local/lib/python/notificationforwarder/split3/forwarder.py")
|
|
110
|
+
assert split3.password == "dem_is_geheim"
|
|
111
|
+
assert split3.queued_events == []
|
|
112
|
+
fsplit3 = split3.formatter()
|
|
113
|
+
assert fsplit3.__class__.__name__ == "Split3Formatter"
|
|
114
|
+
assert fsplit3.__module_file__.endswith("pythonpath/local/lib/python/notificationforwarder/split3/formatter.py")
|
|
115
|
+
split3.forward(eventopts)
|
|
116
|
+
log = open(get_logfile(split3)).read()
|
|
117
|
+
print(log)
|
|
118
|
+
assert re.search(r'forwarder '+split3.__module_file__, log)
|
|
119
|
+
assert re.search(r'formatter '+fsplit3.__module_file__, log)
|
|
120
|
+
|
|
121
|
+
|
|
File without changes
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
2023-10-10 18:33:50,168 3511560 - DEBUG - Logger initialized.
|
|
2
|
+
2023-10-10 18:33:50,197 3511560 - INFO - formatter /home/lausser/git/noteventificationforhandlerwarder/notificationforwarder/tests/pythonpath/local/lib/python/notificationforwarder/split3/formatter.py
|
|
3
|
+
2023-10-10 18:33:50,197 3511560 - DEBUG - flush lock set
|
|
4
|
+
2023-10-10 18:33:50,197 3511560 - INFO - forwarder /home/lausser/git/noteventificationforhandlerwarder/notificationforwarder/tests/pythonpath/local/lib/python/notificationforwarder/split3/forwarder.py
|
|
5
|
+
2023-10-10 18:33:50,198 3511560 - INFO - forwarded description={'description': 'halo i bims 1 alarm vong naemon her', 'omd_site': 'my_devel_site', 'originating_host': 'HULSE', 'originating_fqdn': 'HULSE.consol.lan'}_omd_site={'description': 'halo i bims 1 alarm vong naemon her', 'omd_site': 'my_devel_site', 'originating_host': 'HULSE', 'originating_fqdn': 'HULSE.consol.lan'}_originating_host={'description': 'halo i bims 1 alarm vong naemon her', 'omd_site': 'my_devel_site', 'originating_host': 'HULSE', 'originating_fqdn': 'HULSE.consol.lan'}_originating_fqdn={'description': 'halo i bims 1 alarm vong naemon her', 'omd_site': 'my_devel_site', 'originating_host': 'HULSE', 'originating_fqdn': 'HULSE.consol.lan'}
|
|
Binary file
|