Skip to content

Commit

Permalink
Added two templates, a full and a minimal template as the basis for i…
Browse files Browse the repository at this point in the history
…mplementing new plug-ins.

Renamed option "fallback action" to "fallback"
Fallback action executed repair. Fixed this.
  • Loading branch information
jbaumann committed Jan 5, 2022
1 parent 2874fca commit 842a274
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 22 deletions.
105 changes: 105 additions & 0 deletions plugins/template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from configparser import ConfigParser
import logging
from typing import Tuple, Dict, Any, Callable, List

# Values for the dictionary that provides implementations for
# preparation, check and repair action. Direct copy from system_watchdog.py
PREP = "prep"
CHECK = "check"
REPAIR = "repair"
FALLBACK = "fallback"

# The name of our configuration type as it has been loaded by the system_watchdog
config_name = ""

# This function is called by the system_watchdog to register the functions
# this configuration implementation provides.
def register(config_type: str) -> Dict[str, Callable]:
global config_name
config_name = config_type
logging.debug("registering implementation for '%s'", config_name)
return {
PREP: prepare_configuration,
CHECK: check_configuration,
REPAIR: repair,
FALLBACK: fallback,
}

# After the configuration has been loaded we check whether the general
# necessary prerequisites have been satisfied using the function
# check_prerquisites() which stores the result in the global
# variable prerequisistes_satisfied. This variable is then checked
# in the function prepare_configuration() to determine whether the
# configuration can be executed.
# Optional: If no prerequisites have to satisfied, you can remove this
# function and its call at the end of the file.
prerequisites_satisfied = False
def check_prerequisites() -> None:
global prerequisites_satisfied

# Example for checking the availability of the
# Paho MQTT package
# try:
# import paho.mqtt.subscribe as subscribe
# prerequisites_satisfied = True
# logging.debug("Optional MQTT package found.")
# except ImportError or ModuleNotFoundError:
# logging.debug("No MQTT implementation found. Use 'pip3 install paho-mqtt' to install.")
# logging.debug("Continuing without MQTT support.")
# pass
prerequisites_satisfied = True

# Prepare the specific configuration to be executed. This function checks
# whether necessary options are available and set in a correct manner,
# can convert values for better handling and generally prepares the
# configuration for an efficient execution in the check phase.
# Optional: If no preparation is necessary, you can remove this function
# and its reference in the dictionary that the function register() returns.
def prepare_configuration(section: str, config: ConfigParser) -> None:
logging.debug("Prepare configuration %s" % config_name)
return True

# The actual check of the configuration should be implemented as efficient
# as possible, since it will be executed every 'sleep time' seconds.
# Different return values can be used to signal the time interval that
# had to be used to verify that the check was not successful.
# Return values:
# 0 the check was successful
# 1 the check was not successful and took no relevant time
# 2 the check was not successful and took 'sleep time' seconds
# 3 the check was not sucessful and took 'timeout' seconds
#
# This function is mandatory
def check_configuration(section: str, config: ConfigParser) -> int:
logging.debug("%s: Checking configuration with option '%s'"
% (config_name, config[config_name]))
# Example for executing a shell command
# cp = subprocess.run(shlex.split(config[COMMAND]), capture_output=True)
# return cp.returncode
return 0

# The repair command is executed when checking the configuration hasn't been
# sucessful for more than 'timeout' seconds. This function implements the
# repair action.
# Optional: If no repair function is given in the dictionary that registers
# this configuration then the repair command will be executed as a shell
# command.
def repair(section: str, config: ConfigParser) -> int:
logging.debug("%s: Repairing configuration with '%s'" % (config_name, config[REPAIR]))
return 0

# If the timeout has been reached again after executing the repair command
# without the check command being successful, then the fallback action will
# be executed.
# Optional: If no falback function is given in the dictionary that registers
# this configuration then the fallback command will be executed as a shell
# command.
def fallback(section: str, config: ConfigParser) -> int:
logging.debug("%s: Execute fallback for configuration with '%s'"
% (config_name, config[FALLBACK]))
return 0

###############################################################
# After the configuration has been loaded we check whether the
# necessary prerequisites have been satisfied using this call
check_prerequisites()
37 changes: 37 additions & 0 deletions plugins/template_min.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from configparser import ConfigParser
import logging
from typing import Tuple, Dict, Any, Callable, List

# Values for the dictionary that provides implementations for
# preparation, check and repair action. Direct copy from system_watchdog.py
PREP = "prep"
CHECK = "check"
REPAIR = "repair"
FALLBACK = "fallback"

# The name of our configuration type as it has been loaded by the system_watchdog
config_name = ""

# This function is called by the system_watchdog to register the functions
# this configuration implementation provides.
def register(config_type: str) -> Dict[str, Callable]:
global config_name
config_name = config_type
logging.debug("registering implementation for '%s'", config_name)
return { CHECK: check_configuration }

