/* Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' »heimdall« is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. »heimdall« is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with »heimdall«. If not, see . */ namespace _heimdall.master { /** * @todo automated tests */ export function determine_due( check : _heimdall.type_check, old_item_state : (null | _heimdall.type_item_state), options : { timestamp ?: int; } = {} ) : boolean { options = Object.assign( { "timestamp": _heimdall.get_current_timestamp(), }, options ); return ( check.active && ( (old_item_state === null) || ((options.timestamp - old_item_state.timestamp) >= check.schedule.regular_interval) || ( (old_item_state.count !== null) && (old_item_state.condition !== _heimdall.enum_condition.ok) && ((options.timestamp - old_item_state.timestamp) >= check.schedule.attentive_interval) ) ) ); } /** * @todo automated tests */ export function determine_shall_send_notification( check : _heimdall.type_check, send_ok_notifications : boolean, count : (null | int), timestamp : int, old_item_state : (null | _heimdall.type_item_state), result : _heimdall.type_result ) : boolean { return ( ( ( (! (count === null)) && (count === check.threshold) ) || ( (count === null) && check.annoy ) || ( (count === null) && ( (! (old_item_state === null)) && (! (old_item_state.last_notification_timestamp === null)) && (! (check.schedule.reminding_interval === null)) && ( (timestamp - old_item_state.last_notification_timestamp) >= check.schedule.reminding_interval ) ) ) ) && ( (result.condition !== _heimdall.enum_condition.ok) || send_ok_notifications ) ); } /** */ export async function clean( options : { time_to_live ?: int; erase_state ?: boolean; } = {} ) : Promise { options = Object.assign( { "time_to_live": (7 * 24 * 60 * 60), "erase_state": false, }, options ); const count : int = await _heimdall.state_repository.clean( options.time_to_live, options.erase_state ); lib_plankton.log.info( lib_plankton.translate.get( "misc.cleanup_info", { "count": count.toFixed(0), } ), { } ); } /** */ export async function wrap_database( database_path_raw : (null | string), order_path : string, procedure : (() => Promise) ) : Promise { const nm_path = require("path"); const database_path : string = ( (database_path_raw !== null) ? database_path_raw : nm_path.join( // TODO get path from fs or path module? "/tmp", lib_plankton.string.coin( "monitoring-state-{{id}}.sqlite", { "id": lib_plankton.call.convey( order_path, [ nm_path.normalize, // encode(ascii), x => lib_plankton.sha256.get(x), x => x.slice(0, 8), ] ), } ) ) ); lib_plankton.log.info( lib_plankton.translate.get("misc.state_file_path"), { "database_path": database_path, } ); await _heimdall.state_repository.init(database_path); await procedure(); await _heimdall.state_repository.kill(); } /** * @todo outsource? */ export async function wrap_mutex( mutex_path : string, procedure : (() => Promise) ) : Promise { const nm_fs = require("fs"); if (nm_fs.existsSync(mutex_path)) { lib_plankton.log.error( lib_plankton.translate.get("misc.still_running"), { "mutex_path": mutex_path, } ); return Promise.reject(); } else { await lib_plankton.file.write(mutex_path, ""); await procedure(); await lib_plankton.file.delete_(mutex_path); } } /** * central processing procedure */ export async function run( order : _heimdall.type_order, options : { send_ok_notifications ?: boolean; } = {} ) : Promise { options = Object.assign( { "send_ok_notifications": false }, options ); const notification_kind_implementations : Record = _heimdall.notification_kinds.get_implementations(); const check_kind_implementations : Record = _heimdall.check_kinds.get_implementations(); for await (const check of order.checks) { if (! check.active) { // do nothing } else { let old_item_state : (null | _heimdall.type_item_state); const last_notification_timestamp : (null | int) = await _heimdall.state_repository.get_last_notification_timestamp( check.name ); const rows : Array< { timestamp : int; condition : _heimdall.enum_condition; notification_sent : boolean; } > = await _heimdall.state_repository.probe( check.name, (check.threshold + 1) ); if (rows.length <= 0) { old_item_state = null; } else { let index : int = 1; while (index < rows.length) { if (rows[index].condition === rows[0].condition) { index += 1; } else { break; } } old_item_state = { "timestamp": rows[0].timestamp, "condition": rows[0].condition, "count": ((index < check.threshold) ? index : null), "last_notification_timestamp": last_notification_timestamp, }; } const timestamp : int = _heimdall.get_current_timestamp(); const due : boolean = determine_due( check, old_item_state, { "timestamp": timestamp, } ); if (! due) { // do nothing } else { process.stderr.write( lib_plankton.string.coin( "-- {{check_name}}\n", { "check_name": check.name, } ) ); // execute check and set new state let result : _heimdall.type_result; try { result = await check_kind_implementations[check.kind].run(check.parameters); } catch (error) { result = { "condition": _heimdall.enum_condition.unknown, "info": { "cause": lib_plankton.translate.get("misc.check_procedure_failed"), "error": error.toString(), }, }; } const count : (null | int) = ( ( (old_item_state === null) || (old_item_state.condition !== result.condition) ) ? 1 : ( ( (! (old_item_state.count === null)) && ((old_item_state.count + 1) <= check.threshold) ) ? (old_item_state.count + 1) : null ) ); const shall_send_notification : boolean = determine_shall_send_notification( check, options.send_ok_notifications, count, timestamp, old_item_state, result ); const new_item_state : _heimdall.type_item_state = { "timestamp": timestamp, "condition": result.condition, "count": count, "last_notification_timestamp": ( shall_send_notification ? timestamp : ( (old_item_state === null) ? null : old_item_state.last_notification_timestamp ) ), } await _heimdall.state_repository.feed( check.name, timestamp, result.condition, shall_send_notification, result.info ); // send notifications if (! shall_send_notification) { // do nothing } else { check.notifications.forEach( notification => { notification_kind_implementations[notification.kind].notify( notification.parameters, check.name, check, new_item_state, Object.assign( ( (check.custom === null) ? {} : {"custom": check.custom} ), result.info ) ) } ); } } } } } }