Send email about open notifications to an object's users.
[gnucomo.git] / src / gcm_daemon / gcm_daemon.php
1 #!/usr/bin/php
2 <?PHP
3 /**********************************************************************************
4 **  (c) Copyright 2002, Brenno J.S.A.A.F. de Winter, De Winter Information Solutions
5 ** This is free software; you can redistribute it and/or modify it under the
6 ** terms of the GNU General Public License, see the file COPYING.
7 ***********************************************************************************/
8
9
10 /*
11    NAME          : gcm_daemon
12    AUTHOR        : Brenno J.S.A.A.F. de Winter
13                    De Winter Information Solutions
14    COPYRIGHT     : 2002 - De Winter Information Solutions,
15                    Brenno J.S.A.A.F. de Winter
16
17    * DATES *
18    First        : November 8th 2002
19    Gnucomo-0.0.3: December 6th 2002
20    Gnucomo-0.0.8: September 4th 2003
21
22  $Log: gcm_daemon.php,v $
23  Revision 1.19  2004-01-10 20:04:12  arjen
24  Send email about open notifications to an object's users.
25
26  Revision 1.18  2003/12/03 08:07:21  arjen
27  Changed the type of log_adv_daemon_email.delay and log_adv_daemon_email.xdelay
28  from time to interval. These delays can be more than 24 hours.
29
30  Revision 1.17  2003/10/29 09:58:29  arjen
31  Create separate notifications for different objects in service_check().
32
33  Revision 1.16  2003/09/03 12:48:48  arjen
34  Check the log table against the servies running on an object and
35  create notifications if a service is not supposed to be available
36  or is not known at all.
37
38  Revision 1.15  2003/09/02 12:48:09  arjen
39  BUGFIX: Secondary indices on log_notification were unique.
40  Additional information in the 'usr' table: 'display_name' and 'email'.
41  Added new issues and services.
42
43  Revision 1.14  2003/09/01 06:51:07  arjen
44  Accept command argument '-c config' to use an alternate
45  gnucomo configuration.
46
47  Revision 1.13  2003/08/14 10:22:42  arjen
48  Disabled DEBUG output
49
50  Revision 1.12  2003/08/05 07:46:37  arjen
51  BUGFIX: Print an error message if a parameter does not have
52  any history.
53
54  Revision 1.11  2003/07/09 07:25:02  arjen
55  Gcm_daemon gathers statistics on parameters, notifications, etc. for all objects.
56
57  Revision 1.10  2003/03/29 08:33:58  arjen
58  In phpclasses/db.class.php: Added the database connection string as
59  an argument to the function copy_db_class.
60  Fixed the PHP member function db::db_connect(). The Postgres connection
61  string is now passed as an argument to that function.
62
63  Revision 1.9  2003/02/21 08:37:59  arjen
64  Added new table to the database: log_adv_daemon_email.
65
66
67 */
68
69 // $Id: gcm_daemon.php,v 1.19 2004-01-10 20:04:12 arjen Exp $
70
71 ini_set('include_path', '.:./classes:../phpclasses');
72 ini_set('html_errors', 'false');
73
74 //Tell the log that we're up.
75 define_syslog_variables();
76
77 require_once "gnucomo_config.php";
78 require_once "db.class.php";
79 require_once "gnucomo.process_log.php";
80
81 // Set the standard variables //
82
83 $project_name   = "gnucomo";    // name of the entire project
84 $app_name       = "gcm_daemon"; // name of the application running
85 $developrelease = "FALSE";      // Indicates if special debug settings are needed
86 $db_version     = 44;           // The db_version indicates what the level of
87                                 // the database should be. If the database is
88                                 // old an update will be generated.
89 $gcmd_version   = 5;            // This value indicates the active version of
90                                 // the gcm_daemon, which is saved in the database.
91                                 // Log records that were not recognized before
92                                 // will now be recognized. The version doesn't
93                                 // mean anything in the overall gnucomo project.
94
95 //Avoid time-limit issues
96 set_time_limit(0);
97
98 //  Scan the command arguments
99
100 for ($argi = 1; $argi < $argc; $argi++)
101 {
102    switch ($argv[$argi])
103    {
104    case "-c":
105       $argi++;
106       $project_name = $argv[$argi];
107       break;
108
109    default:
110       echo "Usage: gcm_daemon [-c configname]\n";
111       exit();
112       break;
113    }
114 }
115
116 // Read the database settings //
117 $class_settings = new gnucomo_config();
118 if (!$class_settings->read($project_name))
119 {
120    echo "Can not read Gnucomo configuration file for $project_name.\n";
121    exit();
122 }
123
124 openlog("gnucomo", LOG_PID, LOG_DAEMON);
125 syslog(LOG_INFO, "gcm_daemon started");
126
127 //Open an connection to the database
128 $dbms_type = $class_settings->find_parameter("database", "type");
129 $dbms_host = $class_settings->find_parameter("database", "host");
130 $dbms_name = $class_settings->find_parameter("database", "name");
131 $dbms_user = $class_settings->find_parameter("gcm_daemon", "user");
132 $dbms_password = $class_settings->find_parameter("gcm_daemon", "password");
133
134 db_select($dbms_type);
135 $dbms = new db();
136 $dbms->db_host = $dbms_host;
137 $dbms->db_name = $dbms_name;
138 $dbms->db_user = $dbms_user;
139 $dbms->db_password = $dbms_password;
140 $dbms->db_connect($class_settings->database());
141
142 if ($dbms->have_db_connection() == "FALSE")
143 {
144    exit ("Database connection failed.");
145 }
146 else
147 {
148    // The database connection has been made.
149    $dbms_working = copy_db_class($dbms, $class_settings->database());
150 }
151
152 // Verify if the database is up-to-date by checking the versionnumber
153
154 $local_sql = "SELECT setting_value FROM db_value WHERE setting = 'db_version' ";
155 $dbms->query($local_sql);
156
157 if ($dbms->fetch_row() == "TRUE")
158 {
159   $active_version = $dbms->db_result_row[0];
160
161   // Update the database to the most recent version.
162
163   if ($active_version < $db_version)
164   {
165      include ("gnucomo_db_version.php");
166   }
167 }
168 else
169 {
170   syslog (LOG_INFO, "Couldn't initialize database version. Is this a gnucomo database?");
171   die ("Couldn't initialize database version.\n");
172 }
173
174 // If there is a new gcm_daemon_version the logrecords that couldn't be
175 // understood can be reprocessed. For this reason processed is now changed
176 // to false again for not recognized records.
177
178 $local_sql = "SELECT setting_value FROM db_value
179               WHERE setting = 'gcm_daemon_version'";
180 $dbms->query($local_sql);
181
182 if ($dbms->fetch_row() == "TRUE")
183 {
184    if ($dbms->db_result_row[0] < $gcmd_version)
185    {
186       //Reactive log-records that weren't understood earlier.
187
188       $local_sql = "UPDATE log SET processed = false
189                     WHERE logid NOT IN (SELECT DISTINCT logid FROM log_adv)";
190       $dbms->query($local_sql);
191
192       //Update de gcm_daemon version in the database
193       $local_sql = "UPDATE db_value SET setting_value = '".$gcmd_version;
194       $local_sql .= "' WHERE setting = 'gcm_daemon_version'";
195       $dbms->query($local_sql);
196
197    }
198
199 }
200
201 // Now we loop the tasks that we have to do.
202
203
204 do
205 {
206
207    //At this place we start processing new log-lines
208
209    process_log ();
210    service_check();
211    find_notifications();
212    mail_notifications();
213
214    //  Gather the statistics for each object
215
216    $obj_result = $dbms->query("SELECT objectid FROM object");
217    for ($obj = 0; $obj < $dbms->num_rows($obj_result); $obj++)
218    {
219       $object = $dbms->fetch_object($obj_result, $obj);
220       echo "Gathering statistics for object " . $object->objectid . "\n";
221       GatherStatistics($object->objectid);
222    }
223
224    $keep_running = false;
225
226 } while ($keep_running == true);
227
228 //Tell the log that we're ending our efforts in a nice way
229
230 syslog (LOG_INFO, "gcm_daemon ended nicely");
231
232 function process_log ()
233 {
234
235  /* This function will walk through the log-records that haven't been processed
236   * first a snapshot will be created of a the non-processed records.
237   * sequentially each record will dealt with. By doing that changes will be made
238   * in several log_adv_xxx tables
239   * INPUT  : NONE
240   * OUTPUT : NONE
241   */
242
243   global $dbms;
244   global $dbms_working;
245   global $class_settings;
246
247   $last_log = 0;
248
249   // Find records in log that still have to be processed.
250
251   $local_sql = "SELECT setting_value FROM db_value WHERE setting = 'log_processing'";
252   $dbms->query($local_sql);
253
254   if ($dbms->fetch_row() == "TRUE")
255   {
256      $last_log = $dbms->db_result_row[0];
257   }
258
259   //Query the log-table
260   $local_sql = "SELECT * FROM log WHERE logid > CAST(".$last_log." AS BIGINT)
261                 ORDER BY logid";
262   $dbms->query($local_sql);
263
264   //Update the log-statistics in the object-table
265   $local_statistics_db = copy_db_class($dbms, $class_settings->database());
266   $local_findobject_db = copy_db_class($dbms, $class_settings->database());
267
268   //Make totals
269   $local_upper_row = $dbms->num_rows() + $last_log + 1;
270   $local_sql = "SELECT COUNT(logid), objectid from log WHERE logid > CAST(". $last_log .
271       " AS BIGINT) AND logid < CAST (" . $local_upper_row . " AS BIGINT) GROUP BY objectid";
272   $local_statistics_db->query ($local_sql);
273
274   // Loop the objects
275   for ($i = 1; $i <= $local_statistics_db->num_rows(); $i++)
276   {
277       $local_object_row = $local_statistics_db->fetch_row();
278
279       $local_sql = "UPDATE object SET log_count = log_count + " .
280           $local_statistics_db->db_result_row[0] . " WHERE objectid = '" .
281           $local_statistics_db->db_result_row[1] . "'";
282
283       $local_findobject_db->query($local_sql);
284   }
285
286   $local_counter = 0;
287
288   if ($dbms->num_rows() > 0)
289   {
290
291     //Create a database connection for changes in the database.
292     $dbms_changes = copy_db_class($dbms, $class_settings->database());
293     if ($dbms_changes->have_db_connection() == 'TRUE')
294     {
295
296        $local_sql               = 0 ;
297        $local_sql_statistics    = "";
298        $local_object_os         = "";
299        $local_object_os_version = "";
300
301        while ($local_counter < $dbms->num_rows())
302        {
303
304          $local_return_row = $dbms->fetch_row();
305          if ($local_return_row == 'TRUE')
306          {
307             // Work on active rows
308             $local_log_id = $dbms->db_result_row[0];
309
310             $local_sql_findobject = "SELECT os, os_version FROM object
311                                 WHERE objectid = '".$dbms->db_result_row[1]."'";
312             $local_findobject_db->query($local_sql_findobject);
313             $local_findobject_result = $local_findobject_db->fetch_row();
314             if ($local_findobject_result == 'TRUE')
315             {
316
317                 // Now work on the OS again
318                 $local_object_os = $local_findobject_db->db_result_row[0];
319                 if  ($local_object_os == "")
320                 {
321                     $local_object_os = "Linux";
322                     $local_object_os_version = "Unknown assuming Linux";
323                 }
324                 else
325                 {
326                   $local_object_os_version = $local_findobject_db->db_result_row[1];
327                 }
328              }
329
330             switch (strtolower($local_object_os))
331             {
332             case "linux":
333                 $local_process_return = linux_log ();
334                 break;
335             default:
336                 syslog (LOG_INFO, "Couldn't find suitable OS for processing the logline");
337                 break;
338             }
339
340             if ($local_process_return != 'TRUE')
341             {
342               $local_process_return = 'FALSE';
343             }
344
345          }
346          else
347          {
348
349            break;
350
351          }
352          $local_counter++;
353        }
354
355        // Register that the logrecords have been processed.
356        $local_sql = "UPDATE db_value SET setting_value = '"
357                    .$local_log_id."' where setting = 'log_processing'";
358        $dbms->query($local_sql);
359
360
361        // Update the statistics for the object-table
362
363
364      }
365      else
366      {
367        syslog (LOG_INFO, "Couldn't clone database connection.");
368        die ("Couldn't reconnect to the database.\n");
369      }
370   }
371
372 }
373
374 /*
375  *   Update a single statistic for some object.
376  *   If it does not yet exist, it will be created.
377  */
378
379 function UpdateStatistic($objectid, $name, $value)
380 {
381    global $dbms;
382
383    $result = $dbms->query("SELECT objectid FROM object_statistics WHERE
384              objectid='$objectid' AND statname='$name'");
385    if ($dbms->num_rows() == 0)
386    {
387       $dbms->query("INSERT INTO object_statistics VALUES
388                     ('$objectid', '$name', '$value')");
389    }
390    else
391    {
392       $dbms->query("UPDATE object_statistics SET statvalue='$value' WHERE
393            statname='$name' AND objectid='$objectid'");
394    }
395 }
396
397 /*
398  *   Gather the statistics for a single object ($objectid).
399  *   We count the number of parameters, removed parameters, notifications
400  *   closed notifications and log entries. The totals of these are
401  *   maintained in a separate table: object_statistics.
402  */
403
404 function GatherStatistics($objectid)
405 {
406    global $dbms;
407
408    //  Gather statistics on parameters
409
410    $r = $dbms->query("SELECT paramid FROM parameter WHERE objectid=CAST('"
411                         . $objectid . "' AS BIGINT)");
412    $nr_parameters = $dbms->num_rows($r);
413
414    $removed_parameters = 0;
415    for ($p = 0; $p < $nr_parameters; $p++)
416    {
417       $param = pg_fetch_object($r, $p);
418       $qry ="select change_nature from history where paramid= CAST('";
419       $qry .= $param->paramid . "' AS BIGINT) order by modified desc";
420       $rhist = $dbms->query($qry);
421       if ($dbms->num_rows($rhist) == 0)
422       {
423          echo "ERROR: No history for parameter id " . $param->paramid . "\n";
424       }
425       else
426       {
427          $hist = $dbms->fetch_object($rhist, 0);
428          if ($hist->change_nature == "REMOVED")
429          {
430             $removed_parameters++;
431          }
432       }
433    }
434
435    UpdateStatistic($objectid, 'parameters', $nr_parameters);
436    UpdateStatistic($objectid, 'removed_parameters', $removed_parameters);
437
438    //  Gather statistics on notifications
439
440    $r = $dbms->query("SELECT count(notificationid) FROM notification WHERE
441                        objectid = CAST('" . $objectid . "' AS BIGINT)");
442    $cnt = $dbms->fetch_object($r, 0);
443    UpdateStatistic($objectid, 'notifications', $cnt->count);
444
445    $r = $dbms->query("SELECT count(notificationid) FROM notification WHERE
446                        objectid = CAST('" . $objectid . "' AS BIGINT) AND statuscode ='cls'");
447    $cnt = $dbms->fetch_object($r, 0);
448    UpdateStatistic($objectid, 'closed_notifications', $cnt->count);
449
450    //  Gather statistics on log entries
451
452    $r = $dbms->query("SELECT count(logid) FROM log WHERE
453                        objectid = CAST('" . $objectid . "' AS BIGINT)");
454    $cnt = $dbms->fetch_object($r, 0);
455    UpdateStatistic($objectid, 'logs', $cnt->count);
456 }
457
458 /*
459  *   Service_check - Check the log entries if there are any unknown
460  *   services.
461  */
462
463 function service_check()
464 {
465    global  $dbms;
466
467    $unknown_notification = array();
468    $unused_notification  = array();
469    $last_log             = 0;
470
471    //  How far did we get last time ?
472
473    $lastlogres = $dbms->query("SELECT setting_value FROM db_value
474                                WHERE setting = 'log_servicecheck'");
475
476    if ($dbms->num_rows($lastlogres) == 1)
477    {
478      $last_log = $dbms->Field($lastlogres, 0, 'setting_value');
479    }
480    else
481    {
482       $dbms->query("INSERT INTO db_value (setting, setting_value)
483                             VALUES ('log_servicecheck', '0')");
484    }
485
486    // Query the log-table
487
488    $qry = "SELECT logid, objectid, servicecode FROM log
489            WHERE logid > CAST(".$last_log." AS BIGINT) ORDER BY logid";
490    $log_res = $dbms->query($qry);
491    //$log_res = $dbms->query("SELECT logid, objectid, servicecode,rawdata FROM log");
492
493    for ($log_row = 0; $log_row < $dbms->num_rows($log_res); $log_row++)
494    {
495       $log_entry = $dbms->fetch_object($log_res, $log_row);
496       $last_log  = $log_entry->logid;
497
498       //   Check if the service is used on the object.
499
500       $qry = "SELECT * FROM object_service WHERE objectid='";
501       $qry .= $log_entry->objectid . "' AND servicecode='";
502       $qry .= $log_entry->servicecode . "'";
503
504       $os_res = $dbms->query($qry);
505       if ($dbms->num_rows($os_res) == 0)
506       {
507          //   Service is not found for the object, check if the service
508          //   exists at all.
509
510          $qry = "SELECT * FROM service WHERE servicecode='";
511          $qry .= $log_entry->servicecode . "'";
512
513          if ($dbms->num_rows($dbms->query($qry)) == 0)
514          {
515             if (!isset($unknown_notification[$log_entry->objectid]))
516             {
517                $remark = "One or more log entries from a service that is not in the database";
518                $unknown_notification[$log_entry->objectid] =
519                          $dbms->new_notification($log_entry->objectid, 'service unknown', $remark);
520             }
521             if (isset($unknown_notification[$log_entry->objectid]))
522             {
523                $insertion = "INSERT INTO log_notification (notificationid, logid) VALUES ('";
524                $insertion .= $unknown_notification[$log_entry->objectid] . "', '";
525                $insertion .= $log_entry->logid . "')";
526                $dbms->query($insertion);
527             }
528          }
529          else
530          {
531             if (!isset($unused_notification[$log_entry->objectid]))
532             {
533                $remark = "One or more log entries from a service not running on this object";
534                $unused_notification[$log_entry->objectid] =
535                          $dbms->new_notification($log_entry->objectid, 'service not used', $remark);
536             }
537             if (isset($unused_notification[$log_entry->objectid]))
538             {
539                $insertion = "INSERT INTO log_notification (notificationid, logid) VALUES ('";
540                $insertion .= $unused_notification[$log_entry->objectid] . "', '";
541                $insertion .= $log_entry->logid . "')";
542                $dbms->query($insertion);
543             }
544          }
545       }
546    }
547
548    $qry = "UPDATE db_value SET setting_value = '"
549                    . $last_log . "' WHERE setting = 'log_servicecheck'";
550    $dbms->query($qry);
551 }
552
553 function find_notifications ()
554 {
555
556 /*
557  *  Do something with notification checks.
558  *
559  * INPUT  : NONE
560  * OUTPUT : NONE
561  */
562
563    global $dbms;
564
565    // Find checks that have to be executed.
566    $local_sql = "select * from notification_check where
567                     age(last_execution) > time_between_executions";
568    $dbms->query($local_sql);
569
570    for ($i=0; $i<$dbms->num_rows(); $i++)
571    {
572       // A check has been found that has to be executed
573       $dbms->fetch_row();
574    }
575 }
576
577 /*
578  *  find open notifications and send an email to the object's users.
579  */
580
581 function mail_notifications ()
582 {
583    global $dbms;
584
585    $notifres = $dbms->query("SELECT notificationid, objectid, type_of_issueid FROM notification
586                                WHERE statuscode != 'cls'");
587
588    for ($notifrow = 0; $notifrow < pg_numrows($notifres); $notifrow++)
589    {
590       $notification = pg_fetch_object($notifres, $notifrow);
591
592       $issue = pg_fetch_object($dbms->query("SELECT description FROM type_of_issue
593                                              WHERE type_of_issueid='" . $notification->type_of_issueid . "'"), 0);
594       $object = pg_fetch_object($dbms->query("SELECT objectname FROM object
595                                               WHERE objectid='" . $notification->objectid ."'"), 0);
596
597       $users = $dbms->query("SELECT username FROM object_user WHERE objectid='" . $notification->objectid . "'");
598
599       for ($userrow = 0; $userrow < pg_numrows($users); $userrow++)
600       {
601          $objusr = pg_fetch_object($users, $userrow);
602          $usr = pg_fetch_object($dbms->query("SELECT email FROM usr
603                                               WHERE username='" . $objusr->username . "'"), 0);
604
605          $message = "Notification " . $notification->notificationid . ": " . $issue->description;
606          $message .= "  for object " . $object->objectname . "\n";
607
608          mail($usr->email, "GnuCoMo Notification", $message);
609       }
610    }
611 }
612
613 ?>
614