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
#
Russ Fish's avatar
Russ Fish committed
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);
}