[mod] build-System [add] localization [mod] check:file_state [mod] check:http_request
This commit is contained in:
parent
36412a34af
commit
909314fad7
|
|
@ -4,8 +4,8 @@
|
||||||
"checks": [
|
"checks": [
|
||||||
{
|
{
|
||||||
"name": "test",
|
"name": "test",
|
||||||
"threshold": 1,
|
"threshold": 3,
|
||||||
"annoy": true,
|
"annoy": false,
|
||||||
"schedule": {
|
"schedule": {
|
||||||
"regular_interval": 15,
|
"regular_interval": 15,
|
||||||
"attentive_interval": 5
|
"attentive_interval": 5
|
||||||
|
|
@ -22,11 +22,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"kind": "file_timestamp",
|
"kind": "file_state",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"path": "/tmp/test",
|
"path": "/tmp/test",
|
||||||
"warning_age": 60,
|
"exist": true,
|
||||||
"critical_age": 120
|
"age_threshold": 60,
|
||||||
|
"size_threshold": 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
25
source/localization/de.json
Normal file
25
source/localization/de.json
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"conditions.unknown": "unbekannt",
|
||||||
|
"conditions.ok": "in Ordnung",
|
||||||
|
"conditions.warning": "bedenklich",
|
||||||
|
"conditions.critical": "kritisch",
|
||||||
|
"help.title": "Heimdall — Werkzeug zur System-Überwachungs",
|
||||||
|
"help.args.conf_path": "Pfad zur Konfigurations-Datei",
|
||||||
|
"help.args.state_path": "Pfad zur Zustands-Datei, welche Daten über vorherige Prüfungen enthält; Standard-Wert: Pfad im temporären Verzeichnis des Systems mit eindeutigem Namen in Bezug auf den Pfad zur Konfigurations-Datei",
|
||||||
|
"help.args.send_ok_notifications": "ob '{{condition_name}}'-Zustände gemeldet werden sollen",
|
||||||
|
"help.args.language": "welche Sprache verwendet werden soll (statt der in den Umgebungs-Variablen gesetzten)",
|
||||||
|
"help.args.erase_state": "ob der Zustand bei Start gelöscht werden soll; das hat zur Folge, dass alle Prüfungen unmittelbar durchgeführt werden",
|
||||||
|
"help.args.show_schema": "nur das hmdl-JSON-Schema zur Standard-Ausgabe schreiben und beenden",
|
||||||
|
"help.args.expose_full_conf": "nur die erweiterte Konfiguration zur Standard-Ausgabe schreiben und beenden (nützlich für Fehlersuche)",
|
||||||
|
"checks.file_state.exists": "Datei existiert (soll aber nicht)",
|
||||||
|
"checks.file_state.missing": "Datei existiert nicht (soll aber)",
|
||||||
|
"checks.file_state.timestamp_implausible": "Datei ist scheinbar aus der Zukunft",
|
||||||
|
"checks.file_state.too_old": "Datei ist zu alt",
|
||||||
|
"checks.file_state.too_big": "Datei ist zu groß",
|
||||||
|
"checks.http_request.request_failed": "HTTP-Abfrage fehlgeschlagen",
|
||||||
|
"checks.http_request.status_code_mismatch": "Status-Code {{status_code_actual}} stimmt nicht mit dem erwarteten Wert {{status_code_expected}} überein",
|
||||||
|
"checks.http_request.header_value_mismatch": "Header-Wert für Schlüssel '{{key}}' '{{value_actual}}' stimmt nicht mit erwartetem Wert {{value_expected}} überein",
|
||||||
|
"checks.http_request.body_misses_part": "Rumpf enthält nicht den erwarteten Teil '{{part}}'",
|
||||||
|
"misc.state_file_path": "Pfad zur Zustands-Datei",
|
||||||
|
"misc.check_procedure_failed": "Prüfungs-Prozedur fehlgeschlagen"
|
||||||
|
}
|
||||||
25
source/localization/en.json
Normal file
25
source/localization/en.json
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"conditions.unknown": "unknown",
|
||||||
|
"conditions.ok": "ok",
|
||||||
|
"conditions.warning": "concerning",
|
||||||
|
"conditions.critical": "critical",
|
||||||
|
"help.title": "Heimdall — Monitoring Tool",
|
||||||
|
"help.args.conf_path": "path to the configuration file",
|
||||||
|
"help.args.state_path": "path to the state file, which contains information about the recent checks; default: file in temporary directory, unique for the conf-path input",
|
||||||
|
"help.args.send_ok_notifications": "whether an '{{condition_name}}' condition shall be reported",
|
||||||
|
"help.args.language": "language to use (instead of the language, set in the environment variables)",
|
||||||
|
"help.args.erase_state": "whether the state shall be deleted on start; this will cause that all checks are executed immediatly",
|
||||||
|
"help.args.show_schema": "print the hmdl JSON schema to stdout and exit",
|
||||||
|
"help.args.expose_full_conf": "only print the extended configuration to stdout and exit (useful for debugging)",
|
||||||
|
"checks.file_state.exists": "file exists (but shall not)",
|
||||||
|
"checks.file_state.missing": "file does not exist (but shall)",
|
||||||
|
"checks.file_state.timestamp_implausible": "file is apparently from the future",
|
||||||
|
"checks.file_state.too_old": "file is too old",
|
||||||
|
"checks.file_state.too_big": "file is too big",
|
||||||
|
"checks.http_request.request_failed": "HTTP request failed",
|
||||||
|
"checks.http_request.status_code_mismatch": "actual status code {{status_code_actual}} does not match expected value {{status_code_expected}}",
|
||||||
|
"checks.http_request.header_value_mismatch": "actual header value for key '{{key}}' '{{value_actual}}' and does not match the expected value {{value_expected}}",
|
||||||
|
"checks.http_request.body_misses_part": "body does not contain the expected part '{{part}}'",
|
||||||
|
"misc.state_file_path": "state file path",
|
||||||
|
"misc.check_procedure_failed": "check procedure failed"
|
||||||
|
}
|
||||||
|
|
@ -34,8 +34,8 @@ class implementation_notification_channel_console(interface_notification_channel
|
||||||
"[{{title}}] <{{condition}}> {{info}}\n",
|
"[{{title}}] <{{condition}}> {{info}}\n",
|
||||||
{
|
{
|
||||||
"title": data["title"],
|
"title": data["title"],
|
||||||
"condition": condition_encode(state["condition"]),
|
"condition": condition_show(state["condition"]),
|
||||||
"info": ("(no info)" if (info is None) else info),
|
"info": _json.dumps(info, indent = "\t", ensure_ascii = False),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -81,11 +81,7 @@ class implementation_notification_channel_email(interface_notification_channel):
|
||||||
parameters["access"]["password"]
|
parameters["access"]["password"]
|
||||||
)
|
)
|
||||||
message = MIMEText(
|
message = MIMEText(
|
||||||
string_coin(
|
_json.dumps(info, indent = "\t", ensure_ascii = False)
|
||||||
("(no info)" if (info is None) else info),
|
|
||||||
{
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
message["Subject"] = string_coin(
|
message["Subject"] = string_coin(
|
||||||
"{{tags}} {{title}}",
|
"{{tags}} {{title}}",
|
||||||
|
|
@ -96,7 +92,7 @@ class implementation_notification_channel_email(interface_notification_channel):
|
||||||
(
|
(
|
||||||
parameters["tags"]
|
parameters["tags"]
|
||||||
+
|
+
|
||||||
[condition_encode(state["condition"])]
|
[condition_show(state["condition"])]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
@ -34,16 +34,12 @@ class implementation_notification_channel_libnotify(interface_notification_chann
|
||||||
'''
|
'''
|
||||||
def notify(self, parameters, name, data, state, info):
|
def notify(self, parameters, name, data, state, info):
|
||||||
def condition_translate(condition):
|
def condition_translate(condition):
|
||||||
if (condition == enum_condition.unknown):
|
return {
|
||||||
return "normal"
|
enum_condition.unknown: "normal",
|
||||||
elif (condition == enum_condition.ok):
|
enum_condition.ok: "low",
|
||||||
return "low"
|
enum_condition.warning: "normal",
|
||||||
elif (condition == enum_condition.warning):
|
enum_condition.critical: "critical",
|
||||||
return "normal"
|
}[condition]
|
||||||
elif (condition == enum_condition.critical):
|
|
||||||
return "critical"
|
|
||||||
else:
|
|
||||||
raise ValueError("impossible condition")
|
|
||||||
parts = []
|
parts = []
|
||||||
parts.append(
|
parts.append(
|
||||||
"notify-send"
|
"notify-send"
|
||||||
|
|
@ -84,15 +80,11 @@ class implementation_notification_channel_libnotify(interface_notification_chann
|
||||||
"{{condition}} | {{title}}",
|
"{{condition}} | {{title}}",
|
||||||
{
|
{
|
||||||
"title": data["title"],
|
"title": data["title"],
|
||||||
"condition": condition_encode(state["condition"]).upper(),
|
"condition": condition_show(state["condition"]).upper(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
## body
|
## body
|
||||||
parts.append(
|
parts.append(_json.dumps(info, ensure_ascii = False))
|
||||||
"(no info)"
|
|
||||||
if (info == "") else
|
|
||||||
info
|
|
||||||
)
|
|
||||||
_subprocess.run(parts)
|
_subprocess.run(parts)
|
||||||
|
|
||||||
|
|
@ -8,6 +8,9 @@ class interface_check_kind(object):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
return record<condition:enum_condition,info:any>
|
||||||
|
'''
|
||||||
def run(self, parameters):
|
def run(self, parameters):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
174
source/logic/checks/file_state.py
Normal file
174
source/logic/checks/file_state.py
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
class implementation_check_kind_file_state(interface_check_kind):
|
||||||
|
|
||||||
|
'''
|
||||||
|
[implementation]
|
||||||
|
'''
|
||||||
|
def parameters_schema(self):
|
||||||
|
'''
|
||||||
|
- exists (shall, must, must not)
|
||||||
|
- size (max)
|
||||||
|
- age (max)
|
||||||
|
- contains
|
||||||
|
'''
|
||||||
|
return {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": False,
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"strict": {
|
||||||
|
"description": "whether a violation of this check shall be leveled as critical instead of concerning",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True
|
||||||
|
},
|
||||||
|
"exist": {
|
||||||
|
"description": "whether the file is supposed to exist or not",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True
|
||||||
|
},
|
||||||
|
"age_threshold": {
|
||||||
|
"description": "in seconds; ignored if 'exist' is set to false",
|
||||||
|
"type": ["null", "integer"],
|
||||||
|
"exclusiveMinimum": 0,
|
||||||
|
"default": None,
|
||||||
|
},
|
||||||
|
"size_threshold": {
|
||||||
|
"description": "in bytes; ignored if 'exist' is set to false",
|
||||||
|
"type": "integer",
|
||||||
|
"exclusiveMinimum": 0,
|
||||||
|
"default": None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"path"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
[implementation]
|
||||||
|
'''
|
||||||
|
def normalize_conf_node(self, node):
|
||||||
|
if ("path" not in node):
|
||||||
|
raise ValueError("missing mandatory field 'path'")
|
||||||
|
else:
|
||||||
|
return dict_merge(
|
||||||
|
{
|
||||||
|
"strict": True,
|
||||||
|
"exist": True,
|
||||||
|
"age_threshold": None,
|
||||||
|
"size_threshold": None,
|
||||||
|
},
|
||||||
|
node
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
[implementation]
|
||||||
|
'''
|
||||||
|
def run(self, parameters):
|
||||||
|
exists = _os.path.exists(parameters["path"])
|
||||||
|
if (parameters["exist"]):
|
||||||
|
if (parameters["exist"] and not exists):
|
||||||
|
return {
|
||||||
|
"condition": (
|
||||||
|
enum_condition.critical
|
||||||
|
if parameters["strict"] else
|
||||||
|
enum_condition.warning
|
||||||
|
),
|
||||||
|
"info": {
|
||||||
|
"path": parameters["path"],
|
||||||
|
"faults": [
|
||||||
|
translation_get("checks.file_state.missing"),
|
||||||
|
],
|
||||||
|
"data": {
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
faults = []
|
||||||
|
data = {}
|
||||||
|
stat = _os.stat(parameters["path"])
|
||||||
|
## age
|
||||||
|
if True:
|
||||||
|
if (parameters["age_threshold"] is None):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
timestamp_this = get_current_timestamp()
|
||||||
|
timestamp_that = int(stat.st_atime)
|
||||||
|
age = (timestamp_this - timestamp_that)
|
||||||
|
if (age >= 0):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
faults.append(translation_get("checks.file_state.timestamp_implausible"))
|
||||||
|
if (age <= parameters["age_threshold"]):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
faults.append(translation_get("checks.file_state.too_old"))
|
||||||
|
data = dict_merge(
|
||||||
|
data,
|
||||||
|
{
|
||||||
|
"timestamp_of_checking_instance": timestamp_this,
|
||||||
|
"timestamp_of_file": timestamp_that,
|
||||||
|
"age_value_in_seconds": age,
|
||||||
|
"age_threshold_in_seconds": parameters["age_threshold"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
## size
|
||||||
|
if True:
|
||||||
|
if (parameters["size_threshold"] is None):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
size = stat.st_size
|
||||||
|
if (size <= parameters["size_threshold"]):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
faults.append(translation_get("checks.file_state.too_big"))
|
||||||
|
data = dict_merge(
|
||||||
|
data,
|
||||||
|
{
|
||||||
|
"size_value_in_bytes": size,
|
||||||
|
"size_threshold_in_bytes": parameters["size_threshold_in_bytes"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"condition": (
|
||||||
|
enum_condition.ok
|
||||||
|
if (len(faults) == 0) else
|
||||||
|
(
|
||||||
|
enum_condition.critical
|
||||||
|
if parameters["strict"] else
|
||||||
|
enum_condition.warning
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"info": {
|
||||||
|
"path": parameters["path"],
|
||||||
|
"faults": faults,
|
||||||
|
"data": data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
if (not exists):
|
||||||
|
return {
|
||||||
|
"condition": (
|
||||||
|
enum_condition.critical
|
||||||
|
if parameters["strict"] else
|
||||||
|
enum_condition.warning
|
||||||
|
),
|
||||||
|
"info": {
|
||||||
|
"path": parameters["path"],
|
||||||
|
"faults": [
|
||||||
|
translation_get("checks.file_state.exists")
|
||||||
|
],
|
||||||
|
"data": {
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"condition": enum_condition.ok,
|
||||||
|
"info": {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -75,7 +75,10 @@ class implementation_check_kind_file_timestamp(interface_check_kind):
|
||||||
if (not _os.path.exists(parameters["path"])):
|
if (not _os.path.exists(parameters["path"])):
|
||||||
return {
|
return {
|
||||||
"condition": condition_decode(parameters["condition_on_missing"]),
|
"condition": condition_decode(parameters["condition_on_missing"]),
|
||||||
"info": "file is missing"
|
"info": {
|
||||||
|
"path": parameters["path"],
|
||||||
|
"flaw": translation_get("checks.file_timetsamp.missing"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
result = _os.stat(parameters["path"])
|
result = _os.stat(parameters["path"])
|
||||||
|
|
@ -84,14 +87,12 @@ class implementation_check_kind_file_timestamp(interface_check_kind):
|
||||||
if (age < 0):
|
if (age < 0):
|
||||||
return {
|
return {
|
||||||
"condition": condition_decode(parameters["condition_on_implausible"]),
|
"condition": condition_decode(parameters["condition_on_implausible"]),
|
||||||
"info": string_coin(
|
"info": {
|
||||||
"file is apparently from the future; timestamp of checking instance: {{timestamp_this}}; timestamp of file: {{timestamp_that}} (age in seconds: {{age}})",
|
"path": parameters["path"],
|
||||||
{
|
"flaw": translation_get("checks.file_timetsamp.implausible"),
|
||||||
"timestamp_this": timestamp,
|
"timestamp_of_checking_instance": timestamp,
|
||||||
"timestamp_that": result.st_atime,
|
"timestamp_of_file": result.st_atime,
|
||||||
"age": ("%u" % age),
|
},
|
||||||
}
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
if ((age > 0) and (age <= parameters["warning_age"])):
|
if ((age > 0) and (age <= parameters["warning_age"])):
|
||||||
|
|
@ -101,14 +102,13 @@ class implementation_check_kind_file_timestamp(interface_check_kind):
|
||||||
elif (age > parameters["critical_age"]):
|
elif (age > parameters["critical_age"]):
|
||||||
condition = enum_condition.critical
|
condition = enum_condition.critical
|
||||||
else:
|
else:
|
||||||
raise ValueError("impossible state")
|
raise ValueError("impossible")
|
||||||
return {
|
return {
|
||||||
"condition": condition,
|
"condition": condition,
|
||||||
"info": string_coin(
|
"info": {
|
||||||
"age in seconds: {{age}}",
|
"path": parameters["path"],
|
||||||
{
|
"flaw": translation_get("checks.file_timetsamp.too_old"),
|
||||||
"age": ("%u" % age),
|
"age_in_seconds": ("%u" % age),
|
||||||
}
|
},
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,11 +59,11 @@ class implementation_check_kind_http_request(interface_check_kind):
|
||||||
"required": [
|
"required": [
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"as_warning": {
|
"strict": {
|
||||||
"description": "whether a violation of this check shall be exposed as warning instead of critical; default: false",
|
"description": "whether a violation of this check shall be leveled as critical instead of concerning",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": False
|
"default": True
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"request"
|
"request"
|
||||||
|
|
@ -75,7 +75,7 @@ class implementation_check_kind_http_request(interface_check_kind):
|
||||||
[implementation]
|
[implementation]
|
||||||
'''
|
'''
|
||||||
def normalize_conf_node(self, node):
|
def normalize_conf_node(self, node):
|
||||||
return dict_merge(
|
node_ = dict_merge(
|
||||||
{
|
{
|
||||||
"request": {
|
"request": {
|
||||||
"method": "GET"
|
"method": "GET"
|
||||||
|
|
@ -83,11 +83,16 @@ class implementation_check_kind_http_request(interface_check_kind):
|
||||||
"response": {
|
"response": {
|
||||||
"status_code": 200
|
"status_code": 200
|
||||||
},
|
},
|
||||||
"as_warning": False,
|
"strict": True,
|
||||||
},
|
},
|
||||||
node,
|
node,
|
||||||
True
|
True
|
||||||
)
|
)
|
||||||
|
allowed_methods = set(["GET", "POST"])
|
||||||
|
if (node_["request"]["method"] not in allowed_methods):
|
||||||
|
raise ValueError("invalid HTTP request method: %s" % node_["request"]["method"])
|
||||||
|
else:
|
||||||
|
return node_
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
@ -95,7 +100,6 @@ class implementation_check_kind_http_request(interface_check_kind):
|
||||||
'''
|
'''
|
||||||
def run(self, parameters):
|
def run(self, parameters):
|
||||||
if (parameters["request"]["method"] == "GET"):
|
if (parameters["request"]["method"] == "GET"):
|
||||||
method_handled = True
|
|
||||||
try:
|
try:
|
||||||
response = _requests.get(
|
response = _requests.get(
|
||||||
parameters["request"]["target"]
|
parameters["request"]["target"]
|
||||||
|
|
@ -105,7 +109,6 @@ class implementation_check_kind_http_request(interface_check_kind):
|
||||||
error = error_
|
error = error_
|
||||||
response = None
|
response = None
|
||||||
elif (parameters["request"]["method"] == "POST"):
|
elif (parameters["request"]["method"] == "POST"):
|
||||||
method_handled = True
|
|
||||||
try:
|
try:
|
||||||
response = _requests.post(
|
response = _requests.post(
|
||||||
parameters["request"]["target"]
|
parameters["request"]["target"]
|
||||||
|
|
@ -115,33 +118,31 @@ class implementation_check_kind_http_request(interface_check_kind):
|
||||||
error = error_
|
error = error_
|
||||||
response = None
|
response = None
|
||||||
else:
|
else:
|
||||||
method_handled = False
|
raise ValueError("impossible")
|
||||||
response = None
|
faults = []
|
||||||
if (not method_handled):
|
|
||||||
return {
|
|
||||||
"condition": enum_condition.unknown,
|
|
||||||
"info": ("invalid HTTP request method: %s" % parameters["request"]["method"])
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
if (response is None):
|
if (response is None):
|
||||||
return {
|
return {
|
||||||
"condition": (
|
"condition": (
|
||||||
enum_condition.warning
|
|
||||||
if parameters["as_warning"] else
|
|
||||||
enum_condition.critical
|
enum_condition.critical
|
||||||
|
if parameters["strict"] else
|
||||||
|
enum_condition.warning
|
||||||
),
|
),
|
||||||
"info": "HTTP request failed",
|
"info": {
|
||||||
|
"request": parameters["request"],
|
||||||
|
"faults": [
|
||||||
|
faults.append(translation_get("checks.http_request.request_failed")),
|
||||||
|
],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
lines = []
|
|
||||||
for (key, value, ) in parameters["response"].items():
|
for (key, value, ) in parameters["response"].items():
|
||||||
if (key == "status_code"):
|
if (key == "status_code"):
|
||||||
if ((value is None) or (response.status_code == value)):
|
if ((value is None) or (response.status_code == value)):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
lines.append(
|
faults.append(
|
||||||
string_coin(
|
translation_get(
|
||||||
"actual status code {{status_code_actual}} does not match expected value {{status_code_expected}}",
|
"checks.http_request.status_code_mismatch",
|
||||||
{
|
{
|
||||||
"status_code_actual": ("%u" % response.status_code),
|
"status_code_actual": ("%u" % response.status_code),
|
||||||
"status_code_expected": ("%u" % value),
|
"status_code_expected": ("%u" % value),
|
||||||
|
|
@ -153,9 +154,9 @@ class implementation_check_kind_http_request(interface_check_kind):
|
||||||
if (response.headers[header_key] == header_value):
|
if (response.headers[header_key] == header_value):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
lines.append(
|
faults.append(
|
||||||
string_coin(
|
translation_get(
|
||||||
"actual header value for key {{key}} is {{value_actual}} and does not match the expected value {{value_expected}}",
|
"checks.http_request.header_value_mismatch",
|
||||||
{
|
{
|
||||||
"key": header_key,
|
"key": header_key,
|
||||||
"value_actual": response.headers[header_key],
|
"value_actual": response.headers[header_key],
|
||||||
|
|
@ -167,26 +168,34 @@ class implementation_check_kind_http_request(interface_check_kind):
|
||||||
if (response.text.find(value) >= 0):
|
if (response.text.find(value) >= 0):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
lines.append(
|
faults.append(
|
||||||
string_coin(
|
translation_get(
|
||||||
"body does not contain the expected part '{{part}}'",
|
"checks.http_request.body_misses_part",
|
||||||
{
|
{
|
||||||
"part": value,
|
"part": value,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError("unhandled ")
|
raise ValueError("unhandled")
|
||||||
return {
|
return {
|
||||||
"condition": (
|
"condition": (
|
||||||
enum_condition.ok
|
enum_condition.ok
|
||||||
if (len(lines) <= 0) else
|
if (len(faults) <= 0) else
|
||||||
(
|
(
|
||||||
enum_condition.warning
|
|
||||||
if parameters["as_warning"] else
|
|
||||||
enum_condition.critical
|
enum_condition.critical
|
||||||
|
if parameters["strict"] else
|
||||||
|
enum_condition.warning
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
"info": "\n".join(lines),
|
"info": {
|
||||||
|
"request": parameters["request"],
|
||||||
|
"response": {
|
||||||
|
"status_code": response.status_code,
|
||||||
|
# "headers": dict(map(lambda pair: pair, response.headers.items())),
|
||||||
|
# "body": response.text,
|
||||||
|
},
|
||||||
|
"faults": faults,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,9 +52,13 @@ class implementation_check_kind_script(interface_check_kind):
|
||||||
elif (result.returncode == 3):
|
elif (result.returncode == 3):
|
||||||
condition = enum_condition.critical
|
condition = enum_condition.critical
|
||||||
else:
|
else:
|
||||||
raise ValueError("invalid exit code: %i" % result.returncode)
|
# raise ValueError("invalid exit code: %i" % result.returncode)
|
||||||
|
condition = enum_condition.unknown
|
||||||
return {
|
return {
|
||||||
"condition": condition,
|
"condition": condition,
|
||||||
"info": result.stdout.decode(),
|
"info": {
|
||||||
|
"stdout": result.stdout.decode(),
|
||||||
|
"stderr": result.stderr.decode(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
38
source/logic/condition.py
Normal file
38
source/logic/condition.py
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
class enum_condition(_enum.Enum):
|
||||||
|
unknown = 0
|
||||||
|
ok = 1
|
||||||
|
warning = 2
|
||||||
|
critical = 3
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
converts a condition to a human readable string
|
||||||
|
'''
|
||||||
|
def condition_show(condition):
|
||||||
|
return translation_get(
|
||||||
|
{
|
||||||
|
enum_condition.unknown: "conditions.unknown",
|
||||||
|
enum_condition.ok: "conditions.ok",
|
||||||
|
enum_condition.warning: "conditions.warning",
|
||||||
|
enum_condition.critical: "conditions.critical",
|
||||||
|
}[condition]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def condition_encode(condition):
|
||||||
|
return {
|
||||||
|
enum_condition.unknown: "unknown",
|
||||||
|
enum_condition.ok: "ok",
|
||||||
|
enum_condition.warning: "warning",
|
||||||
|
enum_condition.critical: "critical",
|
||||||
|
}[condition]
|
||||||
|
|
||||||
|
|
||||||
|
def condition_decode(condition_encoded):
|
||||||
|
return {
|
||||||
|
"unknown": enum_condition.unknown,
|
||||||
|
"ok": enum_condition.ok,
|
||||||
|
"warning": enum_condition.warning,
|
||||||
|
"critical": enum_condition.critical,
|
||||||
|
}[condition_encoded]
|
||||||
|
|
||||||
317
source/logic/conf.py
Normal file
317
source/logic/conf.py
Normal file
|
|
@ -0,0 +1,317 @@
|
||||||
|
def conf_schema_active():
|
||||||
|
return {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def conf_schema_threshold():
|
||||||
|
return {
|
||||||
|
"description": "how often a condition has to occur in order to be reported",
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"default": 3
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def conf_schema_annoy():
|
||||||
|
return {
|
||||||
|
"description": "whether notifications shall be kept sending after the threshold has been surpassed",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": False
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def conf_schema_interval(default):
|
||||||
|
return {
|
||||||
|
"description": "in seconds or as text",
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"exclusiveMinimum": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"minute",
|
||||||
|
"hour",
|
||||||
|
"day",
|
||||||
|
"week",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"default": default,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def conf_schema_schedule():
|
||||||
|
return {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": False,
|
||||||
|
"properties": {
|
||||||
|
"regular_interval": conf_schema_interval(60 * 60),
|
||||||
|
"attentive_interval": conf_schema_interval(60 * 2),
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def conf_schema_notifications(notification_channel_implementations):
|
||||||
|
return {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"anyOf": list(
|
||||||
|
map(
|
||||||
|
lambda pair: {
|
||||||
|
"title": ("check kind '%s'" % pair[0]),
|
||||||
|
"type": "object",
|
||||||
|
"unevaluatedProperties": False,
|
||||||
|
"properties": {
|
||||||
|
"kind": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [pair[0]]
|
||||||
|
},
|
||||||
|
"parameters": pair[1].parameters_schema(),
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"kind",
|
||||||
|
"parameters"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
notification_channel_implementations.items()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
{
|
||||||
|
"kind": "console",
|
||||||
|
"parameters": {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def conf_schema_root(check_kind_implementations, notification_channel_implementations):
|
||||||
|
return {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": False,
|
||||||
|
"properties": {
|
||||||
|
"defaults": {
|
||||||
|
"description": "default values for checks",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": False,
|
||||||
|
"properties": {
|
||||||
|
"active": conf_schema_active(),
|
||||||
|
"threshold": conf_schema_threshold(),
|
||||||
|
"annoy": conf_schema_annoy(),
|
||||||
|
"schedule": conf_schema_schedule(),
|
||||||
|
"notifications": conf_schema_notifications(notification_channel_implementations),
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"checks": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"description": "should represent a specific check",
|
||||||
|
"type": "object",
|
||||||
|
"unevaluatedProperties": False,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"active": conf_schema_active(),
|
||||||
|
"threshold": conf_schema_threshold(),
|
||||||
|
"annoy": conf_schema_annoy(),
|
||||||
|
"schedule": conf_schema_schedule(),
|
||||||
|
"notifications": conf_schema_notifications(notification_channel_implementations),
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anyOf": list(
|
||||||
|
map(
|
||||||
|
lambda pair: {
|
||||||
|
"title": ("notification channel '%s'" % pair[0]),
|
||||||
|
"type": "object",
|
||||||
|
"unevaluatedProperties": False,
|
||||||
|
"properties": {
|
||||||
|
"kind": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [pair[0]]
|
||||||
|
},
|
||||||
|
"parameters": pair[1].parameters_schema(),
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"kind",
|
||||||
|
"parameters"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
check_kind_implementations.items()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"defaults",
|
||||||
|
"checks",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def conf_normalize_interval(interval_raw):
|
||||||
|
if (type(interval_raw) == int):
|
||||||
|
return interval_raw
|
||||||
|
elif (type(interval_raw) == str):
|
||||||
|
map_ = {
|
||||||
|
"minute": (60),
|
||||||
|
"hour": (60 * 60),
|
||||||
|
"day": (60 * 60 * 24),
|
||||||
|
"week": (60 * 60 * 24 * 7),
|
||||||
|
}
|
||||||
|
if (interval_raw not in map_):
|
||||||
|
raise ValueError("invalid string interval value: %s" % interval_raw)
|
||||||
|
else:
|
||||||
|
return map_[interval_raw]
|
||||||
|
else:
|
||||||
|
raise ValueError("invalid type for interval value")
|
||||||
|
|
||||||
|
|
||||||
|
def conf_normalize_schedule(node):
|
||||||
|
node_ = dict_merge(
|
||||||
|
{
|
||||||
|
"regular_interval": (60 * 60),
|
||||||
|
"attentive_interval": (60 * 2),
|
||||||
|
},
|
||||||
|
node
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"regular_interval": conf_normalize_interval(node["regular_interval"]),
|
||||||
|
"attentive_interval": conf_normalize_interval(node["attentive_interval"]),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def conf_normalize_notification(notification_channel_implementations, node):
|
||||||
|
if (node["kind"] not in notification_channel_implementations):
|
||||||
|
raise ValueError("invalid notification kind: %s" % notification["kind"])
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"kind": node["kind"],
|
||||||
|
"parameters": notification_channel_implementations[node["kind"]].normalize_conf_node(node["parameters"]),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def conf_normalize_defaults(notification_channel_implementations, node):
|
||||||
|
node_ = dict_merge(
|
||||||
|
{
|
||||||
|
"active": True,
|
||||||
|
"threshold": 3,
|
||||||
|
"annoy": False,
|
||||||
|
"schedule": {
|
||||||
|
"regular_interval": (60 * 60),
|
||||||
|
"attentive_interval": (60 * 2),
|
||||||
|
},
|
||||||
|
"notifications": [
|
||||||
|
],
|
||||||
|
},
|
||||||
|
node
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"active": node_["active"],
|
||||||
|
"threshold": node_["threshold"],
|
||||||
|
"annoy": node_["annoy"],
|
||||||
|
"schedule": node_["schedule"],
|
||||||
|
"notifications": list(
|
||||||
|
map(
|
||||||
|
lambda x: conf_normalize_notification(notification_channel_implementations, x),
|
||||||
|
node_["notifications"]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def conf_normalize_check(check_kind_implementations, notification_channel_implementations, defaults, node):
|
||||||
|
if ("name" not in node):
|
||||||
|
raise ValueError("missing mandatory field in 'check' node: 'name'")
|
||||||
|
else:
|
||||||
|
if ("kind" not in node):
|
||||||
|
raise ValueError("missing mandatory field in 'check' node: 'kind'")
|
||||||
|
else:
|
||||||
|
if (node["kind"] not in check_kind_implementations):
|
||||||
|
raise ValueError("invalid check kind: '%s'" % node["kind"])
|
||||||
|
else:
|
||||||
|
node_ = dict_merge(
|
||||||
|
{
|
||||||
|
"title": node["name"],
|
||||||
|
"active": defaults["active"],
|
||||||
|
"threshold": defaults["threshold"],
|
||||||
|
"annoy": defaults["annoy"],
|
||||||
|
"schedule": defaults["schedule"],
|
||||||
|
"notifications": defaults["notifications"],
|
||||||
|
"parameters": {},
|
||||||
|
},
|
||||||
|
node
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"name": node_["name"],
|
||||||
|
"title": node_["title"],
|
||||||
|
"active": node_["active"],
|
||||||
|
"threshold": node_["threshold"],
|
||||||
|
"annoy": node_["annoy"],
|
||||||
|
"schedule": conf_normalize_schedule(node_["schedule"]),
|
||||||
|
"notifications": list(
|
||||||
|
map(
|
||||||
|
lambda x: conf_normalize_notification(notification_channel_implementations, x),
|
||||||
|
node_["notifications"]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"kind": node_["kind"],
|
||||||
|
"parameters": check_kind_implementations[node_["kind"]].normalize_conf_node(node_["parameters"]),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def conf_normalize_root(check_kind_implementations, notification_channel_implementations, node):
|
||||||
|
counts = {}
|
||||||
|
for node_ in node["checks"]:
|
||||||
|
if (node_["name"] not in counts):
|
||||||
|
counts[node_["name"]] = 0
|
||||||
|
counts[node_["name"]] += 1
|
||||||
|
fails = list(filter(lambda pair: (pair[1] > 1), counts.items()))
|
||||||
|
if (len(fails) > 0):
|
||||||
|
raise ValueError(
|
||||||
|
string_coin(
|
||||||
|
"ambiguous check names: {{names}}",
|
||||||
|
{
|
||||||
|
"names": ",".join(counts.keys()),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
defaults = conf_normalize_defaults(notification_channel_implementations, node["defaults"])
|
||||||
|
return {
|
||||||
|
"defaults": defaults,
|
||||||
|
"checks": list(
|
||||||
|
map(
|
||||||
|
lambda node_: conf_normalize_check(
|
||||||
|
check_kind_implementations,
|
||||||
|
notification_channel_implementations,
|
||||||
|
defaults,
|
||||||
|
node_
|
||||||
|
),
|
||||||
|
node["checks"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
import enum as _enum
|
|
||||||
import time as _time
|
|
||||||
|
|
||||||
|
|
||||||
def file_read(path):
|
def file_read(path):
|
||||||
handle = open(path, "r")
|
handle = open(path, "r")
|
||||||
content = handle.read()
|
content = handle.read()
|
||||||
|
|
@ -42,36 +38,9 @@ def dict_merge(core_dict, mantle_dict, recursive = False):
|
||||||
return result_dict
|
return result_dict
|
||||||
|
|
||||||
|
|
||||||
class enum_condition(_enum.Enum):
|
def env_get_language():
|
||||||
unknown = 0
|
env_lang = _os.environ.get("LANG")
|
||||||
ok = 1
|
locale = env_lang.split(".")[0]
|
||||||
warning = 2
|
language = locale.split("_")[0]
|
||||||
critical = 3
|
return language
|
||||||
|
|
||||||
|
|
||||||
def condition_encode(condition):
|
|
||||||
if (condition == enum_condition.ok):
|
|
||||||
return "ok"
|
|
||||||
elif (condition == enum_condition.unknown):
|
|
||||||
return "unknown"
|
|
||||||
elif (condition == enum_condition.warning):
|
|
||||||
return "warning"
|
|
||||||
elif (condition == enum_condition.critical):
|
|
||||||
return "critical"
|
|
||||||
else:
|
|
||||||
raise ValueError("unhandled condition: %s" % str(condition))
|
|
||||||
|
|
||||||
|
|
||||||
def condition_decode(condition_encoded):
|
|
||||||
if (condition_encoded == "ok"):
|
|
||||||
return enum_condition.ok
|
|
||||||
elif (condition_encoded == "unknown"):
|
|
||||||
return enum_condition.unknown
|
|
||||||
elif (condition_encoded == "warning"):
|
|
||||||
return enum_condition.warning
|
|
||||||
elif (condition_encoded == "critical"):
|
|
||||||
return enum_condition.critical
|
|
||||||
else:
|
|
||||||
raise ValueError("unhandled encoded condition: %s" % condition_encoded)
|
|
||||||
|
|
||||||
|
|
||||||
46
source/logic/localization.py
Normal file
46
source/logic/localization.py
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
translation_language_fallback = None
|
||||||
|
|
||||||
|
translation_language_shall = None
|
||||||
|
|
||||||
|
def translation_initialize(language_fallback, language_shall):
|
||||||
|
global translation_language_fallback
|
||||||
|
global translation_language_shall
|
||||||
|
translation_language_fallback = language_fallback
|
||||||
|
translation_language_shall = language_shall
|
||||||
|
|
||||||
|
|
||||||
|
def translation_get(key, arguments = None):
|
||||||
|
global translation_language_fallback
|
||||||
|
global translation_language_shall
|
||||||
|
global localization_data
|
||||||
|
if (arguments is None):
|
||||||
|
arguments = {}
|
||||||
|
for language in [translation_language_shall, translation_language_fallback]:
|
||||||
|
if (language not in localization_data):
|
||||||
|
_sys.stderr.write(
|
||||||
|
string_coin(
|
||||||
|
"[notice] missing localization data for language '{{language}}'\n",
|
||||||
|
{
|
||||||
|
"language": language,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if (key not in localization_data[language]):
|
||||||
|
_sys.stderr.write(
|
||||||
|
string_coin(
|
||||||
|
"[notice] missing translation in language '{{language}}' for key '{{key}}'\n",
|
||||||
|
{
|
||||||
|
"language": language,
|
||||||
|
"key": key,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return string_coin(
|
||||||
|
localization_data[language][key],
|
||||||
|
arguments
|
||||||
|
)
|
||||||
|
return ("{{%s}}" % key)
|
||||||
|
|
||||||
|
|
||||||
268
source/logic/main.py
Normal file
268
source/logic/main.py
Normal file
|
|
@ -0,0 +1,268 @@
|
||||||
|
def state_encode(state):
|
||||||
|
return {
|
||||||
|
"timestamp": state["timestamp"],
|
||||||
|
"condition": condition_encode(state["condition"]),
|
||||||
|
"count": state["count"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def state_decode(state_encoded):
|
||||||
|
return {
|
||||||
|
"timestamp": state_encoded["timestamp"],
|
||||||
|
"condition": condition_decode(state_encoded["condition"]),
|
||||||
|
"count": state_encoded["count"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
## setup translation for the first time
|
||||||
|
translation_initialize("en", env_get_language())
|
||||||
|
|
||||||
|
## args
|
||||||
|
argumentparser = _argparse.ArgumentParser(
|
||||||
|
description = translation_get("help.title"),
|
||||||
|
formatter_class = _argparse.ArgumentDefaultsHelpFormatter
|
||||||
|
)
|
||||||
|
argumentparser.add_argument(
|
||||||
|
"-c",
|
||||||
|
"--conf-path",
|
||||||
|
type = str,
|
||||||
|
default = "monitoring.hmdl.json",
|
||||||
|
dest = "conf_path",
|
||||||
|
metavar = "<conf-path>",
|
||||||
|
help = translation_get("help.args.conf_path"),
|
||||||
|
)
|
||||||
|
argumentparser.add_argument(
|
||||||
|
"-f",
|
||||||
|
"--state-path",
|
||||||
|
type = str,
|
||||||
|
default = None,
|
||||||
|
dest = "state_path",
|
||||||
|
metavar = "<state-path>",
|
||||||
|
help = translation_get("help.args.state_path"),
|
||||||
|
)
|
||||||
|
argumentparser.add_argument(
|
||||||
|
"-y",
|
||||||
|
"--send-ok-notifications",
|
||||||
|
action = "store_true",
|
||||||
|
default = False,
|
||||||
|
dest = "send_ok_notifications",
|
||||||
|
help = translation_get("help.args.send_ok_notifications", {"condition_name": translation_get("conditions.ok")}),
|
||||||
|
)
|
||||||
|
argumentparser.add_argument(
|
||||||
|
"-l",
|
||||||
|
"--language",
|
||||||
|
type = str,
|
||||||
|
choices = localization_data.keys(),
|
||||||
|
default = None,
|
||||||
|
dest = "language",
|
||||||
|
metavar = "<language>",
|
||||||
|
help = translation_get("help.args.language"),
|
||||||
|
)
|
||||||
|
argumentparser.add_argument(
|
||||||
|
"-x",
|
||||||
|
"--erase-state",
|
||||||
|
action = "store_true",
|
||||||
|
default = False,
|
||||||
|
dest = "erase_state",
|
||||||
|
help = translation_get("help.args.erase_state"),
|
||||||
|
)
|
||||||
|
argumentparser.add_argument(
|
||||||
|
"-s",
|
||||||
|
"--show-schema",
|
||||||
|
action = "store_true",
|
||||||
|
default = False,
|
||||||
|
dest = "show_schema",
|
||||||
|
help = translation_get("help.args.show_schema"),
|
||||||
|
)
|
||||||
|
argumentparser.add_argument(
|
||||||
|
"-e",
|
||||||
|
"--expose-full-conf",
|
||||||
|
action = "store_true",
|
||||||
|
default = False,
|
||||||
|
dest = "expose_full_conf",
|
||||||
|
help = translation_get("help.args.expose_full_conf"),
|
||||||
|
)
|
||||||
|
args = argumentparser.parse_args()
|
||||||
|
|
||||||
|
## vars
|
||||||
|
id_ = _hashlib.sha256(_os.path.abspath(args.conf_path).encode("ascii")).hexdigest()[:8]
|
||||||
|
state_path = (
|
||||||
|
args.state_path
|
||||||
|
if (args.state_path is not None) else
|
||||||
|
_os.path.join(
|
||||||
|
_tempfile.gettempdir(),
|
||||||
|
string_coin("monitoring-state-{{id}}.json", {"id": id_})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
## exec
|
||||||
|
|
||||||
|
### setup translation for the second time
|
||||||
|
if (args.language is not None):
|
||||||
|
translation_initialize("en", args.language)
|
||||||
|
|
||||||
|
### load check kind implementations
|
||||||
|
check_kind_implementations = {
|
||||||
|
"script": implementation_check_kind_script(),
|
||||||
|
"file_state": implementation_check_kind_file_state(),
|
||||||
|
"http_request": implementation_check_kind_http_request(),
|
||||||
|
}
|
||||||
|
|
||||||
|
### load notification channel implementations
|
||||||
|
notification_channel_implementations = {
|
||||||
|
"console": implementation_notification_channel_console(),
|
||||||
|
"email": implementation_notification_channel_email(),
|
||||||
|
"libnotify": implementation_notification_channel_libnotify(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.show_schema):
|
||||||
|
_sys.stdout.write(
|
||||||
|
_json.dumps(
|
||||||
|
conf_schema_root(
|
||||||
|
check_kind_implementations,
|
||||||
|
notification_channel_implementations
|
||||||
|
),
|
||||||
|
indent = "\t"
|
||||||
|
)
|
||||||
|
+
|
||||||
|
"\n"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_sys.stderr.write(
|
||||||
|
string_coin(
|
||||||
|
"[info] {{label}}: {{path}}\n",
|
||||||
|
{
|
||||||
|
"label": translation_get("misc.state_file_path"),
|
||||||
|
"path": state_path,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
### get configuration data
|
||||||
|
conf = conf_normalize_root(
|
||||||
|
check_kind_implementations,
|
||||||
|
notification_channel_implementations,
|
||||||
|
_json.loads(file_read(args.conf_path))
|
||||||
|
)
|
||||||
|
if (args.expose_full_conf):
|
||||||
|
_sys.stdout.write(_json.dumps(checks, indent = "\t") + "\n")
|
||||||
|
_sys.exit(1)
|
||||||
|
else:
|
||||||
|
### get state data
|
||||||
|
if (
|
||||||
|
(not _os.path.exists(state_path))
|
||||||
|
or
|
||||||
|
args.erase_state
|
||||||
|
):
|
||||||
|
state_data = {}
|
||||||
|
file_write(state_path, _json.dumps(state_data, indent = "\t"))
|
||||||
|
else:
|
||||||
|
state_data = _json.loads(file_read(state_path))
|
||||||
|
|
||||||
|
### iterate through checks
|
||||||
|
for check_data in conf["checks"]:
|
||||||
|
if (not check_data["active"]):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
### get old state and examine whether the check shall be executed
|
||||||
|
old_item_state = (
|
||||||
|
None
|
||||||
|
if (check_data["name"] not in state_data) else
|
||||||
|
state_decode(state_data[check_data["name"]])
|
||||||
|
)
|
||||||
|
timestamp = get_current_timestamp()
|
||||||
|
due = (
|
||||||
|
(old_item_state is None)
|
||||||
|
or
|
||||||
|
(old_item_state["condition"] != enum_condition.ok)
|
||||||
|
or
|
||||||
|
((timestamp - old_item_state["timestamp"]) >= check_data["schedule"]["regular_interval"])
|
||||||
|
or
|
||||||
|
(
|
||||||
|
(old_item_state["count"] is not None)
|
||||||
|
and
|
||||||
|
((timestamp - old_item_state["timestamp"]) >= check_data["schedule"]["attentive_interval"])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (not due):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
_sys.stderr.write(
|
||||||
|
string_coin(
|
||||||
|
"-- {{check_name}}\n",
|
||||||
|
{
|
||||||
|
"check_name": check_data["name"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
### execute check and set new state
|
||||||
|
try:
|
||||||
|
result = check_kind_implementations[check_data["kind"]].run(check_data["parameters"])
|
||||||
|
except Exception as error:
|
||||||
|
result = {
|
||||||
|
"condition": enum_condition.unknown,
|
||||||
|
"info": {
|
||||||
|
# "cause": translation_get("misc.check_procedure_failed"),
|
||||||
|
"error": str(error),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
new_item_state = {
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"condition": result["condition"],
|
||||||
|
"count": (
|
||||||
|
1
|
||||||
|
if (
|
||||||
|
(old_item_state is None)
|
||||||
|
or
|
||||||
|
(old_item_state["condition"] != result["condition"])
|
||||||
|
) else
|
||||||
|
(
|
||||||
|
(old_item_state["count"] + 1)
|
||||||
|
if (
|
||||||
|
(old_item_state["count"] is not None)
|
||||||
|
and
|
||||||
|
((old_item_state["count"] + 1) <= check_data["threshold"])
|
||||||
|
) else
|
||||||
|
None
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
state_data[check_data["name"]] = state_encode(new_item_state)
|
||||||
|
file_write(state_path, _json.dumps(state_data, indent = "\t"))
|
||||||
|
|
||||||
|
### send notifications
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(new_item_state["count"] is not None)
|
||||||
|
and
|
||||||
|
(new_item_state["count"] == check_data["threshold"])
|
||||||
|
)
|
||||||
|
or
|
||||||
|
(
|
||||||
|
(new_item_state["count"] is None)
|
||||||
|
and
|
||||||
|
check_data["annoy"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
and
|
||||||
|
(
|
||||||
|
(new_item_state["condition"] != enum_condition.ok)
|
||||||
|
or
|
||||||
|
args.send_ok_notifications
|
||||||
|
)
|
||||||
|
):
|
||||||
|
for notification in check_data["notifications"]:
|
||||||
|
notification_channel_implementations[notification["kind"]].notify(
|
||||||
|
notification["parameters"],
|
||||||
|
check_data["name"],
|
||||||
|
check_data,
|
||||||
|
new_item_state,
|
||||||
|
result["info"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
||||||
|
|
@ -6,5 +6,7 @@ import tempfile as _tempfile
|
||||||
import argparse as _argparse
|
import argparse as _argparse
|
||||||
import json as _json
|
import json as _json
|
||||||
import requests as _requests
|
import requests as _requests
|
||||||
|
import enum as _enum
|
||||||
|
import time as _time
|
||||||
import smtplib as _smtplib
|
import smtplib as _smtplib
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
533
source/main.py
533
source/main.py
|
|
@ -1,533 +0,0 @@
|
||||||
def state_encode(state):
|
|
||||||
return {
|
|
||||||
"timestamp": state["timestamp"],
|
|
||||||
"condition": condition_encode(state["condition"]),
|
|
||||||
"count": state["count"],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def state_decode(state_encoded):
|
|
||||||
return {
|
|
||||||
"timestamp": state_encoded["timestamp"],
|
|
||||||
"condition": condition_decode(state_encoded["condition"]),
|
|
||||||
"count": state_encoded["count"],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def schema_active():
|
|
||||||
return {
|
|
||||||
"type": "boolean",
|
|
||||||
"default": True
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def schema_threshold():
|
|
||||||
return {
|
|
||||||
"description": "how often a condition has to occur in order to be reported",
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 1,
|
|
||||||
"default": 3
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def schema_annoy():
|
|
||||||
return {
|
|
||||||
"description": "whether notifications shall be kept sending after the threshold has been surpassed",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": False
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def schema_interval(default):
|
|
||||||
return {
|
|
||||||
"description": "in seconds or as text",
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"exclusiveMinimum": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"minute",
|
|
||||||
"hour",
|
|
||||||
"day",
|
|
||||||
"week",
|
|
||||||
]
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"default": default,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def schema_schedule():
|
|
||||||
return {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": False,
|
|
||||||
"properties": {
|
|
||||||
"regular_interval": schema_interval(60 * 60),
|
|
||||||
"attentive_interval": schema_interval(60 * 2),
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def schema_notifications(notification_channel_implementations):
|
|
||||||
return {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"anyOf": list(
|
|
||||||
map(
|
|
||||||
lambda pair: {
|
|
||||||
"title": ("check kind '%s'" % pair[0]),
|
|
||||||
"type": "object",
|
|
||||||
"unevaluatedProperties": False,
|
|
||||||
"properties": {
|
|
||||||
"kind": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [pair[0]]
|
|
||||||
},
|
|
||||||
"parameters": pair[1].parameters_schema(),
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"kind",
|
|
||||||
"parameters"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
notification_channel_implementations.items()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
"default": [
|
|
||||||
{
|
|
||||||
"kind": "console",
|
|
||||||
"parameters": {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def schema_root(check_kind_implementations, notification_channel_implementations):
|
|
||||||
return {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": False,
|
|
||||||
"properties": {
|
|
||||||
"defaults": {
|
|
||||||
"description": "default values for checks",
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": False,
|
|
||||||
"properties": {
|
|
||||||
"active": schema_active(),
|
|
||||||
"threshold": schema_threshold(),
|
|
||||||
"annoy": schema_annoy(),
|
|
||||||
"schedule": schema_schedule(),
|
|
||||||
"notifications": schema_notifications(notification_channel_implementations),
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"checks": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"description": "should represent a specific check",
|
|
||||||
"type": "object",
|
|
||||||
"unevaluatedProperties": False,
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"active": schema_active(),
|
|
||||||
"threshold": schema_threshold(),
|
|
||||||
"annoy": schema_annoy(),
|
|
||||||
"schedule": schema_schedule(),
|
|
||||||
"notifications": schema_notifications(notification_channel_implementations),
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"name",
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anyOf": list(
|
|
||||||
map(
|
|
||||||
lambda pair: {
|
|
||||||
"title": ("notification channel '%s'" % pair[0]),
|
|
||||||
"type": "object",
|
|
||||||
"unevaluatedProperties": False,
|
|
||||||
"properties": {
|
|
||||||
"kind": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [pair[0]]
|
|
||||||
},
|
|
||||||
"parameters": pair[1].parameters_schema(),
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"kind",
|
|
||||||
"parameters"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
check_kind_implementations.items()
|
|
||||||
)
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"defaults",
|
|
||||||
"checks",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def conf_normalize_interval(interval_raw):
|
|
||||||
if (type(interval_raw) == int):
|
|
||||||
return interval_raw
|
|
||||||
elif (type(interval_raw) == str):
|
|
||||||
if (interval_raw == "minute"):
|
|
||||||
return (60)
|
|
||||||
elif (interval_raw == "hour"):
|
|
||||||
return (60 * 60)
|
|
||||||
elif (interval_raw == "day"):
|
|
||||||
return (60 * 60 * 24)
|
|
||||||
elif (interval_raw == "week"):
|
|
||||||
return (60 * 60 * 24 * 7)
|
|
||||||
else:
|
|
||||||
raise ValueError("invalid string interval value: %s" % interval_raw)
|
|
||||||
else:
|
|
||||||
raise ValueError("invalid type for interval value")
|
|
||||||
|
|
||||||
|
|
||||||
def conf_normalize_schedule(node):
|
|
||||||
node_ = dict_merge(
|
|
||||||
{
|
|
||||||
"regular_interval": (60 * 60),
|
|
||||||
"attentive_interval": (60 * 2),
|
|
||||||
},
|
|
||||||
node
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"regular_interval": conf_normalize_interval(node["regular_interval"]),
|
|
||||||
"attentive_interval": conf_normalize_interval(node["attentive_interval"]),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def conf_normalize_notification(notification_channel_implementations, node):
|
|
||||||
return {
|
|
||||||
"kind": node["kind"],
|
|
||||||
"parameters": notification_channel_implementations[node["kind"]].normalize_conf_node(node["parameters"]),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def conf_normalize_defaults(notification_channel_implementations, node):
|
|
||||||
node_ = dict_merge(
|
|
||||||
{
|
|
||||||
"active": True,
|
|
||||||
"threshold": 3,
|
|
||||||
"annoy": False,
|
|
||||||
"schedule": {
|
|
||||||
"regular_interval": (60 * 60),
|
|
||||||
"attentive_interval": (60 * 2),
|
|
||||||
},
|
|
||||||
"notifications": [
|
|
||||||
],
|
|
||||||
},
|
|
||||||
node
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"active": node_["active"],
|
|
||||||
"threshold": node_["threshold"],
|
|
||||||
"annoy": node_["annoy"],
|
|
||||||
"schedule": node_["schedule"],
|
|
||||||
"notifications": list(
|
|
||||||
map(
|
|
||||||
lambda x: conf_normalize_notification(notification_channel_implementations, x),
|
|
||||||
node_["notifications"]
|
|
||||||
)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def conf_normalize_check(check_kind_implementations, notification_channel_implementations, defaults, node):
|
|
||||||
if ("name" not in node):
|
|
||||||
raise ValueError("missing mandatory 'check' field 'name'")
|
|
||||||
else:
|
|
||||||
if ("kind" not in node):
|
|
||||||
raise ValueError("missing mandatory 'check' field 'kind'")
|
|
||||||
else:
|
|
||||||
if (node["kind"] not in check_kind_implementations):
|
|
||||||
raise ValueError("unhandled kind: %s" % node["kind"])
|
|
||||||
else:
|
|
||||||
node_ = dict_merge(
|
|
||||||
{
|
|
||||||
"title": node["name"],
|
|
||||||
"active": defaults["active"],
|
|
||||||
"threshold": defaults["threshold"],
|
|
||||||
"annoy": defaults["annoy"],
|
|
||||||
"schedule": defaults["schedule"],
|
|
||||||
"notifications": defaults["notifications"],
|
|
||||||
"parameters": {},
|
|
||||||
},
|
|
||||||
node
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"name": node_["name"],
|
|
||||||
"title": node_["title"],
|
|
||||||
"active": node_["active"],
|
|
||||||
"threshold": node_["threshold"],
|
|
||||||
"annoy": node_["annoy"],
|
|
||||||
"schedule": conf_normalize_schedule(node_["schedule"]),
|
|
||||||
"notifications": list(
|
|
||||||
map(
|
|
||||||
lambda x: conf_normalize_notification(notification_channel_implementations, x),
|
|
||||||
node_["notifications"]
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"kind": node_["kind"],
|
|
||||||
"parameters": check_kind_implementations[node_["kind"]].normalize_conf_node(node_["parameters"]),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def conf_normalize_root(check_kind_implementations, notification_channel_implementations, node):
|
|
||||||
counts = {}
|
|
||||||
for node_ in node["checks"]:
|
|
||||||
if (node_["name"] not in counts):
|
|
||||||
counts[node_["name"]] = 0
|
|
||||||
counts[node_["name"]] += 1
|
|
||||||
fails = list(filter(lambda pair: (pair[1] > 1), counts.items()))
|
|
||||||
if (len(fails) > 0):
|
|
||||||
raise ValueError(
|
|
||||||
string_coin(
|
|
||||||
"ambiguous check names: {{names}}",
|
|
||||||
{
|
|
||||||
"names": ",".join(counts.keys()),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return list(
|
|
||||||
map(
|
|
||||||
lambda node_: conf_normalize_check(
|
|
||||||
check_kind_implementations,
|
|
||||||
notification_channel_implementations,
|
|
||||||
conf_normalize_defaults(notification_channel_implementations, node["defaults"]),
|
|
||||||
node_
|
|
||||||
),
|
|
||||||
node["checks"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
## args
|
|
||||||
argumentparser = _argparse.ArgumentParser(
|
|
||||||
description = "Heimdall-Monitoring-Tool",
|
|
||||||
formatter_class = _argparse.ArgumentDefaultsHelpFormatter
|
|
||||||
)
|
|
||||||
argumentparser.add_argument(
|
|
||||||
"-c",
|
|
||||||
"--conf-path",
|
|
||||||
type = str,
|
|
||||||
default = "monitoring.hmdl.json",
|
|
||||||
dest = "conf_path",
|
|
||||||
metavar = "<conf-path>",
|
|
||||||
help = "path to the configuration file"
|
|
||||||
)
|
|
||||||
argumentparser.add_argument(
|
|
||||||
"-f",
|
|
||||||
"--state-path",
|
|
||||||
type = str,
|
|
||||||
default = None,
|
|
||||||
dest = "state_path",
|
|
||||||
metavar = "<state-path>",
|
|
||||||
help = "path to the state file, which contains information about the recent checks; default: file in temporary directory, unique for the conf-path input"
|
|
||||||
)
|
|
||||||
argumentparser.add_argument(
|
|
||||||
"-x",
|
|
||||||
"--erase-state",
|
|
||||||
action = "store_true",
|
|
||||||
default = False,
|
|
||||||
dest = "erase_state",
|
|
||||||
help = "whether the state shall be deleted on start; this will cause that all checks are executed"
|
|
||||||
)
|
|
||||||
argumentparser.add_argument(
|
|
||||||
"-s",
|
|
||||||
"--show-schema",
|
|
||||||
action = "store_true",
|
|
||||||
default = False,
|
|
||||||
dest = "show_schema",
|
|
||||||
help = "print the hmdl JSON schema to stdout and exit"
|
|
||||||
)
|
|
||||||
argumentparser.add_argument(
|
|
||||||
"-e",
|
|
||||||
"--expose-full-conf",
|
|
||||||
action = "store_true",
|
|
||||||
default = False,
|
|
||||||
dest = "expose_full_conf",
|
|
||||||
help = "only print the extended configuration to stdout and exit (useful for debug purposes)"
|
|
||||||
)
|
|
||||||
args = argumentparser.parse_args()
|
|
||||||
|
|
||||||
## vars
|
|
||||||
id_ = _hashlib.sha256(_os.path.abspath(args.conf_path).encode("ascii")).hexdigest()[:8]
|
|
||||||
state_path = (
|
|
||||||
args.state_path
|
|
||||||
if (args.state_path is not None) else
|
|
||||||
_os.path.join(
|
|
||||||
_tempfile.gettempdir(),
|
|
||||||
string_coin("monitoring-state-{{id}}.json", {"id": id_})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
## exec
|
|
||||||
|
|
||||||
### load check kind implementations
|
|
||||||
check_kind_implementations = {
|
|
||||||
"script": implementation_check_kind_script(),
|
|
||||||
"file_timestamp": implementation_check_kind_file_timestamp(),
|
|
||||||
"http_request": implementation_check_kind_http_request(),
|
|
||||||
}
|
|
||||||
|
|
||||||
### load notification channel implementations
|
|
||||||
notification_channel_implementations = {
|
|
||||||
"console": implementation_notification_channel_console(),
|
|
||||||
"file_touch": implementation_notification_channel_file_touch(),
|
|
||||||
"email": implementation_notification_channel_email(),
|
|
||||||
"libnotify": implementation_notification_channel_libnotify(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.show_schema):
|
|
||||||
_sys.stdout.write(
|
|
||||||
_json.dumps(
|
|
||||||
schema_root(
|
|
||||||
check_kind_implementations,
|
|
||||||
notification_channel_implementations
|
|
||||||
),
|
|
||||||
indent = "\t"
|
|
||||||
)
|
|
||||||
+
|
|
||||||
"\n"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
_sys.stderr.write(">> state file path: %s\n" % state_path)
|
|
||||||
|
|
||||||
### get configuration data
|
|
||||||
checks = conf_normalize_root(
|
|
||||||
check_kind_implementations,
|
|
||||||
notification_channel_implementations,
|
|
||||||
_json.loads(file_read(args.conf_path))
|
|
||||||
)
|
|
||||||
if (args.expose_full_conf):
|
|
||||||
_sys.stdout.write(_json.dumps(checks, indent = "\t") + "\n")
|
|
||||||
_sys.exit(1)
|
|
||||||
else:
|
|
||||||
### get state data
|
|
||||||
if (
|
|
||||||
(not _os.path.exists(state_path))
|
|
||||||
or
|
|
||||||
args.erase_state
|
|
||||||
):
|
|
||||||
state_data = {}
|
|
||||||
file_write(state_path, _json.dumps(state_data, indent = "\t"))
|
|
||||||
else:
|
|
||||||
state_data = _json.loads(file_read(state_path))
|
|
||||||
|
|
||||||
### iterate through checks
|
|
||||||
for check_data in checks:
|
|
||||||
if (not check_data["active"]):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
### get old state and examine whether the check shall be executed
|
|
||||||
old_item_state = (
|
|
||||||
None
|
|
||||||
if (check_data["name"] not in state_data) else
|
|
||||||
state_decode(state_data[check_data["name"]])
|
|
||||||
)
|
|
||||||
timestamp = get_current_timestamp()
|
|
||||||
due = (
|
|
||||||
(old_item_state is None)
|
|
||||||
or
|
|
||||||
(old_item_state["condition"] != enum_condition.ok)
|
|
||||||
or
|
|
||||||
((timestamp - old_item_state["timestamp"]) >= check_data["schedule"]["regular_interval"])
|
|
||||||
or
|
|
||||||
(
|
|
||||||
(old_item_state["count"] is not None)
|
|
||||||
and
|
|
||||||
((timestamp - old_item_state["timestamp"]) >= check_data["schedule"]["attentive_interval"])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (not due):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
_sys.stderr.write(
|
|
||||||
string_coin(
|
|
||||||
"-- {{check_name}}\n",
|
|
||||||
{
|
|
||||||
"check_name": check_data["name"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
### execute check and set new state
|
|
||||||
result = check_kind_implementations[check_data["kind"]].run(check_data["parameters"])
|
|
||||||
new_item_state = {
|
|
||||||
"timestamp": timestamp,
|
|
||||||
"condition": result["condition"],
|
|
||||||
"count": (
|
|
||||||
1
|
|
||||||
if (
|
|
||||||
(old_item_state is None)
|
|
||||||
or
|
|
||||||
(old_item_state["condition"] != result["condition"])
|
|
||||||
) else
|
|
||||||
(
|
|
||||||
(old_item_state["count"] + 1)
|
|
||||||
if (
|
|
||||||
(old_item_state["count"] is not None)
|
|
||||||
and
|
|
||||||
((old_item_state["count"] + 1) <= check_data["threshold"])
|
|
||||||
) else
|
|
||||||
None
|
|
||||||
)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
state_data[check_data["name"]] = state_encode(new_item_state)
|
|
||||||
file_write(state_path, _json.dumps(state_data, indent = "\t"))
|
|
||||||
|
|
||||||
### send notifications
|
|
||||||
if (
|
|
||||||
(
|
|
||||||
(new_item_state["count"] is not None)
|
|
||||||
and
|
|
||||||
(new_item_state["count"] == check_data["threshold"])
|
|
||||||
)
|
|
||||||
or
|
|
||||||
(
|
|
||||||
(new_item_state["count"] is None)
|
|
||||||
and
|
|
||||||
check_data["annoy"]
|
|
||||||
)
|
|
||||||
):
|
|
||||||
for notification in check_data["notifications"]:
|
|
||||||
if (notification["kind"] in notification_channel_implementations):
|
|
||||||
notification_channel_implementations[notification["kind"]].notify(
|
|
||||||
notification["parameters"],
|
|
||||||
check_data["name"],
|
|
||||||
check_data,
|
|
||||||
new_item_state,
|
|
||||||
result["info"]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise ValueError("invalid notification kind: %s" % notification["kind"])
|
|
||||||
|
|
||||||
|
|
||||||
main()
|
|
||||||
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
class implementation_notification_channel_file_touch(interface_notification_channel):
|
|
||||||
|
|
||||||
'''
|
|
||||||
[implementation]
|
|
||||||
'''
|
|
||||||
def parameters_schema(self):
|
|
||||||
return {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": False,
|
|
||||||
"properties": {
|
|
||||||
"path": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"path"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
[implementation]
|
|
||||||
'''
|
|
||||||
def normalize_conf_node(self, node):
|
|
||||||
return dict_merge(
|
|
||||||
{
|
|
||||||
},
|
|
||||||
node
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
[implementation]
|
|
||||||
'''
|
|
||||||
def notify(self, parameters, name, data, state, info):
|
|
||||||
_os.path.touch(parameters["path"])
|
|
||||||
|
|
||||||
2
todo.md
2
todo.md
|
|
@ -6,4 +6,4 @@
|
||||||
- Möglichkeit dauerhaft laufen zulassen (evtl. als systemd-Dienst)
|
- Möglichkeit dauerhaft laufen zulassen (evtl. als systemd-Dienst)
|
||||||
- Versionierung
|
- Versionierung
|
||||||
- Test-Routinen
|
- Test-Routinen
|
||||||
|
- neu schreiben in TypeScript (und plankton dafür nutzen)?
|
||||||
|
|
|
||||||
116
tools/build
116
tools/build
|
|
@ -1,19 +1,99 @@
|
||||||
#!/usr/bin/env sh
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
mkdir -p build
|
import sys as _sys
|
||||||
echo "#!/usr/bin/env python3" > build/heimdall
|
import os as _os
|
||||||
cat \
|
import json as _json
|
||||||
source/packages.py \
|
import stat as _stat
|
||||||
source/lib.py \
|
|
||||||
source/check_kinds/_interface.py \
|
|
||||||
source/check_kinds/script.py \
|
def file_read(path):
|
||||||
source/check_kinds/file_timestamp.py \
|
handle = open(path, "r")
|
||||||
source/check_kinds/http_request.py \
|
content = handle.read()
|
||||||
source/notification_channels/_interface.py \
|
handle.close()
|
||||||
source/notification_channels/console.py \
|
return content
|
||||||
source/notification_channels/file_touch.py \
|
|
||||||
source/notification_channels/email.py \
|
|
||||||
source/notification_channels/libnotify.py \
|
def file_write(path, content):
|
||||||
source/main.py \
|
handle = open(path, "w")
|
||||||
>> build/heimdall
|
handle.write(content)
|
||||||
chmod +x build/heimdall
|
handle.close()
|
||||||
|
|
||||||
|
|
||||||
|
def string_coin(template, arguments):
|
||||||
|
result = template
|
||||||
|
for (key, value, ) in arguments.items():
|
||||||
|
result = result.replace("{{%s}}" % key, value)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def python_data_encode(data):
|
||||||
|
return _json.dumps(data, indent = "\t")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
## consts
|
||||||
|
dir_source = "source"
|
||||||
|
dir_build = "build"
|
||||||
|
sources_logic = [
|
||||||
|
_os.path.join(dir_source, "logic", "packages.py"),
|
||||||
|
_os.path.join(dir_source, "logic", "lib.py"),
|
||||||
|
_os.path.join(dir_source, "logic", "localization.py"),
|
||||||
|
_os.path.join(dir_source, "logic", "condition.py"),
|
||||||
|
_os.path.join(dir_source, "logic", "conf.py"),
|
||||||
|
_os.path.join(dir_source, "logic", "checks", "_interface.py"),
|
||||||
|
_os.path.join(dir_source, "logic", "checks", "script.py"),
|
||||||
|
_os.path.join(dir_source, "logic", "checks", "file_state.py"),
|
||||||
|
_os.path.join(dir_source, "logic", "checks", "http_request.py"),
|
||||||
|
_os.path.join(dir_source, "logic", "channels", "_interface.py"),
|
||||||
|
_os.path.join(dir_source, "logic", "channels", "console.py"),
|
||||||
|
_os.path.join(dir_source, "logic", "channels", "email.py"),
|
||||||
|
_os.path.join(dir_source, "logic", "channels", "libnotify.py"),
|
||||||
|
_os.path.join(dir_source, "logic", "main.py"),
|
||||||
|
]
|
||||||
|
path_compilation = _os.path.join(dir_build, "heimdall")
|
||||||
|
|
||||||
|
## exec
|
||||||
|
if (not _os.path.exists(dir_build)):
|
||||||
|
_os.mkdir(dir_build)
|
||||||
|
|
||||||
|
compilation = ""
|
||||||
|
compilation += "#!/usr/bin/env python3\n\n"
|
||||||
|
|
||||||
|
### localization
|
||||||
|
if True:
|
||||||
|
localization_data = dict(
|
||||||
|
map(
|
||||||
|
lambda entry: (
|
||||||
|
entry.name.split(".")[0],
|
||||||
|
_json.loads(file_read(entry.path)),
|
||||||
|
),
|
||||||
|
filter(
|
||||||
|
lambda entry: (entry.is_file() and entry.name.endswith(".json")),
|
||||||
|
_os.scandir(_os.path.join(dir_source, "localization"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
compilation += string_coin(
|
||||||
|
"localization_data = {{data}}\n\n",
|
||||||
|
{
|
||||||
|
"data": python_data_encode(localization_data),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
### logic
|
||||||
|
for path in sources_logic:
|
||||||
|
compilation += (file_read(path) + "\n")
|
||||||
|
|
||||||
|
### write to file
|
||||||
|
if _os.path.exists(path_compilation):
|
||||||
|
_os.remove(path_compilation)
|
||||||
|
file_write(path_compilation, compilation)
|
||||||
|
|
||||||
|
### postproess
|
||||||
|
_os.chmod(
|
||||||
|
path_compilation,
|
||||||
|
(_stat.S_IRWXU | _stat.S_IXGRP | _stat.S_IXOTH)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue