98_systemd_watchdog.pm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. ###############################################################################
  2. #
  3. # Copyright notice
  4. #
  5. # (c) 2018 Alexander Schulz
  6. #
  7. # This script is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # The GNU General Public License can be found at
  13. # http://www.gnu.org/copyleft/gpl.html.
  14. # A copy is found in the textfile GPL.txt and important notices to the license
  15. # from the author is found in LICENSE.txt distributed with these scripts.
  16. #
  17. # This script is distributed in the hope that it will be useful,
  18. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. # GNU General Public License for more details.
  21. #
  22. # This copyright notice MUST APPEAR in all copies of the script!
  23. #
  24. ###############################################################################
  25. # $Id: 98_systemd_watchdog.pm 17206 2018-08-25 18:18:49Z hexenmeister $
  26. package main;
  27. use strict;
  28. use warnings;
  29. use POSIX;
  30. use Time::HiRes qw(gettimeofday);
  31. use Socket;
  32. sub watchdog_client_NotifySystemD($$);
  33. sub watchdog_client_Stop($);
  34. sub watchdog_client_Start($);
  35. sub watchdog_client_ProcessTimer(@);
  36. sub watchdog_client_IsWDAvailable($);
  37. sub systemd_watchdog_Initialize($) {
  38. my ($hash) = @_;
  39. # Consumer
  40. $hash->{DefFn} = "watchdog_client_Define";
  41. $hash->{UndefFn} = "watchdog_client_Undefine";
  42. $hash->{ShutdownFn} = "watchdog_client_Shutdown";
  43. $hash->{NotifyFn} = "watchdog_client_Notify";
  44. #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_Initialize");
  45. RemoveInternalTimer($hash);
  46. return undef;
  47. }
  48. sub watchdog_client_IsWDAvailable($) {
  49. my ( $hash ) = @_;
  50. #return 1; # TODO XXX TEST
  51. return defined($hash->{'.systemd'});
  52. }
  53. sub watchdog_client_Define($$) {
  54. my ( $hash, $def ) = @_;
  55. #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_Define");
  56. my $name = $hash->{NAME};
  57. # prevent multiple instances
  58. my @devices = devspec2array("TYPE=watchdog_client");
  59. foreach my $dev (@devices) {
  60. if($dev ne $name) {
  61. return "only one instance is allowed";
  62. }
  63. }
  64. # remove old timer
  65. RemoveInternalTimer($hash);
  66. # check systemd watchdog available
  67. my $sname = $ENV{NOTIFY_SOCKET};
  68. if(defined($sname)) {
  69. $hash->{'systemd-watchdog'}="available";
  70. $hash->{'.systemd'}=1;
  71. Log3($hash->{NAME},4,"Watchdog Client: systemd-watchdog available. starting watchdog client");
  72. } else {
  73. $hash->{'systemd-watchdog'}="not available";
  74. $hash->{'.systemd'}=undef;
  75. Log3($hash->{NAME},1,"Watchdog Client: systemd watchdog is not available. Module inactiv.");
  76. }
  77. # Initialize
  78. watchdog_client_Start($hash);
  79. return undef;
  80. }
  81. sub watchdog_client_Undefine($) {
  82. my ($hash) = @_;
  83. #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_Undefine");
  84. # Clean up
  85. watchdog_client_Stop($hash);
  86. return undef;
  87. }
  88. sub watchdog_client_Shutdown($) {
  89. my ($hash) = @_;
  90. #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_Shutdown");
  91. return undef unless defined $hash->{'.initialized'};
  92. # Shutdown => Deaktivate watchdog
  93. my $name = $hash->{NAME};
  94. Log3($name,2,"Watchdog Client: Shutting down");
  95. watchdog_client_Stop($hash);
  96. return undef;
  97. }
  98. sub watchdog_client_Notify($$) {
  99. my ($hash,$dev) = @_;
  100. #Log3($hash->{NAME},1,"Watchdog Client: Debug: watchdog_client_Notify: --- ");
  101. if( $dev->{NAME} eq "global" ) {
  102. # if( grep(m/^INITIALIZED$/, @{$events}) ) {
  103. # Log3($hash->{NAME},1,"Watchdog Client: Debug: watchdog_client_Notify: INITIALIZED");
  104. # watchdog_client_Start($hash) unless defined $hash->{'.initialized'};
  105. # return undef;
  106. # } elsif( grep(m/^REREADCFG$/, @{$events}) ) {
  107. # #
  108. # return undef;
  109. # }
  110. if( grep(m/^(INITIALIZED|REREADCFG)$/, @{$dev->{CHANGED}}) ) {
  111. #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_Notify: GLOBAL");
  112. watchdog_client_Start($hash) unless defined $hash->{'.initialized'};
  113. }
  114. }
  115. }
  116. sub watchdog_client_ProcessTimer(@) {
  117. my ($hash) = @_;
  118. #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_ProcessTimer");
  119. # Reset watchdog
  120. watchdog_client_NotifySystemD($hash, "WATCHDOG=1\n");
  121. my $sleep = $hash->{'sleep-time'};
  122. $sleep = 30 unless defined $sleep;
  123. my $now = gettimeofday();
  124. my $next = int($now) + $sleep;
  125. InternalTimer($next, 'watchdog_client_ProcessTimer', $hash, 0);
  126. readingsSingleUpdate($hash,"next",FmtTime($next),1);
  127. }
  128. sub watchdog_client_Start($) {
  129. my ($hash) = @_;
  130. #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_Start");
  131. unless ($main::init_done) {
  132. return if $hash->{'.firsttime'};
  133. watchdog_client_NotifySystemD($hash, "STATUS=starting\n");
  134. watchdog_client_NotifySystemD($hash, "MAINPID=$$\n");
  135. readingsSingleUpdate($hash,"state","starting",1);
  136. $hash->{'.firsttime'}=1;
  137. return;
  138. }
  139. return if $hash->{'.initialized'};
  140. unless (watchdog_client_IsWDAvailable($hash)) {
  141. Log3($hash->{NAME},2,"Watchdog Client: no systemd watchdog available");
  142. readingsSingleUpdate($hash,"state","inactiv",1);
  143. readingsSingleUpdate($hash,"next","none",1);
  144. return;
  145. }
  146. my $sleep = ($ENV{WATCHDOG_USEC} // 120000000) / 4 / 1000000;
  147. $hash->{'sleep-time'} = $sleep;
  148. $hash->{'.initialized'} = 1;
  149. my $next = int(gettimeofday()) + 1;
  150. InternalTimer($next, 'watchdog_client_ProcessTimer', $hash, 0);
  151. # System ready
  152. watchdog_client_NotifySystemD($hash, "READY=1\n");
  153. watchdog_client_NotifySystemD($hash, "MAINPID=$$\n");
  154. watchdog_client_NotifySystemD($hash, "STATUS=started\n");
  155. Log3($hash->{NAME},2,"Watchdog Client: initialized");
  156. readingsSingleUpdate($hash,"state","active",1);
  157. }
  158. sub watchdog_client_Stop($) {
  159. my ($hash) = @_;
  160. #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_Stop");
  161. watchdog_client_NotifySystemD($hash, "STOPPING=1\n");
  162. watchdog_client_NotifySystemD($hash, "STATUS=stopping\n");
  163. RemoveInternalTimer($hash);
  164. $hash->{'.initialized'} = 0;
  165. my $name = $hash->{NAME};
  166. Log3($name,2,"Watchdog Client: deactivated");
  167. readingsSingleUpdate($hash,"state","deactivated",1);
  168. }
  169. sub watchdog_client_NotifySystemD($$) {
  170. my ($hash,$cmd) = @_;
  171. #Log3($hash->{NAME},5,"Watchdog Client: Debug: watchdog_client_NotifySystemD: $cmd");
  172. return unless defined $hash->{'.initialized'};
  173. return unless watchdog_client_IsWDAvailable($hash);
  174. my $name = $hash->{NAME};
  175. #Log3($name,1,"Watchdog Client: notify systemd-watchdog: $cmd");
  176. my $sname = $ENV{NOTIFY_SOCKET};
  177. if(!defined($sname)) {
  178. #watchdog_client_Stop($hash);
  179. Log3($name,1,"Watchdog Client: NOTIFY_SOCKET not available. Please configure systemd-watchdog properly!");
  180. return;
  181. }
  182. Log3($name,4,"Watchdog Client: notify systemd-watchdog: $cmd");
  183. my $sock_addr = sockaddr_un($sname);
  184. socket(my $server, PF_UNIX,SOCK_DGRAM,0);
  185. connect($server, $sock_addr);
  186. print $server $cmd;
  187. close($server);
  188. }
  189. 1;
  190. =pod
  191. =item summary_DE Sendet periodisch eine keep-alive Nachricht an das Systemd.
  192. =begin html_DE
  193. <a name="systemd_watchdog"></a>
  194. <h3>Systemd Watchdog Client</h3>
  195. <ul>
  196. <p>
  197. Systemd erlaubt Ueberwachung von Programmen mittels eines Watchdogs.
  198. Sendet der Prozess innerhalnb eines definierten Interval kein 'Lebenszeichen',
  199. wird dieser gestoppt und neu gestartet.
  200. Dieses Modul sendet periodisch eine keep-alive Nachricht an das Systemd-Watchdog.
  201. </p>
  202. <p>
  203. FHEM muss unter Kontrolle von Systemd laufen und Watchdog muss korrekt konfiguriert sein.<br/>
  204. Folgendes Script kann benutzt werden:<br/>
  205. <code>
  206. [Unit]<br/>
  207. Description=FHEM Home Automation<br/>
  208. Requires=network.target<br/>
  209. #After=network.target<br/>
  210. After=dhcpcd.service<br/>
  211. <br/>
  212. [Service]<br/>
  213. Type=forking<br/>
  214. NotifyAccess=all<br/>
  215. User=fhem<br/>
  216. Group=dialout<br/>
  217. # Run ExecStartPre with root-permissions<br/>
  218. PermissionsStartOnly=true<br/>
  219. ExecStartPre=-/bin/mkdir -p /var/run/fhem<br/>
  220. ExecStartPre=/bin/chown -R fhem:dialout /var/run/fhem<br/>
  221. # Run ExecStart with defined user and group<br/>
  222. WorkingDirectory=/opt/fhem<br/>
  223. ExecStart=/usr/bin/perl fhem.pl fhem.cfg<br/>
  224. #ExecStart=/usr/bin/perl fhem.pl configDB<br/>
  225. TimeoutStartSec=240<br/>
  226. TimeoutStopSec=120<br/>
  227. #ExecStop=/usr/bin/pkill -U fhem perl<br/>
  228. ExecStop=/usr/bin/pkill -f -U fhem "fhem.pl fhem.cfg"<br/>
  229. # Restart options: no, on-success, on-failure, on-abnormal, on-watchdog, on-abort, or always.<br/>
  230. Restart=on-failure<br/>
  231. RestartSec=3<br/>
  232. WatchdogSec=180<br/>
  233. PIDFile=/var/run/fhem/fhem.pid<br/>
  234. <br/>
  235. [Install]<br/>
  236. WantedBy=multi-user.target<br/>
  237. </code><br/>
  238. Das Script kann unter "/etc/systemd/system/fhem.service" angelegt werden.
  239. Mit "sudo systemctl daemon-reload" wird sysgtemd-Konfiguration erneuert.
  240. Anschliessend kann FHEM mit folgendem Befehl gestartet werden: "sudo systemctl start fhem.service".
  241. <br/>
  242. Wenn in dem Script "Type=notify" verwendet wird, muss global Attribute "nofork=1" gesetzt sein.<br/>
  243. <code>attr global nofork 1</code><br/>
  244. Bei "Type=forking" muss in Script der korrekte Pfad zu dem PID-Datei angegeben werden,
  245. diese Datei muss auch in FHEM mit dem global Attribute "pidfilename" aktiviert sein.<br/>
  246. <code>attr global pidfilename /var/run/fhem/fhem.pid</code><br/>
  247. </p>
  248. <a name="MQTTdefine"></a>
  249. <p><b>Define</b></p>
  250. <ul>
  251. <p><code>define &lt;name&gt; systemd_watchdog</code></p>
  252. <p>Specifies the device.</p>
  253. </ul>
  254. </ul>
  255. =end html_DE
  256. =item summary Sends periodically keep-alive message to the systemd.
  257. =begin html
  258. <a name="systemd_watchdog"></a>
  259. <h3>Systemd Watchdog Client</h3>
  260. <ul>
  261. <p>
  262. Systemd allows monitoring of programs by a watchdog.
  263. If a process does not respond within a certain time interval, it will be stopped and restarted.
  264. This module periodically sends keep-alive message to the systemd.
  265. </p>
  266. <p>
  267. fhem must be started under control of systemd. Watchdog must be also configured properly.<br/>
  268. You can use the following script:<br/>
  269. <code>
  270. [Unit]<br/>
  271. Description=FHEM Home Automation<br/>
  272. Requires=network.target<br/>
  273. #After=network.target<br/>
  274. After=dhcpcd.service<br/>
  275. <br/>
  276. [Service]<br/>
  277. Type=forking<br/>
  278. NotifyAccess=all<br/>
  279. User=fhem<br/>
  280. Group=dialout<br/>
  281. # Run ExecStartPre with root-permissions<br/>
  282. PermissionsStartOnly=true<br/>
  283. ExecStartPre=-/bin/mkdir -p /var/run/fhem<br/>
  284. ExecStartPre=/bin/chown -R fhem:dialout /var/run/fhem<br/>
  285. # Run ExecStart with defined user and group<br/>
  286. WorkingDirectory=/opt/fhem<br/>
  287. ExecStart=/usr/bin/perl fhem.pl fhem.cfg<br/>
  288. #ExecStart=/usr/bin/perl fhem.pl configDB<br/>
  289. TimeoutStartSec=240<br/>
  290. TimeoutStopSec=120<br/>
  291. #ExecStop=/usr/bin/pkill -U fhem perl<br/>
  292. ExecStop=/usr/bin/pkill -f -U fhem "fhem.pl fhem.cfg"<br/>
  293. # Restart options: no, on-success, on-failure, on-abnormal, on-watchdog, on-abort, or always.<br/>
  294. Restart=on-failure<br/>
  295. RestartSec=3<br/>
  296. WatchdogSec=180<br/>
  297. PIDFile=/var/run/fhem/fhem.pid<br/>
  298. <br/>
  299. [Install]<br/>
  300. WantedBy=multi-user.target<br/>
  301. </code><br/>
  302. Create the script as "/etc/systemd/system/fhem.service".
  303. Use "sudo systemctl daemon-reload" to reload systemd configuration.
  304. Start fhem with: "sudo systemctl start fhem.service".
  305. <br/>
  306. If you like to use Type=notify, you must set fhem global attribute nofork=1.<br/>
  307. <code>attr global nofork 1</code><br/>
  308. If you use Type=forking, please set fhem global pidfilename.<br/>
  309. <code>attr global pidfilename /var/run/fhem/fhem.pid</code><br/>
  310. </p>
  311. <a name="MQTTdefine"></a>
  312. <p><b>Define</b></p>
  313. <ul>
  314. <p><code>define &lt;name&gt; systemd_watchdog</code></p>
  315. <p>Specifies the device.</p>
  316. </ul>
  317. </ul>
  318. =end html
  319. =cut