do the process separation dance on server bootstrapping, just like with the C code
[OpenSRF.git] / src / perlmods / OpenSRF / System.pm
1 package OpenSRF::System;
2 use strict; use warnings;
3 use OpenSRF;
4 use base 'OpenSRF';
5 use OpenSRF::Utils::Logger qw(:level);
6 use OpenSRF::Transport::Listener;
7 use OpenSRF::Transport;
8 use OpenSRF::UnixServer;
9 use OpenSRF::Utils;
10 use OpenSRF::Utils::LogServer;
11 #use OpenSRF::DOM;
12 use OpenSRF::EX qw/:try/;
13 use POSIX qw/setsid :sys_wait_h/;
14 use OpenSRF::Utils::Config; 
15 use OpenSRF::Utils::SettingsParser;
16 use OpenSRF::Utils::SettingsClient;
17 use OpenSRF::Application;
18 use Net::Server::PreFork;
19 use strict;
20
21 my $bootstrap_config_file;
22 sub import {
23         my( $self, $config ) = @_;
24         $bootstrap_config_file = $config;
25 }
26
27 =head2 Name/Description
28
29 OpenSRF::System
30
31 To start the system: OpenSRF::System->bootstrap();
32
33 Simple system process management and automation.  After instantiating the class, simply call
34 bootstrap() to launch the system.  Each launched process is stored as a process-id/method-name
35 pair in a local hash.  When we receive a SIG{CHILD}, we loop through this hash and relaunch
36 any child processes that may have terminated.  
37
38 Currently automated processes include launching the internal Unix Servers, launching the inbound 
39 connections for each application, and starting the system shell.
40
41
42 Note: There should be only one instance of this class
43 alive at any given time.  It is designed as a globel process handler and, hence, will cause much
44 oddness if you call the bootstrap() method twice or attempt to create two of these by trickery.
45 There is a single instance of the class created on the first call to new().  This same instance is 
46 returned on subsequent calls to new().
47
48 =cut
49
50 $| = 1;
51
52 sub DESTROY {}
53
54 # ----------------------------------------------
55
56 $SIG{INT} = sub { instance()->killall(); };
57
58 $SIG{HUP} = sub{ instance()->hupall(); };
59
60 #$SIG{CHLD} = \&process_automation;
61
62
63
64         # --- 
65         # put $instance in a closure and return it for requests to new()
66         # since there should only be one System instance running
67         # ----- 
68         my $instance;
69         sub instance { return __PACKAGE__->new(); }
70         sub new {
71                 my( $class ) = @_;
72
73                 if( ! $instance ) {
74                         $class = ref( $class ) || $class;
75                         my $self = {};
76                         $self->{'pid_hash'} = {};
77                         bless( $self, $class );
78                         $instance = $self;
79                 }
80                 return $instance;
81         }
82 }
83
84 # ----------------------------------------------
85 # Commands to execute at system launch
86
87 sub _unixserver {
88         my( $app ) = @_;
89         return "OpenSRF::UnixServer->new( '$app')->serve()";
90 }
91
92 sub _listener {
93         my( $app ) = @_;
94         return "OpenSRF::Transport::Listener->new( '$app' )->initialize()->listen()";
95 }
96
97
98 # ----------------------------------------------
99 # Boot up the system
100
101 sub load_bootstrap_config {
102
103         if(OpenSRF::Utils::Config->current) {
104                 return;
105         }
106
107         if(!$bootstrap_config_file) {
108                 die "Please provide a bootstrap config file to OpenSRF::System!\n" . 
109                         "use OpenSRF::System qw(/path/to/bootstrap_config);";
110         }
111
112         OpenSRF::Utils::Config->load( config_file => $bootstrap_config_file );
113
114         OpenSRF::Utils::JSON->register_class_hint( name => "OpenSRF::Application", hint => "method", type => "hash" );
115
116         OpenSRF::Transport->message_envelope(  "OpenSRF::Transport::SlimJabber::MessageWrapper" );
117         OpenSRF::Transport::PeerHandle->set_peer_client(  "OpenSRF::Transport::SlimJabber::PeerConnection" );
118         OpenSRF::Transport::Listener->set_listener( "OpenSRF::Transport::SlimJabber::Inbound" );
119         OpenSRF::Application->server_class('client');
120 }
121
122 sub bootstrap {
123
124         my $self = __PACKAGE__->instance();
125         load_bootstrap_config();
126         OpenSRF::Utils::Logger::set_config();
127         my $bsconfig = OpenSRF::Utils::Config->current;
128
129         # Start a process group and make me the captain
130         exit if (OpenSRF::Utils::safe_fork());
131         chdir('/');
132         setsid(); 
133         close STDIN;
134         close STDOUT;
135         close STDERR;
136
137         $0 = "OpenSRF System";
138
139         # -----------------------------------------------
140         # Launch the settings sever if necessary...
141         my $are_settings_server = 0;
142         if( (my $cfile = $bsconfig->bootstrap->settings_config) ) {
143                 my $parser = OpenSRF::Utils::SettingsParser->new();
144
145                 # since we're (probably) the settings server, we can go ahead and load the real config file
146                 $parser->initialize( $cfile );
147                 $OpenSRF::Utils::SettingsClient::host_config = 
148                         $parser->get_server_config($bsconfig->env->hostname);
149
150                 my $client = OpenSRF::Utils::SettingsClient->new();
151                 my $apps = $client->config_value("activeapps", "appname");
152                 if(ref($apps) ne "ARRAY") { $apps = [$apps]; }
153
154                 if(!defined($apps) || @$apps == 0) {
155                         print "No apps to load, exiting...";
156                         return;
157                 }
158
159                 for my $app (@$apps) {
160                         # verify we are a settings server and launch 
161                         if( $app eq "opensrf.settings" and 
162                                 $client->config_value("apps","opensrf.settings", "language") =~ /perl/i ) {
163
164                                 $are_settings_server = 1;
165                                 $self->launch_settings();
166                                 sleep 1;
167                                 $self->launch_settings_listener();
168                                 last;
169                         } 
170                 }
171         }
172
173         # Launch everything else
174         OpenSRF::System->bootstrap_client(client_name => "system_client");
175         my $client = OpenSRF::Utils::SettingsClient->new();
176         my $apps = $client->config_value("activeapps", "appname" );
177         if(!ref($apps)) { $apps = [$apps]; }
178
179         if(!defined($apps) || @$apps == 0) {
180                 print "No apps to load, exiting...";
181                 return;
182         }
183
184         my $server_type = $client->config_value("server_type");
185         $server_type ||= "basic";
186
187         my $con = OpenSRF::Transport::PeerHandle->retrieve;
188         if($con) {
189                 $con->disconnect;
190         }
191
192
193
194         if(  $server_type eq "prefork" ) { 
195                 $server_type = "Net::Server::PreFork"; 
196         } else { 
197                 $server_type = "Net::Server::Single"; 
198         }
199
200         _log( " * Server type: $server_type", INTERNAL );
201
202         $server_type->use;
203
204         if( $@ ) {
205                 throw OpenSRF::EX::PANIC ("Cannot set $server_type: $@" );
206         }
207
208         push @OpenSRF::UnixServer::ISA, $server_type;
209
210         _log( " * System bootstrap" );
211         
212         # --- Boot the Unix servers
213         $self->launch_unix($apps);
214
215         sleep 2;
216
217         # --- Boot the listeners
218         $self->launch_listener($apps);
219
220     sleep 1;
221
222         _log( " * System is ready..." );
223
224 #       sleep 1;
225 #       my $ps = `ps ax | grep " Open" | grep -v grep | sort -r -k5`;
226 #       print "\n --- PS --- \n$ps --- PS ---\n\n";
227
228         while( 1 ) { sleep; }
229         exit;
230 }
231         
232         
233
234 # ----------------------------------------------
235 # Bootstraps a single client connection.  
236
237 # named params are 'config_file' and 'client_name'
238 #
239 sub bootstrap_client {
240         my $self = shift;
241
242         my $con = OpenSRF::Transport::PeerHandle->retrieve;
243
244         if($con and $con->tcp_connected) {
245                 return;
246         }
247
248         my %params = @_;
249
250         $bootstrap_config_file = 
251                 $params{config_file} || $bootstrap_config_file;
252
253         my $app = $params{client_name} || "client";
254
255
256         load_bootstrap_config();
257         OpenSRF::Utils::Logger::set_config();
258         OpenSRF::Transport::PeerHandle->construct( $app );
259
260 }
261
262 sub connected {
263         if (my $con = OpenSRF::Transport::PeerHandle->retrieve) {
264                 return 1 if ($con->tcp_connected);
265         }
266         return 0;
267 }
268
269 sub bootstrap_logger {
270         $0 = "Log Server";
271         OpenSRF::Utils::LogServer->serve();
272 }
273
274
275 # ----------------------------------------------
276 # Cycle through the known processes, reap the dead child 
277 # and put a new child in its place. (MMWWAHAHHAHAAAA!)
278
279 sub process_automation {
280
281         my $self = __PACKAGE__->instance();
282
283         foreach my $pid ( keys %{$self->pid_hash} ) {
284
285                 if( waitpid( $pid, WNOHANG ) == $pid ) {
286
287                         my $method = $self->pid_hash->{$pid};
288                         delete $self->pid_hash->{$pid};
289
290                         my $newpid =  OpenSRF::Utils::safe_fork();
291
292                         OpenSRF::Utils::Logger->debug( "Relaunching $method", ERROR );
293                         _log( "Relaunching => $method" );
294
295                         if( $newpid ) {
296                                 $self->pid_hash( $newpid, $method );
297                         }
298                         else { eval $method; exit; }
299                 }
300         }
301
302         $SIG{CHLD} = \&process_automation;
303 }
304
305
306
307 sub launch_settings {
308
309         #       XXX the $self like this and pid automation will not work with this setup....
310         my($self) = @_;
311         @OpenSRF::UnixServer::ISA = qw(OpenSRF Net::Server::PreFork);
312
313         my $pid = OpenSRF::Utils::safe_fork();
314         if( $pid ) {
315                 $self->pid_hash( $pid , "launch_settings()" );
316         }
317         else {
318                 my $apname = "opensrf.settings";
319                 #$0 = "OpenSRF App [$apname]";
320                 eval _unixserver( $apname );
321                 if($@) { die "$@\n"; }
322                 exit;
323         }
324
325         @OpenSRF::UnixServer::ISA = qw(OpenSRF);
326
327 }
328
329
330 sub launch_settings_listener {
331
332         my $self = shift;
333         my $app = "opensrf.settings";
334         my $pid = OpenSRF::Utils::safe_fork();
335         if ( $pid ) {
336                 $self->pid_hash( $pid , _listener( $app ) );
337         }
338         else {
339                 my $apname = $app;
340                 $0 = "OpenSRF listener [$apname]";
341                 eval _listener( $app );
342                 exit;
343         }
344
345 }
346
347 # ----------------------------------------------
348 # Launch the Unix Servers
349
350 sub launch_unix {
351         my( $self, $apps ) = @_;
352
353         my $client = OpenSRF::Utils::SettingsClient->new();
354
355         foreach my $app ( @$apps ) {
356
357                 next unless $app;
358                 my $lang = $client->config_value( "apps", $app, "language");
359                 next unless $lang =~ /perl/i;
360                 next if $app eq "opensrf.settings";
361
362                 _log( " * Starting UnixServer for $app..." );
363
364                 my $pid = OpenSRF::Utils::safe_fork();
365                 if( $pid ) {
366                         $self->pid_hash( $pid , _unixserver( $app ) );
367                 }
368                 else {
369                         my $apname = $app;
370                         $0 = "OpenSRF App ($apname)";
371                         eval _unixserver( $app );
372                         exit;
373                 }
374         }
375 }
376
377 # ----------------------------------------------
378 # Launch the inbound clients
379
380 sub launch_listener {
381
382         my( $self, $apps ) = @_;
383         my $client = OpenSRF::Utils::SettingsClient->new();
384
385         foreach my $app ( @$apps ) {
386
387                 next unless $app;
388                 my $lang = $client->config_value( "apps", $app, "language");
389                 next unless $lang =~ /perl/i;
390                 next if $app eq "opensrf.settings";
391
392                 _log( " * Starting Listener for $app..." );
393
394                 my $pid = OpenSRF::Utils::safe_fork();
395                 if ( $pid ) {
396                         $self->pid_hash( $pid , _listener( $app ) );
397                 }
398                 else {
399                         my $apname = $app;
400                         $0 = "OpenSRF listener [$apname]";
401                         eval _listener( $app );
402                         exit;
403                 }
404         }
405 }
406
407
408 # ----------------------------------------------
409
410 sub pid_hash {
411         my( $self, $pid, $method ) = @_;
412         $self->{'pid_hash'}->{$pid} = $method
413                 if( $pid and $method );
414         return $self->{'pid_hash'};
415 }
416
417 # ----------------------------------------------
418 # If requested, the System can shut down.
419
420 sub killall {
421
422         $SIG{CHLD} = 'IGNORE';
423         $SIG{INT} = 'IGNORE';
424         kill( 'INT', -$$ ); #kill all in process group
425         exit;
426
427 }
428
429 # ----------------------------------------------
430 # Handle $SIG{HUP}
431 sub hupall {
432
433         _log( "HUPping brood" );
434         $SIG{CHLD} = 'IGNORE';
435         $SIG{HUP} = 'IGNORE';
436         kill( 'HUP', -$$ );
437 #       $SIG{CHLD} = \&process_automation;
438         $SIG{HUP} = sub{ instance()->hupall(); };
439 }
440
441
442 # ----------------------------------------------
443 # Log to debug, and stdout
444
445 sub _log {
446         my $string = shift;
447         OpenSRF::Utils::Logger->debug( $string, INFO );
448         print $string . "\n";
449 }
450
451 # ----------------------------------------------
452
453
454 1;
455
456