# The actual check of the configuration should be implemented as efficient
# as possible, since it will be executed every 'sleep time' seconds.
# Different return values can be used to signal the time interval that
# had to be used to verify that the check was not successful.
# Return values:
# 0 the check was successful
# 1 the check was not successful and took no relevant time
# 2 the check was not successful and took 'sleep time' seconds
# 3 the check was not sucessful and took 'timeout' seconds
#
# This function is mandatory
def check_configuration(section: str, config: ConfigParser) -> int:
logging.debug("%s: Checking configuration with option '%s'"
% (config_name, config[config_name]))
return 0
6 changes: 6 additions & 0 deletions system_watchdog.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ timeout = 60
;sleep time = 5
;timeout = 30

;[template]
;type = template
;template = sensible command
;repair = touch repair
;fallback = touch fallback

[mqtt]
type = mqtt
mqtt = timeout
Expand Down
48 changes: 26 additions & 22 deletions system_watchdog.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,29 @@
from typing import Tuple, Dict, Any, Callable, List

MAJOR = 1
MINOR = 5
PATCH = 1
MINOR = 6
PATCH = 2
version = "%i.%i.%i" % (MAJOR, MINOR, PATCH)

# Defaults for values in the general section
# Defaults for values in the general and other sections
GENERAL_SECTION = "general"
PRIMED = "primed"
FALLBACK_ACTION = "fallback action"
FALLBACK = "fallback"
LOG_LEVEL = "loglevel"
SLEEP_TIME = "sleep time"
TIME_OUT = "timeout"
TYPE = "type"
VERSION = "version"
general_config = {
PRIMED : 0,
FALLBACK_ACTION : "sudo reboot",
FALLBACK : "sudo reboot",
LOG_LEVEL : "DEBUG",
SLEEP_TIME : 20,
TIME_OUT : 120,
VERSION : -1,
}
REPAIR = "repair"


# Values for the entries of the different configurations
COMMAND = "command"
Expand All @@ -49,12 +51,9 @@
PASSWORD = "password"

# Values for the dictionary that provides implementations for
# preparation, check and repair action
# preparation, check and repair action if not already defined above
PREP = "prep"
CHECK = "check"
REPAIR = "repair"
FALLBACK = "fallback"


# Values for mapping the different primed levels to numerical values
UNPRIMED = "unprimed"
Expand Down Expand Up @@ -141,8 +140,8 @@ def main(cmd_args: List[str]):
if TYPE not in config[section]:
logging.warning("%s: No type defined in section. Ignoring section." % section)
continue
if FALLBACK_ACTION not in config[section]:
config[section][FALLBACK_ACTION] = general_config[FALLBACK_ACTION]
if FALLBACK not in config[section]:
config[section][FALLBACK] = general_config[FALLBACK]

section_type = config[section][TYPE]
if section_type not in entry_type_implementation:
Expand Down Expand Up @@ -285,8 +284,13 @@ def thread_impl(section: str, callback: Dict[str, Callable], config: ConfigParse
sleeptime = int(config[SLEEP_TIME])
else:
sleeptime = general_config[SLEEP_TIME]

# verify the command and repair option are set in the config

# Verify that a check entry is in the callback dictionary
if not CHECK in callback:
logging.warning("%s: No check function registered by plugin. Aborting." % section)
return

# Verify that the type and repair option are set in the config
msg = "%s: No '%s' option given."
if config[TYPE] not in config:
logging.warning((msg + " Aborting.") % (section, config[TYPE]))
Expand Down Expand Up @@ -363,7 +367,7 @@ def thread_impl(section: str, callback: Dict[str, Callable], config: ConfigParse
if REPAIR in callback:
return_code = callback[REPAIR](section, config)
else:
return_code = generic_execute(section, config)
return_code = generic_exec(section, config, config[REPAIR])

else:
logging.debug("%s: Not executing '%s'. Primed value too low." % (section, config[REPAIR]))
Expand All @@ -381,26 +385,26 @@ def thread_impl(section: str, callback: Dict[str, Callable], config: ConfigParse

if general_config[PRIMED] >= primed_level[FULLY_PRIMED]:
logging.warning("%s: Trying fallback action '%s'"
% (section, config[FALLBACK_ACTION]))
% (section, config[FALLBACK]))

if FALLBACK_ACTION in callback:
return_code = callback[FALLBACK_ACTION](section, config)
if FALLBACK in callback:
return_code = callback[FALLBACK](section, config)
else:
return_code = generic_execute(section, config)
return_code = generic_exec(section, config, config[FALLBACK])

if return_code != 0:
logging.warning("%s: Fallback action '%s' unsuccessful"
% (section, config[FALLBACK_ACTION]))
% (section, config[FALLBACK]))
else:
logging.debug("%s: Not executing '%s'. Primed value too low." % (section, config[FALLBACK_ACTION]))
logging.debug("%s: Not executing '%s'. Primed value too low." % (section, config[FALLBACK]))
except Exception as e:
# if there was an exception we terminate this configuration
logging.debug("%s: Exception occurred. Terminating: %s" % (section, e))
pass

# Implementation of the Callbacks
def generic_execute(section: str, config: ConfigParser) -> int:
cp = subprocess.run(shlex.split(config[REPAIR]), capture_output=True)
def generic_exec(section: str, config: ConfigParser, command: str) -> int:
cp = subprocess.run(shlex.split(command), capture_output=True)
return cp.returncode

# Implementation of the Command Callbacks
Expand Down

0 comments on commit 842a274

Please sign in to comment.