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