Diff for /loncom/Attic/lonManage between versions 1.20 and 1.27

version 1.20, 2003/11/03 10:24:49 version 1.27, 2003/12/22 12:02:19
Line 63  use lib "."; Line 63  use lib ".";
 use strict; # Because it's good practice.  use strict; # Because it's good practice.
 use English; # Cause I like meaningful names.  use English; # Cause I like meaningful names.
 use Getopt::Long;  use Getopt::Long;
 use IO::Socket::UNIX; # To communicate with lonc.  
 use LondConnection;  use LondConnection;
   use IO::Poll qw(POLLRDNORM POLLWRNORM POLLIN POLLHUP POLLOUT);
   
 # File scoped variables:  # File scoped variables:
   
Line 74  my %hostshash;   # Host table as a host Line 74  my %hostshash;   # Host table as a host
 my $MyHost=""; # Host name to use as me.  my $MyHost=""; # Host name to use as me.
 my $ForeignHostTab=""; # Name of foreign hosts table.  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.  #   prints out utility's command usage info.
 #  #
Line 102  sub Usage  { Line 111  sub Usage  {
     If [host] is omitted, all hosts in the hosts.tab file are iterated      If [host] is omitted, all hosts in the hosts.tab file are iterated
     over.      over.
   
   lonManage [--myname=host --hosts=table] --edit=<tablename> 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   For all of the above syntaxes if --myname=host and --hosts=table are
  supplied (both must be present), the utility runs in standalone mode   supplied (both must be present), the utility runs in standalone mode
  presenting itself to the world as 'host' and using the hosts.tab file   presenting itself to the world as 'host' and using the hosts.tab file
Line 110  USAGE Line 131  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;
   }
   #
   #   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
   #   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";
      }
   
      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 $retval;
   }
 #  #
 #   Lifted from lonnet.pm - and we need to figure out a way to get it back in.  # Performs a transaction direct to a remote lond.
 #   Performas a transaction with lond via the lonc proxy server.  
 #   Parameter:  #   Parameter:
 #      cmd  - The text of the request.  #      cmd  - The text of the request.
 #      host - The host to which the request ultimately goes.  #      host - The host to which the request ultimately goes.
Line 121  USAGE Line 275  USAGE
 #      lond/lonc etc.  #      lond/lonc etc.
 #  #
 sub subreply {  sub subreply {
     my ($cmd,$server)=@_;      my $cmd = shift;
     my $peerfile="$perlvar{'lonSockDir'}/$server";      my $host = shift;
     my $client=IO::Socket::UNIX->new(Peer    =>"$peerfile",  
                                      Type    => SOCK_STREAM,  
                                      Timeout => 10)     my $connection  = MakeLondConnection($host);
        or return "con_lost";     if ($connection eq undef) {
     print $client "$cmd\n";        return "Connect Failed";
     my $answer=<$client>;     }
     if (!$answer) { $answer="con_lost"; }     my $reply = NegotiateStartup($connection);
     chomp($answer);     if($reply ne "ok") {
     return $answer;        return "connection negotiation failed";
      }
      my $reply =  PerformTransaction($connection, $cmd);
      return $reply;
   
   
 }  }
 #   >>> BUGBUG <<<   
 #  #
 #  Use Getopt::Long to parse the parameters of the program.  #  Use Getopt::Long to parse the parameters of the program.
 #  #
