core/source/main.py

279 lines
7 KiB
Python
Raw Normal View History

2022-11-29 23:53:14 +01:00
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 conf_normalize_check(check_kind_implementations, defaults, name, node):
if ("kind" not in node):
raise ValueError("missing mandatory 'member' field 'kind'")
else:
if (node["kind"] not in check_kind_implementations):
raise ValueError("unhandled kind: %s" % node["kind"])
else:
node_ = dict_merge(
{
"title": name,
"active": True,
"schedule": defaults["schedule"],
"notifications": defaults["notifications"],
"parameters": {},
},
node
)
return {
"title": node_["title"],
"active": node_["active"],
"schedule": node_["schedule"],
"notifications": node_["notifications"],
"kind": node_["kind"],
"parameters": check_kind_implementations[node_["kind"]].normalize_conf_node(node_["parameters"]),
}
def conf_normalize_defaults(node):
return dict_merge(
{
"active": True,
"schedule": {"kind": "hourly"},
"notifications": [],
},
node
)
def conf_normalize_root(check_kind_implementations, node):
return dict(
map(
lambda check_pair: (
check_pair[0],
conf_normalize_check(
check_kind_implementations,
conf_normalize_defaults(node["defaults"]),
check_pair[0],
check_pair[1]
),
),
node["checks"].items()
)
)
def main():
## args
argumentparser = _argparse.ArgumentParser(
2022-11-30 00:36:39 +01:00
description = "Heimdall-Monitoring-Tool",
2022-11-29 23:53:14 +01:00
formatter_class = _argparse.ArgumentDefaultsHelpFormatter
)
argumentparser.add_argument(
"-c",
"--conf-path",
type = str,
2022-11-30 00:36:39 +01:00
default = "monitoring.hmdl.json",
2022-11-29 23:53:14 +01:00
dest = "conf_path",
metavar = "<conf-path>",
help = "path to the configuration file"
)
argumentparser.add_argument(
"-s",
"--state-path",
type = str,
2022-11-30 00:36:39 +01:00
default = None,
2022-11-29 23:53:14 +01:00
dest = "state_path",
metavar = "<state-path>",
2022-11-30 00:36:39 +01:00
help = "path to the state file, which contains information about the recent checks; default: file in temporary directory, unique for the conf-path input"
2022-11-29 23:53:14 +01:00
)
argumentparser.add_argument(
"-t",
"--threshold",
type = int,
default = 3,
dest = "threshold",
metavar = "<threshold>",
help = "how often a condition has to occur in order to be reported"
)
2022-11-30 00:36:39 +01:00
argumentparser.add_argument(
"-a",
"--awareness-interval",
type = int,
default = 120,
dest = "awareness_interval",
metavar = "<awareness-interval>",
help = "seconds to wait until starting the next run of a check, for which the condition has changed recently"
)
2022-11-29 23:53:14 +01:00
argumentparser.add_argument(
"-k",
"--keep-notifying",
action = "store_true",
default = False,
dest = "keep_notifying",
help = "whether notifications shall be kept sending after the threshold has been surpassed"
)
argumentparser.add_argument(
"-x",
"--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()
2022-11-30 00:36:39 +01:00
## 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_})
)
)
2022-11-29 23:53:14 +01:00
## exec
2022-11-30 00:36:39 +01:00
_sys.stderr.write(">> state file path: %s\n" % state_path)
2022-11-29 23:53:14 +01:00
### load check kind implementations
check_kind_implementations = {
"script": implementation_check_kind_script(),
"http_request": implementation_check_kind_http_request(),
}
### load notification channel implementations
notification_channel_implementations = {
"console": implementation_notification_channel_console(),
"email": implementation_notification_channel_email(),
}
### get configuration data
checks = conf_normalize_root(check_kind_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
2022-11-30 00:36:39 +01:00
if (not _os.path.exists(state_path)):
2022-11-29 23:53:14 +01:00
state_data = {}
2022-11-30 00:36:39 +01:00
file_write(state_path, _json.dumps(state_data, indent = "\t"))
2022-11-29 23:53:14 +01:00
else:
2022-11-30 00:36:39 +01:00
state_data = _json.loads(file_read(state_path))
2022-11-29 23:53:14 +01:00
### iterate through checks
for (check_name, check_data, ) in checks.items():
if (not check_data["active"]):
pass
else:
### get old state and examine whether the check shall be executed
old_item_state = (
None
if (check_name not in state_data) else
state_decode(state_data[check_name])
)
timestamp = get_current_timestamp()
due = (
(old_item_state is None)
or
(
(old_item_state["count"] is not None)
and
2022-11-30 00:36:39 +01:00
((timestamp - old_item_state["timestamp"]) >= args.awareness_interval)
2022-11-29 23:53:14 +01:00
)
or
(
(
(check_data["schedule"]["kind"] == "minutely")
and
((timestamp - old_item_state["timestamp"]) >= (60))
)
or
(
(check_data["schedule"]["kind"] == "hourly")
and
((timestamp - old_item_state["timestamp"]) >= (60 * 60))
)
or
(
(check_data["schedule"]["kind"] == "daily")
and
((timestamp - old_item_state["timestamp"]) >= (60 * 60 * 24))
)
)
)
if (not due):
pass
else:
_sys.stderr.write(
string_coin(
"-- {{check_name}}\n",
{
"check_name": check_name,
}
)
)
### execute check and set new state
result = check_kind_implementations[check_data["kind"]].run(check_data)
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) <= args.threshold)
) else
None
)
),
}
state_data[check_name] = state_encode(new_item_state)
2022-11-30 00:36:39 +01:00
file_write(state_path, _json.dumps(state_data, indent = "\t"))
2022-11-29 23:53:14 +01:00
### send notifications
if (
(
(new_item_state["count"] is not None)
and
(new_item_state["count"] == args.threshold)
)
or
(
(new_item_state["count"] is None)
and
args.keep_notifying
)
):
for notification in check_data["notifications"]:
if (notification["kind"] in notification_channel_implementations):
notification_channel_implementations[notification["kind"]].notify(
notification["parameters"],
check_name,
check_data,
new_item_state,
result["output"]
)
else:
raise ValueError("invalid notification kind: %s" % notification["kind"])
main()