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

#
# EMULAB-COPYRIGHT
5
# Copyright (c) 2000-2004 University of Utah and the Flux Group.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
6 7
# All rights reserved.
#
8
use English;
Chad Barb's avatar
 
Chad Barb committed
9
use Getopt::Std;
10 11 12 13 14 15 16 17

#
# nfree - Takes pysical node names, and frees them from the experiment they
# are allocated to. If nodes are ommited, frees up all nodes in the given
# experiment. Looks in the scheduled_reloads and next_reserve tables to see
# if this node should be re-reserved into another experiment and/or reloaded,
# rather than being put back into the pool of free nodes
#
18 19
sub usage ()
{
20
    die("Usage: nfree [-x] [-o] <pid> <eid> [<node> <node> <...>]\n".
21
	"Releases all nodes in the specified experiment.\n".
Chad Barb's avatar
 
Chad Barb committed
22
	"If nodes are listed, nfree releases only those nodes.\n".
23 24
	" '-x' frees all virtual nodes on any physical node that gets freed.\n".
	" '-o' Moves nodes into a oldreserved holding experiment.\n"
Chad Barb's avatar
 
Chad Barb committed
25
	);
26
}
Mac Newbold's avatar
Mac Newbold committed
27

28
# Configure variables
29
my $TB       = "@prefix@";
30
my $TESTMODE = @TESTMODE@;
31 32 33

# Testbed Support libraries
use lib "@prefix@/lib";
34
use libdb;
35
use libtestbed;
36
use Node;
37

38
my $consetup	= "$TB/libexec/console_setup";
39
my $osselect    = "$TB/bin/os_select";
40 41 42
my $reloadpid	= "emulab-ops";
my $pendingeid  = "reloadpending";
my $reloadeid   = "reloading";
43 44
my $oldreserved_pid = OLDRESERVED_PID;
my $oldreserved_eid = OLDRESERVED_EID;
45 46
my $lockedpid   = NFREELOCKED_PID();
my $lockedeid   = NFREELOCKED_EID();
Mac Newbold's avatar
Mac Newbold committed
47

48
my @nodes;
Mac Newbold's avatar
Mac Newbold committed
49
my @freed_nodes=();
50
my @dynanodes=();
51 52
my $error = 0;

53
$| = 1;  # Turn off line buffering on output
Mac Newbold's avatar
Mac Newbold committed
54

55 56 57 58
# Untaint the path
$ENV{'PATH'} = "/bin:/usr/bin:/sbin:/usr/sbin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

Chad Barb's avatar
 
Chad Barb committed
59 60 61 62
#
# Parse command arguments. Once we return from getopts, all that should
# left are the required arguments.
#
63
my  $optlist = "xo";
Chad Barb's avatar
 
Chad Barb committed
64 65 66 67 68 69 70 71

%options = ();

if (! getopts($optlist, \%options)) {
    usage();
}

my $freeDependantVirtuals = 0;
72
my $moveToOldReserved = 0;
Chad Barb's avatar
 
Chad Barb committed
73 74 75 76

if (defined($options{"x"})) {
    $freeDependantVirtuals = 1;
}
77 78 79
if (defined($options{"o"})) {
    $moveToOldReserved = 1;
}
Chad Barb's avatar
 
Chad Barb committed
80

81 82 83
if (@ARGV < 2) {
    usage();
}
Chad Barb's avatar
 
Chad Barb committed
84

85 86 87
my $pid = shift;
my $eid = shift;

88
# Untaint args.
89 90 91 92
if ($pid =~ /^([-\@\w]+)$/) { $pid = $1; }
else { die("Bad data in pid: $pid."); }
if ($eid =~ /^([-\@\w]+)$/) { $eid = $1; }
else { die("Bad data in eid: $eid."); }
93 94 95 96

# Make sure that the experiment actually exists
if (!ExpState($pid,$eid)) {
    die("There is no experiment '$eid' in project '$pid'.\n");
97 98
}

99 100
# Make sure the user has the ability to modify this experiment
if (!TBExptAccessCheck($UID, $pid, $eid, TB_EXPT_MODIFY)) {
101
    die("You do not have permission to modify '$eid' in project '$pid'.\n");
102 103
}

