#!/usr/bin/perl
#
# This script is a component of Warewulf,
# http://www.runlevelzero.net/greg/warewulf
#
#########################################################################
#
# Copyright (c) 2003, The Regents of the University of California, through
# Lawrence Berkeley National Laboratory (subject to receipt of any
# required approvals from the U.S. Dept. of Energy).  All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# The GNU GPL Document can be found at:
# http://www.gnu.org/copyleft/gpl.html
#
#########################################################################
#
# Written and maintained by:
#       Greg Kurtzer, <gmkurtzer@lbl.gov>

use IO::Socket;
use Net::hostent;

BEGIN {
   push (@INC, "/usr/lib/warewulf/");
   push (@INC, "/usr/lib64/warewulf/");
}
use Warewulf;
use Getopt::Long;
#use strict;

my %nodes;
my (
   $help,
   $dump,
   $dhcpd_template,
   $vnfs,
   $default,
   $debug,
);

my $usage = "USAGE: $0 [options]
  About: 
    Management tool used to facilitate maintaince of the warewulf nodes

  Options:
    --info [node]   Display information about the node designation or nodename
    --sync/update   Update the node's user accounts, and node access rules
      --nonodes     Build the sync files, but don't do the actual node sync
    --add           Scan and configure new nodes
      --scan        Continue scanning for nodes even after one was found
      --group       Add this node to a particular group
      --opts        Define optional options for this node as defined in
                    nodes.conf manual. Example: 
                       'name=foo,other dev=eth2,other addr=10.4.0.1:255.0.0.0'
    --help          Show this banner

  note: When adding new nodes, you can not scan if you define an option which
  would be specific for multiple nodes (ie. name, other*, etc...).

  This tool is part of the Warewulf cluster distribution
     http://warewulf-cluster.org/
";

GetOptions(
   'help'      => \$help,
   'add'       => \$node_add,
   'sync'      => \$node_sync,
   'update'    => \$node_sync,
   'nonodes'   => \$node_sync_not,
   'info=s'    => \$get_node_info,
   'scan'      => \$node_scan,
   'group=s'   => \$add2_group,
   'opts=s'    => \$add_node_opts,
   'debug'     => \$debug
);

if ( $add_node_opts and $node_scan ) {
   die "Don't use --opts with --name options. Remove the --scan. ;)\n";
}

$| = '1';


$PORT = 9875;                  # pick something not in use

%group_info = &group_info();

if ( ! $group_info{"$add2_group"} and $add2_group ) {
   die "GROUP: $add2_group is not configured!\n";
}

