node_update.in 6.82 KB
Newer Older
1
#!/usr/bin/perl -wT
Leigh Stoller's avatar
Leigh Stoller committed
2
#
3
# Copyright (c) 2000-2004, 2007 University of Utah and the Flux Group.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
Leigh Stoller's avatar
Leigh Stoller committed
23
#
24
use strict;
25 26 27 28
use English;
use Getopt::Std;

#
29 30 31
# 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.
32 33 34
#
# XXX There is an inherent race condition with using this script. What if
# nodes are released while it is running?
35
#
36 37
sub usage()
{
38
    print STDOUT "Usage: node_update [-b] pid eid [node ...]\n".
39 40
	"Update user accounts and NFS mounts on nodes in your project.\n".
	"Use -b to use batch operation (place in background, send email).\n";
41 42
    exit(-1);
}
43 44 45 46 47 48 49 50 51 52 53 54 55
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. 
#

56 57 58 59 60 61 62
#
# Function phototypes
#

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

63 64 65 66 67 68 69 70 71 72
#
# Configure variables
#
my $TB		= "@prefix@";
my $TESTMODE    = @TESTMODE@;
my $TBOPS       = "@TBOPSEMAIL@";
my $TBLOGS      = "@TBLOGSEMAIL@";

my $expsetup    = "$TB/sbin/exports_setup";
my $batchmode   = 0;
73 74 75
my @nodes       = ();
my $logname;
my $failed	= 0;
76 77 78 79 80 81 82

#
# Load the Testbed support stuff. 
#
use lib "@prefix@/lib";
use libdb;
use libtestbed;
83 84
use Experiment;
use User;
85 86 87 88 89 90 91 92 93 94 95 96

# 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.
#
97
my %options = ();
98 99 100 101 102 103
if (! getopts($optlist, \%options)) {
    usage();
}
if (defined($options{"b"})) {
    $batchmode = 1;
}
104 105 106 107 108
if (@ARGV < 2) {
    usage();
}
my $pid   = shift(@ARGV);
my $eid   = shift(@ARGV);
109 110 111 112

#
# Untaint the arguments.
#
113
if ($pid =~ /^([-\w]+)$/) {
114 115
    $pid = $1;
}
116 117 118
else {
    die("*** Bad data in pid: $pid\n");
}	
119
if ($eid =~ /^([-\w]+)$/) {
120 121
    $eid = $1;
}
122 123
else {
    die("*** Bad data in eid: $eid\n");
124
}
125

126 127 128 129
my $experiment = Experiment->Lookup($pid, $eid);
if (! defined($experiment)) {
    die("*** $0:\n".
	"    No such experiment $pid/$eid in the Emulab Database.\n");
130 131 132
}

#
133
# Check state. Only ACTIVE experiments. 
134
#
135 136
my $estate = $experiment->state();
if ($estate ne EXPTSTATE_ACTIVE) {
137
    print STDERR "Experiment $pid/$eid is in state $estate, not ACTIVE!\n";
138 139
    # For web page.
    exit(1);
140 141
}

142 143 144 145 146 147
#
# 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!");
148
}
149 150
my $user_name  = $this_user->name();
my $user_email = $this_user->email();
151 152

#
153
# Verify that this person is allowed to do this.
154
#
155 156
if (!$this_user->IsAdmin() &&
    !$experiment->AccessCheck($this_user, TB_EXPT_UPDATE)) {
157 158
    die("*** $0:\n".
	"    You not have permission to update nodes in $pid/$eid!\n");
159 160
}

161
#
162
# If more args, they are node names.
163
#
164 165 166 167 168 169
if (@ARGV) {
    my @allnodes = $experiment->NodeList(1); # Just the names.
    
    foreach my $nodeid ( @ARGV ) {
	my $node = Node->Lookup($nodeid);
	if (!defined($node)) {
170
	    die("*** $0:\n".
171
		"    $nodeid is not a node!\n");
172
	}
173 174 175 176 177
	if (! grep {$_ eq $nodeid} @allnodes) {
	    die("*** $0:\n".
		"    Node $nodeid is not allocated to $pid/$eid!\n");
	}
	push(@nodes, $node);
178 179
    }
}
180 181 182
else {
    @nodes = $experiment->NodeList();
}
183 184 185 186 187 188
if (! scalar(@nodes)) {
    print STDERR "There are no nodes allocated to experiment $pid/$eid\n";
    # For web page.
    exit(1);
}

189 190 191 192
#
# Batchmode (as from the web interface) goes to background and reports
# later via email.
# 
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
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);
    }
}

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

220
#
221 222
# Mark the nodes for auto update. Nodes may not respect this field
# (old local images), but its harmless. 
223 224
#
foreach my $node ( @nodes ) {
225 226
    $node->MarkForUpdate() == 0
	or fatal("Could not mark $node for update!");
227 228
}

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

233 234 235 236
    my @notdone;
    my @done;
    Node->CheckUpdateStatus(\@done, \@notdone, @nodes) == 0
	or fatal("Could not check update status for nodes: @nodes");
237

238 239
    foreach my $node (@done) {
	my $node_id = $node->node_id();
240
	
241
	print STDOUT "$node_id updated.\n";
242
    }
243 244
    @nodes = @notdone;
    
245 246
    last
	if (! @nodes);
247
    
248
    print STDOUT "Still waiting for nodes to auto update ...\n";
249
}
250
foreach my $node ( @nodes ) {
251 252 253
    print STDOUT "Node update failed on $node.\n";
    $failed++;
}
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272

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) {
273
	$subject = "Node Update Failed $pid/$eid";
274 275
    }
    else {
276
	$subject = "Node Update Success $pid/$eid";
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
    }
    $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";
    }

296 297 298 299
    #
    # Send a message to the testbed list. Append the logfile.
    #
    SENDMAIL($to, $subject, $mesg, $from, $hdrs, ($logname));
300 301 302 303 304 305 306 307 308 309 310 311
}

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

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