2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-01-31 18:59:33 +00:00
fhem-mirror/fhem/contrib/98_systemd_watchdog.pm
2018-03-14 20:58:26 +00:00

344 lines
11 KiB
Perl

###############################################################################
#
# 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
<a name="systemd_watchdog"></a>
<h3>Systemd Watchdog Client</h3>
<ul>
<p>
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.
</p>
<p>
FHEM muss unter Kontrolle von Systemd laufen und Watchdog muss korrekt konfiguriert sein.<br/>
Folgendes Script kann benutzt werden:<br/>
<code>
[Unit]<br/>
Description=FHEM Home Automation<br/>
Requires=network.target<br/>
#After=network.target<br/>
After=dhcpcd.service<br/>
<br/>
[Service]<br/>
Type=forking<br/>
NotifyAccess=all<br/>
User=fhem<br/>
Group=dialout<br/>
WorkingDirectory=/opt/fhem<br/>
ExecStart=/usr/bin/perl fhem.pl fhem.cfg<br/>
#ExecStart=/usr/bin/perl fhem.pl configDB<br/>
TimeoutStartSec=240<br/>
TimeoutStopSec=120<br/>
#ExecStop=/usr/bin/pkill -U fhem perl<br/>
ExecStop=/usr/bin/pkill -f -U fhem "fhem.pl fhem.cfg"<br/>
# Restart options: no, on-success, on-failure, on-abnormal, on-watchdog, on-abort, or always.<br/>
Restart=on-failure<br/>
RestartSec=3<br/>
WatchdogSec=180<br/>
PIDFile=/opt/fhem/log/fhem.pid<br/>
<br/>
[Install]<br/>
WantedBy=multi-user.target<br/>
</code><br/>
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".
<br/>
Wenn in dem Script "Type=notify" verwendet wird, muss global Attribute "nofork=1" gesetzt sein.<br/>
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.<br/>
</p>
<a name="MQTTdefine"></a>
<p><b>Define</b></p>
<ul>
<p><code>define &lt;name&gt; systemd_watchdog</code></p>
<p>Specifies the device.</p>
</ul>
</ul>
=end html_DE
=begin html
<a name="systemd_watchdog"></a>
<h3>Systemd Watchdog Client</h3>
<ul>
<p>
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.
</p>
<p>
fhem must be started under control of systemd. Watchdog must be also configured properly.<br/>
You can use the following script:<br/>
<code>
[Unit]<br/>
Description=FHEM Home Automation<br/>
Requires=network.target<br/>
#After=network.target<br/>
After=dhcpcd.service<br/>
<br/>
[Service]<br/>
Type=forking<br/>
NotifyAccess=all<br/>
User=fhem<br/>
Group=dialout<br/>
WorkingDirectory=/opt/fhem<br/>
ExecStart=/usr/bin/perl fhem.pl fhem.cfg<br/>
#ExecStart=/usr/bin/perl fhem.pl configDB<br/>
TimeoutStartSec=240<br/>
TimeoutStopSec=120<br/>
#ExecStop=/usr/bin/pkill -U fhem perl<br/>
ExecStop=/usr/bin/pkill -f -U fhem "fhem.pl fhem.cfg"<br/>
# Restart options: no, on-success, on-failure, on-abnormal, on-watchdog, on-abort, or always.<br/>
Restart=on-failure<br/>
RestartSec=3<br/>
WatchdogSec=180<br/>
PIDFile=/opt/fhem/log/fhem.pid<br/>
<br/>
[Install]<br/>
WantedBy=multi-user.target<br/>
</code><br/>
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".
<br/>
If you like to use Type=notify, you must set fhem global attribute nofork=1.<br/>
If you use Type=forking, please set fhem global pidfilename.<br/>
</p>
<a name="MQTTdefine"></a>
<p><b>Define</b></p>
<ul>
<p><code>define &lt;name&gt; systemd_watchdog</code></p>
<p>Specifies the device.</p>
</ul>
</ul>
=end html
=cut