#!/usr/bin/perl -w # # Copyright (c) 2013-2017 University of Utah and the Flux Group. # # {{{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 . # # }}} # use strict; use English; use Getopt::Std; use Date::Parse; # # Approve a lease. # This forces allocation of storage for dataset leases. # sub usage() { print STDERR "Usage: approvelease [-hd] [-D reason] [-w waittime] [-s state] name\n"; print STDERR " -h This message\n"; print STDERR " -d Print additional debug info\n"; print STDERR " -s state New state for the lease (defaults to 'valid')\n"; print STDERR " -w time Try for up to time seconds to lock lease (0 means forever)\n"; print STDERR " -D reason Deny the lease and destroy it\n"; print STDERR " -b Allocate resources for approved leases in the background\n"; print STDERR " name Name of lease (of form /)\n"; exit(-1); } my $optlist = "dhs:w:D:b"; my $debug = 0; my $background = 0; my $pid; my $gid; my $state = "valid"; my $lname; my $lease; my $waittime; my $deny; # Protos sub fatal($); sub notifyuser($$$); # # Configure variables # my $TB = "@prefix@"; my $TBOPS = "@TBOPSEMAIL@"; my $MANAGE_DATASET = "$TB/bin/manage_dataset"; # # Testbed Support libraries # use lib "@prefix@/lib"; use libdb; use libtestbed; use Lease; use Project; use Group; use User; use APT_Dataset; # # 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++; } if (defined($options{b})) { $background++; } if (defined($options{s})) { $state = $options{s}; } if (defined($options{w})) { $waittime = $options{w}; if ($waittime !~ /^\d+$/) { fatal("Wait time must be >= 0."); } } if (defined($options{D})) { $deny = $options{D}; } 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]+)$/) { $pid = $gid = $1; $lname = $2; } elsif ($lname =~ /^([-\w]+)\/([-\w]+)\/([-\w]+)$/) { $pid = $1; $gid = $2; $lname = $3; } else { fatal("Lease name $lname not in the form /."); } # # Normally, leases are approved at creation time after quota checks. # To approve other leases explicitly, you must be admin. # if (!TBAdmin()) { fatal("Only admins can approve leases."); } my $this_user = User->ThisUser(); if (! defined($this_user)) { fatal("You ($UID) do not exist!"); } # # Check name: must exist, be modifiable and in the unapproved state. # $lease = Lease->Lookup($pid, $gid, $lname); if (!$lease) { fatal("$pid/$lname: lease does not exist."); } my $aptdataset = APT_Dataset->LookupByRemoteUUID($lease->uuid()); # Acquire the lease lock before we start making state changes. if (!defined($waittime)) { fatal("$pid/$lname: could not acquire lock, try again with -w") if ($lease->Lock()); } else { fatal("$pid/$lname: could not acquire lock after $waittime seconds") if ($lease->WaitLock($waittime, 1)); } # Sanity checks on the state. if ($lease->state() ne LEASE_STATE_UNAPPROVED()) { # # XXX since we might have waited for the lock, it is possible that # someone beat us to the punch. Don't consider a current state of # "valid" or "locked" as an error. # if ($lease->state() eq LEASE_STATE_VALID() || $lease->state() eq LEASE_STATE_LOCKED()) { $lease->Unlock(); print "$pid/$lname: has already been approved.\n"; exit(0); } fatal("$pid/$lname: lease is in invalid state '". $lease->state(). "'."); } if (!$lease->ValidTransition($state)) { fatal("$pid/$lname: cannot approve lease to state '$state'."); } # If we are denying, send a message to the owner and destroy the lease if (defined($deny)) { print "$pid/$lname: denied, destroying\n"; notifyuser($lease, 0, $deny); if ($lease->Delete()) { fatal("$pid/$lname: could not destroy lease."); } exit(0); } # Finally, allocate the resources. my $logname; if ($background) { print "Resource allocation proceeding the background ...\n"; $logname = TBMakeLogname("approvelease"); if (my $childpid = TBBackGround($logname)) { exit(0); } # We want the lock in the child. $lease->TakeLock(); # Let parent exit; sleep(2); } my $rv = $lease->AllocResources($state); if ($rv != LEASE_ERROR_NONE()) { my $msg = ($rv == LEASE_ERROR_ALLOCFAILED() ? "Resource allocation failed" : "Unexpected failure"); print STDERR "$msg, contact testbed-ops.\n"; if ($background) { SENDMAIL($TBOPS, "Lease resources allocation failed during approval!", "Background $msg for Lease '$pid/$gid/$lname'; ". "lease left in failed state!\n\n", $TBOPS, undef, $logname); unlink($logname); } $lease->UpdateState("failed"); fatal("$pid/$lname: could not approve lease into state '$state'"); } if ($background) { unlink($logname); } # # If a Portal created dataset, approved from outside the Portal interface, # we want to poke things so that the web UI sees the change. Not super # important, I know. Ignore failure of course. # if (defined($aptdataset)) { system("$MANAGE_DATASET refresh -p ". $aptdataset->pid() . "/" . $aptdataset->dataset_id()); } $lease->Unlock(); print "$pid/$lname: approved, state is now '$state'\n"; # # Send mail to the lease owner. # notifyuser($lease, 1, ""); exit(0); sub notifyuser($$$) { my ($lease,$approved,$msg) = @_; my $action = ($approved ? "approved" : "denied"); my $user = User->LookupByUid($lease->owner()); if ($user) { my $email = $user->email(); my $pid = $lease->pid(); my $lname = $lease->lease_id(); SENDMAIL($email, "Dataset lease $action", "Your Emulab dataset lease $pid/$lname has been $action.\n". "$msg.\n", $TBOPS); } } sub fatal($) { my ($mesg) = $_[0]; $lease->Unlock() if (defined($lease) && $lease->GotLock()); die("*** $0:\n". " $mesg\n"); }