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>