Line 159  sub subreply { Line 317  sub subreply {
   
   
 sub ParseArgs {  sub ParseArgs {
     my $pushing   = '';      my $pushing    = '';
     my $reinitting = '';      my $reinitting = '';
       my $editing    = '';
       
     if(!GetOptions('push=s'    => \$pushing,      if(!GetOptions('push=s'    => \$pushing,
            'reinit=s'  => \$reinitting,                     'reinit=s'  => \$reinitting,
    'myname=s' => \$MyHost,                     'edit=s'    => \$editing,
    'hosts=s' => \$ForeignHostTab)) {               'myname=s'  => \$MyHost,
  return ();               'hosts=s'   => \$ForeignHostTab)) {
     }        return ();
      }
     #  The --myname and --hosts switch must have values and      #  The --myname and --hosts switch must have values and
     #  most both appear if either appears:      #  most both appear if either appears:
   
     if(($MyHost ne "") && ($ForeignHostTab eq "")) {     if(($MyHost ne "") && ($ForeignHostTab eq "")) {
  return ();        return ();
     }     }
     if(($ForeignHostTab ne "") && ($MyHost eq "")) {     if(($ForeignHostTab ne "") && ($MyHost eq "")) {
  return ();        return ();
     }     }
   
     #  Require exactly   one of --push and --reinit      #  Require exactly   one of --push, --reinit, or --edit
   
     my $command    = '';     my $command    = '';
     my $commandarg = '';     my $commandarg = '';
     my $paramcount = @ARGV; # Number of additional arguments.     my $paramcount = @ARGV; # Number of additional arguments.
       
      my $commands = 0;           # Number of commands seen.
     if($pushing ne '') {  
      if($pushing ne '') {
         # --push takes in addition a table, and an optional  host:  
         #        # --push takes in addition a table, and an optional  host:
  if(($paramcount != 2) && ($paramcount != 1)) {        #
     return (); # Invalid parameter count.          
  }     if(($paramcount != 2) && ($paramcount != 1)) {
  if($command ne '') {           return (); # Invalid parameter count.
     return ();        }
  } else {  
           
     $command    = 'push';        $commands++;              # Count a command seen.
     $commandarg = $pushing;        $command    = 'push';
         $commandarg = $pushing;
  }   }
     }  
   
     if ($reinitting ne '') {     if ($reinitting ne '') {
   
  # --reinit takes in addition just an optional  host name   # --reinit takes in addition just an optional  host name
   
  if($paramcount > 1) {        if($paramcount > 1) {
     return ();           return ();
         }
         $commands++;              #  Count a command seen.
         $command    = 'reinit';
         $commandarg = $reinitting; 
  }   }
  if($command ne '') {  
     return ();     # --edit takes a script file and optional host name.
  } else {     #
     $command    = 'reinit';     if ($editing ne "") {
     $commandarg = $reinitting;         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:      #  Build the result list:
   
     my @result = ($command, $commandarg);     my @result = ($command, $commandarg);
     my $i;     my $i;
     for($i = 0; $i < $paramcount; $i++) {     for($i = 0; $i < $paramcount; $i++) {
  push(@result, $ARGV[$i]);        push(@result, $ARGV[$i]);
     }     }
           
     return @result;      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;
      
      #Stub
      
      my @EditScript = (
         "$TableType\:append|".
            "nscll2|nscl\:library\:lonkashy.nscl.msu.edu\:35.8.32.89\n"
    ."delete|nscll2"
         );
      return \@EditScript;
   }
 #  Read the loncapa configuration stuff.  If ForeignHostTab is empty,  #  Read the loncapa configuration stuff.  If ForeignHostTab is empty,
 #  assume we are part of a loncapa cluster and read the hosts.tab  #  assume we are part of a loncapa cluster and read the hosts.tab
 #  file from the config directory.  Otherwise, ForeignHossTab  #  file from the config directory.  Otherwise, ForeignHossTab
Line 235  sub ParseArgs { Line 434  sub ParseArgs {
 #  #
 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.  
     } else {  
  my $hoststab   = LondConnection::read_hosts($ForeignHostTab);  
  %hostshash     = %{$hoststab};  
  $perlvar{londPort}   = 5663;  
  $perlvar{lonSockDir} = "/home/httpd/sockets";  
     }  
   
      
      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.  #  Determine if the target host is valid.
Line 294  sub Transact { Line 497  sub Transact {
     my $body;      my $body;
     my $i;      my $i;
   
     if(scalar @ARG) {     if(scalar @ARG) {
  $body = shift;        $body = shift;
  $haveBody = 1;        $haveBody = 1;
     }     }
     #  Construct the command to send to the server:      #  Construct the command to send to the server:
           
     my $request = "encrypt\:"; # All requests are encrypted.     my $request = "encrypt\:"; # All requests are encrypted.
     $request   .= $command;     $request   .= $command;
     if($haveBody) {     if($haveBody) {
  $request .= "\:";        $request .= "\:";
  my $bodylines = scalar @$body;        my $bodylines = scalar @$body;
  for($i = 0; $i < $bodylines; $i++) {        for($i = 0; $i < $bodylines; $i++) {
     $request .= $$body[$i];           $request .= $$body[$i];
  }        }
     } else {     } else {
  $request .= "\n";        $request .= "\n";
     }     }
     # Body is now built... transact with lond..      # Body is now built... transact with lond..
           
     my $answer = subreply($request, $host);      my $answer = subreply($request, $host);
Line 334  sub Transact { Line 537  sub Transact {
 #     tablefile - name of the file containing the table to push.  #     tablefile - name of the file containing the table to push.
 #     host      - name of the host to push this file to.       #     host      - name of the host to push this file to.     
 #  #
 #    >>>BUGBUG<<< This belongs in lonnet.pm.  #    
 #  #
 sub PushFile {  sub PushFile {
     my $tablename = shift;      my $tablename = shift;
Line 343  sub PushFile { Line 546  sub PushFile {
           
     # Open the table file:      # Open the table file:
   
     if(!open(TABLEFILE, "<$tablefile")) {     if(!open(TABLEFILE, "<$tablefile")) {
  die "ENOENT - No such file or directory $tablefile";        die "ENOENT - No such file or directory $tablefile";
     }     }
       
     # Require that the host be valid:      # Require that the host be valid:
   
     if(!ValidHost($host)) {     if(!ValidHost($host)) {
  die "EHOSTINVAL - Invalid host $host"; # Ok so I invented this 'errno'.        die "EHOSTINVAL - Invalid host $host"; # Ok so I invented this 'errno'.
     }     }
     # Read in the file.  If the table name is valid, push it.      # Read in the file.  If the table name is valid, push it.
   
     my @table = <TABLEFILE>; #  These files are pretty small.     my @table = <TABLEFILE>; #  These files are pretty small.
     close TABLEFILE;     close TABLEFILE;
   
     if( ($tablename eq "host")    ||     if( ($tablename eq "host")    ||
  ($tablename eq "domain")) {         ($tablename eq "domain")) {
  print("Pushing $tablename to $host\n");        print("Pushing $tablename to $host\n");
  Transact($host, "pushfile:$tablename",\@table);        Transact($host, "pushfile:$tablename",\@table);
     } else {     } else {
  die "EINVAL - Invalid parameter. tablename: $tablename must be host or domain";        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.  #   This function is called to reinitialize a server in a remote host.
 #   The servers that can be reinitialized are:  #   The servers that can be reinitialized are:
Line 381  sub PushFile { Line 603  sub PushFile {
 #   >>>BUGBUG<<<< This belongs  in lonnet.pm  #   >>>BUGBUG<<<< This belongs  in lonnet.pm
 #  #
 sub ReinitProcess {  sub ReinitProcess {
     my $process = shift;     my $process = shift;
     my $host    = shift;     my $host    = shift;
   
     #  Ensure the host is valid:      #  Ensure the host is valid:
           
     if(!ValidHost($host)) {     if(!ValidHost($host)) {
  die "EHOSTINVAL - Invalid host $host";        die "EHOSTINVAL - Invalid host $host";
     }     }
     # Ensure target process selector is valid:      # Ensure target process selector is valid:
   
     if(($process eq "lonc") ||     if(($process eq "lonc") ||
        ($process eq "lond")) {        ($process eq "lond")) {
  print("Reinitializing $process in $host\n");        print("Reinitializing $process in $host\n");
  Transact($host, "reinit:$process");        Transact($host, "reinit:$process");
     } else {     } else {
  die "EINVAL -Invalid parameter. Process $process must be lonc or lond";        die "EINVAL -Invalid parameter. Process $process must be lonc or lond";
     }     }
 }  }
 #--------------------------- Entry point: --------------------------  #--------------------------- Entry point: --------------------------
   
Line 435  if($operation eq "push") {  # push table Line 657  if($operation eq "push") {  # push table
     my $tablefile = shift @params;      my $tablefile = shift @params;
     my $host      = shift @params;      my $host      = shift @params;
     if($host) {      if($host) {
  PushFile($tablename, $tablefile, $host);        PushFile($tablename, $tablefile, $host);
     } else { # Push to whole cluster.      } else { # Push to whole cluster.
  foreach my $host (keys %hostshash) {        foreach my $host (keys %hostshash) {
     PushFile($tablename, $tablefile, $host);           PushFile($tablename, $tablefile, $host);
  }        }
     }      }
   
 } elsif($operation eq "reinit") { # reinit processname host.  } elsif($operation eq "reinit") { # reinit processname host.
Line 452  if($operation eq "push") {  # push table Line 674  if($operation eq "push") {  # push table
     ReinitProcess($process,$host);      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 {  else {
     Usage;     Usage;
 }  }
 exit 0;  exit 0;
   
Line 474  Usage: Line 708  Usage:
     B<lonManage  --reinit=lonc host>      B<lonManage  --reinit=lonc host>
            Sends a HUP signal to the remote systems's lond.             Sends a HUP signal to the remote systems's lond.
   
     B<lonmanage  --reinit=lond host>      B<lonManage  --reinit=lond host>
           Requests the remote system's lond perform the same action as if            Requests the remote system's lond perform the same action as if
           it had received a HUP signal.            it had received a HUP signal.
   
     In the above syntax, the host above is the hosts.tab name of a host,      B<lonManage  --edit=<tablename> editscript host>
     not the IP address of the host.           Requests the remote system's lond perform an edit
            on <tablename>  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  =head1 DESCRIPTION
Line 490  Usage: Line 737  Usage:
 =item Getopt::Long  =item Getopt::Long
 =item English  =item English
 =item IO::Socket::UNIX  =item IO::Socket::UNIX
   =item LONCAPA::LondConnection
   
 =head1 KEY Subroutines.  =head1 KEY Subroutines.
   

Removed from v.1.20  
changed lines
  Added in v.1.27


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>