modlease.in 10.5 KB
Newer Older
1 2
#!/usr/bin/perl -w
#
Mike Hibler's avatar
Mike Hibler committed
3
# Copyright (c) 2013-2014 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 23 24 25 26 27 28 29 30 31 32 33 34 35
# 
# {{{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/>.
# 
# }}}
#
use strict;
use English;
use Getopt::Std;
use Date::Parse;

#
# Modify a lease.
# You can update the expiration or "last used" dates, change the state,
# or add/remove/modify the set of attributes.
#
sub usage()
{
Mike Hibler's avatar
Mike Hibler committed
36
    print STDERR "Usage: modlease [-hdRU] [-w time] [-s state] [-e expire] [-l last-used] [-a key=value] [-d key] name\n";
37 38
    print STDERR "   -h         This message\n";
    print STDERR "   -d         Print additional debug info\n";
Mike Hibler's avatar
Mike Hibler committed
39 40 41
    print STDERR "   -R         Permit resource alloc/dealloc to occur on state changes\n";
    print STDERR "   -U         Unlock the lease (do not do this unless you know what you are doing!)\n";
    print STDERR "   -w time    Try for up to time seconds to lock lease (0 means forever)\n";
42
    print STDERR "   -s state   Update the state\n";
43 44
    print STDERR "   -e date    Update the expiration date ('now' for current time, 'never' for never)\n";
    print STDERR "   -l date    Update the last used date ('now' for current time, 'never' for never)\n";
45 46 47 48 49 50

    print STDERR "   -a key=val Add or update attribute 'key' with value 'val'\n";
    print STDERR "   -r key     Remove attribute 'key'\n";
    print STDERR "   name       Name of lease (of form <pid>/<id>)\n";
    exit(-1);
}
Mike Hibler's avatar
Mike Hibler committed
51
my $optlist  = "dhRUw:s:e:l:a:r:";
52 53
my $debug = 0;
my $pid;
54
my $gid;
55 56 57 58 59 60 61
my $state;
my $expire;
my $lastused;
my $addattr;
my $delattr;
my $lname;
my $now = time();
62
my $lease;
Mike Hibler's avatar
Mike Hibler committed
63 64 65
my $waittime;
my $doresources = 0;
my $dounlock = 0;
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81

# Protos
sub fatal($);

#
# Configure variables
#
my $TB		 = "@prefix@";

#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use libdb;
use Lease;
use Project;
82
use Group;
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
use User;

#
# 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();
}
if (defined($options{d})) {
    $debug++;
}
Mike Hibler's avatar
Mike Hibler committed
109 110 111 112 113 114
if (defined($options{R})) {
    $doresources = 1;
}
if (defined($options{U})) {
    $dounlock = 1;
}
115 116 117 118 119 120
if (defined($options{s})) {
    $state = $options{s};
}
if (defined($options{e})) {
    if ($options{e} eq "now") {
	$expire = $now;
121 122
    } elsif ($options{e} eq "never") {
	$expire = 0;
123 124
    } else {
	$expire = str2time($options{e});
125
	if (!defined($expire)) {
126 127 128 129 130 131 132
	    fatal("Could not parse expiration date.");
	}
    }
}
if (defined($options{l})) {
    if ($options{l} eq "now") {
	$lastused = $now;
133 134
    } elsif ($options{l} eq "never") {
	$lastused = 0;
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
    } else {
	$lastused = str2time($options{l});
	if (!defined($lastused)) {
	    fatal("Could not parse last-used date.");
	}
    }
}
if (defined($options{a})) {
    $addattr = $options{a};
    if ($addattr !~ /^([-\w]+)=([-\w\.\+\/:]+)$/) {
	fatal("Malformed attribute name/value '$addattr'.");
    }
}
if (defined($options{r})) {
    $delattr = $options{r};
    if ($delattr !~ /^([-\w]+)$/) {
	fatal("Malformed attribute name '$delattr'.");
    }
}
Mike Hibler's avatar
Mike Hibler committed
154 155 156 157 158 159
if (defined($options{w})) {
    $waittime = $options{w};
    if ($waittime !~ /^\d+$/) {
	fatal("Wait time must be >= 0.");
    }
}
160

161 162
if (!($state || defined($expire) || defined($lastused) ||
      $addattr || $delattr || $dounlock)) {
163 164 165 166 167 168 169 170 171 172 173
    print STDERR "Must specify SOME action!\n";
    usage();
}
if (@ARGV != 1) {
    print STDERR "Must specify exactly one lease.\n";
    usage();
}

