node_update.in 6.59 KB
Newer Older
1
#!/usr/bin/perl -wT
Leigh Stoller's avatar
Leigh Stoller committed
2 3 4

#
# EMULAB-COPYRIGHT
5
# Copyright (c) 2000-2004 University of Utah and the Flux Group.
Leigh Stoller's avatar
Leigh Stoller committed
6 7 8
# All rights reserved.
#

9 10 11 12
use English;
use Getopt::Std;

#
13 14 15
# Mark nodes for update. At the moment all kinds of things will get
# updated (mounts, accounts, tarballs, rpms). At some point these
# should be split up.
16 17 18
#
# XXX There is an inherent race condition with using this script. What if
# nodes are released while it is running?
19
#
20 21
sub usage()
{
22
    print STDOUT "Usage: node_update [-b] pid eid [node ...]\n".
23 24
	"Update user accounts and NFS mounts on nodes in your project.\n".
	"Use -b to use batch operation (place in background, send email).\n";
25 26
    exit(-1);
}
27 28 29 30 31 32 33 34 35 36 37 38 39
my  $optlist = "b";

#
# Exit codes are important; they tell the web page what has happened so
# it can say something useful to the user. Fatal errors are mostly done
# with die(), but expected errors use this routine. At some point we will
# use the DB to communicate the actual error.
#
# $status < 0 - Fatal error. Something went wrong we did not expect.
# $status = 0 - Proceeding in the background. Notified later.
# $status > 0 - Expected error. User not allowed for some reason. 
#

40 41 42 43 44 45 46
#
# Function phototypes
#

sub NotifyUser($$);
sub fatal($);

47 48 49 50 51 52 53 54 55 56
#
# Configure variables
#
my $TB		= "@prefix@";
my $TESTMODE    = @TESTMODE@;
my $TBOPS       = "@TBOPSEMAIL@";
my $TBLOGS      = "@TBLOGSEMAIL@";

my $expsetup    = "$TB/sbin/exports_setup";
my $batchmode   = 0;
57 58 59 60 61 62 63
my @nodes       = ();
my $user_name;
my $user_email;
my $logname;
my $failed	= 0;
my $dbuid;
my $estate;
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89

#
# 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 (defined($options{"b"})) {
    $batchmode = 1;
}
90 91 92 93 94
if (@ARGV < 2) {
    usage();
}
my $pid   = shift(@ARGV);
my $eid   = shift(@ARGV);
95 96 97 98

#
# Untaint the arguments.
#
99
if ($pid =~ /^([-\w]+)$/) {
100 101
    $pid = $1;
}
102 103 104
else {
    die("*** Bad data in pid: $pid\n");
}	
105
if ($eid =~ /^([-\w]+)$/) {
106 107
    $eid = $1;
}
108 109
else {
    die("*** Bad data in eid: $eid\n");
110
}
111 112

#
113
# If more args, they are node names.
114
#
115 116 117 118 119 120 121 122 123 124 125 126
if (@ARGV) {
    # Untaint the nodes.
    foreach my $node ( @ARGV ) {
	if ($node =~ /^([-\w]+)$/) {
	    $node = $1;
	}
	else {
	    die("*** Bad node name: $node.");
	}

	push(@nodes, $node);
    }
127 128 129 130 131 132
}

#
# Verify actual user and get his DB uid.
#
if (! UNIX2DBUID($UID, \$dbuid)) {
133 134
    die("*** $0:\n".
	"    You do not exist in the Emulab Database.\n");
135 136 137
}

if (! UserDBInfo($dbuid, \$user_name, \$user_email)) {
138 139
    die("*** $0:\n".
        "    Cannot determine your name and email address.\n");
140 141 142
}

#
143
# Verify that this person is allowed to do this.
144
#
145 146 147 148
if (!TBAdmin() &&
    ! TBExptAccessCheck($UID, $pid, $eid, TB_EXPT_UPDATE)) {
    die("*** $0:\n".
	"    You not have permission to update nodes in $pid/$eid!\n");
149 150
}

151
#
152
# Check state. Only ACTIVE experiments. 
153
#
154 155 156
$estate = ExpState($pid, $eid);
if ($estate ne EXPTSTATE_ACTIVE) {
    print STDERR "Experiment $pid/$eid is not ACTIVE!\n";
157 158
    # For web page.
    exit(1);
159 160
}

