--- loncom/Attic/lonManage 2003/11/04 11:23:37 1.23 +++ loncom/Attic/lonManage 2003/12/30 11:44:13 1.29 @@ -3,9 +3,9 @@ # # lonManage supports remote management of nodes in a LonCAPA cluster. # -# $Id: lonManage,v 1.23 2003/11/04 11:23:37 foxr Exp $ +# $Id: lonManage,v 1.29 2003/12/30 11:44:13 foxr Exp $ # -# $Id: lonManage,v 1.23 2003/11/04 11:23:37 foxr Exp $ +# $Id: lonManage,v 1.29 2003/12/30 11:44:13 foxr Exp $ # # Copyright Michigan State University Board of Trustees # @@ -36,7 +36,7 @@ # lonManage -push newfile [host] # Push to the lonTabs directory. Note that # must be one of: -# host (hosts.tab) +# hosts (hosts.tab) # domain (domain.tab) # # lonManage -reinit lonc [host] @@ -65,6 +65,7 @@ use English; # Cause I like meaningful use Getopt::Long; use LondConnection; use IO::Poll qw(POLLRDNORM POLLWRNORM POLLIN POLLHUP POLLOUT); +use IO::File; # File scoped variables: @@ -80,7 +81,7 @@ my $ServerPort; # Port used to connect my $TransitionTimeout = 5; # Poll timeout in seconds. -LondConnection::SetDebug(10); +# LondConnection::SetDebug(10); # @@ -92,7 +93,7 @@ sub Usage { lonManage [--myname=host --hosts=table] --push= newfile [host] Push to the lonTabs directory. Note that must be one of: - host (hosts.tab) + hosts (hosts.tab) domain (domain.tab) lonManage [--myname=host --hosts=table] --reinit=lonc [host] @@ -111,6 +112,18 @@ sub Usage { If [host] is omitted, all hosts in the hosts.tab file are iterated over. +lonManage [--myname=host --hosts=table] --edit= editscript [host] + Requests lond edit the hosts or domain table (selected by + tablename) with the editing command in editscript. If + host is supplied the individual host is operated on, + otherwise, the entire cluster is operated on. + The edit file has edit request, one per line of the form: + append|newline + replace|key|newline + delete|key + The key is a loncapa hostname if editing the host file + or a domain name if editing the domain table file. + 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 @@ -133,6 +146,62 @@ sub MakeLondConnection { return return $Connection; } # +# Process the connection state machine until the connection +# becomes idle. This is used both to negotiate the initial +# connection, during which the LondConnection sequences a rather +# complex state machine and during the transaction itself +# for a simpler set of transitions. +# All we really need to be concerned with is whether or not +# we're readable or writable and the final state: +# +# Parameter: +# connection - Represents the LondConnection to be sequenced. +# timeout - Maximum time to wait for readable/writable sockets. +# in seconds. < 0 waits forever. +# Return: +# 'ok' - We got to idle ok. +# 'error:msg' - An error occured. msg describes the error. +# +sub SequenceStateMachine { + my $connection = shift; + my $timeout = shift; + + my $Socket = $connection->GetSocket; + my $returnstatus = "ok"; # optimist!!! + my $error = 0; # Used to force early loop termination + # damned perl has no break!!. + my $state = $connection->GetState; + + while(($connection->GetState ne "Idle") && (!$error)) { + # + # Figure out what the connection wants. read/write and wait for it + # or for the timeout. + # + my $wantread = $connection->WantReadable; + my $poll = new IO::Poll; + $poll->mask($Socket, => $wantread ? POLLIN : POLLOUT); + my $handlecount = $poll->poll($timeout); + if($handlecount == 0) { # no handles ready... timeout!! + $returnstatus = "error:"; + $returnstatus .= "Timeout in state $state\n"; + $error = 1; + } else { + my $done = $poll->handles(); + my $status; + $status = $wantread ? $connection->Readable : + $connection->Writable; + if($status != 0) { + $returnstatus = "error:"; + $returnstatus .= " I/O failed in state $state\n"; + $error = 1; + } + } + $state = $connection->GetState; + } + return $returnstatus; +} + +# # 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 @@ -159,47 +228,43 @@ sub NegotiateStartup { 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; + if($state ne "Connected") { + print "Error: Initial lond connection state: $state should be Connected\n"; + return "error"; + } + + return SequenceStateMachine($connection, $TransitionTimeout); } +# +# 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 $status = SequenceStateMachine($connection, $TransitionTimeout); + if($status eq "ok") { + $retval = $connection->GetReply; + } else { + $retval = $status; + } - return "ok"; + return $retval; } # # Performs a transaction direct to a remote lond. @@ -215,32 +280,19 @@ sub subreply { 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; + my $connection = MakeLondConnection($host); + if ($connection eq undef) { + return "Connect Failed"; + } + my $reply = NegotiateStartup($connection); + if($reply ne "ok") { + return "connection negotiation failed"; + } + my $reply = PerformTransaction($connection, $cmd); + return $reply; + + } -# >>> BUGBUG <<< # # Use Getopt::Long to parse the parameters of the program. # @@ -266,98 +318,153 @@ sub subreply { sub ParseArgs { - my $pushing = ''; + my $pushing = ''; my $reinitting = ''; - + my $editing = ''; + if(!GetOptions('push=s' => \$pushing, - 'reinit=s' => \$reinitting, - 'myname=s' => \$MyHost, - 'hosts=s' => \$ForeignHostTab)) { - return (); - } + 'reinit=s' => \$reinitting, + 'edit=s' => \$editing, + '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 (); - } - - # Require exactly one of --push and --reinit - - my $command = ''; - my $commandarg = ''; - my $paramcount = @ARGV; # Number of additional arguments. - - - if($pushing ne '') { - - # --push takes in addition a table, and an optional host: - # - if(($paramcount != 2) && ($paramcount != 1)) { - return (); # Invalid parameter count. - } - if($command ne '') { - return (); - } else { + if(($MyHost ne "") && ($ForeignHostTab eq "")) { + return (); + } + if(($ForeignHostTab ne "") && ($MyHost eq "")) { + return (); + } + + # Require exactly one of --push, --reinit, or --edit + + my $command = ''; + my $commandarg = ''; + my $paramcount = @ARGV; # Number of additional arguments. + + my $commands = 0; # Number of commands seen. + + if($pushing ne '') { + + # --push takes in addition a table, and an optional host: + # + + if(($paramcount != 2) && ($paramcount != 1)) { + return (); # Invalid parameter count. + } - $command = 'push'; - $commandarg = $pushing; + $commands++; # Count a command seen. + $command = 'push'; + $commandarg = $pushing; } - } - if ($reinitting ne '') { + if ($reinitting ne '') { # --reinit takes in addition just an optional host name - if($paramcount > 1) { - return (); - } - if($command ne '') { - return (); - } else { - $command = 'reinit'; - $commandarg = $reinitting; + if($paramcount > 1) { + return (); + } + $commands++; # Count a command seen. + $command = 'reinit'; + $commandarg = $reinitting; } - } + + # --edit takes a script file and optional host name. + # + if ($editing ne "") { + if(($paramcount != 2) && ($paramcount != 1)) { + return (); # Invalid parameter count. + } + + $commands++; # Count a command seen. + $command = 'edit'; + $commandarg = $editing; + } + + # At this point, $commands must be 1 or else we've seen + # The wrong number of command switches: + + if($commands != 1) { + return (); + } # Build the result list: - my @result = ($command, $commandarg); - my $i; - for($i = 0; $i < $paramcount; $i++) { - push(@result, $ARGV[$i]); - } + my @result = ($command, $commandarg); + my $i; + for($i = 0; $i < $paramcount; $i++) { + push(@result, $ARGV[$i]); + } return @result; } # +# Build the editor script. This function: +# - Opens the edit script file. +# - Reads each line of the edit script file +# - Replaces the ending \n with a / +# - Appends it to the EditScript variable. +# - Returns the contents of the EditScript variable. +# Parameters: +# tabletype - The type of table being built: +# hosts or domain +# scriptname - The script input file. +# +sub BuildEditScript { + my $TableType = shift; + my $ScriptName = shift; + + my $fh = new IO::File "< $ScriptName"; + if (! (defined $fh)) { + print "Unable to open script file: $ScriptName \n"; + Usage; + exit -1; + } + + my @EditScript; + my $scriptline = "$TableType\:"; # First leads with e.g. hosts: + while (! $fh->eof()) { + my $line = <$fh>; + chomp $line; + if($line ne "\n") { + $scriptline .= "$line\n"; + push(@EditScript, $scriptline); + $scriptline = ""; + } + } + + return \@EditScript; +} # 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 { +sub ReadConfig { - 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 { + + + 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; - } - + 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. @@ -403,25 +510,26 @@ sub Transact { my $body; my $i; - if(scalar @ARG) { - $body = shift; - $haveBody = 1; - } + if(scalar @ARG) { + $body = shift; + $haveBody = 1; + } # Construct the command to send to the server: - my $request = "encrypt\:"; # All requests are encrypted. - $request .= $command; - if($haveBody) { - $request .= "\:"; - my $bodylines = scalar @$body; - for($i = 0; $i < $bodylines; $i++) { - $request .= $$body[$i]; - } - } else { - $request .= "\n"; - } + my $request = "encrypt\:"; # All requests are encrypted. + $request .= $command; + if($haveBody) { + $request .= "\:"; + my $bodylines = scalar @$body; + for($i = 0; $i < $bodylines; $i++) { + $request .= $$body[$i]; + } + } else { + $request .= "\n"; + } # Body is now built... transact with lond.. +# print "Final command: '$request'\n"; my $answer = subreply($request, $host); print "$answer\n"; @@ -443,7 +551,7 @@ sub Transact { # tablefile - name of the file containing the table to push. # host - name of the host to push this file to. # -# >>>BUGBUG<<< This belongs in lonnet.pm. +# # sub PushFile { my $tablename = shift; @@ -452,28 +560,47 @@ sub PushFile { # Open the table file: - if(!open(TABLEFILE, "<$tablefile")) { - die "ENOENT - No such file or directory $tablefile"; - } + if(!open(TABLEFILE, "<$tablefile")) { + die "ENOENT - No such file or directory $tablefile"; + } # Require that the host be valid: - if(!ValidHost($host)) { - die "EHOSTINVAL - Invalid host $host"; # Ok so I invented this 'errno'. - } + if(!ValidHost($host)) { + die "EHOSTINVAL - Invalid host $host"; # Ok so I invented this 'errno'. + } # Read in the file. If the table name is valid, push it. - my @table = ; # These files are pretty small. - close TABLEFILE; + my @table = ; # These files are pretty small. + close TABLEFILE; - 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"; - } + if( ($tablename eq "hosts") || + ($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"; + } +} +# +# This function forms and executes an edit file with a +# remote lond server. We build the full transaction string +# and use Transact to perform the transaction. +# Paramters: +# host - loncapa name of host to operate on. +# body - Body of the command. We send: +# edit:$body as the command request. +# +sub EditFile { + my $host = shift; + my $body = shift; + + if(!ValidHost($host)) { + die "EHOSTINVAL - Invalid host $host"; + } + Transact($host, "edit", $body); } + # # This function is called to reinitialize a server in a remote host. # The servers that can be reinitialized are: @@ -490,23 +617,23 @@ sub PushFile { # >>>BUGBUG<<<< This belongs in lonnet.pm # sub ReinitProcess { - my $process = shift; - my $host = shift; + my $process = shift; + my $host = shift; # Ensure the host is valid: - if(!ValidHost($host)) { - die "EHOSTINVAL - Invalid host $host"; - } + if(!ValidHost($host)) { + die "EHOSTINVAL - Invalid host $host"; + } # Ensure target process selector is valid: - 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"; - } + 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"; + } } #--------------------------- Entry point: -------------------------- @@ -544,11 +671,11 @@ if($operation eq "push") { # push table my $tablefile = shift @params; my $host = shift @params; if($host) { - PushFile($tablename, $tablefile, $host); + PushFile($tablename, $tablefile, $host); } else { # Push to whole cluster. - foreach my $host (keys %hostshash) { - PushFile($tablename, $tablefile, $host); - } + foreach my $host (keys %hostshash) { + PushFile($tablename, $tablefile, $host); + } } } elsif($operation eq "reinit") { # reinit processname host. @@ -561,9 +688,21 @@ if($operation eq "push") { # push table ReinitProcess($process,$host); } } -} +} elsif($operation eq "edit") { # Edit a table. + my $tablename = shift @params; + my $scriptfile = shift @params; + my $host = shift @params; + my $CommandBody = BuildEditScript($tablename, $scriptfile); + if ($host) { + EditFile($host, $CommandBody); + } else { + foreach my $ClusterMember (keys %hostshash) { + EditFile($ClusterMember, $CommandBody); + } + } +} else { - Usage; + Usage; } exit 0; @@ -583,12 +722,25 @@ Usage: B Sends a HUP signal to the remote systems's lond. - B + B 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. + B editscript host> + Requests the remote system's lond perform an edit + on editscript supplies a set of + editing commands. Each edit command is one of : + + append|key|newline + delete|key| + replace|key|newline + + The key above is the value of the loncapa host name + in the file. + +In the above syntax, the host above is the +hosts.tab name of a host, +not the IP address of the host. =head1 DESCRIPTION @@ -599,6 +751,7 @@ Usage: =item Getopt::Long =item English =item IO::Socket::UNIX +=item LONCAPA::LondConnection =head1 KEY Subroutines. 500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.