# lease name must include a project
$lname = $ARGV[0];
if ($lname =~ /^([-\w]+)\/([-\w]+)$/) {
174
    $pid   = $gid = $1;
175
    $lname = $2;
176 177 178 179 180 181 182
}
elsif ($lname =~ /^([-\w]+)\/([-\w]+)\/([-\w]+)$/) {
    $pid   = $1;
    $gid   = $2;
    $lname = $3;
}
else {
183 184 185
    fatal("Lease name $lname not in the form <pid>/<lname>.");
}

Mike Hibler's avatar
Mike Hibler committed
186 187 188 189
#
# XXX right now there are no user-modifiable attributes.
# Maybe we could let them manipulate attributes, but we don't right now.
#
190 191 192 193 194 195 196 197 198 199 200 201
if (!TBAdmin()) {
    fatal("Only admins can modify leases right now.");
}

my $this_user = User->ThisUser();
if (! defined($this_user)) {
    fatal("You ($UID) do not exist!");
}

#
# Check dates: must be appropriately in the past/future.
#
202
if (defined($expire) && $expire != 0 && $expire < $now) {
203 204 205 206 207 208 209 210 211
    fatal("Cannot set expiration date in the past.");
}
if (defined($lastused) && $lastused > $now) {
    fatal("Cannot set last-used date in the future.");
}

#
# Check name: must exist and be modifiable.
#
212
$lease = Lease->Lookup($pid, $gid, $lname);
213
if (!$lease) {
214
    fatal("$pid/$lname: lease does not exist.");
215 216
}
if (!$lease->AccessCheck($this_user, LEASE_ACCESS_MODIFY())) {
217
    fatal("$pid/$lname: you are not allowed to modify lease.");
218 219
}

Mike Hibler's avatar
Mike Hibler committed
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
#
# Unlock lease.
# XXX This should only be done if something blew up and left the
# lease locked.
#
if ($dounlock) {
    if ($lease->Lock()) {
	my $lpid = $lease->lockpid();
	if ($lease->Unlock()) {
	    fatal("$pid/$lname: could not force unlock, lock held by pid $lpid");
	} else {
	    print "$pid/$lname: forced lease unlock, was held by pid $lpid\n";
	}
    } else {
	$lease->Unlock();
	print "$pid/$lname: lock not held\n";
    }
    exit(0);
}

240 241
#
# Lock the lease while we change it.
Mike Hibler's avatar
Mike Hibler committed
242 243 244 245 246 247
# If it is already locked, someone else is doing something to it.
#
if (!defined($waittime)) {
    fatal("$pid/$lname: could not acquire lock, try again with -w")
	if ($lease->Lock());
}
248
#
Mike Hibler's avatar
Mike Hibler committed
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
# Check that characteristics of the lease that we are modifying do
# not change while we are waiting for the lock. If they do, we abort.
# XXX this is an epic amount of work for probably very little value!
#
else {
    my $ostate = $lease->state();
    my $oexpire = $lease->expiration();
    my $olused = $lease->last_used();
    my $oattrs = $lease->GetAttributes();

    fatal("$pid/$lname: could not acquire lock after $waittime seconds")
	if ($lease->WaitLock($waittime, 1));

    my $nstate = $lease->state();
    my $nexpire = $lease->expiration();
    my $nlused = $lease->last_used();
    my $nattrs = $lease->GetAttributes();

    if ($state && ($ostate ne $nstate)) {
	fatal("$pid/$lname: lease state changed ".
	      "while waiting for the lock ($ostate => $nstate).");
    }
271
    if (defined($expire) && ($oexpire != $nexpire)) {
Mike Hibler's avatar
Mike Hibler committed
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
	fatal("$pid/$lname: lease expiration changed ".
	      "while waiting for the lock ($oexpire => $nexpire).");
    }
    if (defined($lastused) && ($olused != $nlused)) {
	fatal("$pid/$lname: lease last_used changed ".
	      "while waiting for the lock ($olused => $nlused).");
    }
    if ($addattr || $delattr) {
	foreach my $a (keys %$oattrs) {
	    if (!exists($nattrs->{$a}) || ($oattrs->{$a} ne $nattrs->{$a})) {
		fatal("$pid/$lname: lease attributes changed ".
		      "while waiting for the lock.");
	    }
	}
	foreach my $a (keys %$nattrs) {
	    if (!exists($oattrs->{$a})) {
		fatal("$pid/$lname: lease attributes changed ".
		      "while waiting for the lock.");
	    }
	}
    }
293 294
}

295
#
Mike Hibler's avatar
Mike Hibler committed
296 297 298 299 300 301
# Handle state. Ensure that this is a valid state transition.
#
# N.B.: the transition from/to unapproved is special since it implies
# allocation or deallocation of resources. We may want to rethink the
# ability to alloc/dealloc here as it is not obvious that modifying a
# lease should have such severe side-effects.
302 303 304 305
#
if ($state) {
    my $curstate = $lease->state();

Mike Hibler's avatar
Mike Hibler committed
306 307
    if ($state ne $curstate) {
	if (!$lease->ValidTransition($state)) {
Mike Hibler's avatar
Mike Hibler committed
308
	    fatal("$pid/$lname: cannot transition from $curstate to $state.");
Mike Hibler's avatar
Mike Hibler committed
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
	}

	if ($curstate eq LEASE_STATE_UNAPPROVED()) {
	    if ($doresources) {
		if ($lease->AllocResources($state)) {
		    fatal("$pid/$lname: could not allocate resources ".
			  "when moving from unapproved.");
		}
	    } else {
		fatal("$pid/$lname: transition from 'unapproved' ".
		      "requires allocation of resources, use -R");
	    }
	}
	if ($state eq LEASE_STATE_UNAPPROVED()) {
	    if ($doresources) {
		if ($lease->DeallocResources()) {
		    fatal("$pid/$lname: could not deallocate resources ".
			  "when moving to unapproved.");
		}
	    } else {
		fatal("$pid/$lname: transition to 'unapproved' ".
		      "requires deallocation of resources, use -R");
	    }
	}
333
    }
Mike Hibler's avatar
Mike Hibler committed
334 335 336

    # We do this if ostate == nstate so that you can update the statestamp
    if ($lease->UpdateState($state)) {
337 338
	fatal("$pid/$lname: could not set state to '$state'.");
    }
Mike Hibler's avatar
Mike Hibler committed
339 340 341 342 343
    if ($state eq $curstate) {
	print "$pid/$lname: set state timestamp.\n";
    } else {
	print "$pid/$lname: changed state from '$curstate' to '$state'.\n";
    }
344 345 346
}

# Handle expiration date
347
if (defined($expire)) {
Mike Hibler's avatar
Mike Hibler committed
348 349 350 351 352 353
    # XXX in case time ticked on us after we got "now"
    $expire = time()
	if ($expire == $now);
    if ($lease->SetEndTime($expire)) {
	fatal("$pid/$lname: could not update expiration time.");
    } else {
354 355 356 357 358 359
	if ($expire == 0) {
	    print "$pid/$lname: set to no expiration date.\n";
	} else {
	    my $t = localtime($expire);
	    print "$pid/$lname: set expiration date to '$t'.\n";
	}
Mike Hibler's avatar
Mike Hibler committed
360
    }
361 362 363 364 365 366
}

# Handle last used date
if (defined($lastused)) {
    if (($lastused >= $now && $lease->BumpLastUsed()) ||
	($lastused < $now && $lease->SetLastUsedTime($lastused))) {
367
	fatal("$pid/$lname: could not update last-used time.");
Mike Hibler's avatar
Mike Hibler committed
368 369 370 371 372
    } elsif ($lastused == 0) {
	print "$pid/$lname: set last_used date to never.\n";
    } else {
	my $t = localtime($lastused);
	print "$pid/$lname: set last_used date to '$t'.\n";
373 374 375 376
    }
}

#
Mike Hibler's avatar
Mike Hibler committed
377
# Handle attributes. Delete, then add (or replace).
378
#
Mike Hibler's avatar
Mike Hibler committed
379 380 381 382 383 384
if ($delattr) {
    if ($lease->DeleteAttribute($delattr)) {
	fatal("$pid/$lname: could not remove attribute '$delattr'.");
    } else {
	print "$pid/$lname: deleted attribute '$delattr'.\n";
    }
385 386 387 388
}
if ($addattr) {
    if ($addattr !~ /^([-\w]+)=([-\w\.\+\/:]+)$/ ||
	$lease->SetAttribute($1, $2)) {
389
	fatal("$pid/$lname: could not set attribute '$addattr'.");
Mike Hibler's avatar
Mike Hibler committed
390 391
    } else {
	print "$pid/$lname: added attribute '$addattr'.\n";
392 393 394
    }
}

395 396
$lease->Unlock();

397 398 399 400 401 402
exit(0);

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

403 404 405
    if (defined($lease) && $lease->GotLock()) {
	$lease->Unlock();
    }
406 407 408 409
    die("*** $0:\n".
	"    $mesg\n");
}