Commit 92c00072 authored by Leigh Stoller's avatar Leigh Stoller

Lots more dataset changes. Modify and extend works, and I cleaned

up the embedding so that its less confusing.
parent 76bf66ac
......@@ -476,6 +476,73 @@ sub DeleteDataset($)
return Genixmlrpc::CallMethod($cmurl, $context, "DeleteDataset", $args);
}
#
# Apply some updates to the dataset on server.
#
sub ModifyDataset($)
{
my ($self) = @_;
my $authority = $self->GetGeniAuthority();
my $geniuser = $self->GetGeniUser();
my $context = APT_Geni::GeniContext();
my $cert = $self->GetCertificate();
return undef
if (! (defined($geniuser) && defined($authority) &&
defined($context) && defined($cert)));
my ($credential, $speaksfor_credential) =
APT_Geni::GenCredentials($cert, $geniuser, ["blockstores"]);
return undef
if (! (defined($speaksfor_credential) &&
defined($credential)));
my $args = {
"name" => $self->dataset_id(),
"credentials" => [$credential->asString(),
$speaksfor_credential->asString()],
};
# This is all we can change right now.
$args->{"expires"} = emutil::TBDateStringGMT($self->expires())
if (defined($self->expires()));
my $cmurl = $authority->url();
$cmurl =~ s/protogeni/protogeni\/stoller/;
return Genixmlrpc::CallMethod($cmurl, $context, "ModifyDataset", $args);
}
#
# Request single extension.
#
sub ExtendDataset($)
{
my ($self) = @_;
my $authority = $self->GetGeniAuthority();
my $geniuser = $self->GetGeniUser();
my $context = APT_Geni::GeniContext();
my $cert = $self->GetCertificate();
return undef
if (! (defined($geniuser) && defined($authority) &&
defined($context) && defined($cert)));
my ($credential, $speaksfor_credential) =
APT_Geni::GenCredentials($cert, $geniuser, ["blockstores"]);
return undef
if (! (defined($speaksfor_credential) &&
defined($credential)));
my $args = {
"name" => $self->dataset_id(),
"credentials" => [$credential->asString(),
$speaksfor_credential->asString()],
"extend" => 1,
};
my $cmurl = $authority->url();
$cmurl =~ s/protogeni/protogeni\/stoller/;
return Genixmlrpc::CallMethod($cmurl, $context, "ModifyDataset", $args);
}
#
# Refresh our metadata.
#
......
......@@ -37,6 +37,8 @@ sub usage()
print STDERR "Usage: manage_dataset [options --] create ...\n";
print STDERR "Usage: manage_dataset [options --] delete ...\n";
print STDERR "Usage: manage_dataset [options --] refresh ...\n";
print STDERR "Usage: manage_dataset [options --] modify ...\n";
print STDERR "Usage: manage_dataset [options --] extend ...\n";
exit(-1);
}
my $optlist = "dt:";
......@@ -82,6 +84,8 @@ sub DoCreate();
sub DoDelete();
sub DoRefresh();
sub DoRefreshInternal($$);
sub DoModify();
sub DoExtend();
#
# Parse command arguments. Once we return from getopts, all that should be
......@@ -133,6 +137,12 @@ elsif ($action eq "delete") {
elsif ($action eq "refresh") {
exit(DoRefresh());
}
elsif ($action eq "modify") {
exit(DoModify());
}
elsif ($action eq "extend") {
exit(DoExtend());
}
else {
usage();
}
......@@ -400,7 +410,7 @@ sub DoRefreshInternal($$)
my $response = $dataset->DescribeDataset();
if ($response->code() != GENIRESPONSE_SUCCESS) {
if ($response->code() != GENIRESPONSE_SEARCHFAILED) {
if ($response->code() == GENIRESPONSE_SEARCHFAILED) {
$$pmesg = "Dataset no longer exists at the target\n";
}
else {
......@@ -421,6 +431,131 @@ sub DoRefreshInternal($$)
return 0;
}
#
# Modify
#
sub DoModify()
{
my $errmsg;
my $usage = sub {
print STDERR "Usage: manage_dataset modify ".
"[-e expiration] [-p privacy] pid/name\n";
exit(-1);
};
my $optlist = "e:p:";
my %options = ();
if (! getopts($optlist, \%options)) {
&$usage();
}
if (@ARGV != 1) {
&$usage();
}
my $token = shift(@ARGV);
my $dataset = APT_Dataset->Lookup($token);
if (!defined($dataset)) {
fatal("No such dataset");
}
if (defined($options{"p"})) {
&$usage()
if ($options{"p"} !~ /^(private|shared|public)$/);
}
if (defined($options{"e"})) {
my $expires = str2time($options{"e"});
if (!defined($expires)) {
fatal("Could not parse expiration date.");
}
}
if ($dataset->Lock()) {
fatal("dataset is busy, cannot lock it");
}
if (defined($options{"p"})) {
my $privacy = $options{"p"};
my $blob = {"shared" => 0, "public" => 0};
if ($privacy eq "shared") {
$blob->{'shared'} = 1;
}
elsif ($privacy eq "public") {
$blob->{'public'} = 1;
}
if ($dataset->Update($privacy)) {
$errmsg = "Could not update privacy settings!";
goto failed;
}
}
if (defined($options{"e"})) {
my $expires = $options{"e"};
print "$expires\n";
if ($dataset->Update({"expires" => TBDateStringLocal($expires)})) {
$errmsg = "Could not update expiration!";
goto failed;
}
}
my $response = $dataset->ModifyDataset();
if ($response->code() != GENIRESPONSE_SUCCESS) {
if ($response->code() == GENIRESPONSE_SEARCHFAILED) {
$errmsg = "Dataset no longer exists at the target\n";
}
else {
$errmsg = "ModifyDataset failed: ". $response->output() . "\n";
}
goto failed;
}
my $blob = $response->value();
$dataset->Update({"expires" => TBDateStringLocal($blob->{"expires"})});
$dataset->Unlock();
return 0;
failed:
$dataset->Unlock();
# This will set the webtask, see below.
fatal($errmsg);
}
#
# Extend
#
sub DoExtend()
{
my $errmsg;
my $usage = sub {
print STDERR "Usage: manage_dataset extend pid/name\n";
exit(-1);
};
if (@ARGV != 1) {
&$usage();
}
my $token = shift(@ARGV);
my $dataset = APT_Dataset->Lookup($token);
if (!defined($dataset)) {
fatal("No such dataset");
}
if ($dataset->Lock()) {
fatal("dataset is busy, cannot lock it");
}
my $response = $dataset->ExtendDataset();
if ($response->code() != GENIRESPONSE_SUCCESS) {
if ($response->code() == GENIRESPONSE_SEARCHFAILED) {
$errmsg = "Dataset no longer exists at the target\n";
}
else {
$errmsg = "ExtendDataset failed: ". $response->output() . "\n";
}
goto failed;
}
my $blob = $response->value();
$dataset->Update({"expires" => TBDateStringLocal($blob->{"expires"})});
$dataset->Unlock();
return 0;
failed:
$dataset->Unlock();
# This will set the webtask, see below.
fatal($errmsg);
}
sub fatal($)
{
my ($mesg) = @_;
......
......@@ -3203,6 +3203,8 @@ sub ConsoleURL($)
#
my $CREATEDATASET = "$TB/bin/createdataset";
my $DELETEDATASET = "$TB/bin/deletelease";
my $MODIFYDATASET = "$TB/bin/modlease";
my $EXTENDDATASET = "$TB/bin/extendlease";
sub CreateDataset($)
{
......@@ -3361,12 +3363,85 @@ sub DeleteDataset($)
}
return GeniResponse->Create(GENIRESPONSE_SUCCESS);
}
sub ExtendDataset($)
sub ModifyDataset($)
{
my ($argref) = @_;
my $credentials = $argref->{'credentials'};
my $dataset = $argref->{'name'};
require Lease;
if (! (defined($credentials) && defined($dataset))) {
return GeniResponse->MalformedArgsResponse("Missing arguments");
}
if ($dataset !~ /^[-\w]+$/) {
return GeniResponse->MalformedArgsResponse("Bad dataset name");
}
my ($credential,$speaksfor) = GeniStd::CheckCredentials($credentials);
return $credential
if (GeniResponse::IsResponse($credential));
#
# Only real local users can do blockstores, or the credential
# must include the correct priv.
#
my $user = GeniUser->Lookup($credential->owner_cert()->urn(), 1);
return GeniResponse->Create(GENIRESPONSE_FORBIDDEN)
if (! ((defined($user) && $user->IsLocal()) ||
$credential->HasActualPrivilege("blockstores")));
# Get the project and group. This needs more thought.
my $group = GeniUtil::GetHoldingProject($credential->target_urn(), $user);
return $group
if (GeniResponse::IsResponse($group));
return GeniResponse->Create(GENIRESPONSE_NOT_IMPLEMENTED);
my $lease = Lease->Lookup($group->pid(), $group->gid(), $dataset);
if (!defined($lease)) {
return GeniResponse->Create(GENIRESPONSE_SEARCHFAILED);
}
#
# Only the creator can modify the dataset.
#
return GeniResponse->Create(GENIRESPONSE_FORBIDDEN)
if (0 && $user->uid() ne $lease->owner());
my $cmd;
#
# All we can handle is the expiration date.
#
if (exists($argref->{'expires'})) {
my $expiration = $argref->{'expires'};
# Gack, why does Frontier do this. It is stupid.
if (ref($expiration) eq 'Frontier::RPC2::DateTime::ISO8601') {
$expiration = $expiration->value;
}
if (!str2time($expiration)) {
return GeniResponse->MalformedArgsResponse("Bad expiration");
}
GeniUtil::FlipToElabMan();
$cmd = "$WAP $MODIFYDATASET ";
$cmd .= " -e '" . emutil::TBDateStringLocal($expiration) . "' ";
}
elsif (exists($argref->{'extend'})) {
$cmd = "$EXTENDDATASET ";
}
else {
return GeniResponse->Create(GENIRESPONSE_SUCCESS);
}
$cmd .= " " . $group->pid() . "/" . $group->gid() . "/" . $dataset;
my $output = GeniUtil::ExecQuiet($cmd);
# Not a typical op, so always print debugging info;
print STDERR $output;
if ($?) {
return GeniResponse->Create(GENIRESPONSE_ERROR, undef, $output);
}
$lease->Refresh();
my $blob = {};
$blob->{'expires'} = emutil::TBDateStringGMT($lease->lease_end());
return GeniResponse->Create(GENIRESPONSE_SUCCESS, $blob);
}
sub DescribeDataset($)
{
......
......@@ -94,6 +94,7 @@ function SPITFORM($formfields, $errors)
echo "<script type='text/javascript'>\n";
echo " window.AJAXURL = 'server-ajax.php';\n";
echo " window.TITLE = '$title';\n";
echo " window.EDITING = false;\n";
echo " window.BUTTONLABEL = '$button_label';\n";
echo "</script>\n";
......@@ -116,6 +117,10 @@ if (! isset($create)) {
$defaults["dataset_type"] = 'stdataset';
$defaults["dataset_fstype"] = 'ext3';
$defaults["dataset_who"] = 'shared';
# Default project.
if (count($projlist) == 1) {
$defaults["dataset_pid"] = $projlist[0];
}
SPITFORM($defaults, $errors);
return;
......
......@@ -41,6 +41,7 @@ function Do_CreateDataSet()
$this_uid = $this_user->uid();
# Allow for form precheck only. So JS code knows it will be fast.
$checkonly = isset($ajax_args["checkonly"]) && $ajax_args["checkonly"];
$embedded = isset($ajax_args["embedded"]) && $ajax_args["embedded"];
if (!isset($ajax_args["formfields"])) {
SPITAJAX_ERROR(1, "Missing formfields");
......@@ -55,7 +56,10 @@ function Do_CreateDataSet()
$command = "webmanage_dataset create ";
}
$required = array("dataset_pid", "dataset_name", "dataset_type",
"dataset_size", "dataset_fstype", "dataset_who");
"dataset_size", "dataset_fstype");
if (!$embedded) {
$required[] = "dataset_who";
}
foreach ($required as $field) {
if (!isset($formfields[$field]) || $formfields[$field] == "") {
......@@ -118,13 +122,15 @@ function Do_CreateDataSet()
$command .= " -s " . $formfields["dataset_size"];
}
# Permission bits.
if ($formfields["dataset_who"] != "public" &&
$formfields["dataset_who"] != "shared" &&
$formfields["dataset_who"] != "private") {
$errors["dataset_who"] = "Illegal value";
}
else {
$command .= " -p " . $formfields["dataset_who"];
if (!$embedded) {
if ($formfields["dataset_who"] != "public" &&
$formfields["dataset_who"] != "shared" &&
$formfields["dataset_who"] != "private") {
$errors["dataset_who"] = "Illegal value";
}
else {
$command .= " -p " . $formfields["dataset_who"];
}
}
if (count($errors)) {
SPITAJAX_ERROR(2, $errors);
......@@ -153,7 +159,103 @@ function Do_CreateDataSet()
return 1;
}
$dataset_uuid = $dataset->uuid();
SPITAJAX_RESPONSE("$APTBASE/show-dataset.php?uuid=$dataset_uuid");
SPITAJAX_RESPONSE("show-dataset.php?uuid=$dataset_uuid");
}
#
# Server side of modifying a dataset.
#
function Do_ModifyDataSet()
{
global $this_user;
global $ajax_args;
global $DBFieldErrstr, $TBDIR, $APTBASE, $embedded;
global $suexec_output, $suexec_output_array;
$islease = 1;
$this_idx = $this_user->uid_idx();
$this_uid = $this_user->uid();
# Allow for form precheck only. So JS code knows it will be fast.
$checkonly = isset($ajax_args["checkonly"]) && $ajax_args["checkonly"];
$embedded = isset($ajax_args["embedded"]) && $ajax_args["embedded"];
if (!isset($ajax_args["formfields"])) {
SPITAJAX_ERROR(1, "Missing formfields");
return;
}
$formfields = $ajax_args["formfields"];
$errors = array();
if (!isset($formfields["dataset_uuid"])) {
SPITAJAX_ERROR(1, "Missing dataset uuid");
return 1;
}
$dataset_uuid = $formfields["dataset_uuid"];
#
# Either a local lease or a remote dataset.
#
$dataset = Lease::Lookup($dataset_uuid);
if (!$dataset) {
$dataset = Dataset::Lookup($dataset_uuid);
$islease = 0;
}
if (!$dataset) {
SPITAJAX_ERROR(1, "Unknown dataset");
return;
}
if ($this_uid != $dataset->owner_uid() && !ISADMIN()) {
SPITAJAX_ERROR(1, "Not enough permission");
return;
}
if ($islease) {
$command = "webmodlease ";
}
else {
$command = "webmanage_dataset modify ";
}
if ($dataset->type() == "stdataset") {
if (!isset($formfields["dataset_expires"]) ||
$formfields["dataset_expires"] == "") {
$errors["dataset_expires"] = "Missing field";
}
elseif (!strtotime($formfields["dataset_expires"])) {
$errors["dataset_expires"] = "Illegal value";
}
else {
$command .= " -e " .
escapeshellarg($formfields["dataset_expires"]);
}
}
# Permission bits.
if (isset($formfields["dataset_who"])) {
if ($formfields["dataset_who"] != "public" &&
$formfields["dataset_who"] != "shared" &&
$formfields["dataset_who"] != "private") {
$errors["dataset_who"] = "Illegal value";
}
elseif ($dataset->PrivacyString() != $formfields["dataset_who"]) {
$command .= " -p " . $formfields["dataset_who"];
}
}
if (count($errors)) {
SPITAJAX_ERROR(2, $errors);
return;
}
if ($checkonly) {
SPITAJAX_RESPONSE(0);
return;
}
$command .= " " . $dataset->pid() . "/" . $dataset->id();
$retval = SUEXEC($this_uid, $dataset->pid(),
$command, SUEXEC_ACTION_CONTINUE);
if ($retval) {
SPITAJAX_ERROR(1, $suexec_output);
return;
}
SPITAJAX_RESPONSE("show-dataset.php?uuid=$dataset_uuid");
}
function Do_DeleteDataset()
......@@ -208,7 +310,7 @@ function Do_DeleteDataset()
SPITAJAX_ERROR(1, $error);
return;
}
SPITAJAX_RESPONSE("$APTBASE");
SPITAJAX_RESPONSE("list-datasets.php");
}
function Do_ApproveDataset()
......@@ -255,7 +357,7 @@ function Do_ApproveDataset()
SPITAJAX_ERROR(1, $error);
return;
}
SPITAJAX_RESPONSE("$APTBASE/show-dataset.php?uuid=$lease_uuid");
SPITAJAX_RESPONSE("show-dataset.php?uuid=$dataset_uuid");
}
function Do_RefreshDataset()
......@@ -300,6 +402,62 @@ function Do_RefreshDataset()
SPITAJAX_RESPONSE(0);
}
function Do_ExtendDataset()
{
global $this_user;
global $ajax_args;
global $suexec_output, $suexec_output_array, $APTBASE;
$this_idx = $this_user->uid_idx();
$this_uid = $this_user->uid();
$islease = 1;
if (!isset($ajax_args["uuid"])) {
SPITAJAX_ERROR(1, "Missing uuid");
return;
}
#
# Either a local lease or a remote dataset.
#
$dataset = Lease::Lookup($ajax_args["uuid"]);
if (!$dataset) {
$dataset = Dataset::Lookup($ajax_args["uuid"]);
$islease = 0;
}
if (!$dataset) {
SPITAJAX_ERROR(1, "Unknown dataset");
return;
}
if ($this_uid != $dataset->owner_uid() && !ISADMIN()) {
SPITAJAX_ERROR(1, "Not enough permission");
return;
}
if ($islease) {
$command = "webextendlease ";
}
else {
$command = "webmanage_dataset extend ";
}
$command .= $dataset->pid() . "/" . $dataset->id();
#
# Invoke backend.
#
$retval = SUEXEC($this_uid, $dataset->pid(), $command,
SUEXEC_ACTION_CONTINUE);
if ($retval != 0) {
$error = "Transient error; please try again later";
if ($retval && count($suexec_output_array)) {
$error = $suexec_output_array;
}
SPITAJAX_ERROR(1, $error);
return;
}
$dataset_uuid = $dataset->uuid();
SPITAJAX_RESPONSE("show-dataset.php?uuid=$dataset_uuid");
}
# Local Variables:
# mode:php
# End:
......
<?php
#
# Copyright (c) 2000-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/>.
#
# }}}
#
chdir("..");
include("defs.php3");
include("lease_defs.php");
chdir("apt");
include("quickvm_sup.php");
include("dataset_defs.php");
# Must be after quickvm_sup.php since it changes the auth domain.
$page_title = "Modify Dataset";
#
# Get current user.
#
RedirectSecure();
$this_user = CheckLoginOrRedirect();
$this_idx = $this_user->uid_idx();
#
# Verify page arguments.
#
$optargs = RequiredPageArguments("uuid", PAGEARG_UUID);
$optargs = OptionalPageArguments("create", PAGEARG_STRING,
"formfields", PAGEARG_ARRAY);
#
# Either a local lease or a remote dataset.
#
$dataset = Lease::Lookup($uuid);
if (!$dataset) {
$dataset = Dataset::Lookup($uuid);
}
if (!$dataset) {
SPITUSERERROR("No such dataset!");
}
if (!$dataset->AccessCheck($this_user, $LEASE_ACCESS_MODIFY)) {
SPITUSERERROR("Not enough permission!");
}
#
# Spit the form
#
function SPITFORM($formfields, $errors)
{
global $this_user, $projlist, $embedded;
$button_label = "Save";
$title = "Modify Dataset";
$isadmin = (ISADMIN() ? "true" : "false");
SPITHEADER(1);
# Place to hang the toplevel template.
echo "<div id='main-body'></div>\n";
# I think this will take care of XSS prevention?
echo "<script type='text/plain' id='form-json'>\n";
echo htmlentities(json_encode($formfields)) . "\n";
echo "</script>\n";
echo "<script type='text/plain' id='error-json'>\n";
echo htmlentities(json_encode($errors));
echo "</script>\n";
echo "<link rel='stylesheet'
href='css/jquery-ui.min.css'>\n";
echo "<script type='text/javascript'>\n";
echo " window.AJAXURL = 'server-ajax.php';\n";
echo " window.TITLE = '$title';\n";
echo " window.EDITING = true;\n";
echo " window.ISADMIN = $isadmin;\n";
echo " window.BUTTONLABEL = '$button_label';\n";
echo "</script>\n";
SpitOopsModal("oops");
SpitWaitModal("waitwait");
SPITREQUIRE("create-dataset");
SPITFOOTER();
}
if (! isset($create)) {
$errors = array();
$fields = array();
$fields["dataset_type"] = $dataset->type();
$fields["dataset_pid"] = $dataset->pid();
$fields["dataset_gid"] = $dataset->gid();
$fields["dataset_name"] = $dataset->id();
$fields["dataset_size"] = $dataset->size() . "MiB";
$fields["dataset_fstype"] = ($dataset->fstype() ?
$dataset->fstype() : "none");
$fields["dataset_expires"] = ($dataset->expires() ?
DateStringGMT($dataset->expires()) : "");
$fields["dataset_uuid"] = $uuid;
$fields["dataset_who"] = $dataset->PrivacyString();
SPITFORM($fields, $errors);
return;
}
SPITFORM($formfields, array());
?>
require(window.APT_OPTIONS.configObject,
['underscore', 'js/quickvm_sup',
['underscore', 'js/quickvm_sup', 'moment',
'js/lib/text!template/create-dataset.html',
'jquery-ui'],
function (_, sup, mainString)
function (_, sup, moment, mainString)
{
'use strict';
......@@ -10,18 +10,23 @@ function (_, sup, mainString)
var fields = null;
var fstypes = null;
var projlist = null;
var editing = false;
var isadmin = false;
var embedded = 0;