LP#1711194: avoid division by zero errors
[opensrf-equinox.git] / bin / opensrf-perl.pl.in
index e680f03..40015e3 100755 (executable)
@@ -49,11 +49,23 @@ my $opt_restart = 0;
 my $opt_start_all = 0;
 my $opt_stop_all = 0;
 my $opt_restart_all = 0;
+my $opt_start_services = 0;
+my $opt_stop_services = 0;
+my $opt_restart_services = 0;
 my $opt_force_clean_process = 0;
+my $opt_router_de_register = 0;
+my $opt_router_de_register_all = 0;
+my $opt_router_re_register = 0;
+my $opt_router_re_register_all = 0;
+my $opt_reload = 0;
+my $opt_reload_all = 0;
 my $opt_quiet = 0;
+my $opt_diagnostic = 0;
+my $opt_ignore_orphans = 0;
 my $sclient;
 my @perl_services;
 my @nonperl_services;
+my %max_children_map;
 my $hostname = $ENV{OSRF_HOSTNAME} || hostfqdn();
 
 GetOptions(
@@ -81,7 +93,18 @@ GetOptions(
     'start-all' => \$opt_start_all,
     'stop-all' => \$opt_stop_all,
     'restart' => \$opt_restart,
-    'restart-all' => \$opt_restart_all
+    'restart-all' => \$opt_restart_all,
+    'start-services' => \$opt_start_services,
+    'stop-services' => \$opt_stop_services,
+    'restart-services' => \$opt_restart_services,
+    'router-de-register' => \$opt_router_de_register,
+    'router-de-register-all' => \$opt_router_de_register_all,
+    'router-re-register' => \$opt_router_re_register,
+    'router-re-register-all' => \$opt_router_re_register_all,
+    'reload' => \$opt_reload,
+    'reload-all' => \$opt_reload_all,
+    'diagnostic' => \$opt_diagnostic,
+    'ignore-orphans' => \$opt_ignore_orphans
 );
 
 if ($opt_localhost) {
@@ -92,6 +115,21 @@ if ($opt_localhost) {
 my $C_COMMAND = "opensrf-c -c $opt_config -x opensrf -p $opt_pid_dir -h $hostname";
 my $PY_COMMAND = "opensrf.py -f $opt_config -p $opt_pid_dir ". ($opt_localhost ? '-l' : '');
 
+sub verify_services {
+    my $service = shift;
+    return 1 if $service and $service eq 'router';
+    my @services = (@perl_services, map {$_->{service}} @nonperl_services);
+    if (@services) {
+        return 1 unless $service;
+        return 1 if grep { $_ eq $service } @services;
+        msg("$service is not configured to run on $hostname");
+    } else {
+        msg("No services are configured to run on $hostname");
+    }
+    msg("Perhaps you meant to use --localhost?") unless $opt_localhost;
+    exit;
+}
+
 sub do_signal_send {
     my $service = shift;
     my $signal = shift;
@@ -174,8 +212,8 @@ sub get_service_pids_from_ps {
     my $service = shift;
 
     my $ps = ($service eq 'router') ?
-        "ps ax | grep 'OpenSRF Router'" :
-        "ps ax | grep 'OpenSRF Listener \\[$service\\]'";
+        "ps x | grep 'OpenSRF Router'" :
+        "ps x | grep 'OpenSRF Listener \\[$service\\]'";
 
     $ps .= " | grep -v grep |  sed 's/^\\s*//' | cut -d' ' -f1";
     my @pids = `$ps`;
@@ -185,22 +223,84 @@ sub get_service_pids_from_ps {
 }
 
 
+sub do_diagnostic {
+    my $alive = do_init(1);
 
-sub do_start_router {
-    `opensrf_router $opt_config routers`;
+    my @services = get_service_list_from_files(1);
+    my @conf_services;
+    if ($alive) {
+        @conf_services = (@perl_services, 
+            map {$_->{service}} @nonperl_services);
+        push(@services, @conf_services);
+    }
+    
+    my %services;
+    my $len = 0;
+    for my $svc (@services) {
+        $len = length($svc) if length($svc) > $len;
+        $services{$svc} = 1;
+    }
 
-    sleep 2; # give the router time to fork
-    my @pids = `ps -C opensrf_router -o pid=`;
-    s/^\s*|\n//g for @pids;
+    for my $svc (sort keys %services) {
+        my @pf_pids = get_service_pids_from_file($svc);
+        my @ps_pids = get_service_pids_from_ps($svc); 
+        my $svc_str = sprintf("%-${len}s ", $svc);
+        my %seen;
 
-    my $pidfile = get_pid_file('router');
-    open(PF, '>', $pidfile) or die "Cannot open $pidfile: $!\n";
-    foreach (@pids) {
-        chomp;
-        msg("starting service pid=$_ router");
-        print PF "$_\n";
+        unless(@ps_pids or @pf_pids) {
+            msg("$svc_str is not running");
+            next;
+        }
+
+        for my $pid (@ps_pids) {
+            $seen{$pid} = 1;
+
+            my $str = "$svc_str [$pid] ";
+            my $times = `ps -o etime=,cputime= $pid`;
+            $times =~ s/^\s+|\s+$//g;
+            my @times = split(/ /, $times);
+            $str .= sprintf("uptime=%-11s cputime=%-11s ", $times[0], $times[1]);
+
+            if ($svc eq 'router') {
+                msg($str);
+            } else {
+                my @drones = `pgrep -f "Drone \\[$svc\\]"`;
+                my $dcount = scalar(@drones);
+                my $dmax = $max_children_map{$svc};
+                if (defined($dmax) && $dmax > 0) {
+                    $str .= "#drones=$dcount/$dmax ";
+                    $str .= sprintf('%3d%%', (int(($dcount / $dmax) * 100)));
+                } else {
+                    $str .= "#drones=$dcount";
+                }
+                msg($str);
+                msg("\tERR $svc has no running drones.") unless @drones;
+            }
+
+            msg("\tERR $svc [$pid] NOT configured for this host.")
+                unless grep {$_ eq $svc} @conf_services 
+                or $svc eq 'router';
+
+            msg("\tERR $svc [$pid] NOT found in PID file.")
+                unless grep {$_ eq $pid} @pf_pids;
+        }
+
+        for my $pid (@pf_pids) {
+            next if $seen{$pid};
+            msg("\tERR $svc Has PID file entry [$pid], ".
+                "which matches no running $svc processes");
+        }
     }
-    close PF;
+}
+
+
+
+sub do_start_router {
+
+    my $pidfile = get_pid_file('router');
+    `opensrf_router $opt_config routers $pidfile`;
+
+    sleep 2; # give the router time to fork (probably not need now but w/e)
 }
 
 # stop a specific service
@@ -214,9 +314,14 @@ sub do_stop {
 }
 
 sub do_init {
+    my $fail_ok = shift;
+
     OpenSRF::System->bootstrap_client(config_file => $opt_config);
-    die "Unable to bootstrap client for requests\n"
-        unless OpenSRF::Transport::PeerHandle->retrieve;
+
+    if (!OpenSRF::Transport::PeerHandle->retrieve) {
+        return 0 if $fail_ok;
+        die "Unable to bootstrap client for requests\n";
+    }
 
     load_settings(); # load the settings config if we can
 
@@ -235,6 +340,10 @@ sub do_init {
                 next;
             }
             my $lang = $sclient->config_value('apps', $app, 'language') || '';
+
+            $max_children_map{$app} = $sclient->config_value(
+                'apps', $app, 'unix_config', 'max_children');
+
             if ($lang =~ /perl/i) {
                 push(@perl_services, $app);
             } else {
@@ -265,7 +374,7 @@ sub do_start {
             unlink $pidfile;
         }
 
-    } elsif (@ps_pids) { # orphan process
+    } elsif (@ps_pids and not $opt_ignore_orphans) { # orphan process
 
         if ($opt_force_clean_process) {
             msg("service $service pid=@ps_pids is running with no pidfile");
@@ -300,13 +409,19 @@ sub do_start {
         }
     }
 
-    msg("$service is not configured to run on $hostname");
-    return 1;
+    # should not get here
+    return 0;
 }
 
+
 sub do_start_all {
-    msg("starting all services for $hostname");
+    msg("starting router and services for $hostname");
     do_start('router');
+    return do_start_services();
+}
+
+sub do_start_services {
+    msg("starting services for $hostname");
 
     if(grep {$_ eq 'opensrf.settings'} @perl_services) {
         do_start('opensrf.settings');
@@ -389,9 +504,9 @@ sub get_running_pids {
 
     # start with the listeners, then drones, then routers
     my @greps = (
-        "ps ax | grep 'OpenSRF Listener' ",
-        "ps ax | grep 'OpenSRF Drone' ",
-        "ps ax | grep 'OpenSRF Router' "
+        "ps x | grep 'OpenSRF Listener' ",
+        "ps x | grep 'OpenSRF Drone' ",
+        "ps x | grep 'OpenSRF Router' "
     );
 
     $_ .= "| grep -v grep |  sed 's/^\\s*//' | cut -d' ' -f1" for @greps;
@@ -420,14 +535,13 @@ sub clear_stale_pids {
     }
 }
 
-sub do_stop_all {
+sub do_stop_services {
     my @signals = @_;
+    @signals = qw/TERM INT KILL/ unless @signals;
 
-    msg("stopping all services for $hostname");
-
+    msg("stopping services for $hostname");
     my @services = get_service_list_from_files();
-    @signals = qw/TERM INT KILL/ unless @signals;
-    
+
     for my $signal (@signals) {
         my @redo;
 
@@ -443,6 +557,15 @@ sub do_stop_all {
         last unless @services;
     }
 
+    return 1;
+}
+
+sub do_stop_all {
+    my @signals = @_;
+    @signals = qw/TERM INT KILL/ unless @signals;
+
+    do_stop_services(@signals);
+
     # graceful shutdown requires the presence of the router, so stop the 
     # router last.  See if it's running first to avoid unnecessary warnings.
     do_stop('router', $signals[0]) if get_service_pids_from_file('router'); 
@@ -455,9 +578,11 @@ sub do_daemon {
     return 1 if $opt_no_daemon;
     my $service = shift;
     my $pid_file = get_pid_file($service);
-    #exit if OpenSRF::Utils::safe_fork();
-    return 0 if OpenSRF::Utils::safe_fork();
-    msg("starting service pid=$$ $service");
+    my $pid = OpenSRF::Utils::safe_fork();
+    if ($pid) { # parent
+        msg("starting service pid=$pid $service");
+        return 0;
+    }
     chdir('/');
     setsid();
     close STDIN;
@@ -520,6 +645,9 @@ sub do_help {
     --help
         Print this help message
 
+    --diagnostic
+        Print information about running services
+
     ==== starting services =====
 
     --start-all
@@ -528,16 +656,27 @@ sub do_help {
     --start
         Start the service specified by --service
 
+    --start-services
+        Start all services but do not start any routers
+
     --restart-all
         Restart the router and all services
 
     --restart
         Restart the service specified by --service
 
+    --restart-services
+        Restart all services but do not restart any routers
+
     --force-clean-process
         When starting a service, if a service process is already running 
         but no pidfile exists, kill the service process before starting
-        the new one.
+        the new one. This applies to routers too.
+
+    --ignore-orphans
+        When starting a service, if a service process is already running but
+        no pidfile exists, ignore the existing process and carry on starting
+        the new one (i.e., ignore orphans).  This applies to routers too.
 
     ==== stopping services =====
 
@@ -552,6 +691,9 @@ sub do_help {
         If the requested service does not have a matching PID file, an
         attempt to locate the PID via 'ps' will be made.
 
+    --stop-services
+        Stop all services but do not stop any routers.  See also --stop-all.
+
     --graceful-shutdown-all
         Send TERM signal to all services + router
 
@@ -586,20 +728,59 @@ sub do_help {
     --signal-timeout
         Seconds to wait for a process to die after sending a shutdown signal.
         All signals except HUP, USR1, and USR2 are assumed to be shutdown signals.
-        
+
+    ==== special signals ====
+
+    --router-de-register
+    --router-de-register-all
+        Sends a SIGUSR1 signal to the selected service(s), which causes each 
+        service's listener process to send an "unregister" command to all 
+        registered routers.  The --all variant sends the signal to all 
+        running listeners.  The non-(--all) variant requires a --service.
+
+    --router-re-register
+    --router-re-register-all
+        Sends a SIGUSR2 signal to the selected service(s), which causes each 
+        service's listener process to send a "register" command to all 
+        configured routers.  The --all variant sends the signal to all
+        running listeners.  The non-(--all) variant requires a --service.
+
+    --reload
+    --reload-all
+        Sends a SIGHUP signal to the selected service(s).  SIGHUP causes
+        each listener process to reload its opensrf_core.xml config file 
+        and gracefully re-launch drone processes.  The -all variant sends
+        the signal to all services.  The non-(-all) variant requires a
+        --service.
 HELP
 exit;
 }
 
-# starting services
-do_init() and do_start($opt_service) if $opt_start;
-do_init() and do_stop($opt_service) and do_start($opt_service) if $opt_restart;
-do_init() and do_start_all() if $opt_start_all;
-do_init() and do_stop_all() and do_start_all() if $opt_restart_all;
+# we do not verify services for stop/signal actions, since those may
+# legitimately be used against services not (or no longer) configured
+# to run on the selected host.
+do_init() and verify_services($opt_service) if
+    ($opt_start or
+    $opt_start_all or
+    $opt_start_services or
+    $opt_restart or
+    $opt_restart_all or
+    $opt_restart_services) and (
+        not defined $opt_service or $opt_service ne 'router'
+    );
+
+# starting services.  do_init() handled above
+do_start($opt_service) if $opt_start;
+do_stop($opt_service) and do_start($opt_service) if $opt_restart;
+do_start_all() if $opt_start_all;
+do_start_services() if $opt_start_services;
+do_stop_all() and do_start_all() if $opt_restart_all;
+do_stop_services() and do_start_services() if $opt_restart_services;
 
 # stopping services
 do_stop($opt_service) if $opt_stop;
 do_stop_all() if $opt_stop_all;
+do_stop_services() if $opt_stop_services;
 do_stop($opt_service, 'TERM') if $opt_shutdown_graceful;
 do_stop($opt_service, 'INT') if $opt_shutdown_fast;
 do_stop($opt_service, 'KILL') if $opt_shutdown_immediate;
@@ -609,17 +790,32 @@ do_stop_all('KILL') if $opt_shutdown_immediate_all;
 do_kill_with_fire() if $opt_kill_with_fire;
 
 # signaling
-do_signal($opt_service, $opt_signal) if $opt_signal;
-do_signal_all($opt_signal) if $opt_signal_all;
+$opt_signal = 'USR1' if $opt_router_de_register or $opt_router_de_register_all;
+$opt_signal = 'USR2' if $opt_router_re_register or $opt_router_re_register_all;
+$opt_signal = 'HUP'  if $opt_reload or $opt_reload_all;
+
+do_signal($opt_service, $opt_signal) if $opt_signal and $opt_service;
+do_signal_all($opt_signal) if 
+    $opt_signal_all or 
+    $opt_reload_all or
+    $opt_router_de_register_all or 
+    $opt_router_re_register_all;
+
+# misc
+do_diagnostic() if $opt_diagnostic;
+
 
 # show help if no action was requested
 do_help() if $opt_help or not (
     $opt_start or 
     $opt_start_all or 
+    $opt_start_services or 
     $opt_stop or 
     $opt_stop_all or 
+    $opt_stop_services or 
     $opt_restart or 
     $opt_restart_all or 
+    $opt_restart_services or 
     $opt_signal or 
     $opt_signal_all or
     $opt_shutdown_graceful or
@@ -628,5 +824,6 @@ do_help() if $opt_help or not (
     $opt_shutdown_fast_all or
     $opt_shutdown_immediate or
     $opt_shutdown_immediate_all or
-    $opt_kill_with_fire
+    $opt_kill_with_fire or
+    $opt_diagnostic
 )