![]() ![]() | ![]() |
Add stubs and base logic for --edit switch.
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.26 2003/12/22 11:02:36 foxr Exp $ 7: # 8: # $Id: lonManage,v 1.26 2003/12/22 11:02:36 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 LondConnection; 67: use IO::Poll qw(POLLRDNORM POLLWRNORM POLLIN POLLHUP POLLOUT); 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: 77: my $DefaultServerPort = 5663; # Default server port if standalone. 78: my $ServerPort; # Port used to connect to lond. 79: 80: my $TransitionTimeout = 5; # Poll timeout in seconds. 81: 82: 83: # LondConnection::SetDebug(10); 84: 85: 86: # 87: # prints out utility's command usage info. 88: # 89: sub Usage { 90: print "Usage:"; 91: print <<USAGE; 92: lonManage [--myname=host --hosts=table] --push=<tablename> newfile [host] 93: Push <tablename> to the lonTabs directory. Note that 94: <tablename> must be one of: 95: host (hosts.tab) 96: domain (domain.tab) 97: 98: lonManage [--myname=host --hosts=table] --reinit=lonc [host] 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: 103: 104: lonManage [--myname=host --hosts=table] --reinit=lond [host] 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. 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. 110: 111: If [host] is omitted, all hosts in the hosts.tab file are iterated 112: over. 113: 114: lonManage [--myname=host --hosts=table] --edit=<tablename> editscript [host] 115: Requests lond edit the hosts or domain table (selected by 116: tablename) with the editing command in editscript. If 117: host is supplied the individual host is operated on, 118: otherwise, the entire cluster is operated on. 119: The edit file has edit request, one per line of the form: 120: append|newline 121: replace|key|newline 122: delete|key 123: The key is a loncapa hostname if editing the host file 124: or a domain name if editing the domain table file. 125: 126: For all of the above syntaxes if --myname=host and --hosts=table are 127: supplied (both must be present), the utility runs in standalone mode 128: presenting itself to the world as 'host' and using the hosts.tab file 129: specified in the --hosts switch. 130: USAGE 131: 132: 133: } 134: 135: # 136: # Make a direct connection to the lond in 'host'. The port is 137: # gotten from the global variable: ServerPort. 138: # Returns: 139: # The connection or undef if one could not be formed. 140: # 141: sub MakeLondConnection { 142: my $host = shift; 143: 144: my $Connection = LondConnection->new($host, $ServerPort); 145: return return $Connection; 146: } 147: # 148: # Process the connection state machine until the connection 149: # becomes idle. This is used both to negotiate the initial 150: # connection, during which the LondConnection sequences a rather 151: # complex state machine and during the transaction itself 152: # for a simpler set of transitions. 153: # All we really need to be concerned with is whether or not 154: # we're readable or writable and the final state: 155: # 156: # Parameter: 157: # connection - Represents the LondConnection to be sequenced. 158: # timeout - Maximum time to wait for readable/writable sockets. 159: # in seconds. < 0 waits forever. 160: # Return: 161: # 'ok' - We got to idle ok. 162: # 'error:msg' - An error occured. msg describes the error. 163: # 164: sub SequenceStateMachine { 165: my $connection = shift; 166: my $timeout = shift; 167: 168: my $Socket = $connection->GetSocket; 169: my $returnstatus = "ok"; # optimist!!! 170: my $error = 0; # Used to force early loop termination 171: # damned perl has no break!!. 172: my $state = $connection->GetState; 173: 174: while(($connection->GetState ne "Idle") && (!$error)) { 175: # 176: # Figure out what the connection wants. read/write and wait for it 177: # or for the timeout. 178: # 179: my $wantread = $connection->WantReadable; 180: my $poll = new IO::Poll; 181: $poll->mask($Socket, => $wantread ? POLLIN : POLLOUT); 182: my $handlecount = $poll->poll($timeout); 183: if($handlecount == 0) { # no handles ready... timeout!! 184: $returnstatus = "error:"; 185: $returnstatus .= "Timeout in state $state\n"; 186: $error = 1; 187: } else { 188: my $done = $poll->handles(); 189: my $status; 190: $status = $wantread ? $connection->Readable : 191: $connection->Writable; 192: if($status != 0) { 193: $returnstatus = "error:"; 194: $returnstatus .= " I/O failed in state $state\n"; 195: $error = 1; 196: } 197: } 198: $state = $connection->GetState; 199: } 200: return $returnstatus; 201: } 202: 203: # 204: # This function runs through the section of the connection 205: # state machine that has to do with negotiating the startup 206: # sequence with lond. The general strategy is to loop 207: # until the connection state becomes idle or disconnected. 208: # Disconnected indicates an error or rejection of the 209: # connection at some point in the negotiation. 210: # idle indicates a connection ready for a request. 211: # The main loop consults the object to determine if it 212: # wants to be writeable or readable, waits for that 213: # condition on the socket (with timeout) and then issues 214: # the appropriate LondConnection call. Note that 215: # LondConnection is capable of doing everything necessary 216: # to get to the initial idle state. 217: # 218: # 219: # Parameters: 220: # connection - A connection that has been created with 221: # the remote lond. This connection should 222: # be in the Connected state ready to send 223: # the init sequence. 224: # 225: sub NegotiateStartup { 226: my $connection = shift; 227: my $returnstatus = "ok"; # Optimistic!!. 228: 229: my $state = $connection->GetState; 230: if($state ne "Connected") { 231: print "Error: Initial lond connection state: $state should be Connected\n"; 232: return "error"; 233: } 234: 235: return SequenceStateMachine($connection, $TransitionTimeout); 236: } 237: # 238: # Perform a transaction with the remote lond. 239: # Paramters: 240: # connection - the connection object that represents 241: # a LondConnection to the remote lond. 242: # command - The request to send to the remote system. 243: # Returns: 244: # The 'reaction' of the lond to this command. 245: # However if the connection to lond is lost during the transaction 246: # or some other error occurs, the text "error:con_lost" is returned. 247: # 248: sub PerformTransaction { 249: my $connection = shift; 250: my $command = shift; 251: my $retval; # What we'll returnl. 252: 253: 254: # Set up the connection to do the transaction then 255: # do the I/O until idle or error. 256: # 257: $connection->InitiateTransaction($command); 258: 259: my $status = SequenceStateMachine($connection, $TransitionTimeout); 260: if($status eq "ok") { 261: $retval = $connection->GetReply; 262: } else { 263: $retval = $status; 264: } 265: 266: return $retval; 267: } 268: # 269: # Performs a transaction direct to a remote lond. 270: # Parameter: 271: # cmd - The text of the request. 272: # host - The host to which the request ultimately goes. 273: # Returns: 274: # The text of the reply from the lond or con_lost if not able to contact 275: # lond/lonc etc. 276: # 277: sub subreply { 278: my $cmd = shift; 279: my $host = shift; 280: 281: 282: my $connection = MakeLondConnection($host); 283: if ($connection eq undef) { 284: return "Connect Failed"; 285: } 286: my $reply = NegotiateStartup($connection); 287: if($reply ne "ok") { 288: return "connection negotiation failed"; 289: } 290: my $reply = PerformTransaction($connection, $cmd); 291: return $reply; 292: 293: 294: } 295: # 296: # Use Getopt::Long to parse the parameters of the program. 297: # 298: # Return value is a list consisting of: 299: # A 'command' which is one of: 300: # push - table push requested. 301: # reinit - reinit requested. 302: # Additional parameters as follows: 303: # for push: Tablename, hostname 304: # for reinit: Appname hostname 305: # 306: # This function does not validation of the parameters of push and 307: # reinit. 308: # 309: # returns a list. The first element of the list is the operation name 310: # (e.g. reinit or push). The second element is the switch parameter. 311: # for push, this is the table name, for reinit, this is the process name. 312: # Additional elements of the list are the command argument. The count of 313: # command arguments is validated, but not their semantics. 314: # 315: # returns an empty list if the parse fails. 316: # 317: 318: 319: sub ParseArgs { 320: my $pushing = ''; 321: my $reinitting = ''; 322: my $editing = ''; 323: 324: if(!GetOptions('push=s' => \$pushing, 325: 'reinit=s' => \$reinitting, 326: 'edit=s' => \$editing, 327: 'myname=s' => \$MyHost, 328: 'hosts=s' => \$ForeignHostTab)) { 329: return (); 330: } 331: # The --myname and --hosts switch must have values and 332: # most both appear if either appears: 333: 334: if(($MyHost ne "") && ($ForeignHostTab eq "")) { 335: return (); 336: } 337: if(($ForeignHostTab ne "") && ($MyHost eq "")) { 338: return (); 339: } 340: 341: # Require exactly one of --push, --reinit, or --edit 342: 343: my $command = ''; 344: my $commandarg = ''; 345: my $paramcount = @ARGV; # Number of additional arguments. 346: 347: my $commands = 0; # Number of commands seen. 348: 349: if($pushing ne '') { 350: 351: # --push takes in addition a table, and an optional host: 352: # 353: 354: if(($paramcount != 2) && ($paramcount != 1)) { 355: return (); # Invalid parameter count. 356: } 357: 358: $commands++; # Count a command seen. 359: $command = 'push'; 360: $commandarg = $pushing; 361: } 362: 363: if ($reinitting ne '') { 364: 365: # --reinit takes in addition just an optional host name 366: 367: if($paramcount > 1) { 368: return (); 369: } 370: $commands++; # Count a command seen. 371: $command = 'reinit'; 372: $commandarg = $reinitting; 373: } 374: 375: # --edit takes a script file and optional host name. 376: # 377: if ($editing ne "") { 378: if(($paramcount != 2) && ($paramcount != 1)) { 379: return (); # Invalid parameter count. 380: } 381: 382: $commands++; # Count a command seen. 383: $command = 'edit'; 384: $commandarg = $editing; 385: } 386: 387: # At this point, $commands must be 1 or else we've seen 388: # The wrong number of command switches: 389: 390: if($commands != 1) { 391: return (); 392: } 393: 394: # Build the result list: 395: 396: my @result = ($command, $commandarg); 397: my $i; 398: for($i = 0; $i < $paramcount; $i++) { 399: push(@result, $ARGV[$i]); 400: } 401: 402: return @result; 403: } 404: # 405: # Build the editor script. This function: 406: # - Opens the edit script file. 407: # - Reads each line of the edit script file 408: # - Replaces the ending \n with a / 409: # - Appends it to the EditScript variable. 410: # - Returns the contents of the EditScript variable. 411: # Parameters: 412: # tabletype - The type of table being built: 413: # hosts or domain 414: # scriptname - The script input file. 415: # 416: sub BuildEditScript { 417: my $TableType = shift; 418: my $ScriptName = shift; 419: 420: #Stub 421: 422: my @EditScript = ( 423: "$TableType\:append|". 424: "nscll2\:nscl\:library\:lonkashy.nscl.msu.edu\:35.8.32.89" 425: ); 426: return \@EditScript; 427: } 428: # Read the loncapa configuration stuff. If ForeignHostTab is empty, 429: # assume we are part of a loncapa cluster and read the hosts.tab 430: # file from the config directory. Otherwise, ForeignHossTab 431: # is the name of an alternate configuration file to read in 432: # standalone mode. 433: # 434: sub ReadConfig { 435: 436: 437: 438: if($ForeignHostTab eq "") { 439: my $perlvarref = LondConnection::read_conf('loncapa.conf'); 440: %perlvar = %{$perlvarref}; 441: my $hoststab = LondConnection::read_hosts( 442: "$perlvar{lonTabDir}/hosts.tab"); 443: %hostshash = %{$hoststab}; 444: $MyHost = $perlvar{lonHostID}; # Set hostname from vars. 445: $ServerPort = $perlvar{londPort}; 446: } else { 447: 448: LondConnection::ReadForeignConfig($MyHost, 449: $ForeignHostTab); 450: my $hoststab = LondConnection::read_hosts($ForeignHostTab); # we need to know too. 451: %hostshash = %{$hoststab}; 452: $ServerPort = $DefaultServerPort; 453: } 454: } 455: # 456: # Determine if the target host is valid. 457: # This is done by reading the current hosts.tab file. 458: # For the host to be valid, it must be inthe file. 459: # 460: # Parameters: 461: # host - Name of host to check on. 462: # Returns: 463: # true if host is valid. 464: # false if host is invalid. 465: # 466: sub ValidHost { 467: my $host = shift; 468: 469: 470: return defined $hostshash{$host}; 471: 472: } 473: 474: 475: 476: # 477: # Performs a transaction with lonc. 478: # By the time this is called, the transaction has already been 479: # validated by the caller. 480: # 481: # Parameters: 482: # 483: # host - hosts.tab name of the host whose lonc we'll be talking to. 484: # command - The base command we'll be asking lond to execute. 485: # body - [optional] If supplied, this is a command body that is a ref. 486: # to an array of lines that will be appended to the 487: # command. 488: # 489: # NOTE: 490: # The command will be done as an encrypted operation. 491: # 492: sub Transact { 493: my $host = shift; 494: my $command = shift; 495: my $haveBody= 0; 496: my $body; 497: my $i; 498: 499: if(scalar @ARG) { 500: $body = shift; 501: $haveBody = 1; 502: } 503: # Construct the command to send to the server: 504: 505: my $request = "encrypt\:"; # All requests are encrypted. 506: $request .= $command; 507: if($haveBody) { 508: $request .= "\:"; 509: my $bodylines = scalar @$body; 510: for($i = 0; $i < $bodylines; $i++) { 511: $request .= $$body[$i]; 512: } 513: } else { 514: $request .= "\n"; 515: } 516: # Body is now built... transact with lond.. 517: 518: my $answer = subreply($request, $host); 519: 520: print "$answer\n"; 521: 522: } 523: # 524: # Called to push a file to the remote system. 525: # The only legal files to push are hosts.tab and domain.tab. 526: # Security is somewhat improved by 527: # 528: # - Requiring the user run as root. 529: # - Connecting with lonc rather than lond directly ensuring this is a loncapa 530: # host 531: # - We must appear in the remote host's hosts.tab file. 532: # - The host must appear in our hosts.tab file. 533: # 534: # Parameters: 535: # tablename - must be one of hosts or domain. 536: # tablefile - name of the file containing the table to push. 537: # host - name of the host to push this file to. 538: # 539: # 540: # 541: sub PushFile { 542: my $tablename = shift; 543: my $tablefile = shift; 544: my $host = shift; 545: 546: # Open the table file: 547: 548: if(!open(TABLEFILE, "<$tablefile")) { 549: die "ENOENT - No such file or directory $tablefile"; 550: } 551: 552: # Require that the host be valid: 553: 554: if(!ValidHost($host)) { 555: die "EHOSTINVAL - Invalid host $host"; # Ok so I invented this 'errno'. 556: } 557: # Read in the file. If the table name is valid, push it. 558: 559: my @table = <TABLEFILE>; # These files are pretty small. 560: close TABLEFILE; 561: 562: if( ($tablename eq "host") || 563: ($tablename eq "domain")) { 564: print("Pushing $tablename to $host\n"); 565: Transact($host, "pushfile:$tablename",\@table); 566: } else { 567: die "EINVAL - Invalid parameter. tablename: $tablename must be host or domain"; 568: } 569: } 570: # 571: # This function forms and executes an edit file with a 572: # remote lond server. We build the full transaction string 573: # and use Transact to perform the transaction. 574: # Paramters: 575: # host - loncapa name of host to operate on. 576: # body - Body of the command. We send: 577: # edit:$body as the command request. 578: # 579: sub EditFile { 580: my $host = shift; 581: my $body = shift; 582: 583: if(!ValidHost($host)) { 584: die "EHOSTINVAL - Invalid host $host"; 585: } 586: Transact($host, "edit", $body); 587: } 588: 589: # 590: # This function is called to reinitialize a server in a remote host. 591: # The servers that can be reinitialized are: 592: # - lonc - The lonc client process. 593: # - lond - The lond daemon. 594: # NOTE: 595: # Reinitialization in this case means re-scanning the hosts table, 596: # starting new lond/lonc's as approprate and stopping existing lonc/lond's. 597: # 598: # Parameters: 599: # process - The name of the process to reinit (lonc or lond). 600: # host - The host in which this reinit will happen. 601: # 602: # >>>BUGBUG<<<< This belongs in lonnet.pm 603: # 604: sub ReinitProcess { 605: my $process = shift; 606: my $host = shift; 607: 608: # Ensure the host is valid: 609: 610: if(!ValidHost($host)) { 611: die "EHOSTINVAL - Invalid host $host"; 612: } 613: # Ensure target process selector is valid: 614: 615: if(($process eq "lonc") || 616: ($process eq "lond")) { 617: print("Reinitializing $process in $host\n"); 618: Transact($host, "reinit:$process"); 619: } else { 620: die "EINVAL -Invalid parameter. Process $process must be lonc or lond"; 621: } 622: } 623: #--------------------------- Entry point: -------------------------- 624: 625: 626: 627: # Parse the parameters 628: # If command parsing failed, then print usage: 629: 630: my @params = ParseArgs; 631: my $nparam = @params; 632: 633: if($nparam == 0) { 634: Usage; 635: exit -1; 636: } 637: # 638: # Next, ensure we are running as EID root. 639: # 640: if ($EUID != 0) { 641: die "ENOPRIV - No privilege for requested operation" 642: } 643: 644: # 645: # Read the configuration file. 646: # 647: 648: ReadConfig; # Read the configuration info (incl.hosts). 649: 650: # Based on the operation requested invoke the appropriate function: 651: 652: my $operation = shift @params; 653: 654: if($operation eq "push") { # push tablename filename host 655: my $tablename = shift @params; 656: my $tablefile = shift @params; 657: my $host = shift @params; 658: if($host) { 659: PushFile($tablename, $tablefile, $host); 660: } else { # Push to whole cluster. 661: foreach my $host (keys %hostshash) { 662: PushFile($tablename, $tablefile, $host); 663: } 664: } 665: 666: } elsif($operation eq "reinit") { # reinit processname host. 667: my $process = shift @params; 668: my $host = shift @params; 669: if ($host) { 670: ReinitProcess($process, $host); 671: } else { # Reinit whole cluster. 672: foreach my $host (keys %hostshash) { 673: ReinitProcess($process,$host); 674: } 675: } 676: } elsif($operation eq "edit") { # Edit a table. 677: my $tablename = shift @params; 678: my $scriptfile = shift @params; 679: my $host = shift @params; 680: my $CommandBody = BuildEditScript($tablename, $scriptfile); 681: if ($host) { 682: EditFile($host, $CommandBody); 683: } else { 684: foreach my $ClusterMember (keys %hostshash) { 685: EditFile($ClusterMember, $CommandBody); 686: } 687: } 688: } 689: else { 690: Usage; 691: } 692: exit 0; 693: 694: =head1 NAME 695: lonManage - Command line utility for remote management of lonCAPA 696: cluster nodes. 697: 698: =head1 SYNOPSIS 699: 700: Usage: 701: B<lonManage --push=<tablename> newfile host> 702: Push <tablename> to the lonTabs directory. Note that 703: <tablename> must be one of: 704: hosts (hosts.tab) 705: domain (domain.tab) 706: 707: B<lonManage --reinit=lonc host> 708: Sends a HUP signal to the remote systems's lond. 709: 710: B<lonManage --reinit=lond host> 711: Requests the remote system's lond perform the same action as if 712: it had received a HUP signal. 713: 714: B<lonManage --edit=<tablename> editscript host> 715: Requests the remote system's lond perform an edit 716: on <tablename> editscript supplies a set of 717: editing commands. Each edit command is one of : 718: 719: append|key|newline 720: delete|key| 721: replace|key|newline 722: 723: The key above is the value of the loncapa host name 724: in the file. 725: 726: In the above syntax, the host above is the 727: hosts.tab name of a host, 728: not the IP address of the host. 729: 730: 731: =head1 DESCRIPTION 732: 733: =head1 PREREQUISITES 734: 735: =item strict 736: =item Getopt::Long 737: =item English 738: =item IO::Socket::UNIX 739: =item LONCAPA::LondConnection 740: 741: =head1 KEY Subroutines. 742: 743: =head1 CATEGORIES 744: Command line utility 745: 746: =cut