]> git.evergreen-ils.org Git - OpenSRF.git/blob - bin/opensrf-perl.pl.in
LP1204123 opensrf-perl.pl improve logging
[OpenSRF.git] / bin / opensrf-perl.pl.in
1 #!/usr/bin/perl
2 # ---------------------------------------------------------------
3 # Copyright (C) 2008-2013 Georgia Public Library Service
4 # Copyright (C) 2013 Equinox Software, Inc
5 # Bill Erickson <berick@esilibrary.com>
6 #
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either version 2
10 # of the License, or (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 # ---------------------------------------------------------------
17 use strict; use warnings;
18 use Getopt::Long;
19 use Net::Domain qw/hostfqdn/;
20 use POSIX qw/setsid :sys_wait_h/;
21 use OpenSRF::Utils::Logger q/$logger/;
22 use OpenSRF::System;
23 use OpenSRF::Transport::PeerHandle;
24 use OpenSRF::Utils::SettingsClient;
25 use OpenSRF::Transport::Listener;
26 use OpenSRF::Utils;
27 use OpenSRF::Utils::Config;
28
29 my $opt_service = undef;
30 my $opt_config = "@CONF_DIR@/opensrf_core.xml";
31 my $opt_pid_dir = "@PID_DIR@/run/opensrf";
32 my $opt_no_daemon = 0;
33 my $opt_settings_pause = 0;
34 my $opt_localhost = 0;
35 my $opt_help = 0;
36 my $opt_shutdown_graceful = 0;
37 my $opt_shutdown_fast = 0;
38 my $opt_shutdown_immediate = 0;
39 my $opt_shutdown_graceful_all = 0;
40 my $opt_shutdown_fast_all = 0;
41 my $opt_shutdown_immediate_all = 0;
42 my $opt_kill_with_fire = 0;
43 my $opt_signal = ''; # signal name
44 my $opt_signal_all = 0;
45 my $opt_signal_timeout = 30;
46 my $opt_start = 0;
47 my $opt_stop = 0;
48 my $opt_restart = 0;
49 my $opt_start_all = 0;
50 my $opt_stop_all = 0;
51 my $opt_restart_all = 0;
52 my $opt_force_clean_process = 0;
53 my $opt_quiet = 0;
54 my $sclient;
55 my @perl_services;
56 my @nonperl_services;
57 my $hostname = $ENV{OSRF_HOSTNAME} || hostfqdn();
58
59 GetOptions(
60     'service=s' => \$opt_service,
61     'config=s' => \$opt_config,
62     'pid-dir=s' => \$opt_pid_dir,
63     'no-daemon' => \$opt_no_daemon,
64     'settings-startup-pause=i' => \$opt_settings_pause,
65     'localhost' => \$opt_localhost,
66     'help' => \$opt_help,
67     'quiet' => \$opt_quiet,
68     'graceful-shutdown' => \$opt_shutdown_graceful,
69     'fast-shutdown' => \$opt_shutdown_fast,
70     'immediate-shutdown' => \$opt_shutdown_immediate,
71     'graceful-shutdown-all' => \$opt_shutdown_graceful_all,
72     'fast-shutdown-all' => \$opt_shutdown_fast_all,
73     'immediate-shutdown-all' => \$opt_shutdown_immediate_all,
74     'kill-with-fire' => \$opt_kill_with_fire,
75     'force-clean-process' => \$opt_force_clean_process,
76     'signal-timeout' => \$opt_signal_timeout,
77     'signal=s' => \$opt_signal,
78     'signal-all' => \$opt_signal_all,
79     'start' => \$opt_start,
80     'stop' => \$opt_stop,
81     'start-all' => \$opt_start_all,
82     'stop-all' => \$opt_stop_all,
83     'restart' => \$opt_restart,
84     'restart-all' => \$opt_restart_all
85 );
86
87 if ($opt_localhost) {
88     $hostname = 'localhost';
89     $ENV{OSRF_HOSTNAME} = $hostname;
90 }
91
92 my $C_COMMAND = "opensrf-c -c $opt_config -x opensrf -p $opt_pid_dir -h $hostname";
93 my $PY_COMMAND = "opensrf.py -f $opt_config -p $opt_pid_dir ". ($opt_localhost ? '-l' : '');
94
95 sub do_signal_send {
96     my $service = shift;
97     my $signal = shift;
98
99     my @pids = get_service_pids_from_file($service);
100
101     if (!@pids) {
102         # no PID files exist.  see if the service is running anyway
103
104         @pids = get_service_pids_from_ps($service);
105         if (!@pids) {
106             msg("cannot signal $service : no pid file or running process");
107             return 0;
108         }
109     }
110
111     for my $pid (@pids) {
112         if (kill($signal, $pid) == 0) { # no process was signaled.  
113             msg("cannot signal $service: process $pid is not running");
114             my $pidfile = get_pid_file($service);
115             unlink $pidfile if $pidfile;
116             next;
117         }
118
119         msg("sending $signal signal to pid=$pid $service");
120     }
121
122     return 1;
123 }
124
125 # returns 2 if a process should have gone away but did not
126 # in the case of multiple PIDs (e.g. router), return the 
127 # status of any failures, but not the successes.
128 sub do_signal_wait {
129     my $service = shift;
130     my @pids = get_service_pids_from_file($service);
131
132     my $stat = 1;
133     for my $pid (@pids) {
134
135         # to determine whether a process has died, we have to send
136         # a no-op signal to the PID and check the success of that signal
137         my $sig_count;
138         for my $i (1..$opt_signal_timeout) {
139             $sig_count = kill(0, $pid);
140             last unless $sig_count;
141             sleep(1);
142         }
143
144         if ($sig_count) {
145             msg("timed out waiting on $service pid=$pid to die");
146             $stat = 2;
147             next;
148         }
149
150         # cleanup successful. remove the PID file
151         my $pidfile = get_pid_file($service);
152         unlink $pidfile if $pidfile;
153     }
154
155     return $stat;
156 }
157
158 sub get_pid_file {
159     my $service = shift;
160     return "$opt_pid_dir/$service.pid";
161 }
162
163 # services usually only have 1 pid, but the router will have at least 2
164 sub get_service_pids_from_file {
165     my $service = shift;
166     my $pid_file = get_pid_file($service);
167     return () unless -e $pid_file;
168     my @pids = `cat $pid_file`;
169     s/^\s*|\n//g for @pids;
170     return @pids;
171 }
172
173 sub get_service_pids_from_ps {
174     my $service = shift;
175
176     my $ps = ($service eq 'router') ?
177         "ps ax | grep 'OpenSRF Router'" :
178         "ps ax | grep 'OpenSRF Listener \\[$service\\]'";
179
180     $ps .= " | grep -v grep |  sed 's/^\\s*//' | cut -d' ' -f1";
181     my @pids = `$ps`;
182     s/^\s*|\n//g for @pids;
183
184     return @pids;
185 }
186
187
188
189 sub do_start_router {
190     `opensrf_router $opt_config routers`;
191
192     sleep 2; # give the router time to fork
193     my @pids = `ps -C opensrf_router -o pid=`;
194     s/^\s*|\n//g for @pids;
195
196     my $pidfile = get_pid_file('router');
197     open(PF, '>', $pidfile) or die "Cannot open $pidfile: $!\n";
198     foreach (@pids) {
199         chomp;
200         msg("starting service pid=$_ router");
201         print PF "$_\n";
202     }
203     close PF;
204 }
205
206 # stop a specific service
207 sub do_stop {
208     my ($service, @signals) = @_;
209     @signals = qw/TERM INT KILL/ unless @signals;
210     for my $sig (@signals) {
211         last unless do_signal($service, $sig) == 2;
212     }
213     return 1;
214 }
215
216 sub do_init {
217     OpenSRF::System->bootstrap_client(config_file => $opt_config);
218     die "Unable to bootstrap client for requests\n"
219         unless OpenSRF::Transport::PeerHandle->retrieve;
220
221     load_settings(); # load the settings config if we can
222
223     my $sclient = OpenSRF::Utils::SettingsClient->new;
224     my $apps = $sclient->config_value("activeapps", "appname");
225
226     # disconnect the top-level network handle
227     OpenSRF::Transport::PeerHandle->retrieve->disconnect;
228
229     if($apps) {
230         $apps = [$apps] unless ref $apps;
231         for my $app (@$apps) {
232             if (!$sclient->config_value('apps', $app)) {
233                 msg("Service '$app' is listed for this host, ".
234                     "but there is no configuration for it in $opt_config");
235                 next;
236             }
237             my $lang = $sclient->config_value('apps', $app, 'language') || '';
238             if ($lang =~ /perl/i) {
239                 push(@perl_services, $app);
240             } else {
241                 push(@nonperl_services, {service => $app, lang => $lang});
242             }
243         }
244     }
245     return 1;
246 }
247
248 # start a specific service
249 sub do_start {
250     my $service = shift;
251
252     my @pf_pids = get_service_pids_from_file($service);
253     my @ps_pids = get_service_pids_from_ps($service); 
254
255     if (@pf_pids) { # had pidfile
256
257         if (@ps_pids) {
258             msg("service $service already running : @ps_pids");
259             return;
260
261         } else { # stale pidfile
262
263             my $pidfile = get_pid_file($service);
264             msg("removing stale pid file $pidfile");
265             unlink $pidfile;
266         }
267
268     } elsif (@ps_pids) { # orphan process
269
270         if ($opt_force_clean_process) {
271             msg("service $service pid=@ps_pids is running with no pidfile");
272             do_signal($service, 'KILL');
273         } else {
274             msg("service $service pid=@ps_pids is running with no pidfile! ".
275                 "use --force-clean-process to automatically kill orphan processes");
276             return;
277         }
278     }
279
280     return do_start_router() if $service eq 'router';
281
282     load_settings() if $service eq 'opensrf.settings';
283
284     if(grep { $_ eq $service } @perl_services) {
285         return unless do_daemon($service);
286         OpenSRF::System->run_service($service, $opt_pid_dir);
287
288     } else {
289         # note: we don't daemonize non-perl services, but instead
290         # assume the controller for other languages manages that.
291         my ($svc) = grep { $_->{service} eq $service } @nonperl_services;
292         if ($svc) {
293             if ($svc->{lang} =~ /c/i) {
294                 system("$C_COMMAND -a start -s $service");
295                 return;
296             } elsif ($svc->{lang} =~ /python/i) {
297                 system("$PY_COMMAND -a start -s $service");
298                 return;
299             }
300         }
301     }
302
303     msg("$service is not configured to run on $hostname");
304     return 1;
305 }
306
307 sub do_start_all {
308     msg("starting all services for $hostname");
309     do_start('router');
310
311     if(grep {$_ eq 'opensrf.settings'} @perl_services) {
312         do_start('opensrf.settings');
313         # in batch mode, give opensrf.settings plenty of time to start 
314         # before any non-Perl services try to connect
315         sleep $opt_settings_pause if $opt_settings_pause;
316     }
317
318     # start Perl services
319     for my $service (@perl_services) {
320         do_start($service) unless $service eq 'opensrf.settings';
321     }
322
323     # start each non-perl service individually instead of using the native
324     # start-all command.  this allows us to test for existing pid files 
325     # and/or running processes on each service before starting.
326     # it also means each service has to connect-fetch_setting-disconnect
327     # from jabber, which makes startup slightly slower than native start-all
328     do_start($_->{service}) for @nonperl_services;
329
330     return 1;
331 }
332
333 # signal a single service
334 sub do_signal {
335     my $service = shift;
336     my $signal = shift;
337     return do_signal_all($signal, $service);
338 }
339
340 # returns the list of running services based on presence of PID files.
341 # the 'router' service is not included by deault, since it's 
342 # usually treated special.
343 sub get_service_list_from_files {
344     my $include_router = shift;
345     my @services = `ls $opt_pid_dir/*.pid 2> /dev/null`;
346     s/^\s*|\n//g for @services;
347     s|.*/(.*)\.pid$|$1| for @services;
348     return @services if $include_router;
349     return grep { $_ ne 'router' } @services;
350
351
352 sub do_signal_all {
353     my ($signal, @services) = @_;                                              
354     @services = get_service_list_from_files() unless @services;     
355
356     do_signal_send($_, $signal) for @services;
357
358     # if user passed a know non-shutdown signal, we're done.
359     return if $signal =~ /HUP|USR1|USR2/;
360
361     do_signal_wait($_) for @services;
362 }
363
364 # pull all opensrf listener and drone PIDs from 'ps', 
365 # kill them all, and remove all pid files
366 sub do_kill_with_fire {
367     msg("killing with fire");
368
369     my @pids = get_running_pids();
370     for (@pids) {
371         next unless $_ =~ /\d+/;
372         my $proc = `ps -p $_ -o cmd=`;
373         chomp $proc;
374         msg("killing with fire pid=$_ $proc");
375         kill('KILL', $_);
376     }
377
378     # remove all of the pid files
379     my @files = `ls $opt_pid_dir/*.pid 2> /dev/null`;
380     s/^\s*|\n//g for @files;
381     for (@files) {
382         msg("removing pid file $_");
383         unlink $_;
384     }
385 }
386
387 sub get_running_pids {
388     my @pids;
389
390     # start with the listeners, then drones, then routers
391     my @greps = (
392         "ps ax | grep 'OpenSRF Listener' ",
393         "ps ax | grep 'OpenSRF Drone' ",
394         "ps ax | grep 'OpenSRF Router' "
395     );
396
397     $_ .= "| grep -v grep |  sed 's/^\\s*//' | cut -d' ' -f1" for @greps;
398
399     for my $grep (@greps) {
400         my @spids = `$grep`;
401         s/^\s*|\n//g for @spids;
402         push (@pids, @spids);
403     }
404
405     return @pids;
406 }
407
408 sub clear_stale_pids {
409     my @pidfile_services = get_service_list_from_files(1);
410     my @running_pids = get_running_pids();
411     
412     for my $svc (@pidfile_services) {
413         my @pids = get_service_pids_from_file($svc);
414         for my $pid (@pids) {
415             next if grep { $_ eq $pid } @running_pids;
416             my $pidfile = get_pid_file($svc);
417             msg("removing stale pid file $pidfile");
418             unlink $pidfile;
419         }
420     }
421 }
422
423 sub do_stop_all {
424     my @signals = @_;
425
426     msg("stopping all services for $hostname");
427
428     my @services = get_service_list_from_files();
429     @signals = qw/TERM INT KILL/ unless @signals;
430     
431     for my $signal (@signals) {
432         my @redo;
433
434         # send the signal to all PIDs
435         do_signal_send($_, $signal) for @services;
436
437         # then wait for them to go away
438         for my $service (@services) {
439             push(@redo, $service) if do_signal_wait($service) == 2;
440         }
441
442         @services = @redo;
443         last unless @services;
444     }
445
446     # graceful shutdown requires the presence of the router, so stop the 
447     # router last.  See if it's running first to avoid unnecessary warnings.
448     do_stop('router', $signals[0]) if get_service_pids_from_file('router'); 
449
450     return 1;
451 }
452
453 # daemonize us.  return true if we're the child, false if parent
454 sub do_daemon {
455     return 1 if $opt_no_daemon;
456     my $service = shift;
457     my $pid_file = get_pid_file($service);
458     #exit if OpenSRF::Utils::safe_fork();
459     return 0 if OpenSRF::Utils::safe_fork();
460     msg("starting service pid=$$ $service");
461     chdir('/');
462     setsid();
463     close STDIN;
464     close STDOUT;
465     close STDERR;
466     open STDIN, '</dev/null';
467     open STDOUT, '>/dev/null';
468     open STDERR, '>/dev/null';
469     `echo $$ > $pid_file`;
470     return 1;
471 }
472
473 # parses the local settings file
474 sub load_settings {
475     my $conf = OpenSRF::Utils::Config->current;
476     my $cfile = $conf->bootstrap->settings_config;
477     return unless $cfile;
478     my $parser = OpenSRF::Utils::SettingsParser->new();
479     $parser->initialize( $cfile );
480     $OpenSRF::Utils::SettingsClient::host_config =
481         $parser->get_server_config($conf->env->hostname);
482 }
483
484 sub msg {
485     my $m = shift;
486     print "* $m\n" unless $opt_quiet;
487 }
488
489 sub do_help {
490     print <<HELP;
491
492     Usage: perl $0 --pid-dir @TMP@ --config @CONF_DIR@/opensrf_core.xml --service opensrf.settings --start
493
494     --config <file> [default: @CONF_DIR@/opensrf_core.xml]
495         OpenSRF configuration file 
496         
497     --pid-dir <dir> [default: @PID_DIR@/run/opensrf]
498         Directory where process-specific PID files are kept
499
500     --settings-startup-pause
501         How long to give the opensrf.settings server to start up when running 
502         in batch mode (start_all).  The purpose is to give plenty of time for
503         the settings server to be up and active before any non-Perl services
504         attempt to connect.
505
506     --localhost
507         Force the hostname to be 'localhost', instead of the fully qualified
508         domain name for the machine.
509
510     --service <service>
511         Specifies which OpenSRF service to control
512
513     --quiet
514         Do not print informational messages to STDOUT 
515
516     --no-daemon
517         Do not detach and run as a daemon process.  Useful for debugging.  
518         Only works for Perl services and only when starting a single service.
519
520     --help
521         Print this help message
522
523     ==== starting services =====
524
525     --start-all
526         Start the router and all services
527
528     --start
529         Start the service specified by --service
530
531     --restart-all
532         Restart the router and all services
533
534     --restart
535         Restart the service specified by --service
536
537     --force-clean-process
538         When starting a service, if a service process is already running 
539         but no pidfile exists, kill the service process before starting
540         the new one.
541
542     ==== stopping services =====
543
544     --stop-all
545         Stop the router and all services.  Services are sent the TERM signal,
546         followed by the INT signal, followed by the KILL signal.  With each
547         iteration, the script pauses up to --signal-timeout seconds waiting
548         for each process to die before sending the next signal.
549
550     --stop
551         Stop the service specified by --service.  See also --stop-all.
552         If the requested service does not have a matching PID file, an
553         attempt to locate the PID via 'ps' will be made.
554
555     --graceful-shutdown-all
556         Send TERM signal to all services + router
557
558     --graceful-shutdown
559         Send TERM signal to the service specified by --service
560
561     --fast-shutdown-all
562         Send INT signal to all services + router
563
564     --fast-shutdown
565         Send INT signal to the service specified by --service
566
567     --immediate-shutdown-all
568         Send KILL signal to all services + router
569
570     --immediate-shutdown
571         Send KILL signal to the service specified by --service
572
573     --kill-with-fire
574         Send KILL signal to all running services + routers, regardless of 
575         the presence of a PID file, and remove all PID files indiscriminately.  
576
577     ==== signaling services =====
578
579     --signal-all
580         Send signal to all services
581
582     --signal
583         Name of signal to send.  If --signal-all is not specified, the 
584         signal will be sent to the service specified by --service.
585
586     --signal-timeout
587         Seconds to wait for a process to die after sending a shutdown signal.
588         All signals except HUP, USR1, and USR2 are assumed to be shutdown signals.
589         
590 HELP
591 exit;
592 }
593
594 # starting services
595 do_init() and do_start($opt_service) if $opt_start;
596 do_init() and do_stop($opt_service) and do_start($opt_service) if $opt_restart;
597 do_init() and do_start_all() if $opt_start_all;
598 do_init() and do_stop_all() and do_start_all() if $opt_restart_all;
599
600 # stopping services
601 do_stop($opt_service) if $opt_stop;
602 do_stop_all() if $opt_stop_all;
603 do_stop($opt_service, 'TERM') if $opt_shutdown_graceful;
604 do_stop($opt_service, 'INT') if $opt_shutdown_fast;
605 do_stop($opt_service, 'KILL') if $opt_shutdown_immediate;
606 do_stop_all('TERM') if $opt_shutdown_graceful_all;
607 do_stop_all('INT') if $opt_shutdown_fast_all;
608 do_stop_all('KILL') if $opt_shutdown_immediate_all;
609 do_kill_with_fire() if $opt_kill_with_fire;
610
611 # signaling
612 do_signal($opt_service, $opt_signal) if $opt_signal;
613 do_signal_all($opt_signal) if $opt_signal_all;
614
615 # show help if no action was requested
616 do_help() if $opt_help or not (
617     $opt_start or 
618     $opt_start_all or 
619     $opt_stop or 
620     $opt_stop_all or 
621     $opt_restart or 
622     $opt_restart_all or 
623     $opt_signal or 
624     $opt_signal_all or
625     $opt_shutdown_graceful or
626     $opt_shutdown_graceful_all or
627     $opt_shutdown_fast or
628     $opt_shutdown_fast_all or
629     $opt_shutdown_immediate or
630     $opt_shutdown_immediate_all or
631     $opt_kill_with_fire
632 )