syncdomain: use flock, not file existence
authorAlexey Andriyanov <alan@al-an.info>
Sun, 2 Mar 2014 14:28:06 +0000 (18:28 +0400)
committerAlexey Andriyanov <alan@al-an.info>
Sun, 2 Mar 2014 14:33:36 +0000 (18:33 +0400)
There were cases when syncdomain was terminated abnormally, keeping the
lock file existing. After that the syncdomain was not able to resume
until the lockfile was deleted.

This patch uses flock(), adds ability to disable locking logic
(--nolock), and to print significant errors to the STDERR (--stderr) to inform
sysadmin using mail.

ChangeLog
scripts/syncdomain.php

index ae1aff54587138c205240dd038c483542940a95d..af882a017f1fdf9f95569ff3ede74fa2c99111ef 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -30,6 +30,7 @@
        update: button to fix non-canonical port names (#827)
        update: explicitly require PHP version 5.2.10 or newer (see #1139)
        update: leave secret.php in a safer state upon install
+       update: syncdomain.php now provides some new CLI options and better lockfile handling
        new feature: add IPv4 natting for all protocols (#971) by Tim Wilkes/Mark Wilkinson
        new feature: display point-to-point peers when using new 'Point-to-point' IP allocation type
        new feature: display rack power consumption as sum of mounted objects'
index 6fb5e7f63343e0e926ba376c803449e6adcbe274..2ee6e41b7e5e362fa85d33dfaf87b19c19513840 100755 (executable)
@@ -17,15 +17,25 @@ function usage()
        echo "\t\t--mode=push\n";
        echo "\t\t[--max=<max_to_do>]\n";
        echo "\t\t[--verbose]\n";
+       echo "\t\t[--nolock]\n";
+       echo "\t\t[--stderr]\n";
        exit (1);
 }
 
-function print_message_line($text)
+define ('PML_VERBOSE', 1 << 0); // display message only if --verbose option specified
+define ('PML_NOTICE',  1 << 1); // the message is informational, do not write to STDERR
+function print_message_line($text, $flags = 0)
 {
-       echo gmdate (DATE_RFC1123) . ": ${text}\n";
+       global $options;
+       if (! array_key_exists ('verbose', $options) and $flags & PML_VERBOSE)
+               return;
+       $buff = date (DATE_RFC1123) . ": ${text}\n";
+       echo $buff;
+       if (array_key_exists ('stderr', $options) and ! ($flags & PML_NOTICE))
+               fwrite (STDERR, $buff);
 }
 
-$options = getopt ('', array ('vdid:', 'max::', 'mode:', 'verbose'));
+$options = getopt ('', array ('vdid:', 'max::', 'mode:', 'verbose', 'nolock', 'stderr'));
 if (!array_key_exists ('mode', $options))
        usage();
 
@@ -45,7 +55,7 @@ default:
 }
 
 $max = array_fetch ($options, 'max', 0);
-$verbose = array_key_exists ('verbose', $options);
+$nolock = array_key_exists ('nolock', $options);
 
 $switch_list = array();
 if (! isset ($options['vdid']))
@@ -71,31 +81,42 @@ $todo = array
        'pullall' => array ('sync_ready', 'resync_ready', 'sync_aging', 'resync_aging', 'done'),
 );
 
-$domain_key = isset ($options['vdid']) ? $options['vdid'] : 0;
-$filename = '/var/tmp/RackTables-syncdomain-' . $domain_key . '.pid';
-if (FALSE === $fp = @fopen ($filename, 'x+'))
+if (! $nolock)
 {
-       if (FALSE === $pidfile_mtime = filemtime ($filename))
+       $domain_key = isset ($options['vdid']) ? $options['vdid'] : 0;
+       $filename = '/var/tmp/RackTables-syncdomain-' . $domain_key . '.pid';
+       if (FALSE === $fp = @fopen ($filename, 'c+'))
        {
-               print_message_line ("Failed to obtain mtime of ${filename}");
+               print_message_line ("Failed to open ${filename}");
                exit (1);
        }
-       $current_time = time();
-       if ($current_time < $pidfile_mtime)
+       $wouldblock = 0;
+       if (! flock ($fp, LOCK_EX|LOCK_NB, $wouldblock) || $wouldblock)
        {
-               print_message_line ("Warning: pidfile ${filename} mtime is in future!");
+               $current_time = time();
+               $stat = fstat ($fp);
+               if (! isset ($stat['mtime']))
+               {
+                       print_message_line ("Failed to obtain mtime of ${filename}");
+                       exit (1);
+               }
+               $pidfile_mtime = $stat['mtime'];
+               if ($current_time < $pidfile_mtime)
+               {
+                       print_message_line ("Warning: pidfile ${filename} mtime is in future!");
+                       exit (1);
+               }
+               // don't indicate failure unless the pidfile is 15 minutes or more old
+               if ($current_time < $pidfile_mtime + 15 * 60)
+                       exit (0);
+               print_message_line ("Failed to lock ${filename}, already locked by PID " . trim (fgets ($fp, 10)));
                exit (1);
        }
-       // don't indicate failure unless the pidfile is 15 minutes or more old
-       if ($current_time < $pidfile_mtime + 15 * 60)
-               exit (0);
-       print_message_line ("Failed to lock ${filename}, already locked by PID " . mb_substr (file_get_contents ($filename), 0, 6));
-       exit (1);
-}
 
-ftruncate ($fp, 0);
-fwrite ($fp, getmypid() . "\n");
-fclose ($fp);
+       ftruncate ($fp, 0);
+       fwrite ($fp, getmypid() . "\n");
+       // don't close $fp yet: we need to keep an flock
+}
 
 // fetch all the needed data from DB (preparing for DB connection loss)
 $switch_queue = array();
@@ -142,8 +163,10 @@ foreach ($switch_queue as $object)
                        if ($i_am_child)
                                connectDB();
                        $portsdone = exec8021QDeploy ($object['id'], $do_push);
-                       if ($portsdone or $verbose)
-                               print_message_line ("Done '${object['dname']}': ${portsdone}");
+                       $flags = PML_NOTICE;
+                       if (! $portsdone)
+                               $flags |= PML_VERBOSE;
+                       print_message_line ("Done '${object['dname']}': ${portsdone}", $flags);
                }
                catch (RackTablesError $e)
                {
@@ -157,8 +180,7 @@ foreach ($switch_queue as $object)
 
        if (++$switchesdone == $max)
        {
-               if ($verbose)
-                       print_message_line ("Maximum of ${max} items reached, terminating");
+               print_message_line ("Maximum of ${max} items reached, terminating", PML_NOTICE|PML_VERBOSE);
                break;
        }
 }
@@ -170,10 +192,14 @@ while ($switches_working > 0)
        pcntl_waitpid (-1, $wait_status);
 }
 
-if (FALSE === unlink ($filename))
+if (! $nolock)
 {
-       print_message_line ("Failed removing pidfile ${filename}");
-       exit (1);
+       flock ($fp, LOCK_UN); // explicitly unlock file as PHP 5.3.2 made it mandatory
+       if (FALSE === unlink ($filename))
+       {
+               print_message_line ("Failed removing pidfile ${filename}");
+               exit (1);
+       }
 }
 exit (0);
 ?>