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 ,
2022-11-30 00:45:51 +01:00
" active " : defaults [ " active " ] ,
2022-11-29 23:53:14 +01:00
" 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 ( )