]> git.evergreen-ils.org Git - OpenSRF.git/blob - bin/opensrf-perl.pl.in
8f01200b07699791a19493f0de38eadbc7e8b12e
[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     my $pid = OpenSRF::Utils::safe_fork();
459     if ($pid) { # parent
460         msg("starting service pid=$pid $service");
461         return 0;
462     }
463     chdir('/');
464     setsid();
465     close STDIN;
466     close STDOUT;
467     close STDERR;
468     open STDIN, '</dev/null';
469     open STDOUT, '>/dev/null';
470     open STDERR, '>/dev/null';
471     `echo $$ > $pid_file`;
472     return 1;
473 }
474
475 # parses the local settings file
476 sub load_settings {
477     my $conf = OpenSRF::Utils::Config->current;
478     my $cfile = $conf->bootstrap->settings_config;
479     return unless $cfile;
480     my $parser = OpenSRF::Utils::SettingsParser->new();
481     $parser->initialize( $cfile );
482     $OpenSRF::Utils::SettingsClient::host_config =
483         $parser->get_server_config($conf->env->hostname);
484 }
485
486 sub msg {
487     my $m = shift;
488     print "* $m\n" unless $opt_quiet;
489 }
490
491 sub do_help {
492     print <<HELP;
493
494     Usage: $0 --localhost --start-all
495
496     --config <file> [default: @CONF_DIR@/opensrf_core.xml]
497         OpenSRF configuration file 
498         
499     --pid-dir <dir> [default: @PID_DIR@/run/opensrf]
500         Directory where process-specific PID files are kept
501
502     --settings-startup-pause
503         How long to give the opensrf.settings server to start up when running 
504         in batch mode (start_all).  The purpose is to give plenty of time for
505         the settings server to be up and active before any non-Perl services
506         attempt to connect.
507
508     --localhost
509         Force the hostname to be 'localhost', instead of the fully qualified
510         domain name for the machine.
511
512     --service <service>
513         Specifies which OpenSRF service to control
514
515     --quiet
516         Do not print informational messages to STDOUT 
517
518     --no-daemon
519         Do not detach and run as a daemon process.  Useful for debugging.  
520         Only works for Perl services and only when starting a single service.
521
522     --help
523         Print this help message
524
525     ==== starting services =====
526
527     --start-all
528         Start the router and all services
529
530     --start
531         Start the service specified by --service
532
533     --restart-all
534         Restart the router and all services
535
536     --restart
537         Restart the service specified by --service
538
539     --force-clean-process
540         When starting a service, if a service process is already running 
541         but no pidfile exists, kill the service process before starting
542         the new one.
543
544     ==== stopping services =====
545
546     --stop-all
547         Stop the router and all services.  Services are sent the TERM signal,
548         followed by the INT signal, followed by the KILL signal.  With each
549         iteration, the script pauses up to --signal-timeout seconds waiting
550         for each process to die before sending the next signal.
551
552     --stop
553         Stop the service specified by --service.  See also --stop-all.
554         If the requested service does not have a matching PID file, an
555         attempt to locate the PID via 'ps' will be made.
556
557     --graceful-shutdown-all
558         Send TERM signal to all services + router
559
560     --graceful-shutdown
561         Send TERM signal to the service specified by --service
562
563     --fast-shutdown-all
564         Send INT signal to all services + router
565
566     --fast-shutdown
567         Send INT signal to the service specified by --service
568
569     --immediate-shutdown-all
570         Send KILL signal to all services + router
571
572     --immediate-shutdown
573         Send KILL signal to the service specified by --service
574
575     --kill-with-fire
576         Send KILL signal to all running services + routers, regardless of 
577         the presence of a PID file, and remove all PID files indiscriminately.  
578
579     ==== signaling services =====
580
581     --signal-all
582         Send signal to all services
583
584     --signal
585         Name of signal to send.  If --signal-all is not specified, the 
586         signal will be sent to the service specified by --service.
587
588     --signal-timeout
589         Seconds to wait for a process to die after sending a shutdown signal.
590         All signals except HUP, USR1, and USR2 are assumed to be shutdown signals.
591         
592 HELP
593 exit;
594 }
595
596 # starting services
597 do_init() and do_start($opt_service) if $opt_start;
598 do_init() and do_stop($opt_service) and do_start($opt_service) if $opt_restart;
599 do_init() and do_start_all() if $opt_start_all;
600 do_init() and do_stop_all() and do_start_all() if $opt_restart_all;
601
602 # stopping services
603 do_stop($opt_service) if $opt_stop;
604 do_stop_all() if $opt_stop_all;
605 do_stop($opt_service, 'TERM') if $opt_shutdown_graceful;
606 do_stop($opt_service, 'INT') if $opt_shutdown_fast;
607 do_stop($opt_service, 'KILL') if $opt_shutdown_immediate;
608 do_stop_all('TERM') if $opt_shutdown_graceful_all;
609 do_stop_all('INT') if $opt_shutdown_fast_all;
610 do_stop_all('KILL') if $opt_shutdown_immediate_all;
611 do_kill_with_fire() if $opt_kill_with_fire;
612
613 # signaling
614 do_signal($opt_service, $opt_signal) if $opt_signal;
615 do_signal_all($opt_signal) if $opt_signal_all;
616
617 # show help if no action was requested
618 do_help() if $opt_help or not (
619     $opt_start or 
620     $opt_start_all or 
621     $opt_stop or 
622     $opt_stop_all or 
623     $opt_restart or 
624     $opt_restart_all or 
625     $opt_signal or 
626     $opt_signal_all or
627     $opt_shutdown_graceful or
628     $opt_shutdown_graceful_all or
629     $opt_shutdown_fast or
630     $opt_shutdown_fast_all or
631     $opt_shutdown_immediate or
632     $opt_shutdown_immediate_all or
633     $opt_kill_with_fire
634 )