104
# Make a list of nodes given on the command line, or get the whole list from
105
# the DB if none provided.
106 107 108
if (@ARGV) {
    foreach my $n (@ARGV) {
	# Taint check first! Solves silly perl problems.
109 110
	if ($n =~ /^([-\w]+)$/) { $n = $1; }
	else { die("*** $0:\n    Bad node name: $n.\n"); }
111

112 113 114 115 116 117 118 119 120 121 122
	# Shark hack
	if ($n =~ /(sh\d+)/ ) {
	    # It's a shark - do the whole shelf if its not done already.
	    my $shelf = $1;
	    if ( ! (join(",", @nodes) =~ /,$shelf-\d,/)) {
		# Shelf hasn't been done yet...
		foreach my $n ( 1 .. 8 ) {
		    push(@nodes, "$shelf-$n");
		}
	    }
	    # End shark hack
123
	} else {
124 125
	    # its not a shark - just add it in...
	    push(@nodes, $n);
Chad Barb's avatar
 
Chad Barb committed
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141

	    # if -x was specified, remove any 
	    # mapping to a node which has a phys_nodeid of $n.
	    if ($freeDependantVirtuals) {
		my $result = 
		    DBQueryFatal("SELECT r.node_id FROM reserved AS r ".
				 "LEFT JOIN nodes AS n ".
				 "ON r.node_id=n.node_id ".
				 "WHERE n.phys_nodeid='$n' AND ".
				 "r.eid='$eid' AND r.pid='$pid'");
		while (my ($dependantVirtual) = $result->fetchrow_array()) {
		    if (defined $dependantVirtual && $dependantVirtual ne $n) {
			push(@nodes, $dependantVirtual);
		    }
		}
	    }
142
	}
143
    }
144
} else {
145 146
    print "Releasing all nodes from experiment '$eid' in project '$pid'.\n";
    @nodes = ExpNodes($pid, $eid);
147 148 149
    if (! $moveToOldReserved ) {
	push( @nodes, ExpNodesOldReserved($pid, $eid) );
    }
150 151 152 153
}

######################################################################
# Step 1
154
#
155 156
# See what nodes need to be freed, and then lock them down my moving
# them to a holding reservation.
157
#
158 159 160 161 162
# We lock just the reserved table. The prevents races between multiple
# invocations of nfree trying to free the same node. Rather than lock
# a zillion tables, move the reservation into a holding pattern. This
# effectively prevents someone else from freeing the same nodes, and from
# someone else allocating the nodes until we are done cleaning things up.
163
#
164 165 166 167
# NOTE: My reason for not wanting to lock all those tables (9 in the
# original version) is that library calls will fail since mysql locking
# requires that every table used within the locked area, be locked.
# Of course, who knows what tables the library uses, so thats a silly
168
# way to go.
169
#
170 171 172 173
######################################################################

DBQueryFatal("lock tables reserved write");

174
foreach my $n (@nodes) {
175 176 177 178
    # Check to make sure they have actually reserved the nodes.
    my $result =
	DBQueryFatal("select * from reserved where node_id='$n' ".
		     "and eid='$eid' and pid='$pid'");
179 180 181 182 183 184
    if ($result->numrows == 0) {
	print "Node '$n' is not reserved by your experiment.\n";
	$error++;
	next;
    }

185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
    if ( $moveToOldReserved ) {
	# Move to holding reservation. Node is not free, but is no longer
	# owned by the pid/eid, so cannot be mucked with.
	if (! DBQueryWarn("update reserved " .
			  "set vname='$n', pid='$oldreserved_pid', ".
			  "eid='$oldreserved_eid', old_pid='$pid', ".
			  "old_eid='$eid' where node_id='$n'")) {
	    print "*** WARNING: Error moving node $n to holding pid/eid: ".
	          "$oldreserved_pid/$oldreserved_eid\n";
	    next;
	}
    } else {
	# Move to locked reservation. Node is not free, but is no longer
	# owned by the pid/eid, so cannot be mucked with.
	if (! DBQueryWarn("update reserved " .
200
			  "set vname='$n', pid='$lockedpid', eid='$lockedeid', ".
201 202 203 204 205 206
			  "old_pid='', old_eid='' ".
			  "where node_id='$n'")) {
	    print "*** WARNING: Error locking down node $n!\n";
	    next;
	}
	push(@freed_nodes, $n);
207 208 209 210 211 212
    }

}

