Commit 9a6cdeae authored by Mike Hibler's avatar Mike Hibler

Add support for lease extention (renewal).

Add CLI for extending a lease (called extenddataset on ops). The length
of the extension and the number of times it can be extended are controlled
by site variables.
parent 1c50ebca
......@@ -66,7 +66,7 @@ my @LEASE_TYPES = ("stdataset", "ltdataset");
# maxextend Number of times a user can extend the lease
# (0 == unlimited)
# extendperiod Length (days) of each user-requested extention
# (0 == do not allow extensions (when maxextend != 0))
# (0 == do not allow extensions)
#
my @LEASE_VARS = (
"maxsize",
......@@ -109,7 +109,7 @@ my %LEASE_VAR_DEFAULTS = (
"ltdataset/graceperiod" => 180, # 6 months
"ltdataset/autodestroy" => 0, # no
"ltdataset/usequotas" => 1, # yes
"ltdataset/maxextend" => 1, # combined with...
"ltdataset/maxextend" => 1, # ignored because...
"ltdataset/extendperiod" => 0, # ...means no user extention
);
......@@ -205,6 +205,7 @@ sub last_used($) {return str2time($_[0]->{'DBROW'}->{'last_used'}); }
sub last_checked($) {return str2time($_[0]->{'DBROW'}->{'last_checked'}); }
sub state($) {return $_[0]->{'DBROW'}->{'state'}; }
sub statestamp($) {return str2time($_[0]->{'DBROW'}->{'statestamp'}); }
sub renewals($) {return $_[0]->{'DBROW'}->{'renewals'}; }
sub locktime($) {return str2time($_[0]->{'DBROW'}->{'locked'}); }
sub lockpid($) {return $_[0]->{'DBROW'}->{'locker_pid'}; }
......@@ -604,6 +605,68 @@ sub DeallocResources($) {
return 0;
}
#
# Extend (renew) a lease by the indicated amount.
# Also increments the renewal count and transitions the lease back into
# the valid state. Returns 0 on success, non-zero otherwise.
#
# N.B. The caller is responsible for locking the lease during this operation.
#
sub Extend($$)
{
my ($self,$addtime) = @_;
#
# Lease must be in some state other than unapproved/valid but that
# can transition to valid.
#
my $cstate = $self->state();
if ($cstate eq "unapproved" || $cstate eq "valid" ||
!$self->ValidTransition("valid")) {
print STDERR
"$self: Extend: cannot transition from '$cstate' -> 'valid'\n";
return LEASE_ERROR_FAILED();
}
#
# If the expiration time has been reached, extend it by the
# indicated time.
#
if ($self->IsExpired()) {
if ($self->AddTime($addtime)) {
print STDERR
"$self: Extend: could not extend lease\n";
return LEASE_ERROR_FAILED();
}
}
#
# Otherwise we assume that the lease went idle and we bump
# the last_used time so it is no longer idle.
#
else {
if ($self->BumpLastUsed()) {
print STDERR
"$self: Extend: could not extend lease\n";
return LEASE_ERROR_FAILED();
}
}
#
# Increment the renewal count and put lease back into the valid state.
#
my $idx = $self->idx();
DBQueryWarn("update project_leases set renewals=renewals+1 ".
"where lease_idx=$idx")
or return LEASE_ERROR_FAILED();
if ($self->UpdateState("valid")) {
print STDERR
"$self: Extend: could not extend lease\n";
return LEASE_ERROR_FAILED();
}
return 0;
}
#
# Return a hashref of sitevars for the indicated lease type.
# Why not let the caller make individual GetSiteVar calls? Well, efficiency
......@@ -641,6 +704,13 @@ sub SiteVars($$) {
}
}
# Convert day values to seconds
foreach my $n ("maxlease", "maxidle", "graceperiod", "extendperiod") {
if (exists($vars{$n})) {
$vars{$n} *= (24 * 60 * 60);
}
}
return \%vars;
}
......
#
# Copyright (c) 2000-2013 University of Utah and the Flux Group.
# Copyright (c) 2000-2014 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -36,7 +36,7 @@ BIN_SCRIPTS = delay_config sshtb create_image node_admin link_config \
template_record spewevents \
wbts_dump mkblob rmblob \
showlease createlease deletelease modlease approvelease \
createdataset
extendlease createdataset
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.
# Copyright (c) 2013-2014 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -208,8 +208,6 @@ if (!exists($descrip{$dstype})) {
# usequotas If non-zero, enforce per-project dataset quotas
#
my $vars = Lease->SiteVars($dstype);
# convert to seconds
$vars->{"maxlease"} *= (24 * 60 * 60);
#
# Check size: size must be > 0 and the size must be less than site limit
......
#!/usr/bin/perl -w
#
# Copyright (c) 2013-2014 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;
#
# Extend (renew) a lease.
#
# A user can extend a lease iff the lease type allows extentions
# (sitevar:extendperiod != 0) and either the leasetype allows unlimitied
# extentions (sitevar:maxextend == 0) or the lease has not yet been extended
# the maximum number of times (lease:renewals < sitevar:maxextend).
#
# Currently, the lease must be in the "grace" state to extend it.
# Perhaps at lease an admin should be able to extend a lease in the
# "locked" state.
#
# The caller must be an admin or have modify permissions on the lease.
#
sub usage()
{
print STDERR "Usage: extendlease [-hd] name\n";
print STDERR " -h This message\n";
print STDERR " -d Print additional debug info\n";
print STDERR " -w time Try for up to time seconds to lock lease (0 means forever)\n";
print STDERR " name Name of lease (of form <pid>/<id>)\n";
exit(-1);
}
my $optlist = "dh";
my $debug = 0;
my $pid;
my $lname;
my $now = time();
my $lease;
my $waittime;
# Protos
sub fatal($);
#
# Configure variables
#
my $TB = "@prefix@";
#
# 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{w})) {
$waittime = $options{w};
if ($waittime !~ /^\d+$/) {
fatal("Wait time must be >= 0.");
}
}
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>.");
}
my $this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
#
# Check lease: project must exist, lease must exist,
# caller must have privilege to modify.
#
if (!Project->Lookup($pid) || !($lease = Lease->Lookup($pid, $lname)) ||
!$lease->AccessCheck($this_user, LEASE_ACCESS_MODIFY())) {
fatal("Cannot access lease $pid/$lname.");
}
#
# Perform leasetype-specific checks.
#
my $ltype = $lease->type();
my $vars = Lease->SiteVars($ltype);
if (!defined($vars)) {
fatal("$pid/$lname: do not know anything about '$ltype' leases.");
}
#
# Make sure lease type supports extensions.
#
if ($vars->{'extendperiod'} == 0) {
fatal("$pid/$lname: cannot extend '$ltype' leases");
}
#
# Lock the lease so we do the rest atomically.
#
if (!defined($waittime)) {
if ($lease->Lock()) {
fatal("$pid/$lname: could not acquire lock, try again with -w");
}
}
else {
if ($lease->WaitLock($waittime, 1)) {
fatal("$pid/$lname: could not acquire lock after $waittime seconds");
}
}
#
# Make sure the lease is expired (i.e., is in the 'grace' state).
#
if ($lease->state() ne LEASE_STATE_GRACE()) {
fatal("$pid/$lname: can only renew leases in the 'grace' state");
}
#
# See if we have already hit the extention limit
#
if ($vars->{'maxextend'} > 0 && $lease->renewals() >= $vars->{'maxextend'}) {
fatal("$pid/$lname: already extended maximum number of times");
}
#
# Extend it!
#
if ($lease->Extend($vars->{'extendperiod'})) {
fatal("$pid/$lname: could not extend lease");
}
$lease->Unlock();
exit(0);
sub fatal($)
{
my ($mesg) = $_[0];
if (defined($lease) && $lease->GotLock()) {
$lease->Unlock();
}
die("*** $0:\n".
" $mesg\n");
}
#!/usr/bin/perl -w
#
# Copyright (c) 2013 University of Utah and the Flux Group.
# Copyright (c) 2013-2014 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -199,9 +199,9 @@ if (@lids > 0) {
printf $fmt, "Pid/Name", "Owner", "Type", "State",
"Lock Time", "Lock Process ID";
} else {
$fmt = "%-24s %-8s %-10s %-6s %3s %-15s %-15s %-15s %s\n";
$fmt = "%-24s %-8s %-10s %-6s %3s %3s %-15s %-15s %-15s %s\n";
printf $fmt, "Pid/Name", "Owner", "Type", "State", "Use",
printf $fmt, "Pid/Name", "Owner", "Type", "State", "Use", "Ext",
"Inception", "End", "Last used", "Attrs";
}
......@@ -251,7 +251,7 @@ if (@lids > 0) {
$lusers = ();
}
printf $fmt, $lname, $lease->owner(), $lease->type(),
$states{$lease->state()}, int(@$lusers),
$states{$lease->state()}, int(@$lusers), $lease->renewals(),
$stime, $etime, $ltime, $attrstr;
}
}
......
......@@ -53,7 +53,7 @@ SYMLINKS = node_admin node_reboot os_load create_image node_list \
template_export template_swapin template_swapout \
template_stoprun template_instantiate template_startrun \
template_checkout node_avail_list mkblob rmblob \
createdataset deletedataset showdataset
createdataset deletedataset showdataset extenddataset
ifneq ($(UNIFIED),1)
LINKS = cd $(INSTALL_BINDIR) && \
......
#! /usr/bin/env python
#
# Copyright (c) 2004-2013 University of Utah and the Flux Group.
# Copyright (c) 2004-2014 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -5952,6 +5952,33 @@ class dataset:
else:
return EmulabResponse( RESPONSE_SUCCESS, value=0, output=output )
def extend( self, version, argdict ):
if version != self.VERSION:
return EmulabResponse(RESPONSE_BADVERSION,
output="Client version mismatch!")
try:
checknologins()
pass
except NoLoginsError, e:
return EmulabResponse(RESPONSE_REFUSED, output=str(e))
argerror = CheckRequiredArgs(argdict, ("dataset",))
if (argerror):
return argerror
#
# Pass the whole thing off to the backend script.
#
argstr = escapeshellarg(argdict["dataset"])
(exitval, output) = runcommand( TBDIR + "/bin/extendlease " + argstr)
if exitval:
return EmulabResponse( RESPONSE_ERROR, exitval >> 8, output=output )
else:
return EmulabResponse( RESPONSE_SUCCESS, value=0, output=output )
def getlist( self, version, argdict ):
if version != self.VERSION:
return EmulabResponse(RESPONSE_BADVERSION,
......
#! /usr/bin/env python
#
# Copyright (c) 2004-2013 University of Utah and the Flux Group.
# Copyright (c) 2004-2014 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -165,6 +165,8 @@ API = {
"help" : "Create a persistent dataset" },
"deletedataset" : { "func" : "deletedataset",
"help" : "Delete a persistent dataset" },
"extenddataset" : { "func" : "extenddataset",
"help" : "Extend the lease on a persistent dataset" },
"showdataset" : { "func" : "showdataset",
"help" : "Show persistent datasets" }
};
......@@ -2814,6 +2816,51 @@ class deletedataset:
return
pass
#
# extenddataset
#
class extenddataset:
def __init__(self, argv=None):
self.argv = argv;
return
def apply(self):
try:
opts, req_args = getopt.getopt(self.argv, "h", [ "help" ]);
pass
except getopt.error, e:
print e.args[0]
self.usage();
return -1;
params = {};
for opt, val in opts:
if opt in ("-h", "--help"):
self.usage()
return 0
pass
# Do this after so --help is seen.
if len(req_args) < 1:
self.usage();
return -1;
# Send the rest of the args along as a list.
params["dataset"] = req_args[0];
rval,response = do_method("dataset", "extend", params);
return rval;
def usage(self):
print "extenddataset [-h] dataset_id";
print "";
print "Extend the lease on the dataset with the indicated name.";
print "The lease will be extended by a fixed, site-specific length";
print "of time. Only datasets in the 'grace' state can be extended.";
wrapperoptions();
return
pass
#
# showdataset
#
......
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