Commit 6fef3cce authored by Mike Hibler's avatar Mike Hibler

Add script to approve a lease and add some locking in other scripts.

approvelease is the place where storage actually gets allocated for
a lease. It uses bscontrol to contact an appropriate freeNAS storage
server and allocate a ZFS volume.

deletelease is the place where storage is deallocated. Note that once
a lease has been approved and storage allocated, it cannot be returned
to the unapproved state. The only way to free storage is to delete the
lease.

Both approve and delete use an intermediate state, "initializing", to
signal that the lease is in the middle of a potentially time-consuming
allocation/deallocation procedure. I probably should have just used the
lease locking mechanism instead.

Approve, delete, and mod all DO use the locking mechanism when examining
and manipulating the state of a lease. Nonetheless, I am sure that are
still plenty of races.
parent 5fa14dd7
......@@ -35,7 +35,7 @@ BIN_SCRIPTS = delay_config sshtb create_image node_admin link_config \
setdest loghole webcopy linkmon_ctl snmp-if-deref.sh \
template_record spewevents \
wbts_dump mkblob rmblob \
showlease createlease deletelease modlease
showlease createlease deletelease modlease approvelease
SBIN_SCRIPTS = vlandiff vlansync withadminprivs export_tables cvsupd.pl \
eventping grantnodetype import_commitlog daemon_wrapper \
opsreboot deletenode node_statewait grabwebcams \
......
#!/usr/bin/perl -w
#
# Copyright (c) 2013 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 <http://www.gnu.org/licenses/>.
#
# }}}
#
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] [-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 " name Name of lease (of form <pid>/<id>)\n";
exit(-1);
}
my $optlist = "dhs:";
my $debug = 0;
my $pid;
my $state = "valid";
my $lname;
my $now = time();
my $lease;
# Protos
sub fatal($);
#
# Configure variables
#
my $TB = "@prefix@";
my $BSCONTROL = "$TB/sbin/bscontrol";
#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use libdb;
use Lease;
use Project;
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++;
}
if (defined($options{s})) {
$state = $options{s};
}
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 = $1;
$lname = $2;
} else {
fatal("Lease name $lname not in the form <pid>/<lname>.");
}
# XXX must be admin right now
if (!TBAdmin()) {
fatal("Only admins can approve leases.");
}
my $this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
my $project = Project->Lookup($pid);
if (!defined($project)) {
fatal("No such project $pid\n");
}
#
# Check name: must exist, be modifiable and in the unapproved state.
#
$lease = Lease->Lookup($pid, $lname);
if (!$lease) {
fatal("$pid/$lname: lease does not exist.");
}
if (!$lease->AccessCheck($this_user, LEASE_ACCESS_MODIFY())) {
fatal("$pid/$lname: you are not allowed to modify lease.");
}
# Lock the lease once we start messing with the state.
if ($lease->Lock()) {
fatal("$pid/$lname: currently locked, try again later.");
}
if ($lease->state() ne "unapproved") {
fatal("$pid/$lname: lease has already been approved!?");
}
#
# Check new state. Must be something other than "unapproved".
#
if ($state eq "unapproved") {
fatal("$pid/$lname: must set to one of the approved states.");
}
#
# Force allocation of storage for dataset leases.
# We put the lease in the special "initializing" state while doing this.
#
if ($lease->type() =~ /dataset$/) {
#
# Must be an associated size attribute.
#
my $size = $lease->GetAttribute("size");
if ($size && $size =~ /^(\d+)$/) {
$size = $1;
} else {
fatal("$pid/$lname: no valid 'size' attribute");
}
#
# Mark the lease as in-transition and unlock it since allocating
# storage could take a long time.
#
if ($lease->UpdateState("initializing")) {
fatal("$pid/$lname: could not set state to 'initializing'.");
}
$lease->Unlock();
#
# Call the blockstore control program to handle all things blockstore
# related (e.g., the actual allocation of storage on the storage servers).
#
my $idx = $lease->lease_idx();
if (system("$BSCONTROL -l $idx -s $size create lease-$idx")) {
$lease->UpdateState("unapproved");
fatal("$pid/$lname: could not allocate storage");
}
# no need to re-lock transitioning out of initializing state
$lease->UpdateState($state);
print "$pid/$lname: allocated $size MiB of storage\n";
} else {
$lease->UpdateState($state);
$lease->Unlock();
}
# It all worked!
print "$pid/$lname: state is now '$state'\n";
exit(0);
sub fatal($)
{
my ($mesg) = $_[0];
if (defined($lease) && $lease->GotLock()) {
$lease->Unlock();
}
die("*** $0:\n".
" $mesg\n");
}
......@@ -41,6 +41,7 @@ my $optlist = "dh";
my $debug = 0;
my $pid;
my $lname;
my $lease;
# Protos
sub fatal($);
......@@ -49,7 +50,7 @@ sub fatal($);
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $BSCONTROL = "$TB/sbin/bscontrol";
#
# Testbed Support libraries
......@@ -97,6 +98,11 @@ if ($lname =~ /^([-\w]+)\/([-\w]+)$/) {
fatal("Lease name $lname not in the form <pid>/<lname>.");
}
# XXX must be admin right now
if (!TBAdmin()) {
fatal("Only admins can delete leases.");
}
my $this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
......@@ -106,12 +112,47 @@ if (! defined($this_user)) {
# Check lease: project must exist, lease must exist,
# caller must have privilege to destroy.
#
my $lease;
if (!Project->Lookup($pid) || !($lease = Lease->Lookup($pid, $lname)) ||
!$lease->AccessCheck($this_user, LEASE_ACCESS_DESTROY())) {
fatal("Cannot access lease $pid/$lname.");
}
#
# Lock the lease and handle cleanup.
#
if ($lease->Lock()) {
fatal("$pid/$lname: currently locked, try again later.");
}
my $ostate = $lease->state();
if ($ostate eq "initializing") {
fatal("$pid/$lname: in transition, cannot delete");
}
#
# For an approved dataset lease, we must free up the server storage
#
if ($lease->type() =~ /dataset$/ && $ostate ne "unapproved") {
#
# Put in initializing state and unlock since this could be
# time consuming.
#
if ($lease->UpdateState("initializing")) {
fatal("$pid/$lname: could not mark lease as in-transition.");
}
$lease->Unlock();
#
# Call the blockstore control program to handle all things blockstore
# related (e.g., the actual deallocation of storage on the servers).
#
my $idx = $lease->lease_idx();
if (system("$BSCONTROL destroy lease-$idx")) {
$lease->UpdateState("unapproved");
fatal("$pid/$lname: could not deallocate storage");
}
}
if ($lease->Delete()) {
fatal("Could not destroy lease $pid/$lname.");
}
......@@ -122,6 +163,9 @@ sub fatal($)
{
my ($mesg) = $_[0];
if (defined($lease) && $lease->GotLock()) {
$lease->Unlock();
}
die("*** $0:\n".
" $mesg\n");
}
......
......@@ -55,6 +55,7 @@ my $addattr;
my $delattr;
my $lname;
my $now = time();
my $lease;
# Protos
sub fatal($);
......@@ -180,7 +181,7 @@ if (defined($lastused) && $lastused > $now) {
#
# Check name: must exist and be modifiable.
#
my $lease = Lease->Lookup($pid, $lname);
$lease = Lease->Lookup($pid, $lname);
if (!$lease) {
fatal("$pid/$lname: lease does not exist.");
}
......@@ -188,6 +189,13 @@ if (!$lease->AccessCheck($this_user, LEASE_ACCESS_MODIFY())) {
fatal("$pid/$lname: you are not allowed to modify lease.");
}
#
# Lock the lease while we change it.
#
if ($lease->Lock()) {
fatal("$pid/$lname: could not acquire lock, try again later.");
}
#
# Handle state. The transition from unapproved->* is special and must
# be done via approvelease. It implies allocation of storage for dataset
......@@ -236,12 +244,17 @@ if ($addattr) {
}
}
$lease->Unlock();
exit(0);
sub fatal($)
{
my ($mesg) = $_[0];
if (defined($lease) && $lease->GotLock()) {
$lease->Unlock();
}
die("*** $0:\n".
" $mesg\n");
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment