node_update.in 6.14 KB
Newer Older
1
#!/usr/bin/perl -wT
Leigh B. Stoller's avatar
Leigh B. Stoller committed
2 3
#
# EMULAB-COPYRIGHT
4
# Copyright (c) 2000-2004, 2007 University of Utah and the Flux Group.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
5 6
# All rights reserved.
#
7
use strict;
8 9 10 11
use English;
use Getopt::Std;

#
12 13 14
# 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.
15 16 17
#
# XXX There is an inherent race condition with using this script. What if
# nodes are released while it is running?
18
#
19 20
sub usage()
{
21
    print STDOUT "Usage: node_update [-b] pid eid [node ...]\n".
22 23
	"Update user accounts and NFS mounts on nodes in your project.\n".
	"Use -b to use batch operation (place in background, send email).\n";
24 25
    exit(-1);
}
26 27 28 29 30 31 32 33 34 35 36 37 38
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. 
#

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

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

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

my $expsetup    = "$TB/sbin/exports_setup";
my $batchmode   = 0;
56 57 58
my @nodes       = ();
my $logname;
my $failed	= 0;
59 60 61 62 63 64 65

#
# Load the Testbed support stuff. 
#
use lib "@prefix@/lib";
use libdb;
use libtestbed;
66 67
use Experiment;
use User;
68 69 70 71 72 73 74 75 76 77 78 79

# 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.
#
80
my %options = ();
81 82 83 84 85 86
if (! getopts($optlist, \%options)) {
    usage();
}
if (defined($options{"b"})) {
    $batchmode = 1;
}
87 88 89 90 91
if (@ARGV < 2) {
    usage();
}
my $pid   = shift(@ARGV);
my $eid   = shift(@ARGV);
92 93 94 95

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

109 110 111 112
my $experiment = Experiment->Lookup($pid, $eid);
if (! defined($experiment)) {
    die("*** $0:\n".
	"    No such experiment $pid/$eid in the Emulab Database.\n");
113 114 115
}

#
116
# Check state. Only ACTIVE experiments. 
117
#
118 119
my $estate = $experiment->state();
if ($estate ne EXPTSTATE_ACTIVE) {
120
    print STDERR "Experiment $pid/$eid is in state $estate, not ACTIVE!\n";
121 122
    # For web page.
    exit(1);
123 124
}

125 126 127 128 129 130
#
# Verify user and get his DB uid and other info for later.
#
my $this_user = User->ThisUser();
if (! defined($this_user)) {
    tbdie("You ($UID) do not exist!");
131
}
132 133
my $user_name  = $this_user->name();
my $user_email = $this_user->email();
134 135

#
136
# Verify that this person is allowed to do this.
137
#
138 139
if (!$this_user->IsAdmin() &&
    !$experiment->AccessCheck($this_user, TB_EXPT_UPDATE)) {
140 141
    die("*** $0:\n".
	"    You not have permission to update nodes in $pid/$eid!\n");
142 143
}

144
#
145
# If more args, they are node names.
146
#
147 148 149 150 151 152
if (@ARGV) {
    my @allnodes = $experiment->NodeList(1); # Just the names.
    
    foreach my $nodeid ( @ARGV ) {
	my $node = Node->Lookup($nodeid);
	if (!defined($node)) {
153
	    die("*** $0:\n".
154
		"    $nodeid is not a node!\n");
155
	}
156 157 158 159 160
	if (! grep {$_ eq $nodeid} @allnodes) {
	    die("*** $0:\n".
		"    Node $nodeid is not allocated to $pid/$eid!\n");
	}
	push(@nodes, $node);
161 162
    }
}
163 164 165
else {
    @nodes = $experiment->NodeList();
}
166 167 168 169 170 171
if (! scalar(@nodes)) {
    print STDERR "There are no nodes allocated to experiment $pid/$eid\n";
    # For web page.
    exit(1);
}

172 173 174 175
#
# Batchmode (as from the web interface) goes to background and reports
# later via email.
# 
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
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);
    }
}

#
195
# Currently, we just need to update the mount points.
196 197 198 199 200 201 202
#
if (system("$expsetup")) {
    fatal("Exports Setup Failed");
}
# Give ops a chance to react.
sleep(2);

203
#
204 205
# Mark the nodes for auto update. Nodes may not respect this field
# (old local images), but its harmless. 
206 207
#
foreach my $node ( @nodes ) {
208 209
    $node->MarkForUpdate() == 0
	or fatal("Could not mark $node for update!");
210 211
}

212 213 214
print STDOUT "Waiting a while for nodes to auto update ...\n";
for (my $i = 0; $i < 10; $i++) {
    sleep(30);
215

216 217 218 219
    my @notdone;
    my @done;
    Node->CheckUpdateStatus(\@done, \@notdone, @nodes) == 0
	or fatal("Could not check update status for nodes: @nodes");
220

221 222
    foreach my $node (@done) {
	my $node_id = $node->node_id();
223
	
224
	print STDOUT "$node_id updated.\n";
225
    }
226 227
    @nodes = @notdone;
    
228 229
    last
	if (! @nodes);
230
    
231
    print STDOUT "Still waiting for nodes to auto update ...\n";
232
}
233
foreach my $node ( @nodes ) {
234 235 236
    print STDOUT "Node update failed on $node.\n";
    $failed++;
}
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255

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) {
256
	$subject = "Node Update Failed $pid/$eid";
257 258
    }
    else {
259
	$subject = "Node Update Success $pid/$eid";
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
    }
    $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";
    }

279 280 281 282
    #
    # Send a message to the testbed list. Append the logfile.
    #
    SENDMAIL($to, $subject, $mesg, $from, $hdrs, ($logname));
283 284 285 286 287 288 289 290 291 292 293 294
}

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

    NotifyUser($mesg, 1);
    if (defined($logname)) {
	unlink($logname);
    }
    exit(1);
}