3 # This file is part of Stem.
4 # Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
6 # Stem is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # Stem is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with Stem; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 # For a license to use the Stem under conditions other than those
21 # described here, to purchase support for this software, or to purchase a
22 # commercial warranty contract, please contact Stem Systems at:
24 # Stem Systems, Inc. 781-643-7504
25 # 79 Everett St. info@stemsystems.com
29 package Stem::Portal ;
39 use Stem::Trace 'log' => 'stem_status', 'sub' => 'TraceStatus' ;
40 use Stem::Trace 'log' => 'stem_error' , 'sub' => 'TraceError' ;
48 Stem::Route::register_class( __PACKAGE__, 'port' ) ;
50 my $attr_spec_portal = [
56 This is a unique name used to register this instance of a Portal.
63 This determines if we are a server or a client.
64 If it is true, we are a server. Otherwise, we are a client.
72 Mark this as a synchronously connecting Portal. Default is syncronous
73 connections, set to 0 for asynchronous. In both cases the same method
82 This determines which port we bind to if we are a server.
83 This determines which port we connect to if we are a client.
84 The default value is 10,000.
89 'default' => 'localhost',
92 This determines which host we attach to when we are a client.
93 The default value is localhost.
98 'name' => 'spawn_conf_file',
100 This tells the portal to fork another Stem Hub and pass this value as
101 the configuration file argument to run_stem. The new Hub will be
102 connected to this Portal and be private to it.
107 'name' => 'spawn_conf_args',
110 This tells the portal to fork another Stem Hub and pass (via a
111 message) this data to the Stem::Conf as a configuration
112 The new Hub will be connected to this Portal and be private
120 'name' => 'run_stem_args',
122 These are the command line arguments to run_stem for the spawned Hub.
128 This is the sub-class that is used to convert messages to/from a byte
129 stream for this portal
137 This flag will disable this Portal. It will not construct an object and
138 no errors will be returned.
146 my( $class ) = shift ;
148 my $self = Stem::Class::parse_args( $attr_spec_portal, @_ ) ;
149 return $self unless ref $self ;
151 return if $self->{ 'disable' } ;
153 my $name = $Stem::Vars::Hub_name ;
155 if ( $Env{'portal_use_stdio'} ) {
157 return $self->new_child_portal() ;
160 if ( $self->{'spawn_conf_file'} || $self->{'spawn_conf_args'} ) {
162 return $self->new_parent_portal() ;
165 if ( $self->{'server'} ) {
167 $self->{'type'} = 'listener' ;
168 $self->{'server_name'} = $name ;
172 $self->{'type'} = 'client' ;
173 $self->{'name'} = $name ;
176 #print "REG new [$self->{'reg_name'}]\n" ;
178 my $sock_obj = Stem::Socket->new(
180 'host' => $self->{'host'},
181 'port' => $self->{'port'},
182 'server' => $self->{'server'},
183 'sync' => $self->{'sync'},
186 ref $sock_obj or return <<ERR ;
187 Stem::Portal '$self->{'reg_name'}' tried to connect/listen to $self->{'host'}:$self->{'port'}
191 $self->{'sock_obj'} = $sock_obj ;
198 my( $self, $connected_sock ) = @_ ;
202 TraceStatus "Portal Connected" ;
204 $self->{'read_fh'} = $connected_sock ;
205 $self->{'write_fh'} = $connected_sock ;
207 my $type = $self->{'type'} ;
209 if ( $type eq 'listener' ) {
211 # fork off a new portal by making a clone of the listener portal
213 $portal = bless { %$self } ;
214 $portal->{'type'} = 'accepted' ;
216 my $name = $portal->{'server_name'} ;
218 $portal->{'name'} = $name ;
220 delete( $portal->{'sock_obj'} ) ;
224 #print "Portal Connected\n" ;
226 # a client portal is just itself
230 #print "REG [$self->{'reg_name'}]\n" ;
232 if ( my $name = $self->{'reg_name'} ) {
234 $portal->register( $name ) ;
237 unless ( $default_portal ) {
239 $portal->register( 'DEFAULT' ) ;
240 $default_portal = $portal ;
244 my $err = $portal->_activate() ;
251 sub new_parent_portal {
255 $run_stem_path ||= do {
258 require Stem::InstallConfig ;
260 $Stem::InstallConfig{ run_stem_path } ;
263 my $conf_file = $self->{'spawn_conf_file'} || 'portal_child' ;
265 my @run_stem_args = @{$self->{'run_stem_args'} || []} ;
267 my $proc = Stem::Proc->new(
269 path => $run_stem_path,
271 'portal_use_stdio=1',
281 $self->{'proc'} = $proc ;
283 TraceStatus "Portal Paren" ;
285 $self->{'read_fh'} = $proc->read_fh() ;
286 $self->{'write_fh'} = $proc->write_fh() ;
288 #print "REG [$self->{'reg_name'}]\n" ;
290 my $err = $self->_activate() ;
295 # $self->{'spawn_conf_args'} ) {
296 #### when can we send the conf data?
302 sub new_child_portal {
306 $self->{'type'} = 'child' ;
309 TraceStatus "Portal Child" ;
311 $self->{'read_fh'} = \*STDIN ;
312 $self->{'write_fh'} = \*STDOUT ;
314 #print "REG [$self->{'reg_name'}]\n" ;
316 unless ( $default_portal ) {
318 $self->register( 'DEFAULT' ) ;
319 $default_portal = $self ;
322 if ( my $portal_name = $Env{'portal_name'} ) {
324 $self->register( $portal_name ) ;
327 my $err = $self->_activate() ;
337 TraceStatus "Active portal" ;
339 my $aio = Stem::AsyncIO->new(
342 'read_fh' => $self->{'read_fh'},
343 'write_fh' => $self->{'write_fh'},
344 'read_method' => 'portal_data',
345 'closed_method' => 'portal_closed',
348 return $aio unless ref $aio ;
350 $self->{'aio'} = $aio ;
352 my $packet = Stem::Packet->new( 'codec' => $self->{'codec'} ) ;
353 return $packet unless ref $packet ;
354 $self->{'packet'} = $packet ;
356 my $msg = Stem::Msg->new( 'from' => "${Stem::Vars::Hub_name}:port",
357 'type' => 'register',
360 return $msg unless ref $msg ;
362 $self->write_msg( $msg ) ;
367 # this is not a method, but a class sub
371 my ( $msg, $to_hub ) = @_ ;
373 $to_hub ||= 'DEFAULT' ;
375 my $self = $name_to_portal{ $to_hub } ;
377 return "unknown Portal '$to_hub'" unless $self ;
379 $msg->from_hub( $self->{'name'} ) unless $msg->from_hub() ;
380 # $msg->from_hub( $self->{'name'} ) ;
382 unless( $self->{'remote_hub'} ) {
384 push( @{$self->{'queued_msgs'}}, $msg ) ;
389 $self->write_msg( $msg ) ;
394 # this is a regular method called by the above sub.
398 my( $self, $msg ) = @_ ;
400 my $packet_text = $self->{'packet'}->to_packet( $msg ) ;
402 #print "PACK SEND [$packet_text]\n" ;
404 $self->{'aio'}->write( $packet_text ) ;
409 my( $self, $packet_text ) = @_ ;
411 my $packet = $self->{'packet'} ;
413 # parse out all messages that may be in the input data
415 while( my $msg = $packet->to_data( $packet_text ) ) {
417 $self->_portal_msg_in( $msg ) ;
419 # no more incoming data in this callback
427 my( $self, $msg ) = @_ ;
429 if ( $msg->type() eq 'register' ) {
431 # register the other hub and mark this hub as connecting to it.
433 $self->{'remote_hub'} = $msg->from_hub() ;
434 warn( caller(), $msg->dump() ) and die
435 'Msg Has No Remote Hub' unless $self->{'remote_hub'} ;
436 $self->register( $self->{'remote_hub'} ) ;
438 # handle messages that got queued while the portal was down
440 while( my $queued_msg = shift @{$self->{'queued_msgs'}} ) {
442 #print $queued_msg->dump( 'QUEUED' ) ;
443 $self->write_msg( $queued_msg ) ;
449 $msg->in_portal( $self->{'remote_hub'} ) ;
458 #TraceStatus "Portal closed" ;
460 Stem::Route::unregister_cell( $self ) ;
461 my $names = $self->unregister() ;
463 if ( $self->{'type'} eq 'accepted' ) {
465 # TraceStatus "client hub '$self->{'name'}' closed" ;
471 my @hub_names = ref $names ? @{$names} : 'UNKNOWN' ;
473 Stem::Event::end_loop() ;
475 die "server hub [@hub_names] died" ;
482 TraceStatus "SHUT DOWN port : ". Dumper($self);
484 $self->{'aio'}->shut_down() ;
485 delete @{$self}{qw( object aio )} ;
488 # this is for messages directly to this portal. messages are sent out
489 # the portal via the send class method
495 my( $self, $msg ) = @_ ;
497 TraceStatus "portal msg in" ;
502 my( $self, $name ) = @_ ;
504 #print "NAME [$name]: ", caller(), "\n" ;
506 TraceStatus "portal arg: [$self] [$name]\n\t",
507 map( "<$_>", caller() ), "\n" ;
509 $name_to_portal{ $name } = $self ;
510 push( @{$portal_to_names{ $self }}, $name ) ;
517 # convert a name to its object ;
519 my $portal = ref $name ? $name : $name_to_portal{ $name } ;
523 delete $name_to_portal{ $portal } ;
525 my $names = delete $portal_to_names{ $portal } ;
536 my ($self, $msg ) = @_ ;
538 #print $msg->dump( 'PORT' ) ;
540 my $status = <<STATUS ;
542 Portal Status for Hub '$Stem::Vars::Hub_name'
546 foreach my $port_name ( sort keys %name_to_portal ) {
548 my $portal = $name_to_portal{ $port_name } ;
550 $status .= <<STATUS ;
552 Hub: $portal->{'remote_hub'}
553 Type: $portal->{'type'}