2 # ---------------------------------------------------------------
3 # Copyright (C) 2008-2013 Georgia Public Library Service
4 # Copyright (C) 2013 Equinox Software, Inc
5 # Bill Erickson <berick@esilibrary.com>
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.
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;
19 use Net::Domain qw/hostfqdn/;
20 use POSIX qw/setsid :sys_wait_h/;
21 use OpenSRF::Utils::Logger q/$logger/;
23 use OpenSRF::Transport::PeerHandle;
24 use OpenSRF::Utils::SettingsClient;
25 use OpenSRF::Transport::Listener;
27 use OpenSRF::Utils::Config;
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;
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;
49 my $opt_start_all = 0;
51 my $opt_restart_all = 0;
56 my $hostname = $ENV{OSRF_HOSTNAME} || hostfqdn();
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,
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,
79 'start-all' => \$opt_start_all,
80 'stop-all' => \$opt_stop_all,
81 'restart' => \$opt_restart,
82 'restart-all' => \$opt_restart_all
86 $hostname = 'localhost';
87 $ENV{OSRF_HOSTNAME} = $hostname;
90 my $C_COMMAND = "opensrf-c -c $opt_config -x opensrf -p $opt_pid_dir -h $hostname";
91 my $PY_COMMAND = ""; #TODO
97 my @pids = get_service_pids($service);
100 # no PID files exist. see if the service is running anyway
102 my $ps = ($service eq 'router') ?
103 "ps ax | grep 'OpenSRF Router'" :
104 "ps ax | grep 'OpenSRF Listener \\[$service\\]'";
106 $ps .= " | grep -v grep | sed 's/^\\s*//' | cut -d' ' -f1";
109 s/^\s*|\n//g for @pids;
112 msg("cannont signal $service : no pid file or running procesesses");
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;
125 msg("sending $signal signal to pid=$pid $service", 1);
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.
136 my @pids = get_service_pids($service);
139 for my $pid (@pids) {
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
144 for my $i (1..$opt_signal_timeout) {
145 $sig_count = kill(0, $pid);
146 last unless $sig_count;
151 msg("timed out waiting on $service pid=$pid to die");
156 # cleanup successful. remove the PID file
157 my $pidfile = get_pid_file($service);
158 unlink $pidfile if $pidfile;
166 return "$opt_pid_dir/$service.pid";
169 # services usually only have 1 pid, but the router will have at least 2
170 sub get_service_pids {
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;
179 sub do_start_router {
180 my $pidfile = get_pid_file('router');
182 msg("router already running", 1);
186 `opensrf_router $opt_config routers`;
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;
192 open(PF, '>', $pidfile) or die "Cannot open $pidfile: $!\n";
195 msg("starting service pid=$_ router", 1);
201 # stop a specific service
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;
212 OpenSRF::System->bootstrap_client(config_file => $opt_config);
213 die "Unable to bootstrap client for requests\n"
214 unless OpenSRF::Transport::PeerHandle->retrieve;
216 load_settings(); # load the settings config if we can
218 my $sclient = OpenSRF::Utils::SettingsClient->new;
219 my $apps = $sclient->config_value("activeapps", "appname");
221 # disconnect the top-level network handle
222 OpenSRF::Transport::PeerHandle->retrieve->disconnect;
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");
232 my $lang = $sclient->config_value('apps', $app, 'language') || '';
233 if ($lang =~ /perl/i) {
234 push(@perl_services, $app);
236 push(@nonperl_services, {service => $app, lang => $lang});
243 # start a specific service
246 return do_start_router() if $service eq 'router';
248 if(-e get_pid_file($service)) {
249 msg("$service is already running");
253 load_settings() if $service eq 'opensrf.settings';
255 if(grep { $_ eq $service } @perl_services) {
256 return unless do_daemon($service);
257 OpenSRF::System->run_service($service, $opt_pid_dir);
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;
264 if ($svc->{lang} =~ /c/i) {
265 `$C_COMMAND -a start -s $service`;
271 msg("$service is not configured to run on $hostname");
276 msg("starting all services for $hostname", 1);
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;
287 # start Perl services
288 for my $service (@perl_services) {
289 do_start($service) unless $service eq 'opensrf.settings';
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");
300 # signal a single service
304 return do_signal_all($signal, $service);
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;
320 my ($signal, @services) = @_;
321 @services = get_service_list_from_pids() unless @services;
323 do_signal_send($_, $signal) for @services;
325 # if user passed a know non-shutdown signal, we're done.
326 return if $signal =~ /HUP|USR1|USR2/;
328 do_signal_wait($_) for @services;
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);
336 my @pids = get_running_pids();
338 next unless $_ =~ /\d+/;
339 my $proc = `ps -p $_ -o cmd=`;
341 msg("killing with fire pid=$_ $proc", 1);
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;
349 msg("removing pid file $_");
354 sub get_running_pids {
357 # start with the listeners, then drones, then routers
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"
364 for my $grep (@greps) {
366 s/^\s*|\n//g for @spids;
367 push (@pids, @spids);
373 sub clear_stale_pids {
374 my @pidfile_services = get_service_list_from_pids(1);
375 my @running_pids = get_running_pids();
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");
391 msg("stopping all services for $hostname", 1);
393 my @services = get_service_list_from_pids();
394 @signals = qw/TERM INT KILL/ unless @signals;
396 for my $signal (@signals) {
399 # send the signal to all PIDs
400 do_signal_send($_, $signal) for @services;
402 # then wait for them to go away
403 for my $service (@services) {
404 push(@redo, $service) if do_signal_wait($service) == 2;
408 last unless @services;
411 # finally stop the routers
412 # graceful shutdown requires the presence of the router
413 do_stop('router', $signals[0]);
418 # daemonize us. return true if we're the child, false if parent
420 return 1 if $opt_no_daemon;
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);
431 open STDIN, '</dev/null';
432 open STDOUT, '>/dev/null';
433 open STDERR, '>/dev/null';
434 `echo $$ > $pid_file`;
438 # parses the local settings file
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);
452 print "* $m\n" unless $v and not $verbose;
458 Usage: perl $0 --pid-dir @TMP@ --config @CONF_DIR@/opensrf_core.xml --service opensrf.settings --start
460 --config <file> [default: @CONF_DIR@/opensrf_core.xml]
461 OpenSRF configuration file
463 --pid-dir <dir> [default: @PID_DIR@/run/opensrf]
464 Directory where process-specific PID files are kept
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
473 Force the hostname to be 'localhost', instead of the fully qualified
474 domain name for the machine.
477 Specifies which OpenSRF service to control
480 Print extra info/debug messages to STDOUT
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.
487 Print this help message
489 ==== starting services =====
492 Start the router and all services
495 Start the service specified by --service
498 Restart the router and all services
501 Restart the service specified by --service
503 ==== stopping services =====
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.
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.
516 --graceful-shutdown-all
517 Send TERM signal to all services + router
520 Send TERM signal to the service specified by --service
523 Send INT signal to all services + router
526 Send INT signal to the service specified by --service
528 --immediate-shutdown-all
529 Send KILL signal to all services + router
532 Send KILL signal to the service specified by --service
535 Send KILL signal to all running services + routers, regardless of
536 the presence of a PID file, and remove all PID files indiscriminately.
538 ==== signaling services =====
541 Send signal to all services
544 Name of signal to send. If --signal-all is not specified, the
545 signal will be sent to the service specified by --service.
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.
556 do_help() if $opt_help; # TODO
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;
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;
574 do_kill_with_fire() if $opt_kill_with_fire;
576 do_signal($opt_service, $opt_signal) if $opt_signal;
577 do_signal_all($opt_signal) if $opt_signal_all;