Marius Cramer
2014-08-13 31230cb7cda673db7a96fb14d93dfaf9262c74cf
commit | author | age
dead5c 1 <?php
V 2
3 /*
4   Copyright (c) 2007-2011, Till Brehm, projektfarm Gmbh and Oliver Vogel www.muv.com
5   All rights reserved.
6
7   Redistribution and use in source and binary forms, with or without modification,
8   are permitted provided that the following conditions are met:
9
10  * Redistributions of source code must retain the above copyright notice,
11   this list of conditions and the following disclaimer.
12  * Redistributions in binary form must reproduce the above copyright notice,
13   this list of conditions and the following disclaimer in the documentation
14   and/or other materials provided with the distribution.
15  * Neither the name of ISPConfig nor the names of its contributors
16   may be used to endorse or promote products derived from this software without
17   specific prior written permission.
18
19   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22   IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24   BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26   OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28   EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 class rescue_core_module {
32
33     var $module_name = 'rescue_core_module';
34     var $class_name = 'rescue_core_module';
35     /* No actions at this time. maybe later... */
36     var $actions_available = array();
4ebc7d 37     /**
7fe908 38      * The monitoring-Data of this module.
4ebc7d 39      * [0] are the actual data, [1] are the data 1 minnute ago [2] are teh data 2 minuntes...
V 40      */
7fe908 41
MC 42
4ebc7d 43     private $_monitoringData = array();
7fe908 44
4ebc7d 45     /** The rescue-Data of this module. */
V 46     private $_rescueData = array();
7fe908 47
dead5c 48
V 49     /**
50      *  This function is called during ispconfig installation to determine
51      *  if a symlink shall be created for this plugin.
52      */
53     function onInstall() {
54         return true;
55     }
56
7fe908 57
dead5c 58     /**
V 59      * This function is called when the module is loaded
60      */
61     function onLoad() {
4ebc7d 62         $this->_doRescue();
dead5c 63     }
7fe908 64
dead5c 65
4ebc7d 66     /**
V 67      * This function is called when a change in one of the registered tables is detected.
68      * The function then raises the events for the plugins.
dead5c 69      */
V 70     function process($tablename, $action, $data) {
71         // not needed
72     }
73
4ebc7d 74     /**
V 75      * This Method tries to rescue the server if a service is down.
dead5c 76      */
4ebc7d 77     private function _doRescue() {
V 78         /*
79          * do nothing, if the rescue-system is not enabled
80          */
dead5c 81         global $conf;
7fe908 82         if ((!isset($conf['serverconfig']['rescue']['try_rescue'])) ||
MC 83             ((isset($conf['serverconfig']['rescue']['try_rescue'])) && ($conf['serverconfig']['rescue']['try_rescue'] !='y'))){
dead5c 84             return;
4ebc7d 85         }
dead5c 86
V 87         /*
4ebc7d 88          * First we get the monitoring data needed for rescuing the system
dead5c 89          */
4ebc7d 90         $this->_monitoringData = $this->_getMonitoringData();
V 91
92         /*
93          * Next we get the rescue data needed for rescuing the system
94          */
95         $this->_rescueData = $this->_getRescueData();
7fe908 96
4ebc7d 97         /*
8ab3cd 98          * rescue mysql if needed (maybe httpd depends on mysql, so try this first!)
039b46 99          */
V 100         $this->_rescueMySql();
7fe908 101
039b46 102         /*
8ab3cd 103          * rescue httpd if needed
4ebc7d 104          */
8ab3cd 105         $this->_rescueHttpd();
7fe908 106
eed6b3 107         /*
4ebc7d 108          * The last step is to save the rescue-data
V 109          */
110         $this->_saveRescueData();
111     }
112
113     /**
114      * This gets the Monitoring-Data, needed for rescuing the system.
115      * Because we can not be 100% sure, that the mysql-DB is up and running, so we use the
116      * file-system (this is no problem, because this module is the only one using this data,
117      * so we do not have parallel access.
118      */
119     private function _getMonitoringData() {
dead5c 120         global $app;
7fe908 121
686742 122         $dataFilename = dirname(__FILE__) . "/../temp/rescue_module_monitoringdata.ser.txt";
7fe908 123
4ebc7d 124         /*
V 125          * If the file containing the data is too old (older than 5 minutes) it is better to
7fe908 126          * delete it, because it could be, that the server was down for some times and so the data
4ebc7d 127          * are outdated
V 128          */
129         if (file_exists($dataFilename) && (filemtime($dataFilename) < (time() - 5 * 60))) {
130             unlink($dataFilename);
131         }
dead5c 132
4ebc7d 133         /*
V 134          * Get the monitoring-data
135          */
136         if (file_exists($dataFilename)) {
137             $data = unserialize(file_get_contents($dataFilename));
dead5c 138         } else {
4ebc7d 139             $data = array();
V 140         }
7fe908 141
4ebc7d 142         /*
V 143          * $temp[0] was the data of the last monitoring (means 1 minute ago), $temp[1] is the data
144          * 2 minutes ago and so on. Now we have make place for the newest data...
145          */
146         $max = sizeof($data);
147         if ($max > 10){
148             $max = 10; // not more than 10 histories
149         }
150         for ($i = $max; $i > 0; $i--){
151             $data[$i] = $data[$i -1];
152         }
7fe908 153
4ebc7d 154         /*
V 155          * we need the monitoring tools
156          */
157         $app->load('monitor_tools');
158         $tools = new monitor_tools();
7fe908 159
4ebc7d 160         /*
V 161          * Call the needed Monitoring-step and get the data
162          */
163         $tmp[0] = $tools->monitorServices();
7fe908 164
4ebc7d 165         /* Add the data at the FIRST position of the history */
V 166         $data[0] = $tmp;
7fe908 167
4ebc7d 168         /*
V 169          * We have the newest monitoring data. Save it!
849624 170          * (and protect it, because there may be sensible data in it)
4ebc7d 171          */
V 172         file_put_contents($dataFilename, serialize($data));
849624 173         chmod($dataFilename, 0600);
7fe908 174
4ebc7d 175         /* Thats it */
V 176         return $data;
177     }
7fe908 178
MC 179
180
4ebc7d 181     /**
V 182      * This gets the rescue-Data, needed for rescuing the system.
183      * Because we can not be 100% sure, that the mysql-DB is up and running, so we use the
184      * file-system (this is no problem, because this module is the only one using this data,
185      * so we do not have parallel access.
186      */
187     private function _getRescueData() {
686742 188         $dataFilename = dirname(__FILE__) . "/../temp/rescue_module_rescuedata.ser.txt";
4ebc7d 189
V 190         /*
191          * If the file containing the data is too old (older than 5 minutes) it is better to
7fe908 192          * delete it, because it could be, that the server was down for some times and so the data
4ebc7d 193          * are outdated
V 194          */
195         if (file_exists($dataFilename) && (filemtime($dataFilename) < (time() - 5 * 60))) {
196             unlink($dataFilename);
dead5c 197         }
V 198
199         /*
4ebc7d 200          * Get the rescue-data
dead5c 201          */
4ebc7d 202         if (file_exists($dataFilename)) {
V 203             $data = unserialize(file_get_contents($dataFilename));
204         } else {
205             $data = array();
dead5c 206         }
7fe908 207
4ebc7d 208         /* Thats it */
V 209         return $data;
dead5c 210     }
V 211
4ebc7d 212     /**
V 213      * Writes the rescue data to disk.
214      * Because we can not be 100% sure, that the mysql-DB is up and running, so we use the
215      * file-system (this is no problem, because this module is the only one using this data,
216      * so we do not have parallel access.
217      */
218     private function _saveRescueData() {
686742 219         $dataFilename = dirname(__FILE__) . "/../temp/rescue_module_rescuedata.ser.txt";
849624 220         /*
V 221          * We have the newest data. Save it!
222          * (and protect it, because there may be sensible data in it)
223          */
4ebc7d 224         file_put_contents($dataFilename, serialize($this->_rescueData));
849624 225         chmod($dataFilename, 0600);
4ebc7d 226     }
V 227
228     /**
8ab3cd 229      * restarts httpd, if needed
4ebc7d 230      */
8ab3cd 231     private function _rescueHttpd(){
4ebc7d 232         global $app, $conf;
7fe908 233
dead5c 234         /*
8ab3cd 235          * do nothing, if it is not allowed to rescue httpd
dead5c 236          */
8ab3cd 237         if ((isset($conf['serverconfig']['rescue']['do_not_try_rescue_httpd']) && ($conf['serverconfig']['rescue']['do_not_try_rescue_httpd']) == 'y')){
dead5c 238             return;
4ebc7d 239         }
7fe908 240
4ebc7d 241         /*
V 242          * if the service is up and running, or the service is not installed there is nothing to do...
243          */
244         if ($this->_monitoringData[0][0]['data']['webserver'] != 0){
245             /* Clear the try counter, because we do not have to try to rescue the service */
246             $this->_rescueData['webserver']['try_counter'] = 0;
247             return;
248         }
7fe908 249
4ebc7d 250         /*
V 251          * OK, the service is installed and down.
252          * Maybe this is because of a restart of the service by the admin.
253          * This means, we check the data 1 minute ago
254          */
7fe908 255         if ((!isset($this->_monitoringData[1][0]['data']['webserver'])) ||
MC 256             ((isset($this->_monitoringData[1][0]['data']['webserver'])) && ($this->_monitoringData[1][0]['data']['webserver'] != 0))){
257             /*
258              * We do NOT have this data or we have this data, but the webserver was not down 1 minute ago.
259              * This means, it could be, that the admin is restarting the server.
4ebc7d 260              * We wait one more minute...
dead5c 261              */
4ebc7d 262             return;
V 263         }
7fe908 264
4ebc7d 265         /*#####
V 266          * The service is down and it was down 1 minute ago.
267          * We try to rescue it
268          *#####*/
7fe908 269
4ebc7d 270         /* Get the try counter */
V 271         $tryCount = (!isset($this->_rescueData['webserver']['try_counter']))? 1 : $this->_rescueData['webserver']['try_counter'] + 1;
7fe908 272
4ebc7d 273         /* Set the new try counter */
V 274         $this->_rescueData['webserver']['try_counter'] = $tryCount;
355feb 275         
TB 276         if ($tryCount > 2 && $conf['serverconfig']['web']['server_type'] != 'nginx') {
277             if($app->system->is_user('apache')) {
278                 $app->log("Clearing semaphores table for user apache.",LOGLEVEL_WARN);
279                 exec("ipcs -s | grep apache | awk '{ print $2 }' | xargs ipcrm sem");
280             }
281             if($app->system->is_user('www-data')) {
282                 $app->log("Clearing semaphores table for user apache.",LOGLEVEL_WARN);
283                 exec("ipcs -s | grep www-data | awk '{ print $2 }' | xargs ipcrm sem");
284             }
285         }
7fe908 286
4ebc7d 287         /* if 5 times will not work, we have to give up... */
V 288         if ($tryCount > 5){
8ab3cd 289             $app->log('httpd is down! Rescue will not help!', LOGLEVEL_ERROR);
4ebc7d 290             return;
V 291         }
7fe908 292
MC 293
8ab3cd 294         $app->log('httpd is down! Try rescue httpd (try:' . $tryCount . ')...', LOGLEVEL_WARN);
7fe908 295
8ab3cd 296         if($conf['serverconfig']['web']['server_type'] == 'nginx'){
T 297             $daemon = 'nginx';
dead5c 298         } else {
8ab3cd 299             if(is_file($conf['init_scripts'] . '/' . 'httpd')) {
T 300                 $daemon = 'httpd';
301             } elseif(is_file($conf['init_scripts'] . '/' . 'httpd2')){
302                 $daemon = 'httpd2';
303             } else {
304                 $daemon = 'apache2';
305             }
dead5c 306         }
7fe908 307
eed6b3 308         $this->_rescueDaemon($daemon);
V 309     }
7fe908 310
MC 311
312
eed6b3 313     /**
V 314      * restarts mysql, if needed
315      */
316     private function _rescueMySql(){
317         global $app, $conf;
7fe908 318
eed6b3 319         /*
V 320          * do nothing, if it is not allowed to rescue mysql
321          */
322         if ((isset($conf['serverconfig']['rescue']['do_not_try_rescue_mysql']) && ($conf['serverconfig']['rescue']['do_not_try_rescue_mysql']) == 'y')){
323             return;
324         }
7fe908 325
eed6b3 326         /*
V 327          * if the service is up and running, or the service is not installed there is nothing to do...
328          */
329         if ($this->_monitoringData[0][0]['data']['mysqlserver'] != 0){
330             /* Clear the try counter, because we do not have to try to rescue the service */
331             $this->_rescueData['mysqlserver']['try_counter'] = 0;
332             return;
333         }
7fe908 334
eed6b3 335         /*
V 336          * OK, the service is installed and down.
337          * Maybe this is because of a restart of the service by the admin.
338          * This means, we check the data 1 minute ago
339          */
7fe908 340         if ((!isset($this->_monitoringData[1][0]['data']['mysqlserver'])) ||
MC 341             ((isset($this->_monitoringData[1][0]['data']['mysqlserver'])) && ($this->_monitoringData[1][0]['data']['mysqlserver'] != 0))){
342             /*
343              * We do NOT have this data or we have this data, but the webserver was not down 1 minute ago.
344              * This means, it could be, that the admin is restarting the server.
eed6b3 345              * We wait one more minute...
V 346              */
347             return;
348         }
7fe908 349
eed6b3 350         /*#####
V 351          * The service is down and it was down 1 minute ago.
352          * We try to rescue it
353          *#####*/
7fe908 354
eed6b3 355         /* Get the try counter */
V 356         $tryCount = (!isset($this->_rescueData['mysqlserver']['try_counter']))? 1 : $this->_rescueData['mysqlserver']['try_counter'] + 1;
7fe908 357
eed6b3 358         /* Set the new try counter */
V 359         $this->_rescueData['mysqlserver']['try_counter'] = $tryCount;
7fe908 360
eed6b3 361         /* if 5 times will not work, we have to give up... */
V 362         if ($tryCount > 5){
363             $app->log('MySQL is down! Rescue will not help!', LOGLEVEL_ERROR);
364             return;
365         }
7fe908 366
MC 367
eed6b3 368         $app->log('MySQL is down! Try rescue mysql (try:' . $tryCount . ')...', LOGLEVEL_WARN);
V 369
370         if(is_file($conf['init_scripts'] . '/' . 'mysqld')) {
371             $daemon = 'mysqld';
372         } else {
373             $daemon = 'mysql';
374         }
7fe908 375
eed6b3 376         $this->_rescueDaemon($daemon);
V 377     }
378
379     /**
380      * Tries to stop and then restart the daemon
7fe908 381      *
eed6b3 382      * @param type $daemon the name of the daemon
V 383      */
384     private function _rescueDaemon($daemon){
4f02ea 385         global $conf, $app;
7fe908 386
33bcd0 387         $app->uses('system');
eed6b3 388         // if you need to find all restarts search for "['init_scripts']"
V 389         /*
390          * First we stop the running service "normally"
391          */
7fe908 392
dead5c 393         /*
4ebc7d 394          * ATTENTION!
V 395          * The service hangs. this means it could be, that "stop" will hang also.
396          * So we have to try to stop but if this will not work, we have to kill the stopping
397          * of the service
dead5c 398          */
33bcd0 399         exec($app->system->getinitcommand($daemon, 'stop').' && (sleep 3; kill $!; sleep 2; kill -9 $!) &> /dev/null');
7fe908 400
4ebc7d 401         /*
V 402          * OK, we tryed to stop it normally, maybe this worked maybe not. So we have to look
403          * if the service is already running or not. If so, we have to kill them hard
404          */
405         exec("kill -9 `ps -A | grep " . $daemon . "| grep -v grep | awk '{print $1}'` &> /dev/null");
7fe908 406
4ebc7d 407         /*
V 408          * There are no more zombies left. Lets start the service..
409          */
33bcd0 410         exec($app->system->getinitcommand($daemon, 'start'));
dead5c 411     }
7fe908 412
dead5c 413 }
7fe908 414
dead5c 415 ?>