diff --git a/fhem/contrib/98_systemd_watchdog.pm b/fhem/contrib/98_systemd_watchdog.pm new file mode 100644 index 000000000..30b7dd14b --- /dev/null +++ b/fhem/contrib/98_systemd_watchdog.pm @@ -0,0 +1,343 @@ +############################################################################### +# +# Copyright notice +# +# (c) 2018 Alexander Schulz +# +# This script 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 2 of the License, or +# (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the textfile GPL.txt and important notices to the license +# from the author is found in LICENSE.txt distributed with these scripts. +# +# This script 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. +# +# This copyright notice MUST APPEAR in all copies of the script! +# +############################################################################### + +# $Id$ + +package main; + +use strict; +use warnings; +use POSIX; +use Time::HiRes qw(gettimeofday); +use Socket; + +sub watchdog_client_NotifySystemD($$); +sub watchdog_client_Stop($); +sub watchdog_client_Start($); +sub watchdog_client_ProcessTimer(@); +sub watchdog_client_IsWDAvailable($); + +sub systemd_watchdog_Initialize($) { + my ($hash) = @_; + + # Consumer + $hash->{DefFn} = "watchdog_client_Define"; + $hash->{UndefFn} = "watchdog_client_Undefine"; + $hash->{ShutdownFn} = "watchdog_client_Shutdown"; + $hash->{NotifyFn} = "watchdog_client_Notify"; + + #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_Initialize"); + RemoveInternalTimer($hash); + + return undef; +} + +sub watchdog_client_IsWDAvailable($) { + my ( $hash ) = @_; + #return 1; # TODO XXX TEST + return defined($hash->{'.systemd'}); +} + +sub watchdog_client_Define($$) { + my ( $hash, $def ) = @_; + #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_Define"); + my $name = $hash->{NAME}; + + # prevent multiple instances + my @devices = devspec2array("TYPE=watchdog_client"); + foreach my $dev (@devices) { + if($dev ne $name) { + return "only one instance is allowed"; + } + } + + # remove old timer + RemoveInternalTimer($hash); + + # check systemd watchdog available + my $sname = $ENV{NOTIFY_SOCKET}; + if(defined($sname)) { + $hash->{'systemd-watchdog'}="available"; + $hash->{'.systemd'}=1; + Log3($hash->{NAME},4,"Watchdog Client: systemd-watchdog available. starting watchdog client"); + } else { + $hash->{'systemd-watchdog'}="not available"; + $hash->{'.systemd'}=undef; + Log3($hash->{NAME},1,"Watchdog Client: systemd watchdog is not available. Module inactiv."); + } + # Initialize + watchdog_client_Start($hash); + return undef; +} + +sub watchdog_client_Undefine($) { + my ($hash) = @_; + #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_Undefine"); + # Clean up + watchdog_client_Stop($hash); + return undef; +} + +sub watchdog_client_Shutdown($) { + my ($hash) = @_; + #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_Shutdown"); + return undef unless defined $hash->{'.initialized'}; + # Shutdown => Deaktivate watchdog + my $name = $hash->{NAME}; + Log3($name,2,"Watchdog Client: Shutting down"); + watchdog_client_Stop($hash); + return undef; +} + +sub watchdog_client_Notify($$) { + my ($hash,$dev) = @_; + #Log3($hash->{NAME},1,"Watchdog Client: Debug: watchdog_client_Notify: --- "); + if( $dev->{NAME} eq "global" ) { + # if( grep(m/^INITIALIZED$/, @{$events}) ) { + # Log3($hash->{NAME},1,"Watchdog Client: Debug: watchdog_client_Notify: INITIALIZED"); + # watchdog_client_Start($hash) unless defined $hash->{'.initialized'}; + # return undef; + # } elsif( grep(m/^REREADCFG$/, @{$events}) ) { + # # + # return undef; + # } + if( grep(m/^(INITIALIZED|REREADCFG)$/, @{$dev->{CHANGED}}) ) { + #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_Notify: GLOBAL"); + watchdog_client_Start($hash) unless defined $hash->{'.initialized'}; + } + } +} + +sub watchdog_client_ProcessTimer(@) { + my ($hash) = @_; + #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_ProcessTimer"); + # Reset watchdog + watchdog_client_NotifySystemD($hash, "WATCHDOG=1\n"); + + my $sleep = $hash->{'sleep-time'}; + $sleep = 30 unless defined $sleep; + my $now = gettimeofday(); + my $next = int($now) + $sleep; + InternalTimer($next, 'watchdog_client_ProcessTimer', $hash, 0); + readingsSingleUpdate($hash,"next",FmtTime($next),1); +} + +sub watchdog_client_Start($) { + my ($hash) = @_; + #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_Start"); + unless ($main::init_done) { + return if $hash->{'.firsttime'}; + watchdog_client_NotifySystemD($hash, "STATUS=starting\n"); + watchdog_client_NotifySystemD($hash, "MAINPID=$$\n"); + readingsSingleUpdate($hash,"state","starting",1); + $hash->{'.firsttime'}=1; + return; + } + return if $hash->{'.initialized'}; + + unless (watchdog_client_IsWDAvailable($hash)) { + Log3($hash->{NAME},2,"Watchdog Client: no systemd watchdog available"); + readingsSingleUpdate($hash,"state","inactiv",1); + readingsSingleUpdate($hash,"next","none",1); + return; + } + + my $sleep = ($ENV{WATCHDOG_USEC} // 120000000) / 4 / 1000000; + $hash->{'sleep-time'} = $sleep; + $hash->{'.initialized'} = 1; + + my $next = int(gettimeofday()) + 1; + InternalTimer($next, 'watchdog_client_ProcessTimer', $hash, 0); + + # System ready + watchdog_client_NotifySystemD($hash, "READY=1\n"); + watchdog_client_NotifySystemD($hash, "MAINPID=$$\n"); + watchdog_client_NotifySystemD($hash, "STATUS=started\n"); + + Log3($hash->{NAME},2,"Watchdog Client: initialized"); + readingsSingleUpdate($hash,"state","active",1); +} + +sub watchdog_client_Stop($) { + my ($hash) = @_; + #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_Stop"); + watchdog_client_NotifySystemD($hash, "STOPPING=1\n"); + watchdog_client_NotifySystemD($hash, "STATUS=stopping\n"); + RemoveInternalTimer($hash); + $hash->{'.initialized'} = 0; + my $name = $hash->{NAME}; + Log3($name,2,"Watchdog Client: deactivated"); + readingsSingleUpdate($hash,"state","deactivated",1); +} + +sub watchdog_client_NotifySystemD($$) { + my ($hash,$cmd) = @_; + #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_NotifySystemD: $cmd"); + return unless defined $hash->{'.initialized'}; + return unless watchdog_client_IsWDAvailable($hash); + + my $name = $hash->{NAME}; + #Log3($name,1,"Watchdog Client: notify systemd-watchdog: $cmd"); + + my $sname = $ENV{NOTIFY_SOCKET}; + if(!defined($sname)) { + #watchdog_client_Stop($hash); + Log3($name,1,"Watchdog Client: NOTIFY_SOCKET not available. Please configure systemd-watchdog properly!"); + return; + } + + Log3($name,4,"Watchdog Client: notify systemd-watchdog: $cmd"); + my $sock_addr = sockaddr_un($sname); + socket(my $server, PF_UNIX,SOCK_DGRAM,0); + connect($server, $sock_addr); + print $server $cmd; + close($server); +} + +1; + +=pod +=begin html_DE + + +
+ Systemd erlaubt Ueberwachung von Programmen mittels eines Watchdogs. + Sendet der Prozess innerhalnb eines definierten Interval kein 'Lebenszeichen', + wird dieser gestoppt und neu gestartet. + Dieses Modul sendet periodisch eine keep-alive Nachricht an das Systemd-Watchdog. +
+
+ FHEM muss unter Kontrolle von Systemd laufen und Watchdog muss korrekt konfiguriert sein.
+ Folgendes Script kann benutzt werden:
+
+ [Unit]
+ Description=FHEM Home Automation
+ Requires=network.target
+ #After=network.target
+ After=dhcpcd.service
+
+ [Service]
+ Type=forking
+ NotifyAccess=all
+ User=fhem
+ Group=dialout
+ WorkingDirectory=/opt/fhem
+ ExecStart=/usr/bin/perl fhem.pl fhem.cfg
+ #ExecStart=/usr/bin/perl fhem.pl configDB
+ TimeoutStartSec=240
+ TimeoutStopSec=120
+ #ExecStop=/usr/bin/pkill -U fhem perl
+ ExecStop=/usr/bin/pkill -f -U fhem "fhem.pl fhem.cfg"
+ # Restart options: no, on-success, on-failure, on-abnormal, on-watchdog, on-abort, or always.
+ Restart=on-failure
+ RestartSec=3
+ WatchdogSec=180
+ PIDFile=/opt/fhem/log/fhem.pid
+
+ [Install]
+ WantedBy=multi-user.target
+
+ Das Script kann unter "/etc/systemd/system/fhem.service" angelegt werden.
+ Mit "sudo systemctl daemon-reload" wird sysgtemd-Konfiguration erneuert.
+ Anschliessend kann FHEM mit folgendem Befehl gestartet werden: "sudo systemctl start fhem.service".
+
+ Wenn in dem Script "Type=notify" verwendet wird, muss global Attribute "nofork=1" gesetzt sein.
+ Bei "Type=forking" muss in Script der korrekte Pfad zu dem PID-Datei angegeben werden,
+ diese Datei muss auch in FHEM mit dem global Attribute "pidfilename" aktiviert sein.
+
Define
+define <name> systemd_watchdog
Specifies the device.
++ Systemd allows monitoring of programs by a watchdog. + If a process does not respond within a certain time interval, it will be stopped and restarted. + This module periodically sends keep-alive message to the systemd. +
+
+ fhem must be started under control of systemd. Watchdog must be also configured properly.
+ You can use the following script:
+
+ [Unit]
+ Description=FHEM Home Automation
+ Requires=network.target
+ #After=network.target
+ After=dhcpcd.service
+
+ [Service]
+ Type=forking
+ NotifyAccess=all
+ User=fhem
+ Group=dialout
+ WorkingDirectory=/opt/fhem
+ ExecStart=/usr/bin/perl fhem.pl fhem.cfg
+ #ExecStart=/usr/bin/perl fhem.pl configDB
+ TimeoutStartSec=240
+ TimeoutStopSec=120
+ #ExecStop=/usr/bin/pkill -U fhem perl
+ ExecStop=/usr/bin/pkill -f -U fhem "fhem.pl fhem.cfg"
+ # Restart options: no, on-success, on-failure, on-abnormal, on-watchdog, on-abort, or always.
+ Restart=on-failure
+ RestartSec=3
+ WatchdogSec=180
+ PIDFile=/opt/fhem/log/fhem.pid
+
+ [Install]
+ WantedBy=multi-user.target
+
+ Create the script as "/etc/systemd/system/fhem.service".
+ Use "sudo systemctl daemon-reload" to reload systemd configuration.
+ Start fhem with: "sudo systemctl start fhem.service".
+
+ If you like to use Type=notify, you must set fhem global attribute nofork=1.
+ If you use Type=forking, please set fhem global pidfilename.
+
Define
+define <name> systemd_watchdog
Specifies the device.
+