DBQueryFatal("unlock tables");

213 214 215 216 217
# We are done if called with a -o
if( $moveToOldReserved ) {
    exit($error);
}

218 219 220 221 222 223 224 225 226 227
######################################################################
# Step 2
#
# Go through the list of nodes we successfully locked down, and clean
# up the node state (nodes, delays, interfaces, etc). Once that is done,
# move them to whatever new reservations are pending, or free the node.
#
######################################################################

foreach my $n (@freed_nodes) {
228
    my $mustclean = 1;
229

230 231
    # Clean out all delays
    DBQueryWarn("delete from delays where node_id='$n'") || $error++;
232

233
    # Find the default values for its node type.
234
    my $result =
235
	DBQueryFatal("select nt.osid,n.eventstate, " .
236 237
		     "       nt.isvirtnode, nt.imageable, o.mustclean, ".
		     "       nt.isdynamic ".
238 239
		     " from nodes as n " .
		     "left join node_types as nt on n.type=nt.type " .
240
		     "left join os_info as o on o.osid=n.def_boot_osid ".
241
		     "where node_id='$n'");
242 243
    my ($osid, $estate, $isvirt, $imageable, $clean, $isdynamic) =
	$result->fetchrow_array();
244 245 246 247 248 249

    # See if the OS it was running was marked as mustclean or not. Basically,
    # this is an OSKit hack to avoid reloading disks that have not been
    # touched by the kernel. If a def_boot_path was set, there is nothing
    # we can figure out, so just reload it. This needs to be more general
    # purpose.
250
    if ($isvirt || !$imageable) {
251 252 253
	# VIRTNODE HACK: Virtual nodes are special. Do not clean or reload.
	$mustclean = 0;
    }
254 255 256 257
    elsif (defined($clean)) {
	# If def_boot_osid set, then $clean is defined. Otherwise not set
	# so default to cleaning node. 
	$mustclean = $clean;
258 259 260 261 262 263 264 265 266 267
    }

    #
    # If the node is a dynamic virtual node, just save it for later.
    # We will call into the Node library to delete it. 
    #
    if ($isdynamic) {
	push(@dynanodes, $n);
	next;
    }
268 269 270 271

    # Clean up interfaces by clearing IPs and/or aliases.
    if (! ($n =~ /sh\d+/)) {
        # Its not a shark, so clean out all IPs except the control net.
272 273
	DBQueryWarn("update interfaces set IP='',IPaliases=NULL,mask=NULL,".
		    "       rtabid='0',vnode_id=NULL " .
274 275 276
		    "where node_id='$n' and ".
		    "  role='" . TBDB_IFACEROLE_EXPERIMENT() . "'")
	    || $error++;
277
    } else {
278
	# XXX Shark Hack!
279
	DBQueryWarn("update interfaces set IPalias='',rtabid='0',vnode_id=NULL ".
280
		    "where node_id='$n'") || $error++;
281
    }
282

Chad Barb's avatar
 
Chad Barb committed
283 284
    my $allocFreeState = TBDB_ALLOCSTATE_FREE_DIRTY();

285
    DBQueryWarn("update nodes set startupcmd='',rpms='',deltas='', ".
Robert Ricci's avatar
Robert Ricci committed
286
		"tarballs='',failureaction='fatal', routertype='none', ".
287
		"def_boot_cmd_line='',next_boot_cmd_line='', ".
288
		"temp_boot_osid='',next_boot_osid='', ".
Chad Barb's avatar
 
Chad Barb committed
289
		"update_accounts=0,ipport_next=ipport_low, ".
290
		"sfshostid=NULL,allocstate='$allocFreeState' ".
291
		"where node_id='$n'") || $error++;
292

293 294 295 296 297 298 299 300
    #
    # If the node is a virtnode, force its state to SHUTDOWN. This is mostly
    # to avoid silly stated warnings for nodes that do not have a physical
    # representation most of the time!
    #
    TBSetNodeEventState($n, TBDB_NODESTATE_SHUTDOWN)
	if ($isvirt && $estate ne TBDB_NODESTATE_SHUTDOWN());

301
    # Clean out the current_reloads table (a just in case measure).
302
    DBQueryWarn("delete from current_reloads where node_id='$n'") || $error++;
303

304 305 306
    # Reset its port allocation stuff.
    DBQueryWarn("delete from nodeipportnum where node_id='$n'") || $error++;

307 308 309
    # Clean the veth_interfaces table for this node.
    DBQueryWarn("delete from veth_interfaces where node_id='$n'") || $error++;

310 311 312
    # Clean the interface_settings table for this node.
    DBQueryWarn("delete from interface_settings where node_id='$n'") || $error++;

313
    # Now its safe to change the reservation.
314

315 316 317 318 319 320 321 322 323 324
    # If the node has a next_reserve entry, change the reservation.
    $result =
	DBQueryFatal("select node_id,pid,eid from next_reserve ".
		     "where node_id='$n'");

    if ($result->num_rows()) {
	my ($node, $next_pid, $next_eid) = $result->fetchrow_array();
	
	print "Moving $n to $next_pid/$next_eid.\n";

325
	DBQueryWarn("update reserved set pid='$next_pid',eid='$next_eid'," .
326
		    "vname='$n' where node_id='$n'")
327 328 329 330
	    || $error++;
	
	DBQueryWarn("delete from next_reserve where node_id='$n'")
	    || $error++;
331

332 333 334 335 336
	# This little sillyness is for disk reloading.
	# Kill the last reservation since this path is special.
	DBQueryWarn("delete from last_reservation where node_id='$n'") ||
	    $error++;

337 338
	next;
    }
339

340 341 342 343 344 345
    # If the node has a reloads entry, change the reservation so that the
    # reload_daemon will pick it up.
    $result =
	DBQueryFatal("select node_id,image_id from scheduled_reloads " .
		     "where node_id='$n'");

346
    # XXX force reload hack!
347
    if ( !$TESTMODE && ((!$isvirt && $imageable) || $result->numrows()) ) {
348 349
	print "Moving $n to $reloadpid/$pendingeid.\n";
	
350 351
	DBQueryWarn("update reserved set pid='$reloadpid',eid='$pendingeid',".
		    "vname='$n' where node_id='$n'") || $error++;
352 353 354 355 356 357

	# This little sillyness is for disk reloading.
	# Kill the last reservation since this path is special.
	DBQueryWarn("delete from last_reservation where node_id='$n'") ||
	    $error++;

358 359 360
	next;
    }

361
    # No reloads or reservation changes, so really free the node
362
    #
363 364 365 366 367 368
    # This little sillyness is for disk reloading. Remember the last
    # project a node was reserved into. At present, there might already
    # be an entry. Eventually, os_setup will look for this and force
    # a reload.
    if ($mustclean) {
	DBQueryWarn("replace into last_reservation values ('$n', '$pid')");
369
    }
370

371 372 373 374 375 376 377 378
    print "Releasing node '$n' ... ";
    if (DBQueryWarn("delete from reserved where node_id='$n'")) {
	print "Succeeded.\n";
    }
    else {
	print "Failed!\n";
	$error++;
    }
379 380
}

381 382 383 384 385
# Release dynamic nodes.
if (@dynanodes) {
    Node::DeleteVnodes(@dynanodes);
}

386 387 388 389 390
######################################################################
# Step 3 - Set up console for freed nodes.
#
# Using a list of freed nodes build eariler, run consetup to reset
# their serial consoles.
391 392 393 394 395 396
#
# NOTE: While it may seem like a race to do this after releasing the
# reservation, it really is not. Even if the node is allocated again
# console_setup looks at the current reservation and does the right
# thing, and since nalloc locks the reserved table, ordering will be
# preserved.
397
#
398 399
######################################################################

400
if (@freed_nodes) {
401 402
    system("$consetup @freed_nodes") == 0 ||
	print STDERR "WARNING: $consetup @freed_nodes failed!\n";
Mac Newbold's avatar
Mac Newbold committed
403 404
}

405
exit($error);
406