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