Annotation of loncom/lonManage, revision 1.24
1.1 foxr 1: #!/usr/bin/perl
2: # The LearningOnline Network with CAPA
3: #
4: # lonManage supports remote management of nodes in a LonCAPA cluster.
5: #
1.24 ! foxr 6: # $Id: lonManage,v 1.23 2003/11/04 11:23:37 foxr Exp $
1.1 foxr 7: #
1.24 ! foxr 8: # $Id: lonManage,v 1.23 2003/11/04 11:23:37 foxr Exp $
1.1 foxr 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: #
1.16 foxr 36: # lonManage -push <tablename> newfile [host]
1.1 foxr 37: # Push <tablename> to the lonTabs directory. Note that
38: # <tablename> must be one of:
1.15 foxr 39: # host (hosts.tab)
1.1 foxr 40: # domain (domain.tab)
41: #
1.16 foxr 42: # lonManage -reinit lonc [host]
1.1 foxr 43: # Sends a HUP signal to the remote systems's lond.
44: #
1.16 foxr 45: # lonmanage -reinit lond [host]
1.1 foxr 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,
1.16 foxr 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..
1.1 foxr 54: #
1.3 foxr 55: #
1.10 foxr 56:
1.14 foxr 57:
1.13 foxr 58:
1.10 foxr 59: # Modules required:
60:
1.17 foxr 61: use lib ".";
62:
1.7 foxr 63: use strict; # Because it's good practice.
64: use English; # Cause I like meaningful names.
1.3 foxr 65: use Getopt::Long;
1.17 foxr 66: use LondConnection;
1.23 foxr 67: use IO::Poll qw(POLLRDNORM POLLWRNORM POLLIN POLLHUP POLLOUT);
1.10 foxr 68:
69: # File scoped variables:
70:
71: my %perlvar; # Perl variable defs from apache config.
72: my %hostshash; # Host table as a host indexed hash.
1.2 foxr 73:
1.19 foxr 74: my $MyHost=""; # Host name to use as me.
75: my $ForeignHostTab=""; # Name of foreign hosts table.
1.23 foxr 76:
77: my $DefaultServerPort = 5663; # Default server port if standalone.
1.21 foxr 78: my $ServerPort; # Port used to connect to lond.
1.18 foxr 79:
1.23 foxr 80: my $TransitionTimeout = 5; # Poll timeout in seconds.
81:
82:
1.24 ! foxr 83: # LondConnection::SetDebug(10);
1.23 foxr 84:
85:
1.13 foxr 86: #
87: # prints out utility's command usage info.
88: #
1.3 foxr 89: sub Usage {
1.2 foxr 90: print "Usage:";
91: print <<USAGE;
1.18 foxr 92: lonManage [--myname=host --hosts=table] --push=<tablename> newfile [host]
1.2 foxr 93: Push <tablename> to the lonTabs directory. Note that
94: <tablename> must be one of:
1.15 foxr 95: host (hosts.tab)
1.2 foxr 96: domain (domain.tab)
97:
1.18 foxr 98: lonManage [--myname=host --hosts=table] --reinit=lonc [host]
1.15 foxr 99: Causes lonc in the remote system to reread hosts.tab and
100: adjust the set of clients that are being maintained to match
101: the new file.
102:
1.2 foxr 103:
1.18 foxr 104: lonManage [--myname=host --hosts=table] --reinit=lond [host]
1.15 foxr 105: Causes lond in the remote system to reread the hosts.tab file
106: and adjust the set of servers to match changes in that file.
1.2 foxr 107:
108: In the above syntax, the host above is the hosts.tab name of a host,
109: not the IP address of the host.
1.16 foxr 110:
111: If [host] is omitted, all hosts in the hosts.tab file are iterated
112: over.
113:
1.18 foxr 114: For all of the above syntaxes if --myname=host and --hosts=table are
115: supplied (both must be present), the utility runs in standalone mode
116: presenting itself to the world as 'host' and using the hosts.tab file
117: specified in the --hosts switch.
1.2 foxr 118: USAGE
119:
120:
121: }
1.21 foxr 122:
1.23 foxr 123: #
124: # Make a direct connection to the lond in 'host'. The port is
125: # gotten from the global variable: ServerPort.
126: # Returns:
127: # The connection or undef if one could not be formed.
128: #
1.21 foxr 129: sub MakeLondConnection {
130: my $host = shift;
1.22 foxr 131:
132: my $Connection = LondConnection->new($host, $ServerPort);
133: return return $Connection;
1.21 foxr 134: }
1.23 foxr 135: #
136: # This function runs through the section of the connection
137: # state machine that has to do with negotiating the startup
138: # sequence with lond. The general strategy is to loop
139: # until the connection state becomes idle or disconnected.
140: # Disconnected indicates an error or rejection of the
141: # connection at some point in the negotiation.
142: # idle indicates a connection ready for a request.
143: # The main loop consults the object to determine if it
144: # wants to be writeable or readable, waits for that
145: # condition on the socket (with timeout) and then issues
146: # the appropriate LondConnection call. Note that
147: # LondConnection is capable of doing everything necessary
148: # to get to the initial idle state.
149: #
150: #
151: # Parameters:
152: # connection - A connection that has been created with
153: # the remote lond. This connection should
154: # be in the Connected state ready to send
155: # the init sequence.
156: #
1.21 foxr 157: sub NegotiateStartup {
158: my $connection = shift;
1.23 foxr 159: my $returnstatus = "ok"; # Optimistic!!.
160:
161: my $state = $connection->GetState;
162: if($state ne "Connected") {
163: print "Error: Initial lond connection state: $state should be Connected\n";
164: return "error";
165: }
166: my $Socket = $connection->GetSocket; # This is a IO:Socket::INET object.
167:
168: # Ready now to enter the main loop:
169: #
170: my $error = 0;
171: while (($connection->GetState ne "Idle") && (!$error)) {
172: #
173: # Wait for the socket to get into the appropriate state:
174: #
175: my $wantread = $connection->WantReadable;
176: my $poll = new IO::Poll;
177: $poll->mask($Socket => $wantread ? POLLIN : POLLOUT);
178: $poll->poll($TransitionTimeout);
179: my $done = $poll->handles();
180: if(scalar($done) == 0) { # Timeout!!!
181: print "Error: Timeout in state : $state negotiating connection\n";
182: $returnstatus = "error";
183: $error = 1;
184: } else {
185: my $status;
186: $status = $wantread ? $connection->Readable : $connection->Writable;
187: if ($status != 0) {
188: print "Error: I/O failed in state : $state negotiating connection\n";
189: $returnstatus = "error";
190: $error = 1;
191: }
192: }
193: }
1.21 foxr 194:
1.23 foxr 195:
196: return $returnstatus;
1.21 foxr 197: }
1.24 ! foxr 198: #
! 199: # Perform a transaction with the remote lond.
! 200: # Paramters:
! 201: # connection - the connection object that represents
! 202: # a LondConnection to the remote lond.
! 203: # command - The request to send to the remote system.
! 204: # Returns:
! 205: # The 'reaction' of the lond to this command.
! 206: # However if the connection to lond is lost during the transaction
! 207: # or some other error occurs, the text "error:con_lost" is returned.
! 208: #
1.21 foxr 209: sub PerformTransaction {
210: my $connection = shift;
211: my $command = shift;
1.24 ! foxr 212: my $retval; # What we'll returnl.
! 213:
! 214: # Set up the connection to do the transaction then
! 215: # do the I/O until idle or error.
! 216: #
! 217: $connection->InitiateTransaction($command);
! 218: my $error = 0;
! 219: my $Socket = $connection->GetSocket;
! 220: my $state;
! 221:
! 222: while (($connection->GetState ne "Idle") && (!$error)) {
! 223: #
! 224: # Wait for the socket to get into the appropriate state:
! 225: #
! 226: my $wantread = $connection->WantReadable;
! 227: my $poll = new IO::Poll;
! 228: $poll->mask($Socket => $wantread ? POLLIN : POLLOUT);
! 229: $poll->poll($TransitionTimeout);
! 230: my $done = $poll->handles();
! 231: if(scalar($done) == 0) { # Timeout!!!
! 232: print "Error: Timeout in state : $state negotiating connection\n";
! 233: $retval = "error";
! 234: $error = 1;
! 235: } else {
! 236: my $status;
! 237: $status = $wantread ? $connection->Readable : $connection->Writable;
! 238: if ($status != 0) {
! 239: print "Error: I/O failed in state : $state negotiating connection\n";
! 240: $retval = "error";
! 241: $error = 1;
! 242: }
! 243: }
! 244: }
! 245: #
! 246: # Fetch the reply from the transaction
! 247: #
! 248: if(! $error) {
! 249: $retval = $connection->GetReply;
! 250: }
1.21 foxr 251:
1.24 ! foxr 252: return $retval;
1.21 foxr 253: }
1.13 foxr 254: #
1.21 foxr 255: # Performs a transaction direct to a remote lond.
1.13 foxr 256: # Parameter:
257: # cmd - The text of the request.
258: # host - The host to which the request ultimately goes.
259: # Returns:
260: # The text of the reply from the lond or con_lost if not able to contact
261: # lond/lonc etc.
262: #
263: sub subreply {
1.21 foxr 264: my $cmd = shift;
265: my $host = shift;
266:
267:
268: my $connection = MakeLondConnection($host);
269: if ($connection eq undef) {
270: return "Connect Failed";
271: }
272: my $reply = NegotiateStartup($connection);
1.23 foxr 273: if($reply ne "ok") {
1.21 foxr 274: return "connection negotiation failed";
275: }
1.23 foxr 276: print "Connection negotiated\n";
1.21 foxr 277: my $reply = PerformTransaction($connection, $cmd);
278: return $reply;
279:
280:
281: # my ($cmd,$server)=@_;
282: # my $peerfile="$perlvar{'lonSockDir'}/$server";
283: # my $client=IO::Socket::UNIX->new(Peer =>"$peerfile",
284: # Type => SOCK_STREAM,
285: # Timeout => 10)
286: # or return "con_lost";
287: # print $client "$cmd\n";
288: # my $answer=<$client>;
289: # if (!$answer) { $answer="con_lost"; }
290: # chomp($answer);
291: # return $answer;
1.13 foxr 292: }
293: # >>> BUGBUG <<<
1.2 foxr 294: #
1.3 foxr 295: # Use Getopt::Long to parse the parameters of the program.
296: #
297: # Return value is a list consisting of:
298: # A 'command' which is one of:
299: # push - table push requested.
300: # reinit - reinit requested.
301: # Additional parameters as follows:
302: # for push: Tablename, hostname
303: # for reinit: Appname hostname
304: #
305: # This function does not validation of the parameters of push and
306: # reinit.
1.4 foxr 307: #
308: # returns a list. The first element of the list is the operation name
309: # (e.g. reinit or push). The second element is the switch parameter.
310: # for push, this is the table name, for reinit, this is the process name.
311: # Additional elements of the list are the command argument. The count of
312: # command arguments is validated, but not their semantics.
313: #
1.3 foxr 314: # returns an empty list if the parse fails.
315: #
316:
1.18 foxr 317:
1.3 foxr 318: sub ParseArgs {
1.4 foxr 319: my $pushing = '';
1.7 foxr 320: my $reinitting = '';
1.5 foxr 321:
1.4 foxr 322: if(!GetOptions('push=s' => \$pushing,
1.18 foxr 323: 'reinit=s' => \$reinitting,
324: 'myname=s' => \$MyHost,
325: 'hosts=s' => \$ForeignHostTab)) {
326: return ();
327: }
328: # The --myname and --hosts switch must have values and
329: # most both appear if either appears:
330:
331: if(($MyHost ne "") && ($ForeignHostTab eq "")) {
332: return ();
333: }
334: if(($ForeignHostTab ne "") && ($MyHost eq "")) {
1.4 foxr 335: return ();
336: }
337:
338: # Require exactly one of --push and --reinit
339:
1.5 foxr 340: my $command = '';
1.4 foxr 341: my $commandarg = '';
1.5 foxr 342: my $paramcount = @ARGV; # Number of additional arguments.
343:
344:
1.4 foxr 345: if($pushing ne '') {
1.5 foxr 346:
1.16 foxr 347: # --push takes in addition a table, and an optional host:
1.5 foxr 348: #
1.16 foxr 349: if(($paramcount != 2) && ($paramcount != 1)) {
1.5 foxr 350: return (); # Invalid parameter count.
351: }
1.4 foxr 352: if($command ne '') {
353: return ();
354: } else {
1.5 foxr 355:
1.4 foxr 356: $command = 'push';
357: $commandarg = $pushing;
358: }
359: }
1.5 foxr 360:
1.4 foxr 361: if ($reinitting ne '') {
1.5 foxr 362:
1.16 foxr 363: # --reinit takes in addition just an optional host name
1.5 foxr 364:
1.16 foxr 365: if($paramcount > 1) {
1.5 foxr 366: return ();
367: }
1.4 foxr 368: if($command ne '') {
369: return ();
370: } else {
371: $command = 'reinit';
372: $commandarg = $reinitting;
373: }
374: }
375:
1.5 foxr 376: # Build the result list:
377:
378: my @result = ($command, $commandarg);
379: my $i;
380: for($i = 0; $i < $paramcount; $i++) {
381: push(@result, $ARGV[$i]);
382: }
383:
384: return @result;
1.3 foxr 385: }
1.10 foxr 386: #
1.19 foxr 387: # Read the loncapa configuration stuff. If ForeignHostTab is empty,
388: # assume we are part of a loncapa cluster and read the hosts.tab
389: # file from the config directory. Otherwise, ForeignHossTab
390: # is the name of an alternate configuration file to read in
391: # standalone mode.
1.11 foxr 392: #
393: sub ReadConfig {
1.19 foxr 394:
395: if($ForeignHostTab eq "") {
396: my $perlvarref = LondConnection::read_conf('loncapa.conf');
397: %perlvar = %{$perlvarref};
398: my $hoststab = LondConnection::read_hosts(
399: "$perlvar{'lonTabDir'}/hosts.tab");
400: %hostshash = %{$hoststab};
1.20 foxr 401: $MyHost = $perlvar{lonHostID}; # Set hostname from vars.
1.21 foxr 402: $ServerPort = $perlvar{londPort};
1.19 foxr 403: } else {
1.23 foxr 404:
405: LondConnection::ReadForeignConfig($MyHost, $ForeignHostTab);
406: my $hoststab = LondConnection::read_hosts($ForeignHostTab); # we need to know too.
407: %hostshash = %{$hoststab};
408: $ServerPort = $DefaultServerPort;
409: }
410:
1.11 foxr 411: }
412: #
1.10 foxr 413: # Determine if the target host is valid.
414: # This is done by reading the current hosts.tab file.
415: # For the host to be valid, it must be inthe file.
416: #
417: # Parameters:
418: # host - Name of host to check on.
419: # Returns:
420: # true if host is valid.
421: # false if host is invalid.
422: #
1.8 foxr 423: sub ValidHost {
1.10 foxr 424: my $host = shift;
1.11 foxr 425:
1.10 foxr 426:
427: return defined $hostshash{$host};
428:
1.8 foxr 429: }
1.13 foxr 430:
431:
432:
1.12 foxr 433: #
434: # Performs a transaction with lonc.
435: # By the time this is called, the transaction has already been
436: # validated by the caller.
437: #
438: # Parameters:
439: #
440: # host - hosts.tab name of the host whose lonc we'll be talking to.
441: # command - The base command we'll be asking lond to execute.
442: # body - [optional] If supplied, this is a command body that is a ref.
443: # to an array of lines that will be appended to the
444: # command.
445: #
446: # NOTE:
447: # The command will be done as an encrypted operation.
448: #
1.8 foxr 449: sub Transact {
1.12 foxr 450: my $host = shift;
451: my $command = shift;
452: my $haveBody= 0;
453: my $body;
454: my $i;
455:
456: if(scalar @ARG) {
457: $body = shift;
458: $haveBody = 1;
459: }
460: # Construct the command to send to the server:
461:
462: my $request = "encrypt\:"; # All requests are encrypted.
463: $request .= $command;
464: if($haveBody) {
465: $request .= "\:";
466: my $bodylines = scalar @$body;
467: for($i = 0; $i < $bodylines; $i++) {
468: $request .= $$body[$i];
469: }
470: } else {
471: $request .= "\n";
472: }
1.13 foxr 473: # Body is now built... transact with lond..
474:
475: my $answer = subreply($request, $host);
476:
477: print "$answer\n";
1.10 foxr 478:
1.8 foxr 479: }
1.7 foxr 480: #
481: # Called to push a file to the remote system.
482: # The only legal files to push are hosts.tab and domain.tab.
483: # Security is somewhat improved by
484: #
485: # - Requiring the user run as root.
486: # - Connecting with lonc rather than lond directly ensuring this is a loncapa
487: # host
488: # - We must appear in the remote host's hosts.tab file.
489: # - The host must appear in our hosts.tab file.
490: #
491: # Parameters:
492: # tablename - must be one of hosts or domain.
493: # tablefile - name of the file containing the table to push.
494: # host - name of the host to push this file to.
495: #
1.13 foxr 496: # >>>BUGBUG<<< This belongs in lonnet.pm.
497: #
1.7 foxr 498: sub PushFile {
499: my $tablename = shift;
500: my $tablefile = shift;
501: my $host = shift;
502:
1.8 foxr 503: # Open the table file:
504:
505: if(!open(TABLEFILE, "<$tablefile")) {
506: die "ENOENT - No such file or directory $tablefile";
507: }
508:
509: # Require that the host be valid:
510:
511: if(!ValidHost($host)) {
512: die "EHOSTINVAL - Invalid host $host"; # Ok so I invented this 'errno'.
513: }
514: # Read in the file. If the table name is valid, push it.
515:
516: my @table = <TABLEFILE>; # These files are pretty small.
517: close TABLEFILE;
518:
519: if( ($tablename eq "host") ||
520: ($tablename eq "domain")) {
1.16 foxr 521: print("Pushing $tablename to $host\n");
1.12 foxr 522: Transact($host, "pushfile:$tablename",\@table);
1.8 foxr 523: } else {
524: die "EINVAL - Invalid parameter. tablename: $tablename must be host or domain";
525: }
1.7 foxr 526: }
1.9 foxr 527: #
528: # This function is called to reinitialize a server in a remote host.
529: # The servers that can be reinitialized are:
530: # - lonc - The lonc client process.
531: # - lond - The lond daemon.
532: # NOTE:
533: # Reinitialization in this case means re-scanning the hosts table,
534: # starting new lond/lonc's as approprate and stopping existing lonc/lond's.
535: #
536: # Parameters:
537: # process - The name of the process to reinit (lonc or lond).
538: # host - The host in which this reinit will happen.
539: #
1.13 foxr 540: # >>>BUGBUG<<<< This belongs in lonnet.pm
541: #
1.9 foxr 542: sub ReinitProcess {
543: my $process = shift;
544: my $host = shift;
1.3 foxr 545:
1.9 foxr 546: # Ensure the host is valid:
547:
548: if(!ValidHost($host)) {
549: die "EHOSTINVAL - Invalid host $host";
550: }
551: # Ensure target process selector is valid:
552:
553: if(($process eq "lonc") ||
554: ($process eq "lond")) {
1.16 foxr 555: print("Reinitializing $process in $host\n");
1.9 foxr 556: Transact($host, "reinit:$process");
557: } else {
558: die "EINVAL -Invalid parameter. Process $process must be lonc or lond";
559: }
1.7 foxr 560: }
1.6 foxr 561: #--------------------------- Entry point: --------------------------
562:
1.16 foxr 563:
564:
1.6 foxr 565: # Parse the parameters
566: # If command parsing failed, then print usage:
1.2 foxr 567:
1.7 foxr 568: my @params = ParseArgs;
569: my $nparam = @params;
1.3 foxr 570:
571: if($nparam == 0) {
1.2 foxr 572: Usage;
1.4 foxr 573: exit -1;
1.2 foxr 574: }
1.7 foxr 575: #
576: # Next, ensure we are running as EID root.
577: #
578: if ($EUID != 0) {
579: die "ENOPRIV - No privilege for requested operation"
1.6 foxr 580: }
581:
1.19 foxr 582: #
583: # Read the configuration file.
584: #
585:
586: ReadConfig; # Read the configuration info (incl.hosts).
1.4 foxr 587:
1.6 foxr 588: # Based on the operation requested invoke the appropriate function:
589:
1.7 foxr 590: my $operation = shift @params;
1.6 foxr 591:
592: if($operation eq "push") { # push tablename filename host
1.7 foxr 593: my $tablename = shift @params;
594: my $tablefile = shift @params;
595: my $host = shift @params;
1.16 foxr 596: if($host) {
597: PushFile($tablename, $tablefile, $host);
598: } else { # Push to whole cluster.
599: foreach my $host (keys %hostshash) {
600: PushFile($tablename, $tablefile, $host);
601: }
602: }
1.6 foxr 603:
1.7 foxr 604: } elsif($operation eq "reinit") { # reinit processname host.
605: my $process = shift @params;
606: my $host = shift @params;
1.16 foxr 607: if ($host) {
608: ReinitProcess($process, $host);
609: } else { # Reinit whole cluster.
610: foreach my $host (keys %hostshash) {
611: ReinitProcess($process,$host);
612: }
613: }
614: }
1.7 foxr 615: else {
616: Usage;
1.6 foxr 617: }
1.4 foxr 618: exit 0;
1.2 foxr 619:
620: =head1 NAME
621: lonManage - Command line utility for remote management of lonCAPA
622: cluster nodes.
623:
624: =head1 SYNOPSIS
625:
626: Usage:
1.3 foxr 627: B<lonManage --push=<tablename> newfile host>
1.2 foxr 628: Push <tablename> to the lonTabs directory. Note that
629: <tablename> must be one of:
630: hosts (hosts.tab)
631: domain (domain.tab)
632:
1.3 foxr 633: B<lonManage --reinit=lonc host>
1.2 foxr 634: Sends a HUP signal to the remote systems's lond.
635:
1.3 foxr 636: B<lonmanage --reinit=lond host>
1.2 foxr 637: Requests the remote system's lond perform the same action as if
638: it had received a HUP signal.
639:
640: In the above syntax, the host above is the hosts.tab name of a host,
641: not the IP address of the host.
642:
643:
644: =head1 DESCRIPTION
645:
646: =head1 PREREQUISITES
1.3 foxr 647:
1.7 foxr 648: =item strict
1.3 foxr 649: =item Getopt::Long
1.7 foxr 650: =item English
1.13 foxr 651: =item IO::Socket::UNIX
652:
653: =head1 KEY Subroutines.
1.2 foxr 654:
655: =head1 CATEGORIES
656: Command line utility
657:
658: =cut
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>