--- loncom/Attic/lonManage 2003/10/21 09:44:04 1.16 +++ loncom/Attic/lonManage 2003/11/04 11:36:04 1.24 @@ -3,9 +3,9 @@ # # lonManage supports remote management of nodes in a LonCAPA cluster. # -# $Id: lonManage,v 1.16 2003/10/21 09:44:04 foxr Exp $ +# $Id: lonManage,v 1.24 2003/11/04 11:36:04 foxr Exp $ # -# $Id: lonManage,v 1.16 2003/10/21 09:44:04 foxr Exp $ +# $Id: lonManage,v 1.24 2003/11/04 11:36:04 foxr Exp $ # # Copyright Michigan State University Board of Trustees # @@ -52,100 +52,56 @@ # If [host] is not supplied, every host in the client's hosts.tab # table is iterated through and procesed.. # -# $Log: lonManage,v $ -# Revision 1.16 2003/10/21 09:44:04 foxr -# Add ability to manage entire cluster in hosts table. -# -# 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. -# # # 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. @@ -155,13 +111,148 @@ sub Usage { 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; +} +# +# Perform a transaction with the remote lond. +# Paramters: +# connection - the connection object that represents +# a LondConnection to the remote lond. +# command - The request to send to the remote system. +# Returns: +# The 'reaction' of the lond to this command. +# However if the connection to lond is lost during the transaction +# or some other error occurs, the text "error:con_lost" is returned. +# +sub PerformTransaction { + my $connection = shift; + my $command = shift; + my $retval; # What we'll returnl. + + # Set up the connection to do the transaction then + # do the I/O until idle or error. + # + $connection->InitiateTransaction($command); + my $error = 0; + my $Socket = $connection->GetSocket; + my $state; + + 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"; + $retval = "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"; + $retval = "error"; + $error = 1; + } + } + } + # + # Fetch the reply from the transaction + # + if(! $error) { + $retval = $connection->GetReply; + } + + return $retval; +} # -# 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. @@ -170,17 +261,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 <<< # @@ -206,12 +314,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 (); } @@ -264,15 +384,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. @@ -425,7 +560,6 @@ sub ReinitProcess { } #--------------------------- Entry point: -------------------------- -ReadConfig; # Read the configuration info (incl.hosts). # Parse the parameters @@ -445,6 +579,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: