prereserve.in 21.4 KB
Newer Older
1 2
#!/usr/bin/perl -w
#
3
# Copyright (c) 2003-2018 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/>.
# 
# }}}
23 24 25 26
#
use strict;
use English;
use Getopt::Std;
27
use Date::Parse;
28 29 30 31 32 33

#
# Set up and clear node pre-reservations.
#
sub usage()
{
34
    print STDERR "Usage: prereserve [-f] [-t typelist] [-p priority] ".
35
	"[-s start] [-e end [-r]] [-n resname] pid [count | node_id ...\n";
36
    print STDERR "       prereserve -r -n resname pid\n";
37 38
    print STDERR "       prereserve -i -n resname pid\n";
    print STDERR "       prereserve -a -n resname pid\n";
39 40
    print STDERR "       prereserve -l\n";
    print STDERR "   -h   This message\n";
41
    print STDERR "   -f   Force pre-reservation, even if admission control is violated\n";
42
    print STDERR "   -t   Comma separated list of node types\n";
43 44
    print STDERR "   -p   Priority. Defaults to zero (least priority)\n";
    print STDERR "   -n   Reservation name; defaults to 'default'\n";
45
    print STDERR "   -r   Clear/Revoke prereserve for project\n";
46 47
    print STDERR "   -i   Show pending prereserve for project\n";
    print STDERR "   -l   List all pending prereserves\n";
48 49
    print STDERR "   -s   Optional start time to begin pre reservation\n";
    print STDERR "   -e   Optional end time for pre reservation.\n";
50
    print STDERR "        Implies -r options at termination time.\n";
51
    print STDERR "   -a   Activate a pending reservation (internal option)\n";
52 53
    exit(-1);
}
54
my $optlist  = "hdt:n:ilre:s:map:f";
55 56 57 58 59
my $priority = 0;
my $debug    = 0;
my $info     = 0;
my $list     = 0;
my $revoke   = 0;
60 61
my $sendmail = 0;
my $activate = 0;
62
my $force    = 0;
63
my @nodelist = ();
64
my $resname;
65 66
my $starttime;
my $endtime;
67
my $typelist;
68 69
my $pid;
my $count;
70
my $project;
71 72 73

# Protos
sub fatal($);
74
sub StartReservation($$);
75 76 77 78 79

#
# Configure variables
#
my $TB		 = "@prefix@";
80
my $TBOPS        = "@TBOPSEMAIL@";
81 82 83 84 85 86 87 88 89 90 91 92

#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use emdb;
use NodeType;
use Node;
use libtestbed;
use Experiment;
use Project;
use User;
93
use Reservation;
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115

#
# Turn off line buffering on output
#
$| = 1;

#
# Untaint the path
# 
$ENV{'PATH'} = "/bin:/sbin:/usr/bin:";

#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
my %options = ();
if (! getopts($optlist, \%options)) {
    usage();
}
if (defined($options{h})) {
    usage();
}
116 117 118
if (defined($options{p})) {
    $priority = $options{p};
}
119
if (defined($options{n})) {
120
    $resname = $options{n};
121 122 123 124 125 126 127
}
if (defined($options{r})) {
    $revoke = 1;
}
if (defined($options{d})) {
    $debug = 1;
}
128 129 130 131 132 133
if (defined($options{"m"})) {
    $sendmail = 1;
}
if (defined($options{"a"})) {
    $activate = 1;
}
134 135 136 137 138 139 140 141 142
if (defined($options{t})) {
    $typelist = $options{t};
}
if (defined($options{i})) {
    $info = 1;
}
if (defined($options{l})) {
    $list = 1;
}
143 144 145
if (defined($options{f})) {
    $force = 1;
}
146 147 148 149 150 151 152 153 154 155 156 157 158 159
if (defined($options{"e"})) {
    $endtime = $options{"e"};
    if (!defined(str2time($endtime))) {
	fatal("Could not parse -e option.");
    }
}
if (defined($options{"s"})) {
    $starttime = $options{"s"};
    
    if (!defined(str2time($starttime))) {
	fatal("Could not parse -s option.");
    }
}

160
if ($info || $revoke) {
161
    usage()
162
	if (@ARGV != 1);
163 164
    usage()
	if (!defined($resname));
165 166 167 168 169 170 171
    
    $pid = $ARGV[0];
}
elsif ($list) {
    usage()
	if (@ARGV);
}
172 173
elsif ($activate) {
    usage()
174
	if (@ARGV != 1 || !defined($resname));
175

176
    exit(StartReservation($ARGV[0], $resname));
177
}
178 179
else {
    usage()
180
	if (@ARGV < 2);
181
    
182 183 184
    $pid     = shift(@ARGV);
    $count   = shift(@ARGV);
    $resname = "default" if (!defined($resname));
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199

    if ($count !~ /^\d+$/) {
	if (defined($typelist)) {
	    fatal("Not allowed to specify types and a node list together");
	}
	@nodelist = ($count, @ARGV);
	$count    = scalar(@nodelist);

	foreach my $nodeid (@nodelist) {
	    my $node = Node->Lookup($nodeid);
	    if (!defined($node)) {
		fatal("No such node $nodeid");
	    }
	}
    }
200 201 202 203 204 205 206 207 208 209 210

    if ($priority && ! ($priority =~ /^\d*$/)) {
	usage();
    }
}

#
# List all pending prereserves.
#
if ($list) {
    my $query_result = 
211
	DBQueryFatal("select *,(count>0) as needed from project_reservations ".
212
		     "order by needed desc, priority desc, created asc");
213 214

    if ($query_result->numrows) {
215 216
	printf("%-18s %-12s %-4s  %-3s  %-10s %-18s %-3s %s\n",
	       "Project", "ResName", "Need", "Got", "Creator", "Created", "Pri", "Types");
217
	print "---------------------------------------------------------------------\n";
218 219 220 221
    }
    
    while (my $row = $query_result->fetchrow_hashref()) {
	my $pid     = $row->{'pid'};
222
	my $name    = $row->{'name'};
223 224 225 226 227
	my $count   = $row->{'count'};
	my $created = $row->{'created'};
	my $creator = $row->{'creator'};
	my $types   = $row->{'types'} || "";
	my $priority= $row->{'priority'};
228 229 230
	my $starttime = $row->{'start'};
	my $endtime   = $row->{'end'};
	my $active    = $row->{'active'};
231
	my $current = 0;
232

233
	my $current_result =
234 235 236
	    DBQueryFatal("select count(*) from nodes ".
			 "where reserved_pid='$pid' and ".
			 "      reservation_name='$name'");
237 238 239 240

	($current) = $current_result->fetchrow_array()
	    if ($current_result && $current_result->numrows);

241 242
	printf("%-20s %-12s %-4d %-3d %-10s %-18s %-3d %s\n",
	       $pid, $name, $count, $current, $creator, $created,
243 244 245 246 247 248 249 250 251 252 253 254
	       $priority, $types);

	if (defined($starttime)) {
	    print "  *** Starts: $starttime";
	    if (! $active) {
		print " (pending)";
	    }
	    if (defined($endtime)) {
		print " Ends: $endtime";
	    }
	    print "\n";
	}
255 256 257 258
    }
    exit(0);
}

259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
if ($pid =~ /^(.*):(.*)$/) {
    require GeniHRN;

    my $urn = GeniHRN::Generate($pid, "authority", "sa");

    $project = Project->LookupNonLocal($urn);
    if (!defined($project)) {
	fatal("No such nonlocal project $pid\n");
    }
    $pid = $project->pid();
}
else {
    $project = Project->Lookup($pid);

    if (!defined($project)) {
	fatal("No such project $pid\n");
    }
276 277 278 279 280 281 282 283
}
my $pid_idx = $project->pid_idx();

#
# Show and exit.
#
if ($info) {
    my $current = 0;
284
    my $pending = 0;
285
    
286
    my $nodes_result =
287 288 289
	DBQueryFatal("select node_id from nodes ".
		     "where reserved_pid='$pid' and ".
		     "      reservation_name='$resname'");
290

291 292
    ($current) = $nodes_result->numrows
	if ($nodes_result && $nodes_result->numrows);
293
    
294
    my $query_result =
295
	DBQueryFatal("select * from project_reservations ".
296
		     "where pid_idx='$pid_idx' and name='$resname'");
297 298 299 300 301 302 303 304 305

    if ($query_result->numrows) {
	my $row = $query_result->fetchrow_hashref();
	my $pid     = $row->{'pid'};
	my $count   = $row->{'count'};
	my $created = $row->{'created'};
	my $creator = $row->{'creator'};
	my $types   = $row->{'types'} || "*";
	my $priority= $row->{'priority'};
306 307 308
	my $starttime = $row->{'start'};
	my $endtime   = $row->{'end'};
	my $active    = $row->{'active'};
309

310 311
	printf("%-4s  %-3s %-10s %-18s %-3s %s\n",
	       "Need", "Got", "Creator", "When", "Pri", "Types");
312
	print "-------------------------------------------------------------\n";
313 314 315 316 317 318 319 320 321 322 323 324 325
	printf("%-4s  %-3s %-10s %-18s %-3d %s\n",
	       $count, $current, $creator, $created, $priority, $types);

	if (defined($starttime)) {
	    print "*** Starts: $starttime";
	    if (! $active) {
		print " (pending)";
	    }
	    if (defined($endtime)) {
		print " Ends: $endtime";
	    }
	    print "\n";
	}
326
    }
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
    if ($current) {
	print "-------------------------------------------------------------\n";
	printf("%-15s %-10s %-32s\n",
	       "NodeID", "Type", "Pid/Eid");
	print "-------------------------------------------------------------\n";

	while (my ($node_id) = $nodes_result->fetchrow_array()) {
	    my $node   = Node->Lookup($node_id);
	    my $type   = $node->type();
	    my $pideid = "--";

	    if ($node->IsReserved()) {
		$pideid = $node->pid() . "/" . $node->eid();
	    }
	    printf("%-15s %-10s %-32s\n", $node_id, $type, $pideid);
	}
    }
344 345
    my $noderes_result =
	DBQueryFatal("select node_id from node_reservations ".
346 347
		     "where pid_idx='$pid_idx' and ".
		     "      reservation_name='$resname'");
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
    if ($noderes_result->numrows) {
	print "-------------------------------------------------------------\n";
	print "Still waiting for these nodes\n";
	printf("%-15s %-10s %-32s\n",
	       "NodeID", "Type", "Pid/Eid");
	print "-------------------------------------------------------------\n";

	while (my ($node_id) = $noderes_result->fetchrow_array()) {
	    my $node   = Node->Lookup($node_id);
	    my $type   = $node->type();
	    my $pideid = "--";

	    if ($node->IsReserved()) {
		$pideid = $node->pid() . "/" . $node->eid();
	    }
	    printf("%-15s %-10s %-32s\n", $node_id, $type, $pideid);
	}
    }
366 367 368
    exit(0);
}

369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
#
# Verify user, must be admin or root.
#
my $this_user;
if ($UID) {
    $this_user = User->ThisUser();
    if (! defined($this_user)) {
	fatal("You ($UID) do not exist!");
    }
    if (!$this_user->IsAdmin()) {
	fatal("You are not a testbed administrator!");
    }
}

#
384
# Revoke/Clear and exit.
385
#
386
if ($revoke) {
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
    my $query_result =
	DBQueryFatal("select name from project_reservations ".
		     "where pid_idx='$pid_idx' and name='$resname'");
    if (!$query_result->numrows) {
	fatal("No such prereserve $pid,$resname");
    }

    #
    # Mark reservation as terminal to prevent it from being used.
    # Turning off active is no good, it will just get turned on.
    #
    DBQueryFatal("update project_reservations set terminal=1 ".
		 "where pid_idx='$pid_idx' and name='$resname'");

    #
402
    # When revoking the reservation, lets see if any nodes can be
403 404
    # moved to a another prereserve instead of going into the free pool.
    #
Leigh Stoller's avatar
Leigh Stoller committed
405
    $query_result =
406
	DBQueryFatal("select node_id from nodes ".
407 408
		     "where reserved_pid='$pid' and ".
		     "      reservation_name='$resname'");
409 410 411
    while (my ($node_id) = $query_result->fetchrow_array()) {
	my $node = Node->Lookup($node_id);
	$node->CheckPreReserve(0, 0);
412
    }
413 414 415 416 417
    DBQueryFatal("update nodes set reserved_pid=null, ".
		 "   reservation_name=null ".
		 "where reserved_pid='$pid' and ".
		 "      reservation_name='$resname'");

418 419 420 421 422
    DBQueryFatal("delete from node_reservations ".
		 "where pid_idx='$pid_idx' and reservation_name='$resname'");
    DBQueryFatal("delete from project_reservations ".
		 "where pid_idx='$pid_idx' and name='$resname'");
    
423 424 425 426 427 428 429 430 431 432 433 434
    exit(0);
}

#
# Do not allow this as root; we want proper history.
#
if ($UID == 0) {
    fatal("Please do not run this as root!");
}
my $uid = $this_user->uid();
my $uid_idx = $this_user->uid_idx();

435 436
my %nodetypes = ();

437
# Sanity check the type list.
438
if (defined($typelist)) {
439
    my @types = split(",", $typelist);
440 441 442 443 444 445 446 447 448 449 450

    if( @types > 1 && !$force ) {
	fatal( "Sorry, can't handle admission control check with multiple " .
	       "node types.\nEither make multiple pre-reservations with a " .
	       "dedicated node count per\nnode type, or retry with -f if " .
	       "you want a single count covering multiple\ntypes, bypassing " .
	       "admission control." );
    }

    $nodetypes{ $types[ 0 ] } = $count;
    
451 452 453 454 455 456
    foreach my $typename (@types) {
	my $type = NodeType->Lookup($typename);
	if (!defined($type)) {
	    fatal("No such node type $typename");
	}
    }
457 458 459 460 461 462 463 464 465
} else {
    foreach my $nodeid (@nodelist) {
	my $node = Node->Lookup( $nodeid );
	if( exists( $nodetypes{ $node->type() } ) ) {
	    $nodetypes{ $node->type() }++;
	} else {
	    $nodetypes{ $node->type() } = 1;
	}
    }
466 467
}

468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
# Run admission control now -- unfortunately there's a nasty race condition
# here.  Unfortunately it's messy to make the admission control check and
# the reservation table updates atomic, because of MySQL's stupid locking
# semantics (both components want to independently lock different tables
# and MySQL forbids acquiring more locks if any are held, which precludes
# nesting either section inside the other).
#
# Let's just ignore all that for now, because the intention is that
# future reservations will eventually be used everywhere and
# prereservations will be deprecated.

if( !$force ) {
    my $start = defined( $starttime ) ? str2time( $starttime ) : 0;
    my $end = defined( $endtime ) ? str2time( $endtime ) : 0x7FFFFFFF;
    foreach my $type ( keys( %nodetypes ) ) {
	my $reservations = Reservation->LookupAll( $type );
	my $res = Reservation->Create( $pid, undef, $start, $end, $type,
				       $nodetypes{ $type } );
	push( @$reservations, $res );
	my $error;
	if( !Reservation->IsFeasible( $reservations, \$error ) ) {
	    print STDERR "prereserve: $error\n";
	    exit( 1 );
	}
    }
}

# Ideally, we would now call Reservation->BeginTransaction, but that won't
# work because we will later want to acquire table locks, so don't bother.

498 499 500
# 
# Lets say that a current request is an error. delete and recreate.
#
501 502
DBQueryFatal("lock tables project_reservations write, ".
	     "            node_reservations write");
503
my $query_result =
504 505
    DBQueryFatal("select * from project_reservations ".
		 "where pid_idx='$pid_idx' and name='$resname'");
506
if ($query_result->numrows) {
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530
    fatal("Already have a reservation request $pid,$resname; ".
	  "please clear it first");
}

#
# It would be odd to allow the same node to be in more then one
# reservation in the same project. Well, I can imagine a scenario; we
# have a bunch of pcXXXs in a pre-reserve, some already allocated.  We
# want to revoke that pre-reserve, but keep some subset of the pcXXXs
# in that project. If there was another pre-reserve in the same
# project, we would never allow that subset to get back into the
# wild. It might be useful to support this at some point, I think it
# can be done. Lets see if its needed.
#
if (@nodelist) {
    foreach my $node_id (@nodelist) {
	my $query_result =
	    DBQueryFatal("select node_id from node_reservations ".
			 "where node_id='$node_id' and pid_idx='$pid_idx'");
	if ($query_result->numrows) {
	    fatal("There is already a pre-reservation for $node_id in\n".
		  "this project; this is not allowed");
	}
    }
531 532 533
}

#
534
# Enter the table info, but mark as not active until later.
535
#
Leigh Stoller's avatar
Leigh Stoller committed
536
my $typearg  = (defined($typelist)  ? ",types='$typelist'" : "");
537 538
my $startarg = "";
my $endarg   = "";
539

540 541 542 543 544 545 546 547
if ($starttime) {
    my $tmp = str2time($starttime);
    $startarg = ",start=FROM_UNIXTIME($tmp)";
}
if ($endtime) {
    my $tmp = str2time($endtime);
    $endarg = ",end=FROM_UNIXTIME($tmp)";
}
548
DBQueryFatal("insert into project_reservations set ".
549 550
	     "  pid='$pid', pid_idx='$pid_idx', name='$resname',".
	     "  count='$count', creator='$uid', creator_idx='$uid_idx', ".
551
	     "  created=now(),active=0 $typearg $startarg $endarg");
552 553 554 555
if (@nodelist) {
    foreach my $node_id (@nodelist) {
	if (!DBQueryWarn("insert into node_reservations set ".
			 "  pid='$pid', pid_idx='$pid_idx', ".
556
			 "  reservation_name='$resname', ".
557 558
			 "  node_id='$node_id'")) {
	    DBQueryWarn("delete from node_reservations ".
559 560 561 562
			"where pid_idx='$pid_idx' and ".
			"      reservation_name='$resname'");
	    DBQueryWarn("delete from project_reservations ".
			"where pid_idx='$pid_idx' and name='$resname'");
563 564 565 566 567
	    exit(-1);
	}
    }
}
DBQueryFatal("unlock tables");
568
print "Node reservation request for $count nodes has been created.\n";
569

570 571 572 573 574
#
# Activate, although note that the cron job (prereserve_check) might
# have beat us to it already. We check the active bit below.
#
if (!defined($starttime) || str2time($starttime) <= time()) {
575
    exit(StartReservation($pid, $resname));
576
}
577
exit(0);
578 579

#
580 581
# Activate a reservation request; find as many nodes as possible,
# and then mark it as active. 
582
#
583
sub StartReservation($$)
584
{
585
    my ($pid, $resname) = @_;
586

587
    DBQueryFatal("lock tables nodes write, node_types read, ".
588 589 590 591
		 " project_reservations write, ".
		 " node_reservations write, reserved write");

    my $noderes_result =
592 593
	DBQueryFatal("select node_id from node_reservations ".
		     "where pid='$pid' and reservation_name='$resname'");
594

595
    my $query_result =
596 597
	DBQueryFatal("select * from project_reservations ".
		     "where pid='$pid' and name='$resname'");
598
    if (!$query_result->numrows) {
599
	fatal("No reservation $resname defined for project");
600
    }
601 602 603 604 605 606 607 608 609 610 611 612
    my $row = $query_result->fetchrow_hashref();
    my $active = $row->{'active'};
    my $count  = $row->{'count'};
    my $types  = $row->{'types'};
    my @types  = (defined($types) ? split(",", $types) : ("*"));

    # Someone beat us to it. 
    if ($active) {
	DBQueryFatal("unlock tables");
	return 0;
    }
    
613
    #
614 615
    # First see if we can find enough (or any) nodes to satisfy the
    # prereserve, from nodes already prereserved.
616 617
    #
    $query_result =
618 619 620
	DBQueryFatal("select node_id from nodes ".
		     "where reserved_pid='$pid' and ".
		     "      reservation_name='$resname'");
621 622 623 624 625 626 627 628 629 630 631

    my $current = $query_result->numrows;

    if ($current) {
	print "There are currently $current nodes with a pre-reservation ".
	    "for project $pid.\n";
	if ($current >= $count) {
	    goto done;
	}
    }

632 633
    #
    # If we have a node list, then we do not do any type stuff, we
634
    # operate on specific nodes only.
635 636 637 638 639 640
    #
    if ($noderes_result->numrows) {
	@types = ();

	while (my ($node_id) = $noderes_result->fetchrow_array()) {
	    my $query_result =
641 642 643 644 645 646 647 648 649 650 651 652 653 654 655
		DBQueryFatal("select pid,reserved_pid from nodes ".
			     "left join reserved on ".
			     "   reserved.node_id=nodes.node_id ".
			     "where nodes.node_id='$node_id'");

	    next
		if (!$query_result->numrows);

	    my ($curpid,$rpid) = $query_result->fetchrow_array();

	    if ((defined($curpid) && $curpid ne $pid) ||
		(defined($rpid) && $rpid ne $pid)) {
		#
		# Someone else still has it, skip.
		#
656 657
		print "$node_id is still reserved to project $curpid, ".
		    "will be done later.\n"
658
		    if (defined($curpid));
659 660
		print "$node_id is still pre-reserved to project $rpid, ".
		    "will be done later.\n"
661 662
		    if (defined($rpid));
		next;
663
	    }
664 665
	    # Free or we already have it. Note that we do not allow a
	    # specific node to be in more then one reservation in the
Leigh Stoller's avatar
Leigh Stoller committed
666
	    # same project. See note above.
667 668 669
	    $current++;
	    DBQueryFatal("delete from node_reservations ".
			 "where node_id='$node_id'");
670 671
	    DBQueryFatal("update nodes set reserved_pid='$pid', ".
			 "   reservation_name='$resname' ".
672 673 674 675
			 "where node_id='$node_id'");
	}
    }

676
    #
677
    # Then check free/allocated nodes of each type.
678 679
    #
    foreach my $type (@types) {
680 681
	last
	    if ($current >= $count);
682 683 684 685 686 687

	my $tcount  = 0;
	my $tclause = "";
	if ($type ne "*") {
	    $tclause = "and node_types.type='$type'";
	}
688 689

	#
690 691 692 693
	# check for nodes already reserved to the project; they count
	# against the prereserve request. If the reserved_pid is not
	# set, we set it to indicate that they are attached to this
	# pre-reserve.
694 695
	#
	$query_result =
696 697 698
	    DBQueryFatal("select reserved.node_id,nodes.reserved_pid, ".
			 "       nodes.reservation_name ".
			 "  from reserved ".
699 700 701 702 703 704
			 "left join nodes on nodes.node_id=reserved.node_id ".
			 "left join node_types on node_types.type=nodes.type ".
			 "where nodes.role='testnode' and ".
			 "      node_types.class='pc' and ".
			 "      reserved.pid='$pid' $tclause");
	
705 706 707 708 709 710 711
	while (my ($node_id,$reserved_pid,
		   $reservation_name) = $query_result->fetchrow_array()) {
	    if (!defined($reserved_pid)) {
		DBQueryFatal("update nodes set reserved_pid='$pid', ".
			     "   reservation_name='$resname' ".
			     "where node_id='$node_id'");
		$tcount++;
712
		$current++;
713
	    }
714 715 716 717 718 719 720
	    #
	    # It is unlikely this will happen, unless someone mucks with
	    # the database directly. 
	    #
	    elsif ($reserved_pid eq $pid && $reservation_name eq $resname) {
		$current++;
	    }
721 722 723 724 725 726 727 728 729 730 731
	    last
		if ($current >= $count);
	}
	if ($tcount) {
	    print "Set reserved_pid for $tcount (allocated)" .
		($type eq "*" ? "" : " $type") . " nodes.\n";

	    last
		if ($current >= $count);
	}

732 733 734 735 736 737 738 739 740
	$query_result =
	    DBQueryFatal("select nodes.node_id from nodes ".
			 "left join reserved on ".
			 "     reserved.node_id=nodes.node_id ".
			 "left join node_types on node_types.type=nodes.type ".
			 "where reserved.node_id is null and ".
			 "      nodes.role='testnode' and ".
			 "      node_types.class='pc' and ".
			 "      nodes.reserved_pid is null $tclause");
741 742

	$tcount = 0;
743
	while (my ($node_id) = $query_result->fetchrow_array()) {
744 745
	    DBQueryFatal("update nodes set reserved_pid='$pid', ".
			 "   reservation_name='$resname' ".
746 747 748 749 750 751 752 753 754 755
			 "where node_id='$node_id'");
	    $current++;
	    $tcount++;
	    last
		if ($current >= $count);
	}
	if ($tcount) {
	    print "Set reserved_pid for $tcount (free)" .
		($type eq "*" ? "" : " $type") . " nodes.\n";
	}
756
    }
757
    if ($current >= $count) {
758
	print "Got as many nodes as you wanted. Yippie!\n";
759
    }
760 761 762 763 764 765 766
    #
    # Update the reservation entry, and mark as active.
    #
  done:
    $count -= $current;
    $count = 0 if ($count < 0);

767
    DBQueryFatal("update project_reservations set ".
768
		 "       count='$count',active=1 ".
769
		 "where pid='$pid' and name='$resname'");
770 771 772
    DBQueryFatal("unlock tables");

    if ($count == 0 && $sendmail) {
773 774
	SENDMAIL($TBOPS, "Pre Reservation for $pid,$resname has completed",
		 "The pre reservation request for $pid,$resname ".
775 776 777
		 "has been fullfilled\n", $TBOPS);
    }
    return 0
778 779 780 781 782 783 784 785 786 787
}

sub fatal($)
{
    my ($mesg) = $_[0];

    die("*** $0:\n".
	"    $mesg\n");
}