#!/usr/bin/perl -wT # # EMULAB-COPYRIGHT # Copyright (c) 2000-2003 University of Utah and the Flux Group. # All rights reserved. # use English; use Getopt::Std; # # Set up the vnode state on a virtual (multiplexed) node. # # XXX - This script should only be run from os_setup! # # The output is all jumbled together since the updates are issued in parallel. # Might be a pain when debugging. # sub usage() { print STDOUT "Usage: vnode_setup [-f] [-k] [node ...]\n"; exit(-1); } my $optlist = "fdk"; # # We don't want to run this script unless its the real version. # That is, it must be setuid root. # if ($EUID != 0) { die("*** $0:\n". " Must be root! Maybe its a development version?\n"); } # # Configure variables # my $TB = "@prefix@"; my $TESTMODE = @TESTMODE@; my $TBOPS = "@TBOPSEMAIL@"; my $TBLOGS = "@TBLOGSEMAIL@"; my $CLIENT_BIN = "@CLIENT_BINDIR@"; my $ssh = "$TB/bin/sshtb -n"; my $debug = 0; my $force = 0; my $failed = 0; my $killmode = 0; my $mode = "setup"; my $dbuid; # # Load the Testbed support stuff. # use lib "@prefix@/lib"; use libdb; use libtestbed; # un-taint path $ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin'; delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; # Turn off line buffering on output $| = 1; # # Parse command arguments. Once we return from getopts, all that should be # left are the required arguments. # %options = (); if (! getopts($optlist, \%options)) { usage(); } if (@ARGV < 2) { usage(); } if (defined($options{"f"})) { $force = 1; } if (defined($options{"d"})) { $debug = 1; } if (defined($options{"k"})) { $killmode = 1; $mode = "teardown"; } my $pid = shift(@ARGV); my $eid = shift(@ARGV); # # Untaint the arguments. # if ($pid =~ /^([-\@\w]+)$/) { $pid = $1; } else { die("*** Bad data in pid: $pid\n"); } if ($eid =~ /^([-\@\w]+)$/) { $eid = $1; } else { die("*** Bad data in eid: $eid\n"); } # # Verify permission to muck with this experiment. # if (!TBAdmin($UID) && !TBExptAccessCheck($UID, $pid, $eid, TB_EXPT_DESTROY)) { die("*** $0:\n". " You do not have permission to mess with $pid/$eid!\n"); } # # Get the list of nodes in this experiment. # my @nodes = ExpNodes($pid, $eid); if (! @nodes) { warn("*** $0:\n". " No allocated nodes in experiment $pid/$eid!\n"); exit(0); } # Nodes on the command line. Operate only on this set. if (@ARGV) { my %fulllist = (); # Temporary hash list for searching. foreach my $node ( @nodes ) { $fulllist{$node} = 1; } @nodes = (); foreach my $node ( @ARGV ) { if ($node =~ /^([-\@\w]+)$/) { $node = $1; if (!defined($fulllist{$node})) { die("*** $0:\n". " Node $node is not allocated to $pid/$eid!\n"); } } else { die("Bad node name: $node."); } push(@nodes, $node); } } my $exptstate = ExpState($pid, $eid); # Just the vnodes mam. foreach my $node (@nodes) { my $pnode; my $jailed; my $allocstate; if (! TBIsNodeVirtual($node, \$jailed)) { next; } if (! TBPhysNodeID($node, \$pnode)) { die("*** $0:\n". " No physical node for $node!\n"); } TBGetNodeAllocState($node, \$allocstate); # # On remote nodes, or when forcemode is on, always do the deed. # Otherwise, look at experiment state. # if (!$force) { if ($exptstate eq EXPTSTATE_SWAPPING) { # # When swapping, local vnodes go down with the physnode. # if (! TBIsNodeRemote($node)) { print "$node will $mode with local node $pnode.\n"; next; } elsif ($allocstate eq TBDB_ALLOCSTATE_DOWN) { print "$node never booted; skipping.\n"; next; } } elsif ($exptstate eq EXPTSTATE_ACTIVATING) { # # The allocstate determines if the vnode actually needs to # be setup or torndown. Note that a failed experiment will # cause a bunch of vnodes to be torndown, while in the # ACTIVATING state. See os_setup and assign_wrapper; the # idea is to avoid doing setup/teardown up vnodes on # machines that are rebooting anyway, or that failed. # Complicated by modify which could add/subtract a vnode on # an existing machine, but not reboot the machine. Note that # free (now unused) vnodes will land in RES_TEARDOWN. It is # assumed that these booted okay, and need to be torndown, # even though they are not RES_READY. # if (! TBIsNodeRemote($node)) { if ($killmode) { if ($allocstate eq TBDB_ALLOCSTATE_DOWN) { print "$node failed to boot; skipping $mode.\n"; next; } elsif ($allocstate eq TBDB_ALLOCSTATE_RES_INIT_CLEAN()) { print "$node never booted; skipping $mode.\n"; next; } } elsif ($allocstate eq TBDB_ALLOCSTATE_RES_READY()) { print "$node is already setting up on local node $pnode\n"; next; } } elsif ($killmode && $allocstate eq TBDB_ALLOCSTATE_DOWN) { print "$node failed to boot; skipping $mode.\n"; next; } } } # # When setting up a vnode, force its event state into SHUTDOWN since # no telling what its initial state is. # if (!$killmode) { TBSetNodeEventState($node, TBDB_NODESTATE_SHUTDOWN); } print STDOUT "Doing $mode of vnode $node on $pnode ...\n"; # # Run an ssh command in a child process, protected by an alarm to # ensure that the ssh is not hung up forever if the machine is in # some funky state. # my $syspid = fork(); if ($syspid) { local $SIG{ALRM} = sub { kill("TERM", $syspid); }; alarm 60; waitpid($syspid, 0); alarm 0; print STDERR "vnode $mode on $pnode returned $?.\n" if $debug; # # Look for setup failure, reported back through ssh. # if ($?) { my $exitstatus = $?; if ($exitstatus == 256) { print STDERR "$node is not running sshd.\n" if $debug; } elsif ($exitstatus == 15) { print STDERR "$node is wedged.\n" if $debug; } # Send mail to testbed-ops about it SENDMAIL($TBOPS, "Virtual Node $node $mode failure", "Virtual node $node $mode (on physical node $pnode) in pid/eid\n". "$pid/$eid has failed! Exit status was $exitstatus.\n"); if (!$killmode) { die("*** $0:\n". " Virtual node $node setup failure!\n"); } else { warn("*** $0:\n". " Virtual node $node teardown failure!\n"); } } } else { my $args = ($killmode ? "-k " : " "); $args .= ($jailed ? "-j " : " "); $args .= "$node "; # Must change our real UID to root so that ssh will work. $UID = 0; exec("$ssh -host $pnode $CLIENT_BIN/vnodesetup $args"); die("*** $0:\n". " exec failed!\n"); } } if ($killmode) { print STDOUT "Vnode teardown finished.\n"; } else { print STDOUT "Vnode setup initiated on all nodes ...\n"; } exit 0;