161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
#
# Get the list of nodes that need to be "updated." We already have the
# exerpiment locked, so if we have a problem, must unlock it.
#
if (! scalar(@nodes)) {
    @nodes = ExpNodes($pid, $eid);
}
else {
    #
    # Must verify that user supplied node names are really in the
    # experiment.
    #
    my @allnodes = ExpNodes($pid, $eid);

    foreach my $n1 (@nodes) {
	if (! grep {$_ eq $n1} @allnodes) {
	    die("*** $0:\n".
		"    Node $n1 is not allocated to $pid/$eid!\n");
	}
    }
}
if (! scalar(@nodes)) {
    print STDERR "There are no nodes allocated to experiment $pid/$eid\n";
    # For web page.
    exit(1);
}

188 189 190 191
#
# Batchmode (as from the web interface) goes to background and reports
# later via email.
# 
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
if ($batchmode) {
    #
    # Create a temporary name for a log file.
    #
    $logname = `mktemp /tmp/node_update-$pid-$eid.XXXXXX`;
    chop($logname);
    
    if (TBBackGround($logname)) {
	#
	# Parent exits normally
	#
	print STDOUT
	    "Node Update for $pid/$eid is now in progress.\n".
	    "You will be notified via email when the is complete.\n";
	exit(0);
    }
}

#
211
# Currently, we just need to update the mount points.
212 213 214 215 216 217 218
#
if (system("$expsetup")) {
    fatal("Exports Setup Failed");
}
# Give ops a chance to react.
sleep(2);

219
#
220 221
# Mark the nodes for auto update. Nodes may not respect this field
# (old local images), but its harmless. 
222 223
#
foreach my $node ( @nodes ) {
224 225 226 227
    if (! DBQueryWarn("update nodes set ".
		      "update_accounts=GREATEST(update_accounts,1) ".
		      "where node_id='$node'")) {
	fatal("DB Error!");
228 229 230
    }
}

231 232 233
print STDOUT "Waiting a while for nodes to auto update ...\n";
for (my $i = 0; $i < 10; $i++) {
    sleep(30);
234

235 236
    my $firstnode = pop(@nodes);
    my $querystr  = "node_id='$firstnode' ";
237

238 239
    while (@nodes) {
	my $node = pop(@nodes);
240

241
	$querystr .= "or node_id='$node' ";
242
    }
243 244 245 246
	
    my $query_result =
	DBQueryFatal("select node_id,update_accounts from nodes ".
		     "where ($querystr)");
247

248 249 250
    while (my ($node,$notdone) = $query_result->fetchrow_array) {
	if ($notdone) {
	    push(@nodes, $node);
251
	}
252 253
	else {
	    print STDOUT "$node updated.\n";
254
	}
255
    }
256 257 258
    last
	if (! @nodes);
    print STDOUT "Still waiting for nodes to auto update ...\n";
259
}
260
foreach my $node ( @nodes ) {
261 262 263
    print STDOUT "Node update failed on $node.\n";
    $failed++;
}
264

Robert Ricci's avatar
Robert Ricci committed
265
TBUnLockExp($pid, $eid, BATCHSTATE_RUNNING());
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
NotifyUser("Node Update Complete", $failed);
if (defined($logname)) {
    unlink($logname);
}
exit($failed);

sub NotifyUser($$)
{
    my($mesg, $iserr) = @_;
    my($subject, $from, $to, $hdrs);

    print STDOUT "$mesg\n";

    if (! $batchmode) {
	return;
    }

    if ($iserr) {
284
	$subject = "Node Update Failed $pid/$eid";
285 286
    }
    else {
287
	$subject = "Node Update Success $pid/$eid";
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
    }
    $from  = $TBOPS;
    $hdrs  = "Reply-To: $TBOPS";
    
    #
    # Message goes to user. If a failure, TBOPS also gets it, otherwise
    # it goes into the logs.
    #
    $to    = "$user_name <$user_email>";    
    
    if ($iserr) {
	$hdrs = "Cc: $TBOPS\n".
	        "$hdrs";
    }
    else {
	$hdrs = "Bcc: $TBLOGS\n".
	        "$hdrs";
    }

307 308 309 310
    #
    # Send a message to the testbed list. Append the logfile.
    #
    SENDMAIL($to, $subject, $mesg, $from, $hdrs, ($logname));
311 312 313 314 315
}

sub fatal($) {
    my($mesg) = @_;

316
    TBUnLockExp($pid, $eid, BATCHSTATE_RUNNING());
317 318 319 320 321 322 323
    NotifyUser($mesg, 1);
    if (defined($logname)) {
	unlink($logname);
    }
    exit(1);
}