moving to UNIVERSAL::require to suck in implementation modules
[Evergreen.git] / OpenSRF / 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 ":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         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         setpgrp( 0, 0 ); 
131         $0 = "OpenSRF System";
132
133         # -----------------------------------------------
134         # Launch the settings sever if necessary...
135         my $are_settings_server = 0;
136         if( (my $cfile = $bsconfig->bootstrap->settings_config) ) {
137                 my $parser = OpenSRF::Utils::SettingsParser->new();
138
139                 # since we're (probably) the settings server, we can go ahead and load the real config file
140                 $parser->initialize( $cfile );
141                 $OpenSRF::Utils::SettingsClient::host_config = 
142                         $parser->get_server_config($bsconfig->env->hostname);
143
144                 my $client = OpenSRF::Utils::SettingsClient->new();
145                 my $apps = $client->config_value("activeapps", "appname");
146                 if(ref($apps) ne "ARRAY") { $apps = [$apps]; }
147
148                 if(!defined($apps) || @$apps == 0) {
149                         print "No apps to load, exiting...";
150                         return;
151                 }
152
153                 for my $app (@$apps) {
154                         # verify we are a settings server and launch 
155                         if( $app eq "opensrf.settings" and 
156                                 $client->config_value("apps","opensrf.settings", "language") =~ /perl/i ) {
157
158                                 $are_settings_server = 1;
159                                 $self->launch_settings();
160                                 sleep 1;
161                                 $self->launch_settings_listener();
162                                 last;
163                         } 
164                 }
165         }
166
167         # Launch everything else
168         OpenSRF::System->bootstrap_client(client_name => "system_client");
169         my $client = OpenSRF::Utils::SettingsClient->new();
170         my $apps = $client->config_value("activeapps", "appname" );
171         if(!ref($apps)) { $apps = [$apps]; }
172
173         if(!defined($apps) || @$apps == 0) {
174                 print "No apps to load, exiting...";
175                 return;
176         }
177
178         my $server_type = $client->config_value("server_type");
179         $server_type ||= "basic";
180
181         my $con = OpenSRF::Transport::PeerHandle->retrieve;
182         if($con) {
183                 $con->disconnect;
184         }
185
186
187
188         if(  $server_type eq "prefork" ) { 
189                 $server_type = "Net::Server::PreFork"; 
190         } else { 
191                 $server_type = "Net::Server::Single"; 
192         }
193
194         _log( " * Server type: $server_type", INTERNAL );
195
196         $server_type->use;
197
198         if( $@ ) {
199                 throw OpenSRF::EX::PANIC ("Cannot set $server_type: $@" );
200         }
201
202         push @OpenSRF::UnixServer::ISA, $server_type;
203
204         _log( " * System boostrap" );
205         
206         # --- Boot the Unix servers
207         $self->launch_unix($apps);
208
209
210         _sleep();
211         sleep 2;
212
213         # --- Boot the listeners
214         $self->launch_listener($apps);
215
216         _sleep();
217
218         _log( " * System is ready..." );
219
220         sleep 1;
221         my $ps = `ps ax | grep " Open" | grep -v grep | sort -r -k5`;
222
223         print "\n --- PS --- \n$ps --- PS ---\n\n";
224
225         while( 1 ) { sleep; }
226         exit;
227 }
228         
229         
230
231 # ----------------------------------------------
232 # Bootstraps a single client connection.  
233
234 # named params are 'config_file' and 'client_name'
235 #
236 sub bootstrap_client {
237         my $self = shift;
238
239         my $con = OpenSRF::Transport::PeerHandle->retrieve;
240         if($con and $con->tcp_connected) {
241                 warn "PeerHandle is already connected in 'bootstrap_client'... returning\n";
242                 _log( "PeerHandle is already connected in 'bootstrap_client'... returning");
243                 return;
244         }
245
246         my %params = @_;
247
248         $bootstrap_config_file = 
249                 $params{config_file} || $bootstrap_config_file;
250
251         my $app = $params{client_name} || "client";
252
253
254         load_bootstrap_config();
255         OpenSRF::Utils::Logger::set_config();
256         OpenSRF::Transport::PeerHandle->construct( $app );
257
258 }
259
260 sub bootstrap_logger {
261         $0 = "Log Server";
262         OpenSRF::Utils::LogServer->serve();
263 }
264
265
266 # ----------------------------------------------
267 # Cycle through the known processes, reap the dead child 
268 # and put a new child in its place. (MMWWAHAHHAHAAAA!)
269
270 sub process_automation {
271
272         my $self = __PACKAGE__->instance();
273
274         foreach my $pid ( keys %{$self->pid_hash} ) {
275
276                 if( waitpid( $pid, WNOHANG ) == $pid ) {
277
278                         my $method = $self->pid_hash->{$pid};
279                         delete $self->pid_hash->{$pid};
280
281                         my $newpid =  OpenSRF::Utils::safe_fork();
282
283                         OpenSRF::Utils::Logger->debug( "Relaunching $method", ERROR );
284                         _log( "Relaunching => $method" );
285
286                         if( $newpid ) {
287                                 $self->pid_hash( $newpid, $method );
288                         }
289                         else { eval $method; exit; }
290                 }
291         }
292
293         $SIG{CHLD} = \&process_automation;
294 }
295
296
297
298 sub launch_settings {
299
300         #       XXX the $self like this and pid automation will not work with this setup....
301         my($self) = @_;
302         @OpenSRF::UnixServer::ISA = qw(OpenSRF Net::Server::PreFork);
303
304         my $pid = OpenSRF::Utils::safe_fork();
305         if( $pid ) {
306                 $self->pid_hash( $pid , "launch_settings()" );
307         }
308         else {
309                 my $apname = "opensrf.settings";
310                 #$0 = "OpenSRF App [$apname]";
311                 eval _unixserver( $apname );
312                 if($@) { die "$@\n"; }
313                 exit;
314         }
315
316         @OpenSRF::UnixServer::ISA = qw(OpenSRF);
317
318 }
319
320
321 sub launch_settings_listener {
322
323         my $self = shift;
324         my $app = "opensrf.settings";
325         my $pid = OpenSRF::Utils::safe_fork();
326         if ( $pid ) {
327                 $self->pid_hash( $pid , _listener( $app ) );
328         }
329         else {
330                 my $apname = $app;
331                 $0 = "OpenSRF listener [$apname]";
332                 eval _listener( $app );
333                 exit;
334         }
335
336 }
337
338 # ----------------------------------------------
339 # Launch the Unix Servers
340
341 sub launch_unix {
342         my( $self, $apps ) = @_;
343
344         my $client = OpenSRF::Utils::SettingsClient->new();
345
346         foreach my $app ( @$apps ) {
347
348                 next unless $app;
349                 my $lang = $client->config_value( "apps", $app, "language");
350                 next unless $lang =~ /perl/i;
351                 next if $app eq "opensrf.settings";
352
353                 _log( " * Starting UnixServer for $app..." );
354
355                 my $pid = OpenSRF::Utils::safe_fork();
356                 if( $pid ) {
357                         $self->pid_hash( $pid , _unixserver( $app ) );
358                 }
359                 else {
360                         my $apname = $app;
361                         $0 = "OpenSRF App ($apname)";
362                         eval _unixserver( $app );
363                         exit;
364                 }
365         }
366 }
367
368 # ----------------------------------------------
369 # Launch the inbound clients
370
371 sub launch_listener {
372
373         my( $self, $apps ) = @_;
374         my $client = OpenSRF::Utils::SettingsClient->new();
375
376         foreach my $app ( @$apps ) {
377
378                 next unless $app;
379                 my $lang = $client->config_value( "apps", $app, "language");
380                 next unless $lang =~ /perl/i;
381                 next if $app eq "opensrf.settings";
382
383                 _log( " * Starting Listener for $app..." );
384
385                 my $pid = OpenSRF::Utils::safe_fork();
386                 if ( $pid ) {
387                         $self->pid_hash( $pid , _listener( $app ) );
388                 }
389                 else {
390                         my $apname = $app;
391                         $0 = "OpenSRF listener [$apname]";
392                         eval _listener( $app );
393                         exit;
394                 }
395         }
396 }
397
398 # ----------------------------------------------
399
400 =head comment
401 sub launch_shell {
402
403         my $self = shift;
404
405         my $pid = OpenSRF::Utils::safe_fork();
406
407         if( $pid ) { $self->pid_hash( $pid , _shell() ); }
408         else {
409                 $0 = "System Shell";
410                 for( my $x = 0; $x != 10; $x++ ) {
411                         eval _shell();
412                         if( ! $@ ) { last; }
413                 }
414                 exit;
415         }
416 }
417 =cut
418
419
420 # ----------------------------------------------
421
422 sub pid_hash {
423         my( $self, $pid, $method ) = @_;
424         $self->{'pid_hash'}->{$pid} = $method
425                 if( $pid and $method );
426         return $self->{'pid_hash'};
427 }
428
429 # ----------------------------------------------
430 # If requested, the System can shut down.
431
432 sub killall {
433
434         $SIG{CHLD} = 'IGNORE';
435         $SIG{INT} = 'IGNORE';
436         kill( 'INT', -$$ ); #kill all in process group
437         exit;
438
439 }
440
441 # ----------------------------------------------
442 # Handle $SIG{HUP}
443 sub hupall {
444
445         _log( "HUPping brood" );
446         $SIG{CHLD} = 'IGNORE';
447         $SIG{HUP} = 'IGNORE';
448         set_config(); # reload config
449         kill( 'HUP', -$$ );
450 #       $SIG{CHLD} = \&process_automation;
451         $SIG{HUP} = sub{ instance()->hupall(); };
452 }
453
454
455 # ----------------------------------------------
456 # Log to debug, and stdout
457
458 sub _log {
459         my $string = shift;
460         OpenSRF::Utils::Logger->debug( $string, INFO );
461         #print $string . "\n";
462 }
463
464 # ----------------------------------------------
465
466 sub _sleep {
467         select( undef, undef, undef, 0.3 );
468 }
469
470 1;
471
472