File:  [LON-CAPA] / loncom / Attic / lonManage
Revision 1.16: download - view: text, annotated - select for diffs
Tue Oct 21 09:44:04 2003 UTC (20 years, 6 months ago) by foxr
Branches: MAIN
CVS tags: HEAD
Add ability to manage entire cluster in hosts table.

    1: #!/usr/bin/perl
    2: # The LearningOnline Network with CAPA
    3: #
    4: #  lonManage supports remote management of nodes in a LonCAPA cluster.
    5: #
    6: #  $Id: lonManage,v 1.16 2003/10/21 09:44:04 foxr Exp $
    7: #
    8: # $Id: lonManage,v 1.16 2003/10/21 09:44:04 foxr Exp $
    9: #
   10: # Copyright Michigan State University Board of Trustees
   11: #
   12: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
   13: ## LON-CAPA is free software; you can redistribute it and/or modify
   14: # it under the terms of the GNU General Public License as published by
   15: # the Free Software Foundation; either version 2 of the License, or
   16: # (at your option) any later version.
   17: #
   18: # LON-CAPA is distributed in the hope that it will be useful,
   19: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   20: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   21: # GNU General Public License for more details.
   22: #
   23: # You should have received a copy of the GNU General Public License
   24: # along with LON-CAPA; if not, write to the Free Software
   25: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   26: #
   27: # /home/httpd/html/adm/gpl.txt
   28: #
   29: # http://www.lon-capa.org/
   30: #
   31: #
   32: #   lonManage supports management of remot nodes in a lonCAPA cluster.
   33: #   it is a command line tool.  The following command line syntax (usage)
   34: #   is supported:
   35: #
   36: #    lonManage  -push   <tablename>  newfile  [host]
   37: #        Push <tablename> to the lonTabs directory.  Note that
   38: #        <tablename> must be one of:
   39: #           host  (hosts.tab)
   40: #           domain (domain.tab)
   41: #
   42: #    lonManage  -reinit lonc [host]
   43: #           Sends a HUP signal to the remote systems's lond.
   44: #
   45: #    lonmanage  -reinit lond [host]
   46: #          Requests the remote system's lond perform the same action as if
   47: #          it had received a HUP signal.
   48: #
   49: #    In the above syntax, the host above is the hosts.tab name of a host,
   50: #    not the IP address of the host
   51: #  
   52: #   If [host] is not supplied, every host in the client's hosts.tab
   53: #   table is iterated through and procesed..
   54: #
   55: #  $Log: lonManage,v $
   56: #  Revision 1.16  2003/10/21 09:44:04  foxr
   57: #  Add ability to manage entire cluster in hosts table.
   58: #
   59: #  Revision 1.15  2003/09/16 09:49:54  foxr
   60: #  Adjust the usage message to reflect what actually will happen on
   61: #  --reinit={lond|lonc}
   62: #
   63: #  Revision 1.14  2003/09/08 09:45:20  foxr
   64: #  Remove BUGBUG about comment about authentication as we'll be doing
   65: #  host based authentication initially (no need for lonManage to do anything),
   66: #  and certificate based later (need at that time).
   67: #
   68: #  Revision 1.13  2003/08/19 10:26:24  foxr
   69: #  Initial working version... tested against an unmodified lond this
   70: #  produces an unknown_cmd response which is about what I'd expect.
   71: #
   72: #  Revision 1.12  2003/08/18 11:08:07  foxr
   73: #  Debug request building in Transact.
   74: #
   75: #  Revision 1.11  2003/08/18 10:45:32  foxr
   76: #  Felt strongly enough about hoisting ReadConfiguration into a separate sub
   77: #  that I did it now before I forgot.
   78: #
   79: #  Revision 1.10  2003/08/18 10:43:31  foxr
   80: #  Code/test ValidHost.  The hosts.tab and the perl variables are read in as
   81: #  global hashes as a side effect.  May later want to clean this up by making
   82: #  a separate getconfig function and hoisting the config reads into that.
   83: #
   84: #  Revision 1.9  2003/08/18 10:25:46  foxr
   85: #  Write ReinitProcess function in terms of ValidHost and Transact.
   86: #
   87: #  Revision 1.8  2003/08/18 10:18:21  foxr
   88: #  Completed PushFile function in terms of
   89: #  - ValidHost - Determines if target host is valid.
   90: #  - Transact  - Performs one of the valid transactions with the
   91: #                appropriate lonc<-->lond client/server pairs.
   92: #
   93: #  Revision 1.7  2003/08/18 09:56:01  foxr
   94: #  1. Require to be run as root.
   95: #  2. Catch case where no operation switch is supplied and put out usage.
   96: #  3. skeleton/comments for PushFile function.
   97: #
   98: #  Revision 1.6  2003/08/12 11:02:59  foxr
   99: #  Implement command switch dispatching.
  100: #
  101: #  Revision 1.5  2003/08/12 10:55:42  foxr
  102: #  Complete command line parsing (tested)
  103: #
  104: #  Revision 1.4  2003/08/12 10:40:44  foxr
  105: #  Get switch parsing right.
  106: #
  107: #  Revision 1.3  2003/08/12 10:22:35  foxr
  108: #  Put in parameter parsing infrastructure
  109: #
  110: #  Revision 1.2  2003/08/12 09:58:49  foxr
  111: #  Add usage and skeleton documentation.
  112: #
  113: #
  114: 
  115: 
  116: 
  117: # Modules required:
  118: 
  119: use strict;			# Because it's good practice.
  120: use English;			# Cause I like meaningful names.
  121: use Getopt::Long;
  122: use LONCAPA::Configuration;	# To handle configuration I/O.
  123: use IO::Socket::UNIX;		# To communicate with lonc.
  124: 
  125: # File scoped variables:
  126: 
  127: my %perlvar;			# Perl variable defs from apache config.
  128: my %hostshash;			# Host table as a host indexed hash.
  129: 
  130: #
  131: #   prints out utility's command usage info.
  132: #
  133: sub Usage  {
  134:     print "Usage:";
  135:     print <<USAGE;
  136:     lonManage  --push=<tablename>  newfile  [host]
  137:         Push <tablename> to the lonTabs directory.  Note that
  138:         <tablename> must be one of:
  139:            host  (hosts.tab)
  140:            domain (domain.tab)
  141: 
  142:     lonManage  --reinit=lonc [host]
  143:        Causes lonc in the remote system to reread hosts.tab and
  144:        adjust the set of clients that are being maintained to match
  145:        the new file.
  146:        
  147: 
  148:     lonManage  --reinit=lond [host]
  149:        Causes lond in the remote system to reread the hosts.tab file
  150:        and adjust the set of servers to match changes in that file.
  151: 
  152:     In the above syntax, the host above is the hosts.tab name of a host,
  153:     not the IP address of the host.
  154: 
  155:     If [host] is omitted, all hosts in the hosts.tab file are iterated
  156:     over.
  157: 
  158: USAGE
  159: 
  160: 
  161: }
  162: #
  163: #   Lifted from lonnet.pm - and we need to figure out a way to get it back in.
  164: #   Performas a transaction with lond via the lonc proxy server.
  165: #   Parameter:
  166: #      cmd  - The text of the request.
  167: #      host - The host to which the request ultimately goes.
  168: #   Returns:
  169: #      The text of the reply from the lond or con_lost if not able to contact
  170: #      lond/lonc etc.
  171: #
  172: sub subreply {
  173:     my ($cmd,$server)=@_;
  174:     my $peerfile="$perlvar{'lonSockDir'}/$server";
  175:     my $client=IO::Socket::UNIX->new(Peer    =>"$peerfile",
  176:                                      Type    => SOCK_STREAM,
  177:                                      Timeout => 10)
  178:        or return "con_lost";
  179:     print $client "$cmd\n";
  180:     my $answer=<$client>;
  181:     if (!$answer) { $answer="con_lost"; }
  182:     chomp($answer);
  183:     return $answer;
  184: }
  185: #   >>> BUGBUG <<< 
  186: #
  187: #  Use Getopt::Long to parse the parameters of the program.
  188: #
  189: #  Return value is a list consisting of:
  190: #    A 'command' which is one of:
  191: #       push   - table push requested.
  192: #       reinit - reinit requested.
  193: #   Additional parameters as follows:
  194: #       for push: Tablename, hostname
  195: #       for reinit: Appname  hostname
  196: #
  197: #   This function does not validation of the parameters of push and
  198: #   reinit.
  199: #
  200: #   returns a list.  The first element of the list is the operation name
  201: #   (e.g. reinit or push).  The second element is the switch parameter.
  202: #   for push, this is the table name, for reinit, this is the process name.
  203: #   Additional elements of the list are the command argument.  The count of
  204: #   command arguments is validated, but not their semantics.
  205: #
  206: #   returns an empty list if the parse fails.
  207: #
  208: 
  209: sub ParseArgs {
  210:     my $pushing   = '';
  211:     my $reinitting = '';
  212: 
  213:     if(!GetOptions('push=s'    => \$pushing,
  214: 	           'reinit=s'  => \$reinitting)) {
  215: 	return ();
  216:     }
  217: 
  218:     #  Require exactly   one of --push and --reinit
  219: 
  220:     my $command    = '';
  221:     my $commandarg = '';
  222:     my $paramcount = @ARGV; 	# Number of additional arguments.
  223:     
  224: 
  225:     if($pushing ne '') {
  226: 
  227:         # --push takes in addition a table, and an optional  host:
  228:         #
  229: 	if(($paramcount != 2) && ($paramcount != 1)) {
  230: 	    return ();		# Invalid parameter count.
  231: 	}
  232: 	if($command ne '') {
  233: 	    return ();
  234: 	} else {
  235: 	    
  236: 	    $command    = 'push';
  237: 	    $commandarg = $pushing;
  238: 	}
  239:     }
  240: 
  241:     if ($reinitting ne '') {
  242: 
  243: 	# --reinit takes in addition just an optional  host name
  244: 
  245: 	if($paramcount > 1) {
  246: 	    return ();
  247: 	}
  248: 	if($command ne '') {
  249: 	    return ();
  250: 	} else {
  251: 	    $command    = 'reinit';
  252: 	    $commandarg = $reinitting; 
  253: 	}
  254:     }
  255: 
  256:     #  Build the result list:
  257: 
  258:     my @result = ($command, $commandarg);
  259:     my $i;
  260:     for($i = 0; $i < $paramcount; $i++) {
  261: 	push(@result, $ARGV[$i]);
  262:     }
  263:     
  264:     return @result;
  265: }
  266: #
  267: #  Read the loncapa configuration stuff.
  268: #
  269: sub ReadConfig {
  270:     my $perlvarref = LONCAPA::Configuration::read_conf('loncapa.conf');
  271:     %perlvar       = %{$perlvarref};
  272:     my $hoststab   = LONCAPA::Configuration::read_hosts(
  273: 					"$perlvar{'lonTabDir'}/hosts.tab");
  274:     %hostshash     = %{$hoststab};
  275: 
  276: }
  277: #
  278: #  Determine if the target host is valid.
  279: #  This is done by reading the current hosts.tab file.
  280: #  For the host to be valid, it must be inthe file.
  281: #
  282: #  Parameters:
  283: #     host   - Name of host to check on.
  284: #  Returns:
  285: #     true   if host is valid.
  286: #     false  if host is invalid.
  287: #
  288: sub ValidHost {
  289:     my $host       = shift;
  290:    
  291: 
  292:     return defined $hostshash{$host};
  293: 
  294: }
  295: 
  296: 
  297: 
  298: #
  299: #  Performs a transaction with lonc.
  300: #  By the time this is called, the transaction has already been
  301: #  validated by the caller.
  302: #
  303: #   Parameters:
  304: #
  305: #   host    - hosts.tab name of the host whose lonc we'll be talking to.
  306: #   command - The base command we'll be asking lond to execute.
  307: #   body    - [optional] If supplied, this is a command body that is a ref.
  308: #             to an array of lines that will be appended to the 
  309: #             command.
  310: #
  311: #  NOTE:
  312: #    The command will be done as an encrypted operation.
  313: #
  314: sub Transact {
  315:     my $host    = shift;
  316:     my $command = shift;
  317:     my $haveBody= 0;
  318:     my $body;
  319:     my $i;
  320: 
  321:     if(scalar @ARG) {
  322: 	$body = shift;
  323: 	$haveBody = 1;
  324:     }
  325:     #  Construct the command to send to the server:
  326:     
  327:     my $request = "encrypt\:";	# All requests are encrypted.
  328:     $request   .= $command;
  329:     if($haveBody) {
  330: 	$request .= "\:";
  331: 	my $bodylines = scalar @$body;
  332: 	for($i = 0; $i < $bodylines; $i++) {
  333: 	    $request .= $$body[$i];
  334: 	}
  335:     } else {
  336: 	$request .= "\n";
  337:     }
  338:     # Body is now built... transact with lond..
  339:     
  340:     my $answer = subreply($request, $host);
  341: 
  342:     print "$answer\n";
  343: 
  344: }
  345: #
  346: #   Called to push a file to the remote system.
  347: #   The only legal files to push are hosts.tab and domain.tab.
  348: #   Security is somewhat improved by
  349: #   
  350: #   - Requiring the user run as root.
  351: #   - Connecting with lonc rather than lond directly ensuring this is a loncapa
  352: #     host
  353: #   - We must appear in the remote host's hosts.tab file.
  354: #   - The host must appear in our hosts.tab file.
  355: #
  356: #  Parameters:
  357: #     tablename - must be one of hosts or domain.
  358: #     tablefile - name of the file containing the table to push.
  359: #     host      - name of the host to push this file to.     
  360: #
  361: #    >>>BUGBUG<<< This belongs in lonnet.pm.
  362: #
  363: sub PushFile {
  364:     my $tablename = shift;
  365:     my $tablefile = shift;
  366:     my $host      = shift;
  367:     
  368:     # Open the table file:
  369: 
  370:     if(!open(TABLEFILE, "<$tablefile")) {
  371: 	die "ENOENT - No such file or directory $tablefile";
  372:     }
  373:   
  374:     # Require that the host be valid:
  375: 
  376:     if(!ValidHost($host)) {
  377: 	die "EHOSTINVAL - Invalid host $host"; # Ok so I invented this 'errno'.
  378:     }
  379:     # Read in the file.  If the table name is valid, push it.
  380: 
  381:     my @table = <TABLEFILE>;	#  These files are pretty small.
  382:     close TABLEFILE;
  383: 
  384:     if( ($tablename eq "host")    ||
  385: 	($tablename eq "domain")) {
  386: 	print("Pushing $tablename to $host\n");
  387: 	Transact($host, "pushfile:$tablename",\@table);
  388:     } else {
  389: 	die "EINVAL - Invalid parameter. tablename: $tablename must be host or domain";
  390:     }
  391: }
  392: #
  393: #   This function is called to reinitialize a server in a remote host.
  394: #   The servers that can be reinitialized are:
  395: #   - lonc   - The lonc client process.
  396: #   - lond   - The lond daemon.
  397: #  NOTE:
  398: #    Reinitialization in this case means re-scanning the hosts table,
  399: #    starting new lond/lonc's as approprate and stopping existing lonc/lond's.
  400: #
  401: #  Parameters:
  402: #     process - The name of the process to reinit (lonc or lond).
  403: #     host    - The host in which this reinit will happen.
  404: #
  405: #   >>>BUGBUG<<<< This belongs  in lonnet.pm
  406: #
  407: sub ReinitProcess {
  408:     my $process = shift;
  409:     my $host    = shift;
  410: 
  411:     #  Ensure the host is valid:
  412:     
  413:     if(!ValidHost($host)) {
  414: 	die "EHOSTINVAL - Invalid host $host";
  415:     }
  416:     # Ensure target process selector is valid:
  417: 
  418:     if(($process eq "lonc") ||
  419:        ($process eq "lond")) {
  420: 	print("Reinitializing $process in $host\n");
  421: 	Transact($host, "reinit:$process");
  422:     } else {
  423: 	die "EINVAL -Invalid parameter. Process $process must be lonc or lond";
  424:     }
  425: }
  426: #--------------------------- Entry point: --------------------------
  427: 
  428: ReadConfig;			# Read the configuration info (incl.hosts).
  429: 
  430: 
  431: #  Parse the parameters
  432: #  If command parsing failed, then print usage:
  433: 
  434: my @params   = ParseArgs;
  435: my $nparam   = @params;
  436: 
  437: if($nparam == 0) {
  438:     Usage;
  439:     exit -1;
  440: }
  441: #
  442: #   Next, ensure we are running as EID root.
  443: #
  444: if ($EUID != 0) {
  445:     die "ENOPRIV - No privilege for requested operation"
  446: }
  447: 
  448: 
  449: #   Based on the operation requested invoke the appropriate function:
  450: 
  451: my $operation = shift @params;
  452: 
  453: if($operation eq "push") {  # push tablename filename host
  454:     my $tablename = shift @params;
  455:     my $tablefile = shift @params;
  456:     my $host      = shift @params;
  457:     if($host) {
  458: 	PushFile($tablename, $tablefile, $host);
  459:     } else {			# Push to whole cluster.
  460: 	foreach my $host (keys %hostshash) {
  461: 	    PushFile($tablename, $tablefile, $host);
  462: 	}
  463:     }
  464: 
  465: } elsif($operation eq "reinit") {	# reinit processname host.
  466:     my $process   = shift @params;
  467:     my $host      = shift @params;
  468:     if ($host) {
  469: 	ReinitProcess($process, $host);
  470:     } else {			# Reinit whole cluster.
  471: 	foreach my $host (keys %hostshash) {
  472: 	    ReinitProcess($process,$host);
  473: 	}
  474:     }
  475: } 
  476: else {
  477:     Usage;
  478: }
  479: exit 0;
  480: 
  481: =head1 NAME
  482:     lonManage - Command line utility for remote management of lonCAPA
  483:     cluster nodes.
  484: 
  485: =head1 SYNOPSIS
  486: 
  487: Usage:
  488:     B<lonManage  --push=<tablename>  newfile  host>
  489:         Push <tablename> to the lonTabs directory.  Note that
  490:         <tablename> must be one of:
  491:            hosts  (hosts.tab)
  492:            domain (domain.tab)
  493: 
  494:     B<lonManage  --reinit=lonc host>
  495:            Sends a HUP signal to the remote systems's lond.
  496: 
  497:     B<lonmanage  --reinit=lond host>
  498:           Requests the remote system's lond perform the same action as if
  499:           it had received a HUP signal.
  500: 
  501:     In the above syntax, the host above is the hosts.tab name of a host,
  502:     not the IP address of the host.
  503: 
  504: 
  505: =head1 DESCRIPTION
  506: 
  507: =head1 PREREQUISITES
  508: 
  509: =item strict
  510: =item Getopt::Long
  511: =item English
  512: =item IO::Socket::UNIX
  513: 
  514: =head1 KEY Subroutines.
  515: 
  516: =head1  CATEGORIES
  517:     Command line utility
  518: 
  519: =cut

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