--- loncom/Attic/lonManage 2003/09/16 09:49:54 1.15 +++ loncom/Attic/lonManage 2003/11/04 11:23:37 1.23 @@ -3,9 +3,9 @@ # # lonManage supports remote management of nodes in a LonCAPA cluster. # -# $Id: lonManage,v 1.15 2003/09/16 09:49:54 foxr Exp $ +# $Id: lonManage,v 1.23 2003/11/04 11:23:37 foxr Exp $ # -# $Id: lonManage,v 1.15 2003/09/16 09:49:54 foxr Exp $ +# $Id: lonManage,v 1.23 2003/11/04 11:23:37 foxr Exp $ # # Copyright Michigan State University Board of Trustees # @@ -33,76 +33,24 @@ # it is a command line tool. The following command line syntax (usage) # is supported: # -# lonManage -push newfile host +# lonManage -push newfile [host] # Push to the lonTabs directory. Note that # must be one of: # host (hosts.tab) # domain (domain.tab) # -# lonManage -reinit lonc host +# lonManage -reinit lonc [host] # Sends a HUP signal to the remote systems's lond. # -# lonmanage -reinit lond host +# lonmanage -reinit lond [host] # Requests the remote system's lond perform the same action as if # it had received a HUP signal. # # In the above syntax, the host above is the hosts.tab name of a host, -# not the IP address of the host. -# -# $Log: lonManage,v $ -# Revision 1.15 2003/09/16 09:49:54 foxr -# Adjust the usage message to reflect what actually will happen on -# --reinit={lond|lonc} -# -# Revision 1.14 2003/09/08 09:45:20 foxr -# Remove BUGBUG about comment about authentication as we'll be doing -# host based authentication initially (no need for lonManage to do anything), -# and certificate based later (need at that time). -# -# Revision 1.13 2003/08/19 10:26:24 foxr -# Initial working version... tested against an unmodified lond this -# produces an unknown_cmd response which is about what I'd expect. -# -# Revision 1.12 2003/08/18 11:08:07 foxr -# Debug request building in Transact. -# -# Revision 1.11 2003/08/18 10:45:32 foxr -# Felt strongly enough about hoisting ReadConfiguration into a separate sub -# that I did it now before I forgot. -# -# Revision 1.10 2003/08/18 10:43:31 foxr -# Code/test ValidHost. The hosts.tab and the perl variables are read in as -# global hashes as a side effect. May later want to clean this up by making -# a separate getconfig function and hoisting the config reads into that. -# -# Revision 1.9 2003/08/18 10:25:46 foxr -# Write ReinitProcess function in terms of ValidHost and Transact. -# -# Revision 1.8 2003/08/18 10:18:21 foxr -# Completed PushFile function in terms of -# - ValidHost - Determines if target host is valid. -# - Transact - Performs one of the valid transactions with the -# appropriate lonc<-->lond client/server pairs. -# -# Revision 1.7 2003/08/18 09:56:01 foxr -# 1. Require to be run as root. -# 2. Catch case where no operation switch is supplied and put out usage. -# 3. skeleton/comments for PushFile function. -# -# Revision 1.6 2003/08/12 11:02:59 foxr -# Implement command switch dispatching. -# -# Revision 1.5 2003/08/12 10:55:42 foxr -# Complete command line parsing (tested) -# -# Revision 1.4 2003/08/12 10:40:44 foxr -# Get switch parsing right. -# -# Revision 1.3 2003/08/12 10:22:35 foxr -# Put in parameter parsing infrastructure -# -# Revision 1.2 2003/08/12 09:58:49 foxr -# Add usage and skeleton documentation. +# not the IP address of the host +# +# If [host] is not supplied, every host in the client's hosts.tab +# table is iterated through and procesed.. # # @@ -110,48 +58,151 @@ # Modules required: +use lib "."; + use strict; # Because it's good practice. use English; # Cause I like meaningful names. use Getopt::Long; -use LONCAPA::Configuration; # To handle configuration I/O. -use IO::Socket::UNIX; # To communicate with lonc. +use LondConnection; +use IO::Poll qw(POLLRDNORM POLLWRNORM POLLIN POLLHUP POLLOUT); # File scoped variables: my %perlvar; # Perl variable defs from apache config. my %hostshash; # Host table as a host indexed hash. +my $MyHost=""; # Host name to use as me. +my $ForeignHostTab=""; # Name of foreign hosts table. + +my $DefaultServerPort = 5663; # Default server port if standalone. +my $ServerPort; # Port used to connect to lond. + +my $TransitionTimeout = 5; # Poll timeout in seconds. + + +LondConnection::SetDebug(10); + + # # prints out utility's command usage info. # sub Usage { print "Usage:"; print < newfile host + lonManage [--myname=host --hosts=table] --push= newfile [host] Push to the lonTabs directory. Note that must be one of: host (hosts.tab) domain (domain.tab) - lonManage --reinit=lonc host + lonManage [--myname=host --hosts=table] --reinit=lonc [host] Causes lonc in the remote system to reread hosts.tab and adjust the set of clients that are being maintained to match the new file. - lonManage --reinit=lond host + lonManage [--myname=host --hosts=table] --reinit=lond [host] Causes lond in the remote system to reread the hosts.tab file and adjust the set of servers to match changes in that file. In the above syntax, the host above is the hosts.tab name of a host, not the IP address of the host. + + If [host] is omitted, all hosts in the hosts.tab file are iterated + over. + + For all of the above syntaxes if --myname=host and --hosts=table are + supplied (both must be present), the utility runs in standalone mode + presenting itself to the world as 'host' and using the hosts.tab file + specified in the --hosts switch. USAGE } + +# +# Make a direct connection to the lond in 'host'. The port is +# gotten from the global variable: ServerPort. +# Returns: +# The connection or undef if one could not be formed. +# +sub MakeLondConnection { + my $host = shift; + + my $Connection = LondConnection->new($host, $ServerPort); + return return $Connection; +} +# +# This function runs through the section of the connection +# state machine that has to do with negotiating the startup +# sequence with lond. The general strategy is to loop +# until the connection state becomes idle or disconnected. +# Disconnected indicates an error or rejection of the +# connection at some point in the negotiation. +# idle indicates a connection ready for a request. +# The main loop consults the object to determine if it +# wants to be writeable or readable, waits for that +# condition on the socket (with timeout) and then issues +# the appropriate LondConnection call. Note that +# LondConnection is capable of doing everything necessary +# to get to the initial idle state. +# +# +# Parameters: +# connection - A connection that has been created with +# the remote lond. This connection should +# be in the Connected state ready to send +# the init sequence. +# +sub NegotiateStartup { + my $connection = shift; + my $returnstatus = "ok"; # Optimistic!!. + + my $state = $connection->GetState; + if($state ne "Connected") { + print "Error: Initial lond connection state: $state should be Connected\n"; + return "error"; + } + my $Socket = $connection->GetSocket; # This is a IO:Socket::INET object. + + # Ready now to enter the main loop: + # + my $error = 0; + while (($connection->GetState ne "Idle") && (!$error)) { + # + # Wait for the socket to get into the appropriate state: + # + my $wantread = $connection->WantReadable; + my $poll = new IO::Poll; + $poll->mask($Socket => $wantread ? POLLIN : POLLOUT); + $poll->poll($TransitionTimeout); + my $done = $poll->handles(); + if(scalar($done) == 0) { # Timeout!!! + print "Error: Timeout in state : $state negotiating connection\n"; + $returnstatus = "error"; + $error = 1; + } else { + my $status; + $status = $wantread ? $connection->Readable : $connection->Writable; + if ($status != 0) { + print "Error: I/O failed in state : $state negotiating connection\n"; + $returnstatus = "error"; + $error = 1; + } + } + } + + + return $returnstatus; +} +sub PerformTransaction { + my $connection = shift; + my $command = shift; + + return "ok"; +} # -# Lifted from lonnet.pm - and we need to figure out a way to get it back in. -# Performas a transaction with lond via the lonc proxy server. +# Performs a transaction direct to a remote lond. # Parameter: # cmd - The text of the request. # host - The host to which the request ultimately goes. @@ -160,17 +211,34 @@ USAGE # lond/lonc etc. # sub subreply { - my ($cmd,$server)=@_; - my $peerfile="$perlvar{'lonSockDir'}/$server"; - my $client=IO::Socket::UNIX->new(Peer =>"$peerfile", - Type => SOCK_STREAM, - Timeout => 10) - or return "con_lost"; - print $client "$cmd\n"; - my $answer=<$client>; - if (!$answer) { $answer="con_lost"; } - chomp($answer); - return $answer; + my $cmd = shift; + my $host = shift; + + + my $connection = MakeLondConnection($host); + if ($connection eq undef) { + return "Connect Failed"; + } + my $reply = NegotiateStartup($connection); + if($reply ne "ok") { + return "connection negotiation failed"; + } + print "Connection negotiated\n"; + my $reply = PerformTransaction($connection, $cmd); + return $reply; + + + # my ($cmd,$server)=@_; + # my $peerfile="$perlvar{'lonSockDir'}/$server"; + # my $client=IO::Socket::UNIX->new(Peer =>"$peerfile", + # Type => SOCK_STREAM, + # Timeout => 10) + # or return "con_lost"; + # print $client "$cmd\n"; + # my $answer=<$client>; + # if (!$answer) { $answer="con_lost"; } + # chomp($answer); + # return $answer; } # >>> BUGBUG <<< # @@ -196,12 +264,24 @@ sub subreply { # returns an empty list if the parse fails. # + sub ParseArgs { my $pushing = ''; my $reinitting = ''; if(!GetOptions('push=s' => \$pushing, - 'reinit=s' => \$reinitting)) { + 'reinit=s' => \$reinitting, + 'myname=s' => \$MyHost, + 'hosts=s' => \$ForeignHostTab)) { + return (); + } + # The --myname and --hosts switch must have values and + # most both appear if either appears: + + if(($MyHost ne "") && ($ForeignHostTab eq "")) { + return (); + } + if(($ForeignHostTab ne "") && ($MyHost eq "")) { return (); } @@ -214,9 +294,9 @@ sub ParseArgs { if($pushing ne '') { - # --push takes in addition a table, and a host: + # --push takes in addition a table, and an optional host: # - if($paramcount != 2) { + if(($paramcount != 2) && ($paramcount != 1)) { return (); # Invalid parameter count. } if($command ne '') { @@ -230,9 +310,9 @@ sub ParseArgs { if ($reinitting ne '') { - # --reinit takes in addition just a host name + # --reinit takes in addition just an optional host name - if($paramcount != 1) { + if($paramcount > 1) { return (); } if($command ne '') { @@ -254,15 +334,30 @@ sub ParseArgs { return @result; } # -# Read the loncapa configuration stuff. +# Read the loncapa configuration stuff. If ForeignHostTab is empty, +# assume we are part of a loncapa cluster and read the hosts.tab +# file from the config directory. Otherwise, ForeignHossTab +# is the name of an alternate configuration file to read in +# standalone mode. # sub ReadConfig { - my $perlvarref = LONCAPA::Configuration::read_conf('loncapa.conf'); - %perlvar = %{$perlvarref}; - my $hoststab = LONCAPA::Configuration::read_hosts( - "$perlvar{'lonTabDir'}/hosts.tab"); - %hostshash = %{$hoststab}; + if($ForeignHostTab eq "") { + my $perlvarref = LondConnection::read_conf('loncapa.conf'); + %perlvar = %{$perlvarref}; + my $hoststab = LondConnection::read_hosts( + "$perlvar{'lonTabDir'}/hosts.tab"); + %hostshash = %{$hoststab}; + $MyHost = $perlvar{lonHostID}; # Set hostname from vars. + $ServerPort = $perlvar{londPort}; + } else { + + LondConnection::ReadForeignConfig($MyHost, $ForeignHostTab); + my $hoststab = LondConnection::read_hosts($ForeignHostTab); # we need to know too. + %hostshash = %{$hoststab}; + $ServerPort = $DefaultServerPort; + } + } # # Determine if the target host is valid. @@ -278,7 +373,6 @@ sub ReadConfig { sub ValidHost { my $host = shift; - ReadConfig; return defined $hostshash{$host}; @@ -374,6 +468,7 @@ sub PushFile { if( ($tablename eq "host") || ($tablename eq "domain")) { + print("Pushing $tablename to $host\n"); Transact($host, "pushfile:$tablename",\@table); } else { die "EINVAL - Invalid parameter. tablename: $tablename must be host or domain"; @@ -407,6 +502,7 @@ sub ReinitProcess { if(($process eq "lonc") || ($process eq "lond")) { + print("Reinitializing $process in $host\n"); Transact($host, "reinit:$process"); } else { die "EINVAL -Invalid parameter. Process $process must be lonc or lond"; @@ -414,6 +510,8 @@ sub ReinitProcess { } #--------------------------- Entry point: -------------------------- + + # Parse the parameters # If command parsing failed, then print usage: @@ -431,6 +529,11 @@ if ($EUID != 0) { die "ENOPRIV - No privilege for requested operation" } +# +# Read the configuration file. +# + +ReadConfig; # Read the configuration info (incl.hosts). # Based on the operation requested invoke the appropriate function: @@ -440,13 +543,25 @@ if($operation eq "push") { # push table my $tablename = shift @params; my $tablefile = shift @params; my $host = shift @params; - PushFile($tablename, $tablefile, $host); + if($host) { + PushFile($tablename, $tablefile, $host); + } else { # Push to whole cluster. + foreach my $host (keys %hostshash) { + PushFile($tablename, $tablefile, $host); + } + } } elsif($operation eq "reinit") { # reinit processname host. my $process = shift @params; my $host = shift @params; - ReinitProcess($process, $host); -} + if ($host) { + ReinitProcess($process, $host); + } else { # Reinit whole cluster. + foreach my $host (keys %hostshash) { + ReinitProcess($process,$host); + } + } +} else { Usage; }