#!/usr/bin/perl -w
# 
# -------------------------------------------------------------------
#                                   X-BONE
#
#                          http://www.isi.edu/xbone
#                USC Information Sciences Institute (USC/ISI)
#                   Marina del Rey, California 90292, USA
#                          Copyright (c) 1998-2005
# 
# -------------------------------------------------------------------
#
# Copyright (c) 1998-2005 by the University of Southern California.
# All rights reserved.
#
# Permission to use, copy, modify, and distribute this software and
# its documentation in source and binary forms for non-commercial
# purposes and without fee is hereby granted, provided that the above
# copyright notice appear in all copies and that both the copyright
# notice and this permission notice appear in supporting
# documentation, and that any documentation, advertising materials,
# and other materials related to such distribution and use acknowledge
# that the software was developed by the University of Southern
# California, Information Sciences Institute.  The name of the
# University may not be used to endorse or promote products derived
# from this software without specific prior written permission.
# 
# THE UNIVERSITY OF SOUTHERN CALIFORNIA MAKES NO REPRESENTATIONS ABOUT
# THE SUITABILITY OF THIS SOFTWARE FOR ANY PURPOSE.  THIS SOFTWARE IS
# PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#
# Other copyrights might apply to parts of this software and are so
# noted when applicable.
#
# -------------------------------------------------------------------
#
# Effort partly sponsored by the Defense Advanced Research Projects
# Agency (DARPA) and Air Force Research Laboratory, Air Force Materiel
# Command, USAF, under agreement numbers F30602-98-1-0200 (X-Bone) and
# F30602-01-2-0529 (DynaBone). The views and conclusions contained
# herein are those of the authors and should not be interpreted as
# necessarily representing the official policies or endorsements,
# either expressed or implied, of the Defense Advanced Research
# Projects Agency (DARPA), the Air Force Research Laboratory, or the
# U.S. Government.
#
# This work was partly supported by the NSF STI-XTEND (ANI-0230789)
# and NETFS (ANI-0129689) projects. Any opinions, findings, and
# conclusions or recommendations expressed in this material are those
# of the authors and do not necessarily reflect the views of the
# National Science Foundation.
#
# -------------------------------------------------------------------
#  $RCSfile: vserver-script.pl,v $
#
# $Revision: 1.5 $
#   $Author: pingali $
#     $Date: 2005/03/31 07:03:42 $
#    $State: Exp $
# ----------------------------------------------------------------------------
#
# Primary Author: Venkata Pingali

use Getopt::Long qw(:config no_ignore_case);  
use Data::Dumper; 
use FileHandle; 

####################################################
# This is a xbone application deployment script that 
# deploys linux vservers on a bunch of machines. 
# To use this you need to: 
#  1. install vserver rpms + kernel patch 
#  2. install xbone 
#  3. create a set of vservers 
#  4. create a list of vservers in the/etc/vserver-list 
#     (e.g. $cat /etc/vserver-list 
#           vserver1.conf
#           vserver2.conf)  
#  5. start the RD 
#  6. use this script as the application deployment 
#     script in the XBone gui. 
#
# Assumes: /etc/vservers/vserver-list 
#                   a list of available vservers
#  The format of the vserver-list file is simple. 
#  It has the name of each configured vserver 
#  on a separate line. 
#                   
# Logfile: /etc/vservers/log 
#                   the location can be changed 
####################################################

#---------------------------------
#my $VSERVERDIR = "/tmp/etc/vservers"; 
my $VSERVERDIR = "/etc/vservers"; 
my $LOGFILE = "$VSERVERDIR/log"; 
my $VSERVERLIST = "$VSERVERDIR/vserver-list"; 


####################################################
# NOT USED BUT MAY BE NECESSARY LATER ON 
# Save the configuration 
#
####################################################
sub do_save_config($) { 
  my $temp = shift; 

  #=> Save the configuration
  my $dir = `dirname $0` ; 
  chomp($dir); 
  my $file = $dir . "/.conf";
  my $fh = new FileHandle;
    
  eval{
    # open the file for writing
    if(!$fh->open ($file, ">")){
      die "open";
    }
    # set permission
    if(!chmod(0600, $file)){
      die "chmod";
    }
    
    $state = Data::Dumper->Dump([$temp], ["temp"]);
    $fh->print("$state"); 
    $fh->close or die "close: $!"; 
  };
  
  
}; # do save 

