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.22 2003/11/03 10:48:18 foxr Exp $
7: #
8: # $Id: lonManage,v 1.22 2003/11/03 10:48:18 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: #
56:
57:
58:
59: # Modules required:
60:
61: use lib ".";
62:
63: use strict; # Because it's good practice.
64: use English; # Cause I like meaningful names.
65: use Getopt::Long;
66: use IO::Socket::UNIX; # To communicate with lonc.
67: use LondConnection;
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.
73:
74: my $MyHost=""; # Host name to use as me.
75: my $ForeignHostTab=""; # Name of foreign hosts table.
76: my $ServerPort; # Port used to connect to lond.
77:
78: #
79: # prints out utility's command usage info.
80: #
81: sub Usage {
82: print "Usage:";
83: print <<USAGE;
84: lonManage [--myname=host --hosts=table] --push=<tablename> newfile [host]
85: Push <tablename> to the lonTabs directory. Note that
86: <tablename> must be one of:
87: host (hosts.tab)
88: domain (domain.tab)
89:
90: lonManage [--myname=host --hosts=table] --reinit=lonc [host]
91: Causes lonc in the remote system to reread hosts.tab and
92: adjust the set of clients that are being maintained to match
93: the new file.
94:
95:
96: lonManage [--myname=host --hosts=table] --reinit=lond [host]
97: Causes lond in the remote system to reread the hosts.tab file
98: and adjust the set of servers to match changes in that file.
99:
100: In the above syntax, the host above is the hosts.tab name of a host,
101: not the IP address of the host.
102:
103: If [host] is omitted, all hosts in the hosts.tab file are iterated
104: over.
105:
106: For all of the above syntaxes if --myname=host and --hosts=table are
107: supplied (both must be present), the utility runs in standalone mode
108: presenting itself to the world as 'host' and using the hosts.tab file
109: specified in the --hosts switch.
110: USAGE
111:
112:
113: }
114:
115: sub MakeLondConnection {
116: my $host = shift;
117:
118: my $Connection = LondConnection->new($host, $ServerPort);
119: return return $Connection;
120: }
121:
122: sub NegotiateStartup {
123: my $connection = shift;
124:
125: return "ok";
126: }
127: sub PerformTransaction {
128: my $connection = shift;
129: my $command = shift;
130:
131: return "ok";
132: }
133: #
134: # Performs a transaction direct to a remote lond.
135: # Parameter:
136: # cmd - The text of the request.
137: # host - The host to which the request ultimately goes.
138: # Returns:
139: # The text of the reply from the lond or con_lost if not able to contact
140: # lond/lonc etc.
141: #
142: sub subreply {
143: my $cmd = shift;
144: my $host = shift;
145:
146:
147: my $connection = MakeLondConnection($host);
148: if ($connection eq undef) {
149: return "Connect Failed";
150: }
151: my $reply = NegotiateStartup($connection);
152: if($reply != "ok") {
153: return "connection negotiation failed";
154: }
155: my $reply = PerformTransaction($connection, $cmd);
156: return $reply;
157:
158:
159: # my ($cmd,$server)=@_;
160: # my $peerfile="$perlvar{'lonSockDir'}/$server";
161: # my $client=IO::Socket::UNIX->new(Peer =>"$peerfile",
162: # Type => SOCK_STREAM,
163: # Timeout => 10)
164: # or return "con_lost";
165: # print $client "$cmd\n";
166: # my $answer=<$client>;
167: # if (!$answer) { $answer="con_lost"; }
168: # chomp($answer);
169: # return $answer;
170: }
171: # >>> BUGBUG <<<
172: #
173: # Use Getopt::Long to parse the parameters of the program.
174: #
175: # Return value is a list consisting of:
176: # A 'command' which is one of:
177: # push - table push requested.
178: # reinit - reinit requested.
179: # Additional parameters as follows:
180: # for push: Tablename, hostname
181: # for reinit: Appname hostname
182: #
183: # This function does not validation of the parameters of push and
184: # reinit.
185: #
186: # returns a list. The first element of the list is the operation name
187: # (e.g. reinit or push). The second element is the switch parameter.
188: # for push, this is the table name, for reinit, this is the process name.
189: # Additional elements of the list are the command argument. The count of
190: # command arguments is validated, but not their semantics.
191: #
192: # returns an empty list if the parse fails.
193: #
194:
195:
196: sub ParseArgs {
197: my $pushing = '';
198: my $reinitting = '';
199:
200: if(!GetOptions('push=s' => \$pushing,
201: 'reinit=s' => \$reinitting,
202: 'myname=s' => \$MyHost,
203: 'hosts=s' => \$ForeignHostTab)) {
204: return ();
205: }
206: # The --myname and --hosts switch must have values and
207: # most both appear if either appears:
208:
209: if(($MyHost ne "") && ($ForeignHostTab eq "")) {
210: return ();
211: }
212: if(($ForeignHostTab ne "") && ($MyHost eq "")) {
213: return ();
214: }
215:
216: # Require exactly one of --push and --reinit
217:
218: my $command = '';
219: my $commandarg = '';
220: my $paramcount = @ARGV; # Number of additional arguments.
221:
222:
223: if($pushing ne '') {
224:
225: # --push takes in addition a table, and an optional host:
226: #
227: if(($paramcount != 2) && ($paramcount != 1)) {
228: return (); # Invalid parameter count.
229: }
230: if($command ne '') {
231: return ();
232: } else {
233:
234: $command = 'push';
235: $commandarg = $pushing;
236: }
237: }
238:
239: if ($reinitting ne '') {
240:
241: # --reinit takes in addition just an optional host name
242:
243: if($paramcount > 1) {
244: return ();
245: }
246: if($command ne '') {
247: return ();
248: } else {
249: $command = 'reinit';
250: $commandarg = $reinitting;
251: }
252: }
253:
254: # Build the result list:
255:
256: my @result = ($command, $commandarg);
257: my $i;
258: for($i = 0; $i < $paramcount; $i++) {
259: push(@result, $ARGV[$i]);
260: }
261:
262: return @result;
263: }
264: #
265: # Read the loncapa configuration stuff. If ForeignHostTab is empty,
266: # assume we are part of a loncapa cluster and read the hosts.tab
267: # file from the config directory. Otherwise, ForeignHossTab
268: # is the name of an alternate configuration file to read in
269: # standalone mode.
270: #
271: sub ReadConfig {
272:
273: if($ForeignHostTab eq "") {
274: my $perlvarref = LondConnection::read_conf('loncapa.conf');
275: %perlvar = %{$perlvarref};
276: my $hoststab = LondConnection::read_hosts(
277: "$perlvar{'lonTabDir'}/hosts.tab");
278: %hostshash = %{$hoststab};
279: $MyHost = $perlvar{lonHostID}; # Set hostname from vars.
280: $ServerPort = $perlvar{londPort};
281: } else {
282: my $hoststab = LondConnection::read_hosts($ForeignHostTab);
283: %hostshash = %{$hoststab};
284: $ServerPort = 5663;
285: }
286:
287: }
288: #
289: # Determine if the target host is valid.
290: # This is done by reading the current hosts.tab file.
291: # For the host to be valid, it must be inthe file.
292: #
293: # Parameters:
294: # host - Name of host to check on.
295: # Returns:
296: # true if host is valid.
297: # false if host is invalid.
298: #
299: sub ValidHost {
300: my $host = shift;
301:
302:
303: return defined $hostshash{$host};
304:
305: }
306:
307:
308:
309: #
310: # Performs a transaction with lonc.
311: # By the time this is called, the transaction has already been
312: # validated by the caller.
313: #
314: # Parameters:
315: #
316: # host - hosts.tab name of the host whose lonc we'll be talking to.
317: # command - The base command we'll be asking lond to execute.
318: # body - [optional] If supplied, this is a command body that is a ref.
319: # to an array of lines that will be appended to the
320: # command.
321: #
322: # NOTE:
323: # The command will be done as an encrypted operation.
324: #
325: sub Transact {
326: my $host = shift;
327: my $command = shift;
328: my $haveBody= 0;
329: my $body;
330: my $i;
331:
332: if(scalar @ARG) {
333: $body = shift;
334: $haveBody = 1;
335: }
336: # Construct the command to send to the server:
337:
338: my $request = "encrypt\:"; # All requests are encrypted.
339: $request .= $command;
340: if($haveBody) {
341: $request .= "\:";
342: my $bodylines = scalar @$body;
343: for($i = 0; $i < $bodylines; $i++) {
344: $request .= $$body[$i];
345: }
346: } else {
347: $request .= "\n";
348: }
349: # Body is now built... transact with lond..
350:
351: my $answer = subreply($request, $host);
352:
353: print "$answer\n";
354:
355: }
356: #
357: # Called to push a file to the remote system.
358: # The only legal files to push are hosts.tab and domain.tab.
359: # Security is somewhat improved by
360: #
361: # - Requiring the user run as root.
362: # - Connecting with lonc rather than lond directly ensuring this is a loncapa
363: # host
364: # - We must appear in the remote host's hosts.tab file.
365: # - The host must appear in our hosts.tab file.
366: #
367: # Parameters:
368: # tablename - must be one of hosts or domain.
369: # tablefile - name of the file containing the table to push.
370: # host - name of the host to push this file to.
371: #
372: # >>>BUGBUG<<< This belongs in lonnet.pm.
373: #
374: sub PushFile {
375: my $tablename = shift;
376: my $tablefile = shift;
377: my $host = shift;
378:
379: # Open the table file:
380:
381: if(!open(TABLEFILE, "<$tablefile")) {
382: die "ENOENT - No such file or directory $tablefile";
383: }
384:
385: # Require that the host be valid:
386:
387: if(!ValidHost($host)) {
388: die "EHOSTINVAL - Invalid host $host"; # Ok so I invented this 'errno'.
389: }
390: # Read in the file. If the table name is valid, push it.
391:
392: my @table = <TABLEFILE>; # These files are pretty small.
393: close TABLEFILE;
394:
395: if( ($tablename eq "host") ||
396: ($tablename eq "domain")) {
397: print("Pushing $tablename to $host\n");
398: Transact($host, "pushfile:$tablename",\@table);
399: } else {
400: die "EINVAL - Invalid parameter. tablename: $tablename must be host or domain";
401: }
402: }
403: #
404: # This function is called to reinitialize a server in a remote host.
405: # The servers that can be reinitialized are:
406: # - lonc - The lonc client process.
407: # - lond - The lond daemon.
408: # NOTE:
409: # Reinitialization in this case means re-scanning the hosts table,
410: # starting new lond/lonc's as approprate and stopping existing lonc/lond's.
411: #
412: # Parameters:
413: # process - The name of the process to reinit (lonc or lond).
414: # host - The host in which this reinit will happen.
415: #
416: # >>>BUGBUG<<<< This belongs in lonnet.pm
417: #
418: sub ReinitProcess {
419: my $process = shift;
420: my $host = shift;
421:
422: # Ensure the host is valid:
423:
424: if(!ValidHost($host)) {
425: die "EHOSTINVAL - Invalid host $host";
426: }
427: # Ensure target process selector is valid:
428:
429: if(($process eq "lonc") ||
430: ($process eq "lond")) {
431: print("Reinitializing $process in $host\n");
432: Transact($host, "reinit:$process");
433: } else {
434: die "EINVAL -Invalid parameter. Process $process must be lonc or lond";
435: }
436: }
437: #--------------------------- Entry point: --------------------------
438:
439:
440:
441: # Parse the parameters
442: # If command parsing failed, then print usage:
443:
444: my @params = ParseArgs;
445: my $nparam = @params;
446:
447: if($nparam == 0) {
448: Usage;
449: exit -1;
450: }
451: #
452: # Next, ensure we are running as EID root.
453: #
454: if ($EUID != 0) {
455: die "ENOPRIV - No privilege for requested operation"
456: }
457:
458: #
459: # Read the configuration file.
460: #
461:
462: ReadConfig; # Read the configuration info (incl.hosts).
463:
464: # Based on the operation requested invoke the appropriate function:
465:
466: my $operation = shift @params;
467:
468: if($operation eq "push") { # push tablename filename host
469: my $tablename = shift @params;
470: my $tablefile = shift @params;
471: my $host = shift @params;
472: if($host) {
473: PushFile($tablename, $tablefile, $host);
474: } else { # Push to whole cluster.
475: foreach my $host (keys %hostshash) {
476: PushFile($tablename, $tablefile, $host);
477: }
478: }
479:
480: } elsif($operation eq "reinit") { # reinit processname host.
481: my $process = shift @params;
482: my $host = shift @params;
483: if ($host) {
484: ReinitProcess($process, $host);
485: } else { # Reinit whole cluster.
486: foreach my $host (keys %hostshash) {
487: ReinitProcess($process,$host);
488: }
489: }
490: }
491: else {
492: Usage;
493: }
494: exit 0;
495:
496: =head1 NAME
497: lonManage - Command line utility for remote management of lonCAPA
498: cluster nodes.
499:
500: =head1 SYNOPSIS
501:
502: Usage:
503: B<lonManage --push=<tablename> newfile host>
504: Push <tablename> to the lonTabs directory. Note that
505: <tablename> must be one of:
506: hosts (hosts.tab)
507: domain (domain.tab)
508:
509: B<lonManage --reinit=lonc host>
510: Sends a HUP signal to the remote systems's lond.
511:
512: B<lonmanage --reinit=lond host>
513: Requests the remote system's lond perform the same action as if
514: it had received a HUP signal.
515:
516: In the above syntax, the host above is the hosts.tab name of a host,
517: not the IP address of the host.
518:
519:
520: =head1 DESCRIPTION
521:
522: =head1 PREREQUISITES
523:
524: =item strict
525: =item Getopt::Long
526: =item English
527: =item IO::Socket::UNIX
528:
529: =head1 KEY Subroutines.
530:
531: =head1 CATEGORIES
532: Command line utility
533:
534: =cut
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>