sub node_add {
   $name = 'node' unless $name;
   $formt = '04d' unless $formt;
   $number = '0';

   $server = IO::Socket::INET->new( Proto     => 'tcp',
                                    LocalPort => $PORT,
                                    Listen    => SOMAXCONN,
                                    Reuse     => 1);
   
   die "can't setup server" unless $server;
   if ( $node_scan ) {
      print "Listening for new nodes... ([ctrl]-c to quit)\n";
   }
   my %nodes = &node_info();
   foreach $node ( keys %nodes ) {
      if ( $nodes{$node}{"mac address"} ) {
         $knownmac{"$nodes{$node}{'mac address'}"} = $node;
      }
      $node =~ /^\D+(\d+)$/;
      $number = $1 unless ( $number > $1 );
   }
   $number++ if $number;
   $number = sprintf("%$formt", $number);
   if ( $add_nodename ) {
      print "Waiting for $name$number (aka: $add_nodename)... ";
   } else {
      print "Waiting for $name$number... ";
   }
   
   while ($client = $server->accept()) {
     $client->autoflush(1);
     while ( <$client>) {
        chomp;
        if ( $_ =~ /^GET \/(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w) HTTP.+/ ) {
           $mac_addr = lc($1);
           $mac_addr =~ s/-/:/g;
           if ( $knownmac{"$mac_addr"} ) {
   #           print "IGNORING: [$knownmac{$mac_addr}] $mac_addr\n";
           } else {
   #           print "ADDING: [$name$number] $mac_addr\n";
              print "        [$mac_addr]\n";
              $out = ();
              $lgroup = ();
              $wrote_entry = ();
              open(NODECFG, "/etc/warewulf/nodes.conf");
              foreach $line (<NODECFG>) {
                 chomp $line;
                 if ( $line =~ /^\[(.+)\]$/ ) {
                    $group = $1;
                    if (( $group ne $lgroup and $lgroup and ! $wrote_entry ) and 
                          ( ( $add2_group eq $lgroup ) or ! $add2_group )) {
                       chomp $out;
                       $out .= "\t$name$number\t= $mac_addr\n\n";
                       $wrote_entry = '1';
                    }
                    $lgroup = $group;
                 }
                 $out .= "$line\n";
              }
              if ( ! $wrote_entry ) {
                 $out .= "\t$name$number\t= $mac_addr\n";
              }
              if ( $add_node_opts ) {
                 $out .= "\n[$name$number]\n";
                 @opts = split(/\s*,\s*/, $add_node_opts );
                 foreach $opt ( @opts ) {
                    ($k, $v) = split(/\s*=\s*/, $opt);
                    if (split(//,$k) < 8 ) {
                       $out .= "\t$k\t\t= $v\n";
                    } else {
                       $out .= "\t$k\t= $v\n";
                    }
                 }
              }
              close NODECFG;
              open(NODECFG, "> /etc/warewulf/nodes.conf");
              print NODECFG $out;
              close NODECFG;
              &pxe_update(0);
              &write_hosts();
              #system("/usr/sbin/wwpxeconfig >/dev/null");
               %nodes = &node_info();
               foreach $node ( keys %nodes ) {
                  if ( $nodes{$node}{"mac address"} ) {
                     $knownmac{"$nodes{$node}{'mac address'}"} = $node;
                  }
                  $node =~ /^\D+(\d+)$/;
                  $number = $1 unless ( $number > $1 );
               }
               $number++ if $number;
               $number = sprintf("%$formt", $number);
           }
        }
        if ( $_ !~ /\S/ ) {
           print $client "HTTP/1.0 200 OK\n";
           print $client "\nSUCCESSFUL\n";
           last;
        }
     }
     close $client;
     if ( ! $node_scan ) {
        last;
     }
     print "Waiting for $name$number... ";
   }
}

sub gen_syncs {
   my %groups = &group_info();
   @scripts = glob("/usr/lib/warewulf/modules/sync_*");
   foreach $group ( keys %groups ) {
      $created = ();
      print "Generating syncs for [$group]\n";
      foreach $module ( @scripts ) {
         chomp ($tmpdir = `$module $group`)
            or die "ERROR: Could not execute sync module: $module!\n";
         if ( ! $created ) {
            system("cd $tmpdir; tar -cf /srv/vnfs/$groups{$group}{'vnfs'}/synctree.tar .");
         } else {
            system("cd $tmpdir; tar -rf /srv/vnfs/$groups{$group}{'vnfs'}/synctree.tar .");
         }
         $created = "1";
         system("rm -rf $tmpdir");
      }
      system("gzip -9 -f /srv/vnfs/$groups{$group}{'vnfs'}/synctree.tar");
   }

}

sub sync_nodes {
   my %nodestatus = &node_status();
   my %nodes = &node_info();
   my %master = &master_info();
   my %node_ips = &gen_node_ips(\%nodes, \%master);
   my $nodename;
   foreach $node ( sort keys %nodes ) {
      next if ( $nodes{$node}{"designation"} ne $node );
      $nodename = $nodes{$node}{"nodename"};
      if ( $nodestatus{$nodename}{'NODESTATUS'} eq 'READY' ) {
         $debug and warn "spawning update command on $node/$nodename:$node_ips{$node}{admin}\n";
         system("$master{'network'}{'rsh command'} $node_ips{$node}{admin} /wwtools/syncnode");
      } elsif ( $nodestatus{$nodename}{'NODESTATUS'} eq 'unavailable' ) {
         $debug and warn "spawning update command on $node/$nodename:$node_ips{$node}{admin}\n";
         system("$master{'network'}{'rsh command'} $node_ips{$node}{admin} /wwtools/syncnode");
      } elsif ( $nodestatus{$nodename}{'NODESTATUS'} eq 'ERROR' ) {
         $debug and warn "spawning update command on $node/$nodename:$node_ips{$node}{admin}\n";
         system("$master{'network'}{'rsh command'} $node_ips{$node}{admin} /wwtools/syncnode");
      } else {
         print "   $nodes{$node}{nodename}: skipping, node down!\n";
      }
   }
}

sub show_node_info {
   my ( $node, @NULL ) = @_;
   my %nodestatus = &node_status();
   my %nodes = &node_info();
   my %master = &master_info();
   my %node_ips = &gen_node_ips(\%nodes, \%master);
   $node = $nodes{$node}{"designation"};
   if ( ! $nodes{$node} ) {
      die "That node is not configured!\n";
   }
   print "\n                       === NODE CONFIGIRATION ===\n\n";
   printf("   Node name: %-20.20s    Node designation: %-20.20s\n", 
         $nodes{$node}{"nodename"}, $node);
   printf("  Node group: %-20.20s                VNFS: %-20.20s\n", 
         $nodes{$node}{"group name"}, $nodes{$node}{"vnfs"});
   printf("      Kernel: %-20.20s            wwinitrd: %-20.20s\n", 
         $nodes{$node}{"kernel image"}, $nodes{$node}{"wwinitrd image"});
   printf(" Description: %-66.66s\n", $nodes{$node}{"desc"});
   printf("       Users: %-66.66s\n", $nodes{$node}{"user names"});
   printf(" User Groups: %-66.66s\n", $nodes{$node}{"user groups"});
   printf("    Boot MAC: %s\n", $nodes{$node}{"mac address"});
   printf("   Admin dev: %-5s         Admin address: %s/%s\n",
         $nodes{$node}{"admin device"}, $node_ips{$node}{"admin"},
         $master{"network"}{"admin netmask"});
   printf(" Cluster dev: %-5s       Cluster address: %s/%s\n",
         $nodes{$node}{"cluster device"}, $node_ips{$node}{"cluster"},
         $master{"network"}{"cluster netmask"});
   printf("     SFS dev: %-5s           SFS address: %s/%s\n",
         $nodes{$node}{"sharedfs device"}, $node_ips{$node}{"sharedfs"},
         $master{"network"}{"sharedfs netmask"});

   if ( ! $nodestatus{$node} or $nodestatus{$node}{"LASTCONTACT"} eq "never" ) {
      print "\n$node has not checked in with warewulfd yet!\n\n";
      exit;
   } else {
      if ( $nodestatus{$node}{"NODESTATUS"} ne "READY" ) {
         print "\n              === NODE HAS BEEN UNREACHABLE FOR $nodestatus{$node}{LASTCONTACT} seconds===\n";
         print "                content displayed is from last known state\n\n";
      } else {
         print "\n                       === CURRENT NODE STATS ===\n\n";
      }
      printf(" Kernel Version: %-20.20s                     ARCH: %8.15s\n",
            $nodestatus{$node}{"RELEASE"}, $nodestatus{$node}{"MACHINE"});
      printf("       Load Avg:     %2.2f    Processes:       %-6.6s    Uptime: %7dh\n",
            $nodestatus{$node}{"LOADAVG"}, $nodestatus{$node}{"PROCS"}, $nodestatus{$node}{"UPTIME"}/3600);
      printf("       CPU Util: %7.5s%%    CPU COUNT:   %6.6s     CPU CLOCK:  %7.7s\n",
            $nodestatus{$node}{"CPUUTIL"}, $nodestatus{$node}{"CPUCOUNT"}, $nodestatus{$node}{"CPUCLOCK"});
      printf("       MEM Util: %7.5s%%     MEM USED: %7.7sM     MEM Avail: %7.7sM\n",
            $nodestatus{$node}{"MEMPERCENT"}, $nodestatus{$node}{"MEMUSED"}, $nodestatus{$node}{"MEMAVAIL"});
      printf("      SWAP Util: %7.5s%%    SWAP USED: %7.7sM    SWAP Avail: %7.7sM\n",
            $nodestatus{$node}{"SWAPPERCENT"}, $nodestatus{$node}{"SWAPUSED"}, $nodestatus{$node}{"SWAPUSED"});
      printf("   IP Transmits:  %7.10s                          IP Recieves:  %7.10s\n",
            $nodestatus{$node}{"NETTRANSMIT"}, $nodestatus{$node}{"NETRECIEVE"});



   }

}

if ( $node_add ) {
   &pxe_update(0);
   &node_add();
} elsif ( $node_sync ) {
   &gen_syncs();
   if ( ! $node_sync_not ) {
      &sync_nodes();
   }
} elsif ( $get_node_info ) {
   &show_node_info($get_node_info);
} else {
   print "$usage\n";
}