####################################################
#
# NOT USED BUT MAY BE NECESSARY LATER ON 
# Save the configuration 
#
####################################################
sub do_load_config() { 

  #=> Save the configuration
  my $temp; 
  my $dir = `dirname $0` ; 
  chomp($dir); 
  my $file = $dir . "/.conf";
  my $fh = new FileHandle;
   
  my $data = ""; 
  if($fh->open($file, "<")){
    while(<$fh>){
      $data .= $_;
    }
    $data =~ /(.*)/s;       # untaint the value
      eval $data;
    $fh->close or	die "close";
  }else{
    die ("Cannot open file"); 
  }; 
  
  #print Dumper($temp); 
  return $temp; 
  
}; # do save 

####################################################
#
# configure the vserver. look at the list of available
# vservers from the vserver-list file, extract one
# assign it to this overlay. 
#
####################################################
sub do_conf($) { 

    my $cmds = shift; 
    my $freeserver = ""; 

    my $logfile = $LOGFILE; 
    my $MY_OVL_NAME = $cmds->{"ovlname"}; 
    my $ALL_MY_IPS = join(" ", @{$cmds->{"myvirtips"}}); 
    
    #print "MY_OVL_NAME = $MY_OVL_NAME ALL_MY_IPS = \"$ALL_MY_IPS\"\n"; 

    my $msg = `date`; 
    open(LOG, ">>$logfile") || die("logfile cannot be removed"); 

    my $oldfile = $VSERVERLIST;
    my $newfile = $VSERVERLIST . ".new"; 
            
    open(CONF, "<$oldfile") || do {
	print LOG "Cannot open the file: $oldfile"; 
	die("Cannot open the file: $oldfile"); 
    };

    while (<CONF>) {
	/:$MY_OVL_NAME/ and do {
	    print LOG "Error. The overlay is already assigned to one vserver\n"; 
	    die("overlay already assigned an overlay"); 
	}
    }

    # ok. you can proceed to assign a free vserver to the 
    # overlay. 
    open(CONFNEW, ">$newfile") || do { 
	print LOG "Cannot open the file: $newfile"; 
	die("Cannot open the file: $newfile"); 
    }; 
    
    seek CONF, 0, 0; # reset the file pointer. 

    while (<CONF>) {
	/^\s*#/ and do { print CONFNEW $_; next; }; 
	/^\s+$/ and do { print CONFNEW $_; next; }; 
	/:/ and do { print CONFNEW $_; next; }; 
	chomp($_); 
	$freeserver = $_; 
	print CONFNEW $freeserver . ":$MY_OVL_NAME\n";
	last; 
    }
    
    #copy the rest of the file. 
    while(<CONF>) { print CONFNEW $_; } 
    
    close(CONF); 
    close(CONFNEW); 
    close(LOG); 

    if ($freeserver eq "") { 
	unlink($newfile); 
	die ("Dont have a free server");
    };
    
    # got a freeserver 
    rename($newfile, $oldfile);
    
    # extracted the vserver filename 
    # update the vserver configuration file. 
    $oldfile = "$VSERVERDIR/" . $freeserver; 
    $newfile = "$VSERVERDIR/" . $freeserver . ".new"; 
    open(CONF, "<$oldfile") || die("Cannot open the file"); 
    open(CONFNEW, ">$newfile") || die("Cannot open the file"); 
    
    while(<CONF>){ 
	chomp; 

	/^\s*#/ and do { print CONFNEW $_ . "\n"; next; }; 
	/^\s+$/ and do { print CONFNEW $_ . "\n"; next; }; 
	
	# replace IPROOT 
	/(.*)IPROOT=(\".*\")(.*)/ and do {
	    print CONFNEW "$1" . "IPROOT=\"$ALL_MY_IPS\"" . "$3". "\t\t\#XBONENEW\n";
	    print CONFNEW "\#$_\t\t\#XBONEORIG\n";
	    next; 
	};
	
	# replace the capabilities 
	/(.*)S_CAPS=(\".*\")(.*)/ and do {
	    print CONFNEW "$1" . "S_CAPS=\"CAP_NET_RAW\"" . "$3". "\t\t\#XBONENEW\n"; 
	    print CONFNEW "\#$_\t\t\#XBONEORIG\n"; 
	    next; 
	};

	/IPROOTDEV/ and do { print CONFNEW "\#$_\t\t\#XBONEORIG\n"; next; };
	/S_HOSTNAME/ and do { print CONFNEW "\#$_\t\t\#XBONEORIG\n"; next; };

	print CONFNEW $_. "\n";  
    }
    
    close(CONF); 
    close(CONFNEW); 
    
    rename($newfile, $oldfile); 
    
}

