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 " ] ,
}
2022-11-30 10:26:27 +01:00
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_defaults ( node ) :
return dict_merge (
{
" active " : True ,
" threshold " : 3 ,
" annoy " : False ,
" schedule " : {
" regular_interval " : ( 60 * 60 ) ,
" attentive_interval " : ( 60 * 2 ) ,
} ,
" notifications " : [
] ,
} ,
node
)
2022-11-29 23:53:14 +01:00
def conf_normalize_check ( check_kind_implementations , defaults , name , node ) :
if ( " kind " not in node ) :
2022-11-30 08:15:35 +01:00
raise ValueError ( " missing mandatory ' check ' field ' kind ' " )
2022-11-29 23:53:14 +01:00
else :
if ( node [ " kind " ] not in check_kind_implementations ) :
raise ValueError ( " unhandled kind: %s " % node [ " kind " ] )
else :
node_ = dict_merge (
{
" title " : name ,
2022-11-30 00:45:51 +01:00
" active " : defaults [ " active " ] ,
2022-11-30 10:26:27 +01:00
" threshold " : defaults [ " threshold " ] ,
" annoy " : defaults [ " annoy " ] ,
2022-11-29 23:53:14 +01:00
" schedule " : defaults [ " schedule " ] ,
" notifications " : defaults [ " notifications " ] ,
" parameters " : { } ,
} ,
node
)
return {
" title " : node_ [ " title " ] ,
" active " : node_ [ " active " ] ,
2022-11-30 10:26:27 +01:00
" threshold " : node_ [ " threshold " ] ,
" annoy " : node_ [ " annoy " ] ,
" schedule " : conf_normalize_schedule ( node_ [ " schedule " ] ) ,
2022-11-29 23:53:14 +01:00
" notifications " : node_ [ " notifications " ] ,
" kind " : node_ [ " kind " ] ,
" parameters " : check_kind_implementations [ node_ [ " kind " ] ] . normalize_conf_node ( node_ [ " parameters " ] ) ,
}
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
)
2022-11-30 08:15:35 +01:00
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 "
)
2022-11-29 23:53:14 +01:00
argumentparser . add_argument (
2022-11-30 08:15:35 +01:00
" -e " ,
2022-11-29 23:53:14 +01:00
" --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 ( ) ,
2022-11-30 10:26:27 +01:00
" file_timestamp " : implementation_check_kind_file_timestamp ( ) ,
2022-11-29 23:53:14 +01:00
" http_request " : implementation_check_kind_http_request ( ) ,
}
### load notification channel implementations
notification_channel_implementations = {
" console " : implementation_notification_channel_console ( ) ,
2022-11-30 10:26:27 +01:00
" file_touch " : implementation_notification_channel_file_touch ( ) ,
2022-11-29 23:53:14 +01:00
" email " : implementation_notification_channel_email ( ) ,
2022-11-30 10:26:27 +01:00
" libnotify " : implementation_notification_channel_libnotify ( ) ,
2022-11-29 23:53:14 +01:00
}
### 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 08:15:35 +01:00
if (
( not _os . path . exists ( state_path ) )
or
args . erase_state
) :
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
2022-11-30 08:15:35 +01:00
( old_item_state [ " condition " ] != enum_condition . ok )
or
2022-11-30 10:26:27 +01:00
( ( timestamp - old_item_state [ " timestamp " ] ) > = check_data [ " schedule " ] [ " regular_interval " ] )
or
2022-11-29 23:53:14 +01:00
(
( old_item_state [ " count " ] is not None )
and
2022-11-30 10:26:27 +01:00
( ( timestamp - old_item_state [ " timestamp " ] ) > = check_data [ " schedule " ] [ " attentive_interval " ] )
2022-11-29 23:53:14 +01:00
)
)
if ( not due ) :
pass
else :
_sys . stderr . write (
string_coin (
" -- {{ check_name}} \n " ,
{
" check_name " : check_name ,
}
)
)
### execute check and set new state
2022-11-30 08:15:35 +01:00
result = check_kind_implementations [ check_data [ " kind " ] ] . run ( check_data [ " parameters " ] )
2022-11-29 23:53:14 +01:00
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
2022-11-30 10:26:27 +01:00
( ( old_item_state [ " count " ] + 1 ) < = check_data [ " threshold " ] )
2022-11-29 23:53:14 +01:00
) 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
2022-11-30 10:26:27 +01:00
( new_item_state [ " count " ] == check_data [ " threshold " ] )
2022-11-29 23:53:14 +01:00
)
or
(
( new_item_state [ " count " ] is None )
and
2022-11-30 10:26:27 +01:00
check_data [ " annoy " ]
2022-11-29 23:53:14 +01:00
)
) :
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 ( )