####################################################
#
# stop the vserver 
#
####################################################
sub do_kill($) {    

    my $cmds = shift; 
    my $server = ""; 
    my $oldfile = $VSERVERLIST; 
    my $MY_OVL_NAME = $cmds->{"ovlname"}; 

    open(CONF, "<$oldfile") || die("Cannot open the file: $oldfile"); 
    while (<CONF>) {
	/$MY_OVL_NAME/ and do { 
	    chomp($_); 
	    $_ =~ s/:.*$//; 
	    $server = $_; 
	    last; 
	}; 
    }    
    close(CONF);     
    die ("unable to find the correct server") if ($server eq ""); 

    $server =~ s/.conf$//;     
    my @cmd = ("/usr/sbin/vserver", "$server", "stop"); 
    #system(@cmd) == 0 || die("Cannot stop the vserver @cmd ");     
    system(@cmd); # some scripts may fail. 
}

####################################################
#
# start the vserver and possibly in future an application
#
####################################################
sub do_run($) {    

    my $cmds = shift; 
    my $server = ""; 
    my $oldfile = $VSERVERLIST; 
    my $MY_OVL_NAME = $cmds->{"ovlname"}; 


    open(CONF, "<$oldfile") || die("Cannot open the file: $oldfile"); 
    while (<CONF>) {
	/$MY_OVL_NAME/ and do { 
	    chomp($_); 
	    $_ =~ s/:.*$//; 
	    $server = $_; 
	    last; 
	}; 
    }    
    close(CONF); 
    die ("unable to find the correct server") if ($server eq ""); 
    
    $server =~ s/.conf$//;     
    my @cmd = ("/usr/sbin/vserver", "$server", "start"); 
    system(@cmd); # some scripts may fail. 
}

####################################################
#
# remove the vserver assignment. future work: cleanup
# the vserver conf file as well. 
#
####################################################
sub do_cleanup($) {
    
    my $cmds = shift; 
    my $freeserver = ""; 
    my $oldfile = $VSERVERLIST; 
    my $newfile = $VSERVERLIST . ".new"; 
    my $MY_OVL_NAME = $cmds->{"ovlname"}; 

    open(CONF, "<$oldfile") || die("Cannot open the file: $oldfile"); 
    open(CONFNEW, ">$newfile") || die("Cannot open the file: $newfile"); 

    while (<CONF>) {
	/:$MY_OVL_NAME/ and do { 
	    $_ =~ s/:$MY_OVL_NAME//; 
	    $freeserver = $_;
	    chomp($freeserver);
	    print CONFNEW $_; 
	    last; 
	}; 
	print CONFNEW $_; 
    }
    
    #copy the rest of the file. 
    while(<CONF>) { print CONFNEW $_; } 
    
    close(CONF); 
    close(CONFNEW); 
  
    die ("Cannot find the correct free server to cleanup")
      if (not defined($freeserver));

    # got a freeserver 
    rename($newfile, $oldfile); 
    
    # extracted the vserver filename 
    # update the vserver configuration file. 
    $oldfile = "$VSERVERDIR/" . $freeserver; 
    $newfile = "$VSERVERDIR/" . $freeserver . ".new"; 
    open(CONF, "<$oldfile") || die("Cannot open the file"); 
    open(CONFNEW, ">$newfile") || die("Cannot open the file"); 
    
    while(<CONF>){ 
	/\#XBONENEW/ and next; 
	/\#XBONEORIG/ and do {
	    $_ =~ s/^\#//; 
	    $_ =~ s/\#XBONEORIG//; 
	}; 

	print CONFNEW $_; 
    }
    
    close(CONF); 
    close(CONFNEW); 
    
    rename($newfile, $oldfile); 
    
}

####################################################
#
# log changes/errors to /etc/vservers/log
#
####################################################

sub note($$) {    
    my ($cmds, $input) = @_; 
    
    my $logfile = $LOGFILE;

    open(LOG, ">>$logfile") || die("logfile $logfile cannot be created"); 

    my $msg = `date`; 
    chomp($msg); 
    print LOG ">>>[$input] at $msg<<<\n"; 
    if ($input !~ /(done|error)/i){ 
	print LOG "Command = " . Dumper($cmds); 
    }
    close(LOG); 
};

####################################################
#
# Process command line 
#
####################################################

# cmdline 
#-t run -f tunl3 -f tunl5 -H mtv.isi.edu -i 172.26.0.13 -i 172.26.0.9
#-N mtv.isi.edu -N sci.isi.edu -N ifc.isi.edu -O 128.9.112.68 -P 4165
#-I 128.9.160.79 -a 172.26.0.9 -a 172.26.0.14 -a 172.26.0.10 -d
#test1.xbone.overlay -A 128.9.160.79 -A 128.9.160.93 -A 128.9.160.95 -p
#172.26.0.0/28)

sub process_cmdline {

    my %cmdl_opts;	
    my (@ilist, @flist, @alist, @Alist, @Nlist); 

    #print "\nThe commandline was:\n";
    #foreach (@ARGV){
    #  print "\"$_\" "; 
    #}
    #print "\n"; 

    my @opts_spec = 
	(
	 "op|t=s",  # operation 
	 
	 # host properties
	 "myinterfaces|f=s" => \@flist,  # interface 
	 "myhostname|H=s",  # localhost name
	 "myphyip|I=s",  # ip address of the local host 
	 "myvirtips|i=s" => \@ilist,  # addresses 
	 
	 # overlay properties 
	 "prefix|p=s",  # prefix for the entire network 
	 "ovlmgr|O=s",  # name of the overlay manager 
	 "ovlmgrport|P=s",  # control port of the OM 
	 
	 # global peroperties 
	 "allvirtips|a=s" => \@alist,  # other virtual addresses within the network 
	 "ovlname|d=s",  # name of the overlay 
	 "allphyhostnames|N=s" => \@Nlist,  # names of physical hosts in the network 
	 "allphyips|A=s" => \@Alist,  # ip addresses of other hosts in the network 
    ); 
    
    
    if (GetOptions(\%cmdl_opts, @opts_spec)==0) { 
	print "Usage: vserver-script.pl [<option>]\n";
	print "The following options are supported\n";
	foreach my $opt (@opts_spec){
	  if (ref($opt) !~ /(ARRAY|HASH)/){
	    $opt =~ s/=s/ = <value>/;
	    print "\t$opt\n";
	  }
	}

	die("Command line could not be processed"); 
    }
    

    $cmdl_opts{"allphyhostnames"} = \@Nlist;
    $cmdl_opts{"allphyips"}= \@Alist; 
    $cmdl_opts{"allvirtips"}= \@alist; 
    $cmdl_opts{"myinterfaces"}= \@flist; 
    $cmdl_opts{"myvirtips"}= \@ilist; 
    
    #print Dumper(\%cmdl_opts); 

    return \%cmdl_opts; 
}

####################################################
#
# main..
#
####################################################

my $cmds = process_cmdline(); 
$ENV{"PATH"} = "/bin:/usr/bin:/usr/sbin"; 

eval { 
  my $op = ${$cmds}{"op"}; 
  if ($op !~ /verify/){
    if ($op !~ /conf/){
      $cmds = do_load_config(); 
    } else {
      do_save_config($cmds); 
    }
  }

  SWITCH: for ($op) { 

      # verify is run as nobody. so we cannot do anything privileged like logging.  
      /verify/ and do { last SWITCH; };

      /conf/ and do { note($cmds, "conf"); do_conf($cmds); note($cmds, "conf done"); last SWITCH; };
      /run/ and do { note($cmds, "run"); do_run($cmds); note($cmds, "run done"); last SWITCH; };
      /pid/ and do { note($cmds, "pid"); note($cmds, "pid done"); last SWITCH; };
      /kill/ and do { note($cmds, "kill"); do_kill($cmds); note($cmds, "kill done"); last SWITCH; };
      /cleanup/ and do { note($cmds, "cleanup"); do_cleanup($cmds); note($cmds, "cleanup done"); last SWITCH; };

      # install and deinstall are use in this experiment
      /install/ and do { note($cmds, "install"); note($cmds, "install done"); last SWITCH; }; 
      /deinstall/ and do { note($cmds, "deinstall"); note($cmds, "deinstall done");last SWITCH; };
      
      # default 
      note($cmds, "Unknown input:" . $op); 
  }
}; 

if ($@) {
    note($cmds, "ERROR : $@"); 
    exit(1); 
}

exit 0; 



syntax highlighted by Code2HTML, v. 0.9.1