diff --git a/apt/APT_Dataset.pm.in b/apt/APT_Dataset.pm.in index d4206d4f3dc809af61e3572ea14af8588bfc94f9..fa49790e3681c6648e5163431691eda14c501648 100644 --- a/apt/APT_Dataset.pm.in +++ b/apt/APT_Dataset.pm.in @@ -53,7 +53,7 @@ my $TBOPS = "@TBOPSEMAIL@"; my $OURDOMAIN = "@OURDOMAIN@"; # Debugging -my $usemydevtree = 0; +my $usemydevtree = 1; # # Lookup by uuid. diff --git a/apt/APT_Instance.pm.in b/apt/APT_Instance.pm.in index b58c5cd56b23eba53abed21a59e4da21ea7edd6b..cb3bd89efb1b514aa0ab2ab223aaae0f46c3a6fa 100644 --- a/apt/APT_Instance.pm.in +++ b/apt/APT_Instance.pm.in @@ -57,6 +57,9 @@ my $GENEXTENDCRED = "$TB/sbin/protogeni/genextendcred"; my %instances = (); my $debug = 0; +# Debugging +my $usemydevtree = 1; + # # Lookup by uuid. # @@ -266,6 +269,28 @@ sub Delete($) return 0; } +# +# Lock and unlock operate on the underlying slice. +# +sub Lock($) +{ + my ($self) = @_; + my $slice = $self->GetGeniSlice(); + if (!defined($slice)) { + return -1; + } + return $slice->Lock(); +} +sub Unlock($) +{ + my ($self) = @_; + my $slice = $self->GetGeniSlice(); + if (!defined($slice)) { + return -1; + } + return $slice->UnLock(); +} + sub SetStatus($$) { my ($self,$status) = @_; @@ -449,9 +474,10 @@ sub ConsoleURL($$) "credentials" => [$slice_credential->asString(), $speaksfor_credential->asString()], }; + my $cmurl = $authority->url(); + $cmurl =~ s/protogeni/protogeni\/stoller/ if ($usemydevtree); - return Genixmlrpc::CallMethod($authority->url(), - $context, "ConsoleURL", $args); + return Genixmlrpc::CallMethod($cmurl, $context, "ConsoleURL", $args); } # @@ -510,6 +536,8 @@ sub Terminate($) "slice_urn" => $slice->urn(), "credentials" => $credentials, }; + my $cmurl = $authority->url(); + $cmurl =~ s/protogeni/protogeni\/stoller/ if ($usemydevtree); # # We have to watch for resource busy errors, and retry. For a while @@ -520,8 +548,7 @@ sub Terminate($) my $tries = 10; while ($tries) { $response = - Genixmlrpc::CallMethod($authority->url(), - $context, "DeleteSlice", $args); + Genixmlrpc::CallMethod($cmurl, $context, "DeleteSlice", $args); # SEARCHFAILED is success. return $response @@ -615,17 +642,18 @@ sub Extend($$) $speaksfor_credential->asString(), $extcred], }; + my $cmurl = $authority->url(); + $cmurl =~ s/protogeni/protogeni\/stoller/ if ($usemydevtree); - return Genixmlrpc::CallMethod($authority->url(), - $context, "RenewSlice", $args); + return Genixmlrpc::CallMethod($cmurl, $context, "RenewSlice", $args); } # # Create an Image, # -sub CreateImage($$$) +sub CreateImage($$$;$) { - my ($self, $sliver_urn, $imagename) = @_; + my ($self, $sliver_urn, $imagename, $bsname) = @_; my $authority = $self->GetGeniAuthority(); my $geniuser = $self->GetGeniUser(); my $slice = $self->GetGeniSlice(); @@ -648,9 +676,13 @@ sub CreateImage($$$) "credentials" => [$slice_credential->asString(), $speaksfor_credential->asString()], }; + $args->{'bsname'} = $bsname + if (defined($bsname)); - return Genixmlrpc::CallMethod($authority->url(), - $context, "CreateImage", $args); + my $cmurl = $authority->url(); + $cmurl =~ s/protogeni/protogeni\/stoller/ if ($usemydevtree); + + return Genixmlrpc::CallMethod($cmurl, $context, "CreateImage", $args); } # @@ -678,9 +710,10 @@ sub SliceStatus($) "credentials" => [$slice_credential->asString(), $speaksfor_credential->asString()], }; + my $cmurl = $authority->url(); + $cmurl =~ s/protogeni/protogeni\/stoller/ if ($usemydevtree); - return Genixmlrpc::CallMethod($authority->url(), - $context, "SliverStatus", $args); + return Genixmlrpc::CallMethod($cmurl, $context, "SliverStatus", $args); } # @@ -708,9 +741,10 @@ sub RestartSliver($@) "credentials" => [$slice_credential->asString(), $speaksfor_credential->asString()], }; + my $cmurl = $authority->url(); + $cmurl =~ s/protogeni/protogeni\/stoller/ if ($usemydevtree); - return Genixmlrpc::CallMethod($authority->url(), - $context, "RestartSliver", $args); + return Genixmlrpc::CallMethod($cmurl, $context, "RestartSliver", $args); } # _Always_ make sure that this 1 is at the end of the file... diff --git a/apt/APT_Profile.pm.in b/apt/APT_Profile.pm.in index 7ad86a30a90f7c35fa5e829af82743d98ecdcf9e..8dbedbfa4dfdbf3b552e925909a636d60219cd5e 100644 --- a/apt/APT_Profile.pm.in +++ b/apt/APT_Profile.pm.in @@ -1,6 +1,6 @@ #!/usr/bin/perl -wT # -# Copyright (c) 2007-2014 University of Utah and the Flux Group. +# Copyright (c) 2007-2015 University of Utah and the Flux Group. # # {{{EMULAB-LICENSE # diff --git a/apt/create_instance.in b/apt/create_instance.in index 6213796b23c7dbd10298afbcb1a7c827dc6e5cc2..c355ca244b7e4926a286bc910a9992d15382639f 100755 --- a/apt/create_instance.in +++ b/apt/create_instance.in @@ -225,7 +225,7 @@ if (!defined($cm_authority)) { } } my $cmurl = $cm_authority->url(); -#$cmurl =~ s/protogeni/protogeni\/stoller/; +$cmurl =~ s/protogeni/protogeni\/stoller/; my $iscloudlab = ($CMURN eq "urn:publicid:IDN+utah.cloudlab.us+authority+cm" ? 1 : 0); diff --git a/apt/manage_dataset.in b/apt/manage_dataset.in index aeffaf2944b3b54950d1f17c3bebaf60a2d957c7..366e7cb122d40095fb68bfbd6318ba1cdb934e9b 100644 --- a/apt/manage_dataset.in +++ b/apt/manage_dataset.in @@ -39,6 +39,7 @@ sub usage() print STDERR "Usage: manage_dataset [options --] refresh ...\n"; print STDERR "Usage: manage_dataset [options --] modify ...\n"; print STDERR "Usage: manage_dataset [options --] extend ...\n"; + print STDERR "Usage: manage_dataset [options --] snapshot ...\n"; exit(-1); } my $optlist = "dt:"; @@ -74,9 +75,11 @@ use emutil; use User; use Project; use APT_Dataset; +use APT_Instance; use WebTask; use Blockstore; use GeniResponse; +use GeniXML; # Protos sub fatal($); @@ -86,6 +89,7 @@ sub DoRefresh(); sub DoRefreshInternal($$); sub DoModify(); sub DoExtend(); +sub DoSnapshot(); # # Parse command arguments. Once we return from getopts, all that should be @@ -143,6 +147,9 @@ elsif ($action eq "modify") { elsif ($action eq "extend") { exit(DoExtend()); } +elsif ($action eq "snapshot") { + exit(DoSnapshot()); +} else { usage(); } @@ -155,7 +162,7 @@ sub DoCreate() { my $usage = sub { print STDERR "Usage: manage_dataset create ". - "[-t type] [-f fstype] [-e expiration] ". + "[-t type] [-f fstype] [-e expiration] [-a am_urn] ". "[-R global|project] [-W creator|project] ". "-s size pid/name\n"; exit(-1); @@ -165,13 +172,13 @@ sub DoCreate() my $errmsg; my $pid; my $expires; - my $size; + my $size = 0; my $type = "stdataset"; my $fstype; my $read_access; my $write_access; - my $optlist = "ds:t:e:f:w:p:R:W:"; + my $optlist = "ds:t:e:f:w:p:R:W:a:"; my %options = (); if (! getopts($optlist, \%options)) { &$usage(); @@ -182,12 +189,16 @@ sub DoCreate() if (defined($options{"t"})) { $type = $options{"t"}; &$usage() - if (! ($type eq "stdataset" || $type eq "ltdataset")); + if (! ($type eq "stdataset" || $type eq "ltdataset" || + $type eq "imdataset")); } - if (defined($options{"f"})) { - $fstype = $options{"f"}; - &$usage() - if ($fstype !~ /^(ext2|ext3|ext4|ufs|ufs2)$/); + # We ignore the aggregate urn unless its an imdataset. + if ($type eq "imdataset") { + if (!exists($options{"a"})) { + print STDERR "Must provide -a opton for imdatasets\n"; + &$usage(); + } + $aggregate_urn = $options{"a"}; } if (defined($options{"f"})) { $fstype = $options{"f"}; @@ -225,8 +236,10 @@ sub DoCreate() } $expires = $options{"e"}; } + &$usage() - if (@ARGV != 1 || !defined($size) || + if (@ARGV != 1 || + ($type ne "imdataset" && !defined($size)) || ($type eq "stdataset" && !defined($expires))); my $name = shift(@ARGV); @@ -357,7 +370,7 @@ sub DoCreate() # sub DoDelete() { - my $errmsg; + my $errmsg = "Could not delete dataset"; if (@ARGV != 1) { fatal("usage: $0 delete pid/name"); @@ -439,6 +452,9 @@ sub DoRefreshInternal($$) else { $dataset->Update({"state" => $blob->{"state"}}); } + if ($dataset->type() eq "imdataset") { + $dataset->Update({"size" => $blob->{"size"}}); + } return 0; } @@ -556,6 +572,104 @@ sub DoExtend() fatal($errmsg); } +# +# Snapshot an image backed dataset +# +sub DoSnapshot() +{ + my $errmsg; + + my $usage = sub { + print STDERR "Usage: manage_dataset snapshot ". + "-i instance -b bsname pid/name nodeid\n"; + exit(-1); + }; + my $optlist = "b:i:"; + my %options = (); + if (! getopts($optlist, \%options)) { + &$usage(); + } + &$usage() + if (! (@ARGV == 2 && exists($options{"b"}) && exists($options{"i"}))); + + my $bsname = $options{"b"}; + my $token = shift(@ARGV); + my $nodeid = 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"); + } + if ($dataset->type() ne "imdataset") { + $errmsg = "Only image backed datasets supported"; + goto failed; + } + my $instance = APT_Instance->Lookup($options{"i"}); + if (!defined($instance)) { + $errmsg = "No such instance"; + goto failed; + } + if ($instance->Lock()) { + # undef so we do not try to unlock it below. + $instance = undef; + $errmsg = "instance is busy, cannot lock it"; + goto failed + } + my $manifest = GeniXML::Parse($instance->manifest()); + if (! defined($manifest)) { + $errmsg = "Could not parse manifest"; + goto failed; + } + my $sliver_urn; + my @nodes = GeniXML::FindNodes("n:node", $manifest)->get_nodelist(); + foreach my $node (@nodes) { + my $client_id = GeniXML::GetVirtualId($node); + if ($nodeid eq $client_id) { + $sliver_urn = GeniXML::GetSliverId($node); + # + # But check that the bsname is on this node. + # + my $found = 0; + foreach my $blockref + (GeniXML::FindNodesNS("n:blockstore", $node, + $GeniXML::EMULAB_NS)->get_nodelist()) { + my $name = GeniXML::GetText("name", $blockref); + if ($name eq $bsname) { + $found = 1; + last; + } + } + if (!$found) { + $errmsg = "No such blockstore $bsname on node $nodeid"; + goto failed; + } + last; + } + } + if (!defined($sliver_urn)) { + $errmsg = "Could not find node '$nodeid' in manifest"; + goto failed; + } + my $response = $instance->CreateImage($sliver_urn, + $dataset->dataset_id(), $bsname); + if ($response->code() != GENIRESPONSE_SUCCESS) { + $errmsg = "SnapshotDataset failed: ". $response->output() . "\n"; + goto failed; + } + $dataset->Update({"state" => "busy"}); + $instance->Unlock(); + $dataset->Unlock(); + return 0; + + failed: + $instance->Unlock() if (defined($instance)); + $dataset->Unlock(); + # This will set the webtask, see below. + fatal($errmsg); +} + sub fatal($) { my ($mesg) = @_; diff --git a/apt/manage_instance.in b/apt/manage_instance.in index 03a3cb20cd2921b59c57aca2cf7a39b31bd34a89..5b073164dc6fed56715d8ccbb1f89a77081c99fa 100644 --- a/apt/manage_instance.in +++ b/apt/manage_instance.in @@ -57,7 +57,7 @@ my $TBOPS = "@TBOPSEMAIL@"; my $QUICKVM = "$TB/sbin/protogeni/quickvm"; # Debugging -my $usemydevtree = 0; +my $usemydevtree = 1; # # Untaint the path diff --git a/backend/newimageid_ez.in b/backend/newimageid_ez.in index b973a7a5f59e2f21527086658457ad56d7af3c11..af993ca3d942bd612986a79f52e19235dc8cc738 100644 --- a/backend/newimageid_ez.in +++ b/backend/newimageid_ez.in @@ -1,4 +1,4 @@ -#!/usr/bin/perl -wT +#!/usr/bin/perl -w # # Copyright (c) 2000-2015 University of Utah and the Flux Group. # @@ -180,6 +180,7 @@ else { my $SLOT_OPTIONAL = 0x1; # The field is not required. my $SLOT_REQUIRED = 0x2; # The field is required and must be non-null. my $SLOT_ADMINONLY = 0x4; # Only admins can set this field. +my $SLOT_OSREQUIRED = 0x8; # Required on real OS's not datasets. # # XXX We should encode all of this in the DB so that we can generate the # forms on the fly, as well as this checking code. @@ -192,13 +193,13 @@ my %xmlfields = "pid" => ["pid", $SLOT_REQUIRED], "gid" => ["gid", $SLOT_OPTIONAL], "description" => ["description", $SLOT_REQUIRED], - "loadpart" => ["loadpart", $SLOT_REQUIRED], - "OS" => ["OS", $SLOT_REQUIRED], + "loadpart" => ["loadpart", $SLOT_OSREQUIRED], + "OS" => ["OS", $SLOT_OSREQUIRED], "version" => ["version", $SLOT_OPTIONAL, ""], "path" => ["path", $SLOT_OPTIONAL, ""], "node_id" => ["node_id", $SLOT_OPTIONAL, ""], "osfeatures", => ["osfeatures", $SLOT_OPTIONAL, ""], - "op_mode", => ["op_mode", $SLOT_REQUIRED], + "op_mode", => ["op_mode", $SLOT_OSREQUIRED], "mtype_*" => ["mtype", $SLOT_OPTIONAL], "wholedisk", => ["wholedisk", $SLOT_OPTIONAL, 0], "max_concurrent", => ["max_concurrent", $SLOT_OPTIONAL, 0], @@ -212,6 +213,7 @@ my %xmlfields = "hash", => ["hash", $SLOT_ADMINONLY], "nextosid", => ["nextosid", $SLOT_ADMINONLY], "def_parentosid", => ["def_parentosid", $SLOT_OPTIONAL], + "isdataset", => ["isdataset", $SLOT_OPTIONAL], ); # @@ -231,6 +233,13 @@ fatal($@) # my %errors = (); +# Image backed datasets are special. +my $isdataset = 0; +if (exists($xmlparse->{'attribute'}->{"isdataset"}) && + $xmlparse->{'attribute'}->{"isdataset"}->{'value'}) { + $isdataset = 1; +} + # # Make sure all the required arguments were provided. # @@ -239,7 +248,8 @@ foreach $key (keys(%xmlfields)) { my (undef, $required, undef) = @{$xmlfields{$key}}; $errors{$key} = "Required value not provided" - if ($required & $SLOT_REQUIRED && + if (($required & $SLOT_REQUIRED || + ($required & $SLOT_OSREQUIRED && !$isdataset)) && ! exists($xmlparse->{'attribute'}->{"$key"})); } UserError() @@ -299,7 +309,7 @@ foreach $key (keys(%{ $xmlparse->{'attribute'} })) { } } if (!$wild) { - $errors{$key} = "Unknown attribute"; + # We now ignore unknown keys, to maintain compatability. next; # foreach $key } } @@ -421,7 +431,6 @@ else { my $isadmin = $this_user->IsAdmin(); my $imagename = $newimageid_args{"imagename"}; -my $OS = $newimageid_args{"OS"}; # In this form, we make the images:imagename and the os_info:osname the # same. @@ -536,12 +545,19 @@ if (-d $newimageid_args{"path"}) { UserError("Path: invalid path, its a directory"); } -# We allow loadpart=0 for whole-disk images in the Long Form, and it uses the -# same table_regex checking patterns. Here "wholedisk" is a separate checkbox. -my $loadpart = $newimageid_args{"loadpart"}; -if ($loadpart < 1 || $loadpart > 4) { - UserError("DOS Partion: Out of range."); -} +# +# We allow loadpart=0 for wholedisk images in the Long Form, and it +# uses the same table_regex checking patterns. Here "wholedisk" is a +# separate checkbox. Note that for datasets we do not care about +# loadpart, it can be zero. +# +my $loadpart = 0; +if (!$isdataset) { + $loadpart = $newimageid_args{"loadpart"}; + if ($loadpart < 1 || $loadpart > 4) { + UserError("DOS Partion: Out of range."); + } +} # # Check sanity of node name and that user can create an image from it. @@ -566,7 +582,8 @@ if (exists($newimageid_args{"node_id"}) && # try to deduce the default MBR version based on what is currently # on the node we are snapshotting. # -if (!exists($newimageid_args{"mbr_version"}) && defined($node_id)) { +if (!$isdataset && + !exists($newimageid_args{"mbr_version"}) && defined($node_id)) { my $mbrvers = 1; # @@ -590,7 +607,7 @@ if (!exists($newimageid_args{"mbr_version"}) && defined($node_id)) { # See what node types this image will work on. Must be at least one! # UserError("Node Types: Must have at least one node type") - if (!keys(%mtypes_array)); + if (!$isdataset && !keys(%mtypes_array)); my $node_types_selected = 0; # Check validity of mtype_* args, since the keys are dynamically generated. @@ -629,7 +646,7 @@ if ($allpc) { } UserError("Node Types: Must select at least one node type") - if ($node_types_selected == 0 && !$force); + if ($node_types_selected == 0 && !($force || $isdataset)); # # We perform a further check for non-admins. When a node to snapshot @@ -639,7 +656,7 @@ UserError("Node Types: Must select at least one node type") # old OSes from being checked as runnable on newer HW where they do not # stand a chance. # -if (!$isadmin && defined($node) && !$node->isvirtnode()) { +if (!($isadmin || $isdataset) && defined($node) && !$node->isvirtnode()) { my $query_result = DBQueryFatal("select oi.type from osidtoimageid as oi ". "left join partitions as p on oi.osid=p.osid ". @@ -678,13 +695,18 @@ my %osid_reboot_waitlist = ("Linux", 120, "Fedora", 120, "FreeBSD", 120, # if not set. If no default, complain (unless they failed to specify an OS # in which case we will complain about that instead). # -my $reboot_waittime; -if (!exists($osid_reboot_waitlist{$OS})) { - UserError("OS: Invalid OS"); -} elsif (exists($newimageid_args{"reboot_waittime"})) { - $reboot_waittime = $newimageid_args{"reboot_waittime"} -} else { - $reboot_waittime = $osid_reboot_waitlist{$OS}; +# XXX Do not think this code does anything useful. +# +if (!$isdataset) { + my $OS = $newimageid_args{"OS"}; + my $reboot_waittime; + if (!exists($osid_reboot_waitlist{$OS})) { + UserError("OS: Invalid OS"); + } elsif (exists($newimageid_args{"reboot_waittime"})) { + $reboot_waittime = $newimageid_args{"reboot_waittime"} + } else { + $reboot_waittime = $osid_reboot_waitlist{$OS}; + } } exit(0) @@ -699,50 +721,57 @@ exit(0) delete($newimageid_args{"imagename"}); # -# XXX note that osid "path" is not the same as imageid "path". The former -# is for multiboot (aka OSKit) kernels. So we remove the path arg temporary. +# No need for an osid when creating a dataset. # -my $ipath = $newimageid_args{"path"}; -delete($newimageid_args{"path"}); - -# Cross-connect: Make the os descriptor first with the imagename, then take -# the osid index and use it as the imageid index as well. -$newimageid_args{"ezid"} = 1; -# OSInfo->Create args not quite the same as Image->Create ones so copy -# newimageid_args into a new hash and fix up values. -my %newosid_args = %newimageid_args; -$newosid_args{"shared"} = $newimageid_args{"global"} = 1 - if ($global); +my $osid = undef; +my $new_osinfo = undef; my $usrerr; -my $new_osinfo = OSinfo->Create($project, $this_user, $imagename, - \%newosid_args, \$usrerr); -UserError($usrerr) - if (defined($usrerr)); -fatal("Could not create new OSID!") - if (!defined($new_osinfo)); -$newimageid_args{"path"} = $ipath; +if (!$isdataset) { + # + # XXX note that osid "path" is not the same as imageid "path". The former + # is for multiboot (aka OSKit) kernels. So we remove the path arg + # temporary. + # + my $ipath = $newimageid_args{"path"}; + delete($newimageid_args{"path"}); + + # Cross-connect: Make the os descriptor first with the imagename, then take + # the osid index and use it as the imageid index as well. + $newimageid_args{"ezid"} = 1; + # OSInfo->Create args not quite the same as Image->Create ones so copy + # newimageid_args into a new hash and fix up values. + my %newosid_args = %newimageid_args; + $newosid_args{"shared"} = 1 + if ($global); + my $new_osinfo = OSinfo->Create($project, $this_user, $imagename, + \%newosid_args, \$usrerr); + UserError($usrerr) + if (defined($usrerr)); + fatal("Could not create new OSID!") + if (!defined($new_osinfo)); + + $newimageid_args{"path"} = $ipath; + $osid = $new_osinfo->osid(); -my $osid = $new_osinfo->osid(); + # + # Special option. Whole disk image, but only one partition that actually + # matters. None of this matters for datasets. + # + my $loadlen = 1; + $newimageid_args{"part${loadpart}_osid"} = $osid; + if ($newimageid_args{"wholedisk"}) { + $loadlen = 4; + $loadpart = 0; + } + $newimageid_args{"loadpart"} = $loadpart; + $newimageid_args{"loadlength"} = $loadlen; + $newimageid_args{"default_osid"} = $osid; -# -# Special option. Whole disk image, but only one partition that actually -# matters. -# -my $loadlen = 1; -$newimageid_args{"part${loadpart}_osid"} = $osid; -if ($newimageid_args{"wholedisk"}) { - $loadlen = 4; - $loadpart = 0; + # Create the osidtoimageid mapping too. + $newimageid_args{"makedefault"} = 1 + if ($node_types_selected); } -$newimageid_args{"loadpart"} = $loadpart; -$newimageid_args{"loadlength"} = $loadlen; -$newimageid_args{"default_osid"} = $osid; - -# Create the osidtoimageid mapping too. -$newimageid_args{"makedefault"} = 1 - if ($node_types_selected); - my $new_image = Image->Create($project, $group, $this_user, $imagename, $osid, \%newimageid_args, \$usrerr); UserError($usrerr) diff --git a/clientside/os/GNUmakefile.in b/clientside/os/GNUmakefile.in index c1a5f44be450ee7dc0246acd53b0cf34c269126b..ca918bbb8491a6fda8bfe6f421503cd09642ed44 100644 --- a/clientside/os/GNUmakefile.in +++ b/clientside/os/GNUmakefile.in @@ -1,5 +1,5 @@ # -# Copyright (c) 2000-2014 University of Utah and the Flux Group. +# Copyright (c) 2000-2015 University of Utah and the Flux Group. # # {{{EMULAB-LICENSE # @@ -78,9 +78,9 @@ endif $(MAKE) -C genhostsfile client-install ifeq ($(SYSTEM),FreeBSD) $(MAKE) -C growdisk client-install - $(INSTALL_PROGRAM) $(SRCDIR)/create-image $(LBINDIR)/ $(INSTALL_PROGRAM) $(SRCDIR)/create-swapimage $(LBINDIR)/ endif + $(INSTALL_PROGRAM) $(SRCDIR)/create-image $(LBINDIR)/ $(INSTALL_PROGRAM) $(SRCDIR)/create-versioned-image $(LBINDIR)/ mfs: diff --git a/clientside/os/create-image b/clientside/os/create-image index 5442151921bfc1b58e131ecbf5b4877917f83201..2535019fccfb12b051f34d12572ebfd5b1201618 100755 --- a/clientside/os/create-image +++ b/clientside/os/create-image @@ -31,10 +31,16 @@ use Getopt::Std; # sub usage() { - print STDOUT "Usage: create-image [-S image-server] [-F imageid] [-s slice] \n"; + print STDOUT "Usage: create-image [-S image-server] [-F imageid] ". + "[-s slice] [-b bsname | ] []\n"; exit(-1); } -my $optlist = "F:S:s:"; +my $optlist = "F:S:s:b:"; + +# Drag in path stuff so we can find emulab stuff. +BEGIN { require "/etc/emulab/paths.pm"; import emulabpaths; } +use libsetup; +use liblocsetup; # # Turn off line buffering on output @@ -58,6 +64,10 @@ my $slice = ""; my $slicenum = 0; my $device; my $filename; +my $bsname; +my $bsref; +my $isgpt = 0; +my $exitval = 0; # # If we are running as a user, then we will need sudo @@ -96,10 +106,6 @@ my $imageid; if (! getopts($optlist, \%options)) { usage(); } -if (@ARGV != 2) { - usage(); -} - if (defined($options{"S"})) { $iserver = $options{"S"}; if ($iserver =~ /^([-\w\.]+)$/) { @@ -116,7 +122,6 @@ if (defined($options{"F"})) { die("Bad -F imageid: '$imageid'"); } } - if (defined($options{"s"})) { $slicenum = $options{"s"}; @@ -133,37 +138,117 @@ if (defined($options{"s"})) { # XXX there are still some issues with LILO/GRUB $slice = "-N $slice"; } -$device = $ARGV[0]; -if (defined($imageid)) { - $filename = "-"; -} else { - $filename = $ARGV[1]; -} - -# -# Untaint the arguments. -# -# Note different taint check (allow /). -if ($device =~ /^([-\w.\/]+)$/) { - $device = $1; +if (defined($options{"b"})) { + $bsname = $options{"b"}; + require liblocstorage; + require liblocsetup; } else { - die("Tainted device name: $device"); + # + # Normal imaging operation. + # + usage() + if (!@ARGV); + + $device = shift(@ARGV); + + # Untaint the arguments. + if ($device =~ /^([-\w.\/]+)$/) { + $device = $1; + } + else { + die("Tainted device name: $device"); + } } -if ($filename =~ /^([-\w.\/\+]+)$/) { - $filename = $1; +if (defined($imageid)) { + $filename = "-"; } else { - die("Tainted output filename: $filename"); -} + usage() + if (!@ARGV); + + $filename = shift(@ARGV); -# Hack for the Linux MFS: we still use the BSD device -# names in the database so we try to convert them to -# the equivalent Linux devices here. This happens to -# work at the moment, but if device names change again -# it could break. + # Untaint. + if ($filename =~ /^([-\w.\/\+]+)$/) { + $filename = $1; + } + else { + die("Tainted output filename: $filename"); + } +} -if ($^O eq 'linux') { +if (defined($bsname)) { + # + # Taking a snapshot of a blockstore. We need to find the info for + # the blockstore so we know the device and mount point, etc. + # + my @allcmds = (); + if (getstorageconfig(\@allcmds) != 0) { + die("Error grabbing storage config!\n"); + } + # Find the blockstore we care about. + foreach my $ref (@allcmds) { + if ($ref->{'CMD'} eq "SLICE" && $ref->{'VOLNAME'} eq $bsname) { + $bsref = $ref; + last; + } + } + if (!defined($bsref)) { + die("Could not find storage configuration for $bsname\n"); + } + # + # The storage map tells us the device info. + # + open(MAP, TMSTORAGEMAP()) or + die("Could not open the storage map!\n"); + while () { + if ($_ =~ /^([-\w]+)\s+([-\w\.\/]+)\s+([-\w\.\/]+)\s*/) { + if ($1 eq $bsname) { + $device = $2; + # + # Hmm, storagemap does not have the actual mount device, + # which seems wrong since now I have to figure it out + # in order to check to see if its mounted. + # + if ($bsref ne "SYSVOL") { + $device = "/dev/mapper/emulab-${bsname}"; + } + $bsref->{'DEVICE'} = $device; + } + } + } + close(MAP); + if (!defined($device)) { + die("Could not find $bsname in the storage map!\n"); + } + # + # Need to unmount the FS so we can take the snapshot. + # + if (os_ismounted($device)) { + os_unmount($bsref->{'MOUNTPOINT'}) == 0 or + die("Could not unmount $device!\n"); + } + my $fstype = liblocstorage::get_fstype($bsref, $device); + if (!defined($fstype)) { + os_mount($bsref->{'MOUNTPOINT'}); + die("Could not determine fstype of $device\n"); + } + # Hacky, cause the blockstore filesystems do not have stub MBRs. + if ($fstype eq "ufs") { + $slice = "-b"; + } + else { + $slice = "-l"; + } +} +else { + # Hack for the Linux MFS: we still use the BSD device + # names in the database so we try to convert them to + # the equivalent Linux devices here. This happens to + # work at the moment, but if device names change again + # it could break. + if ($^O eq 'linux') { $device =~ m#/dev/(\D+)(\d+)#; ($dtype, $dunit) = ($1, $2); $dunit -= 4 if ($dtype eq 'ad' && $dunit > 3); @@ -183,6 +268,7 @@ if ($^O eq 'linux') { } $device = "/dev/sd$dunit"; + } } # @@ -196,6 +282,8 @@ if (defined($imageid)) { $cmd .= " | $uploader -S $iserver -F $imageid -"; } +print STDERR "Command: '$cmd'\n"; + # # Run the command using sudo, since by definition only testbed users # with proper trust should be able to zip up a disk. sudo will fail @@ -215,7 +303,11 @@ if (system("$cmd") || -e "/tmp/imagezip.stat") { print STDERR " status: $stat\n"; print STDERR " izstatus: $izstat\n" if ($izstat); - exit 1; + $exitval = 1; } - -exit 0; +if (defined($bsref) && + os_mount($bsref->{'MOUNTPOINT'})) { + print STDERR "Could not remount " . $bsref->{'MOUNTPOINT'} . "\n"; + exit(6); +} +exit $exitval; diff --git a/clientside/os/create-versioned-image b/clientside/os/create-versioned-image index 12506f55046bcf1b6b83c02e95d5fb637c31b45f..f2c23510e60d370c0e3362f7ac96ebb7ac631f49 100644 --- a/clientside/os/create-versioned-image +++ b/clientside/os/create-versioned-image @@ -1,7 +1,7 @@ #!/usr/bin/perl -w # -# Copyright (c) 2000-2014 University of Utah and the Flux Group. +# Copyright (c) 2000-2015 University of Utah and the Flux Group. # # {{{EMULAB-LICENSE # @@ -123,6 +123,11 @@ $| = 1; $ENV{'PATH'} = "/bin:/sbin:/usr/bin:"; delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; +# Drag in path stuff so we can find emulab stuff. +BEGIN { require "/etc/emulab/paths.pm"; import emulabpaths; } +use libsetup; +use liblocsetup; + # # No configure vars. # @@ -193,6 +198,87 @@ sub map_diskname($) return "/dev/$dtype$dunit"; } +# +# Map a blockstore name to its device and fstype. +# +my $storageconfig = undef; + +sub map_bsname($) +{ + my ($bsname) = @_; + my $bsref; + my $device; + require liblocstorage; + + if (!defined($storageconfig)) { + my @allcmds = (); + if (getstorageconfig(\@allcmds) != 0) { + print STDERR "Error grabbing storage config!\n"; + goto bad; + } + $storageconfig = \@allcmds; + } + # Find the blockstore we care about. + foreach my $ref (@$storageconfig) { + if ($ref->{'CMD'} eq "SLICE" && $ref->{'VOLNAME'} eq $bsname) { + $bsref = $ref; + last; + } + } + if (!defined($bsref)) { + print STDERR "Could not find storage configuration for $bsname\n"; + goto bad; + } + # + # The storage map tells us the device info. + # + open(MAP, TMSTORAGEMAP()) or + die("Could not open the storage map!\n"); + while () { + if ($_ =~ /^([-\w]+)\s+([-\w\.\/]+)\s+([-\w\.\/]+)\s*/) { + if ($1 eq $bsname) { + $device = $2; + # + # Hmm, storagemap does not have the actual mount device, + # which seems wrong since now I have to figure it out + # in order to check to see if its mounted. + # + if ($bsref ne "SYSVOL") { + $device = "/dev/mapper/emulab-${bsname}"; + } + $bsref->{'DEVICE'} = $device; + } + } + } + close(MAP); + if (!defined($device)) { + print STDERR "Could not find $bsname in the storage map!\n"; + goto bad; + } + # + # Make sure we can unmount now. Remount it, we will unmount again + # later in process_image. + # + if (os_ismounted($device) && + os_unmount($bsref->{'MOUNTPOINT'})) { + print STDERR "Could not unmount $device!\n"; + goto bad; + } + my $fstype = liblocstorage::get_fstype($bsref, $device); + if (os_mount($bsref->{'MOUNTPOINT'})) { + print STDERR "Could not remount $device!\n"; + goto bad; + } + if (!defined($fstype)) { + print STDERR "Could not determine fstype of $device\n"; + goto bad; + } + $bsref->{'FSTYPE'} = $fstype; + return $bsref; + bad: + return undef; +} + sub parse_params(@) { my ($method,$iname,$disk,$part,$sigfile,$nsigfile); @@ -235,6 +321,13 @@ sub parse_params(@) } next; } + if ($key eq "bsname") { + $iinfo{$iid}{'bsref'} = map_bsname($val); + if (!defined($iinfo{$iid}{'bsref'})) { + $errors++; + } + next; + } if ($key eq "part") { if ($val =~ /^(\d+)$/) { $iinfo{$iid}{'part'} = $1; @@ -344,8 +437,8 @@ if (defined($options{"f"})) { # my $dofrisbee = 0; foreach my $iid (sort keys %iinfo) { - if (!defined($iinfo{$iid}{'disk'})) { - print STDERR "Must specify disk\n"; + if (!(defined($iinfo{$iid}{'disk'}) || defined($iinfo{$iid}{'bsref'}))) { + print STDERR "Must specify disk or blockstore\n"; exit(1); } if (!defined($iinfo{$iid}{'part'})) { @@ -536,6 +629,7 @@ sub writetest($$) sub process_image($) { my ($iid) = @_; + my $retval = 0; if ($verbose) { print "Image #$iid:\n"; @@ -600,7 +694,25 @@ sub process_image($) if (exists($iinfo{$iid}{'nsigfile'})) { $cmd .= " -U $localdir/nsigfile"; } - $cmd .= " " . $iinfo{$iid}{'disk'}; + if (exists($iinfo{$iid}{'bsref'})) { + my $bsref = $iinfo{$iid}{'bsref'}; + + $cmd .= " " . $bsref->{'DEVICE'}; + # Must tell imagezip what is in the partition. + if ($bsref->{'FSTYPE'} eq "ufs") { + $cmd .= " -b"; + } + else { + $cmd .= " -l"; + } + } + else { + $cmd .= " " . $iinfo{$iid}{'disk'}; + if ($isgpt) { + # use the slice device + $cmd .= $iinfo{$iid}{'part'}; + } + } my $image = $iinfo{$iid}{'iname'}; if ($iinfo{$iid}{'method'} eq "file") { @@ -619,8 +731,27 @@ sub process_image($) $cmd .= " - "; } - if (mysystem("$cmd") || -e "$localdir/imagezip.stat") { - my $stat = sprintf("0x%04x", $?); + if (exists($iinfo{$iid}{'bsref'}) && + os_ismounted($iinfo{$iid}{'bsref'}->{'DEVICE'}) && + os_unmount($iinfo{$iid}{'bsref'}->{'MOUNTPOINT'})) { + print STDERR "Could not unmount " . + $iinfo{$iid}{'bsref'}->{'MOUNTPOINT'} . "!\n"; + exit(4); + } + + if (mysystem("$cmd")) { + $retval = $?; + } + + if (exists($iinfo{$iid}{'bsref'}) && + os_mount($iinfo{$iid}{'bsref'}->{'MOUNTPOINT'})) { + print STDERR "Could not remount ". + $iinfo{$iid}{'bsref'}->{'MOUNTPOINT'} . "!\n"; + exit(6); + } + + if ($retval || -e "$localdir/imagezip.stat") { + my $stat = sprintf("0x%04x", $retval); my $izstat = 0; if (-e "$localdir/imagezip.stat") { $izstat = `cat $localdir/imagezip.stat`; diff --git a/clientside/os/frisbee.redux/GNUmakefile.in b/clientside/os/frisbee.redux/GNUmakefile.in index e5cbd856100c6788db55ec43db9079cc7ac906be..92b3ccf77e013fdb6c7faf2013003c8230adc6ab 100644 --- a/clientside/os/frisbee.redux/GNUmakefile.in +++ b/clientside/os/frisbee.redux/GNUmakefile.in @@ -309,8 +309,8 @@ subboss-install: subboss client: frisbee frisupload client-install: client - $(INSTALL_PROGRAM) frisbee $(DESTDIR)$(CLIENT_BINDIR) - $(INSTALL_PROGRAM) frisupload $(DESTDIR)$(CLIENT_BINDIR) + $(INSTALL_PROGRAM) frisbee $(DESTDIR)/usr/local/bin/frisbee + $(INSTALL_PROGRAM) frisupload $(DESTDIR)/usr/local/bin/frisupload clean: /bin/rm -f *.o *.a *.debug diff --git a/clientside/tmcc/common/config/rc.storage b/clientside/tmcc/common/config/rc.storage index 380f9c780994a8d52cec32c3349e0ce74bfd9831..febbdb5b68afd662c96cb088439fca1f5a8330f1 100644 --- a/clientside/tmcc/common/config/rc.storage +++ b/clientside/tmcc/common/config/rc.storage @@ -655,6 +655,26 @@ sub process($$$$) warn("*** Unknown storage slice bsid '".$href->{'BSID'}."'\n"); return 0; } + if (exists($href->{'DATASET'}) && $dosetup) { + # + # We are going to load from a dataset image via frisbee. + # but first we need to know the extent of the image to + # create the storage slice. We get this info from tmcd. + # + my $iref; + getimagesize($href->{'DATASET'}, \$iref); + if (!defined($iref)) { + warn("*** No image size info for '".$href->{'DATASET'}."'\n"); + return 0; + } + if (exists($iref->{'IMAGELOW'}) && + exists($iref->{'IMAGEHIGH'})) { + # Bad, but don't want to use bignums to get MBs. + $href->{'VOLSIZE'} = + int(($iref->{'IMAGEHIGH'} - $iref->{'IMAGELOW'} + 1) * + ($iref->{'IMAGESSIZE'} / (1024.0 * 1024.0))) + 1; + } + } } else { warn("*** Unrecognized storage command '".$href->{'CMD'}."'\n"); return 0; diff --git a/clientside/tmcc/common/libsetup.pm b/clientside/tmcc/common/libsetup.pm index a3ff76c8147f024701ed684e5c81846b97086268..1205fc00f5f127fc42471c931ff49f1ad7ae6624 100644 --- a/clientside/tmcc/common/libsetup.pm +++ b/clientside/tmcc/common/libsetup.pm @@ -41,7 +41,7 @@ use Exporter; getlocalevserver genvnodesetup getgenvnodeconfig stashgenvnodeconfig getlinkdelayconfig getloadinfo getbootwhat getnodeattributes copyfilefromnfs getnodeuuid getarpinfo - getstorageconfig getstoragediskinfo + getstorageconfig getstoragediskinfo getimagesize getmanifest fetchmanifestblobs runbootscript runhooks build_fake_macs getenvvars @@ -3531,6 +3531,7 @@ sub getstorageconfig($;$) { 'UUID_TYPE'=> '(iqn|serial)', 'VOLNAME' => '[-\w]+', 'VOLSIZE' => '\d+', + 'DATASET' => '[-\w\/\.:]+', ); my @ops = (); @@ -3614,6 +3615,65 @@ sub getstoragediskinfo() return undef; } +sub getimagesize($$;$) { + my ($iname,$rptr,$nocache) = @_; + my @tmccresults = (); + my %opthash = (); + + if (defined($nocache) && $nocache) { + $opthash{'nocache'} = 1; + } + + if (tmcc(TMCCCMD_IMAGESIZE, $iname, \@tmccresults, %opthash) < 0 || + @tmccresults == 0) { + warn("*** WARNING: Could not get imagesize from server!\n"); + return -1; + } + + my %fields = ( + 'IMAGELOW' => '\d+', + 'IMAGEHIGH' => '\d+', + 'IMAGESSIZE' => '\d+', + 'IMAGERELOC' => '(0|1)', + ); + + # + # Note that any error is fatal since these lines are interdependent. + # + my $line = shift(@tmccresults); + chomp($line); + + # + # Break the line into a hash of key/values + # + my @kvs = split(/\s+/, $line); + my %res = (); + foreach my $kv (@kvs) { + my ($key,$val,$foo) = split(/=/, $kv); + if (defined($foo)) { + warn("*** WARNING: ". + "malformed key-val pair in imagesize: '$kv'\n"); + return -1; + } + + # + # Validate the info and untaint. + # + if (!exists($fields{$key})) { + warn("*** WARNING: invalid keyword in imagesize: '$key'\n"); + return -1; + } + if ($val !~ /^$fields{$key}$/) { + warn("*** WARNING: invalid value for $key in imagesize: ". + "'$val'\n"); + return -1; + } + $res{$key} = $val; + } + $$rptr = \%res; + return 0; +} + # # Fork a process to exec a command. Return the pid to wait on. # diff --git a/clientside/tmcc/common/libtmcc.pm b/clientside/tmcc/common/libtmcc.pm index 89ee0d74ffb7df6c11a82b6358746acee57479a2..8e62692e7e03e82d22c51c4e226324258857b231 100755 --- a/clientside/tmcc/common/libtmcc.pm +++ b/clientside/tmcc/common/libtmcc.pm @@ -1,6 +1,6 @@ #!/usr/bin/perl -wT # -# Copyright (c) 2000-2013 University of Utah and the Flux Group. +# Copyright (c) 2000-2015 University of Utah and the Flux Group. # # {{{EMULAB-LICENSE # @@ -53,7 +53,7 @@ use Exporter; TMCCCMD_LTMAP TMCCCMD_LTPMAP TMCCCMD_TOPOMAP TMCCCMD_LOADINFO TMCCCMD_TPMBLOB TMCCCMD_TPMPUB TMCCCMD_DHCPDCONF TMCCCMD_MANIFEST TMCCCMD_NODEUUID TMCCCMD_NODEATTRIBUTES TMCCCMD_DISKS - TMCCCMD_ARPINFO TMCCCMD_STORAGE + TMCCCMD_ARPINFO TMCCCMD_STORAGE TMCCCMD_IMAGESIZE ); # Must come after package declaration! @@ -219,6 +219,7 @@ my %commandset = "disks" => {TAG => "disks"}, "arpinfo" => {TAG => "arpinfo"}, "storageconfig" => {TAG => "storageconfig"}, + "imagesize" => {TAG => "imagesize"}, ); # @@ -294,6 +295,7 @@ sub TMCCCMD_NODEATTRIBUTES() { $commandset{"nodeattributes"}->{TAG}; } sub TMCCCMD_DISKS() { $commandset{"disks"}->{TAG}; } sub TMCCCMD_ARPINFO() { $commandset{"arpinfo"}->{TAG}; } sub TMCCCMD_STORAGE() { $commandset{"storageconfig"}->{TAG}; } +sub TMCCCMD_IMAGESIZE() { $commandset{"imagesize"}->{TAG}; } # # Caller uses this routine to set configuration of this library diff --git a/clientside/tmcc/freebsd/liblocsetup.pm b/clientside/tmcc/freebsd/liblocsetup.pm index dea9b503e9e7f760a6c43644082c54197f7ae354..3cbf6559d2579d4a6d52ecab3ef1c868d71699ab 100644 --- a/clientside/tmcc/freebsd/liblocsetup.pm +++ b/clientside/tmcc/freebsd/liblocsetup.pm @@ -1,6 +1,6 @@ #!/usr/bin/perl -wT # -# Copyright (c) 2000-2014 University of Utah and the Flux Group. +# Copyright (c) 2000-2015 University of Utah and the Flux Group. # # {{{EMULAB-LICENSE # @@ -40,7 +40,7 @@ use Exporter; os_fwconfig_line os_fwrouteconfig_line os_config_gre os_nfsmount os_find_freedisk os_get_ctrlnet_ip os_getarpinfo os_createarpentry os_removearpentry - os_getstaticarp os_setstaticarp + os_getstaticarp os_setstaticarp os_ismounted os_unmount os_mount ); sub VERSION() { return 1.0; } @@ -1502,4 +1502,29 @@ sub os_setstaticarp($$) return 0; } +# Is a device mounted. +sub os_ismounted($) +{ + my ($device) = @_; + + my $line = `$MOUNT | grep '^$device on '`; + + return ($line ? 1 : 0); +} + +sub os_unmount($) +{ + my ($mpoint) = @_; + + return system("$UMOUNT $mpoint"); +} + +sub os_mount($;$) +{ + my ($mpoint, $device) = @_; + $device = "" if (!defined($device)); + + return system("$MOUNT $mpoint $device"); +} + 1; diff --git a/clientside/tmcc/linux/liblocsetup.pm b/clientside/tmcc/linux/liblocsetup.pm index 4812c228275b882a0e7a0974b07f7334cb9b1fc5..f97f6c04650381badf44221bf0a93a715ceedc70 100644 --- a/clientside/tmcc/linux/liblocsetup.pm +++ b/clientside/tmcc/linux/liblocsetup.pm @@ -41,7 +41,7 @@ use Exporter; os_get_disks os_get_disk_size os_get_partition_info os_nfsmount os_get_ctrlnet_ip os_getarpinfo os_createarpentry os_removearpentry - os_getstaticarp os_setstaticarp + os_getstaticarp os_setstaticarp os_ismounted os_unmount os_mount ); sub VERSION() { return 1.0; } @@ -88,6 +88,7 @@ $EGREP = "/bin/egrep -q"; $NFSMOUNT = "/bin/mount -o nolock,udp"; $LOOPBACKMOUNT = "/bin/mount -n -o bind "; $UMOUNT = "/bin/umount"; +$MOUNT = "/bin/mount"; $TMPASSWD = "$ETCDIR/passwd"; $TMGROUP = "$ETCDIR/group"; $TMSHADOW = "$ETCDIR/shadow"; @@ -2548,4 +2549,29 @@ sub os_setstaticarp($$) return 0; } +# Is a device mounted. +sub os_ismounted($) +{ + my ($device) = @_; + + my $line = `$MOUNT | grep '^${device} on'`; + + return ($line ? 1 : 0); +} + +sub os_unmount($) +{ + my ($mpoint) = @_; + + return system("$UMOUNT $mpoint"); +} + +sub os_mount($;$) +{ + my ($mpoint, $device) = @_; + $device = "" if (!defined($device)); + + return system("$MOUNT $mpoint $device"); +} + 1; diff --git a/clientside/tmcc/linux/liblocstorage.pm b/clientside/tmcc/linux/liblocstorage.pm index acbc159b94f6822dde8d5094199714776b2d33de..49077e2b3cfc434f486cbe78c41cd8b1859feea6 100644 --- a/clientside/tmcc/linux/liblocstorage.pm +++ b/clientside/tmcc/linux/liblocstorage.pm @@ -39,6 +39,8 @@ sub VERSION() { return 1.0; } # Must come after package declaration! use English; use Cwd 'abs_path'; +use libsetup; +use libtmcc; # Load up the paths. Its conditionalized to be compatabile with older images. # Note this file has probably already been loaded by the caller. @@ -70,6 +72,7 @@ my $SFDISK = "/sbin/sfdisk"; my $SGDISK = "/sbin/sgdisk"; my $GDISK = "/sbin/gdisk"; my $PPROBE = "/sbin/partprobe"; +my $FRISBEE = "/usr/local/bin/frisbee"; # # @@ -366,15 +369,15 @@ sub get_diskinfo() # # Get the list of partitions. - # XXX only care about sd[a-z] devices and their partitions. + # XXX only care about xvd|sd[a-z] devices and their partitions. # if (!open(FD, "/proc/partitions")) { warn("*** get_diskinfo: could not get disk info from /proc/partitions\n"); return undef; } while () { - if (/^\s+\d+\s+\d+\s+(\d+)\s+(sd[a-z])(\d+)?/) { - my ($size,$dev,$part) = ($1,$2,$3); + if (/^\s+\d+\s+\d+\s+(\d+)\s+((xvd|sd)[a-z])(\d+)?/) { + my ($size,$dev,$part) = ($1,$2,$4); # DOS partition if (defined($part)) { my $pttype = "MBR"; @@ -416,7 +419,7 @@ sub get_diskinfo() return undef; } while () { - if (/^\/dev\/(sd\S+)/) { + if (/^\/dev\/((xvd|sd)\S+)/) { my $dev = $1; if (exists($geominfo{$dev}) && $geominfo{$dev}{'inuse'} == 0) { $geominfo{$dev}{'inuse'} = -1; @@ -1076,6 +1079,35 @@ sub os_create_storage($$) } } + elsif (exists($href->{'DATASET'})) { + # + # Load with the dataset. + # + my $proxyopt = ""; + my $imageid = $href->{'DATASET'}; + my $imagepath = $mdev; + my (undef,$ip) = tmccbossinfo(); + + if (SHAREDHOST()) { + my $TMNODEID = TMNODEID(); + my $node_id = `cat $TMNODEID`; + chomp($node_id); + $proxyopt = "-P $nodeid"; + } + + my $command = "$FRISBEE -f -M 128 $proxyopt ". + " -S $ip -B 30 -F $imageid $imagepath"; + + if (mysystem($command)) { + warn("*** $lv: frisbee of dataset to $mdev failed!\n"); + return 0; + } + $fstype = get_fstype($href, $mdev); + if (!$fstype) { + warn("*** $lv: unknown FS in dataset on $mdev\n"); + return 0; + } + } # # Otherwise, create the filesystem: # @@ -1301,7 +1333,8 @@ sub os_create_storage_slice($$$) if ($bsid eq "ANY") { $dev = $bdisk . "4"; - if ($ginfo->{$dev}->{'inuse'} == 0) { + if (exists($ginfo->{$dev}) && + $ginfo->{$dev}->{'inuse'} == 0) { push(@devs, "/dev/$dev"); } } diff --git a/db/Experiment.pm.in b/db/Experiment.pm.in index 5364e7ea9adf07ca1d75b915eeff9acbd30ebcfd..a226eb0b5020fca39b50b72c00a34a85ae8ce995 100644 --- a/db/Experiment.pm.in +++ b/db/Experiment.pm.in @@ -6219,5 +6219,25 @@ sub ClearGlobalIPAllocation($) return 0; } +# +# Find the blockstore details from the virt table. +# +sub LookupBlockstore($$) +{ + my ($self, $bsname) = @_; + my $blockstore = undef; + + # Now get this after we are sure we need it. + my $virtexp = $self->GetVirtExperiment(); + + foreach my $bs ($virtexp->Table("virt_blockstores")->Rows()) { + if ($bs->vname() eq $bsname) { + $blockstore = $bs; + last; + } + } + return $blockstore; +} + # _Always_ make sure that this 1 is at the end of the file... 1; diff --git a/db/Image.pm.in b/db/Image.pm.in index c9e5619d529a89511879ff334b3be5abb358651a..7b65e44452576855c47199a9e14c65d6b302db1e 100644 --- a/db/Image.pm.in +++ b/db/Image.pm.in @@ -62,6 +62,12 @@ sub versid($) return $self->imageid() . ":" . $self->version(); } +sub versname($) +{ + my ($self) = @_; + + return $self->pid() . "/" . $self->imagename() . ":" . $self->version(); +} # Little helper and debug function. sub mysystem($) @@ -432,22 +438,27 @@ sub Create($$$$$$$$) if (ref($class) || !ref($project)); my $isadmin = $creator->IsAdmin(); + my $isdataset = (exists($argref->{"isdataset"}) ? + $argref->{"isdataset"} : 0); # We may ignore particular partN_osid's by deleting them. my @arg_slots = grep(/^part[1-4]_osid$/, keys(%{$argref})); # Pass-through a bunch of required slots, ignoring any extras # and stuff we handle explicitly. - foreach my $key ("loadpart", "loadlength", "default_osid") { - if (!exists($argref->{$key})) { - $$usrerr_ref = "Error: $key missing in Image->Create!"; - return undef; + if (!$isdataset) { + foreach my $key ("loadpart", "loadlength", "default_osid") { + if (!exists($argref->{$key})) { + $$usrerr_ref = "Error: $key missing in Image->Create!"; + return undef; + } + push(@arg_slots, $key); } - push(@arg_slots, $key); } # Pass-through optional slots, otherwise the DB default is used. foreach my $key ("path", "shared", "global", "ezid", "mbr_version", - "metadata_url", "imagefile_url", "released") { + "metadata_url", "imagefile_url", "released", + "isdataset") { if (exists($argref->{$key})) { push(@arg_slots, $key); } @@ -496,7 +507,7 @@ sub Create($$$$$$$$) # We allow providing an image descriptor index, so newimageid_ez can use # the same name, description, and index for both the OS and Image it makes. - if ($imageid > 0 ) { + if (defined($imageid) && $imageid > 0 ) { # Make sure the OS descriptor for it already exists. if (!OSinfo->Lookup($imageid)) { DBQueryWarn("unlock tables"); @@ -555,6 +566,7 @@ sub Create($$$$$$$$) } # And the other entry. if (! DBQueryWarn($query)) { + DBQueryWarn("delete from images where uuid='$image_uuid'"); DBQueryWarn("unlock tables"); tberror("Error inserting new images record for $pid/$imagename!"); return undef; @@ -565,7 +577,7 @@ sub Create($$$$$$$$) # Create the osidtoimageid mapping. Admins have an option to do it or not. my $makedefault = exists($argref->{"makedefault"}) && $argref->{"makedefault"} eq "1"; - if (!$isadmin || $makedefault) { + if (!$isdataset && (!$isadmin || $makedefault)) { # # Dig out the mtypes we want to turn on. The caller has already # sanity checked them to make sure the types actually exist, and @@ -1677,11 +1689,16 @@ sub GrantAccess($$$) $perm_id = $target->uid(); $perm_type = "user"; } - if (ref($target) eq "Group") { + elsif (ref($target) eq "Group") { $perm_idx = $target->gid_idx(); $perm_id = $target->pid() . "/" . $target->gid(); $perm_type = "group"; } + elsif (ref($target) eq "Project") { + $perm_idx = $target->gid_idx(); + $perm_id = $target->pid() . "/" . $target->gid(); + $perm_type = "group"; + } return -1 if (!DBQueryWarn("replace into image_permissions set ". diff --git a/protogeni/lib/GeniAggregate.pm.in b/protogeni/lib/GeniAggregate.pm.in index 027311bb28c8a87102d13973977d3852fbbf39d5..95d458c3c732d7c20f09e04e97b54c86bb5a069c 100755 --- a/protogeni/lib/GeniAggregate.pm.in +++ b/protogeni/lib/GeniAggregate.pm.in @@ -1106,10 +1106,10 @@ sub Action($$$) if (defined($image)) { print STDERR "Setting $vnode to load $image\n"; - if (!exists($reloads{$image->imageid()})) { - $reloads{$image->imageid()} = [ ]; + if (!exists($reloads{$image->versid()})) { + $reloads{$image->versid()} = [ ]; } - push(@{ $reloads{$image->imageid()} }, $vnode); + push(@{ $reloads{$image->versid()} }, $vnode); $imageinfo{$vnode->node_id()} = [$osinfo, $image]; $vnode->_reloaded(1); @@ -1220,10 +1220,10 @@ sub Action($$$) $msg .= " No image for $osinfo on $node"; goto bad; } - if (!exists($reloads{$image->imageid()})) { - $reloads{$image->imageid()} = [ ]; + if (!exists($reloads{$image->versid()})) { + $reloads{$image->versid()} = [ ]; } - push(@{ $reloads{$image->imageid()} }, $node); + push(@{ $reloads{$image->versid()} }, $node); $node->_reloaded(1); # As with os_setup, we do not count images unless diff --git a/protogeni/lib/GeniCM.pm.in b/protogeni/lib/GeniCM.pm.in index eb1f840b2563a6a922d7e2dc6a006f1861cfe948..1338d8776b9bed6d8e2ae78cb02152eeb8fec482 100644 --- a/protogeni/lib/GeniCM.pm.in +++ b/protogeni/lib/GeniCM.pm.in @@ -973,14 +973,15 @@ sub GetTicketAuxAux($$$$$$$$$$) goto bad; } my ($auth,$type,$id) = GeniHRN::Parse($dname); - my ($ospid,$os) = ($id =~ m{(.*)//(.*)}); + my ($ospid,$os,undef,$vers) = + ($id =~ m{([^/]+)//([^/]+)(//(\d+))?}); if ($type ne "image" || !defined($ospid) || !defined($os)){ $response = GeniResponse->Create(GENIRESPONSE_BADARGS, undef, "Malformed image URN: $dname"); goto bad; } - $osinfo = OSinfo->Lookup($ospid, $os); + $osinfo = OSinfo->Lookup($ospid, $os, $vers); if (!defined($osinfo)) { $response = GeniResponse->Create(GENIRESPONSE_BADARGS, undef, @@ -1002,7 +1003,8 @@ sub GetTicketAuxAux($$$$$$$$$$) # # This is only going to be used in raw mode. # - $osname = "$ospid/$os"; + $osname = "$ospid/$os"; + $osname .= ":${vers}" if (defined($vers)); } } @@ -6809,7 +6811,9 @@ sub HandleBlockstore($$$$$$@) my $errorcode = GENIRESPONSE_ERROR; my $message = "Unknown Error"; my $nodename = GeniXML::GetVirtualId($noderef); - + my @attributes = (); + + require Image; require Lease; require Blockstore; @@ -6818,6 +6822,7 @@ sub HandleBlockstore($$$$$$@) my $mount = GeniXML::GetText("mountpoint", $blockref); my $readonly = GeniXML::GetText("readonly", $blockref); my $leasename = GeniXML::GetText("persistent", $blockref); + my $dataset_id= GeniXML::GetText("dataset", $blockref); my $type = ""; my $fixed = $nodename; my $size = 0; @@ -6844,14 +6849,85 @@ sub HandleBlockstore($$$$$$@) $readonly = ($readonly eq "true" ? 1 : 0); } # - # If ephemeral, size must be given, else we get it below from the lease. + # If ephemeral, size must be given. Else we get it below from the lease, + # or we get it from the image if its an image backed blockstore. # if (!defined($leasename)) { - $size = GeniXML::GetText("size", $blockref); + if (defined($dataset_id)) { + # Default project to lookup lease. + my $pid = $experiment->pid(); + my $gid = $pid; + my $vers; - if (!defined($size)) { - $message = "Missing blockstore size for $bsname"; - goto bad; + my ($domainsubauth,$type,$id) = GeniHRN::Parse($dataset_id); + if ($type ne "dataset") { + $message = "Illegal dataset urn for $dataset_id"; + goto bad; + } + my ($domain,$subauth) = split(":", $domainsubauth); + if ($domain ne $OURDOMAIN) { + $message = "This is not the correct site for this dataset"; + goto bad; + } + if (defined($subauth)) { + $pid = $subauth; + } + if ($id =~ /^([^\/]+)\/\/([^\/]+)(\/\/(\d+))?$/) { + $id = $2; + if (defined($4)) { + $vers = $4; + } + } + my $image = Image->Lookup($pid, $id, $vers); + if (!defined($image)) { + $message = "Unknown dataset: $dataset_id"; + goto bad; + } + if (!$image->isdataset()) { + $message = "Not a dataset: $dataset_id"; + goto bad; + } + if ($PROTOGENI_LOCALUSER) { + # + # We use the Emulab permission system. + # + if (!$image->AccessCheck($geniuser->emulab_user(), + TB_IMAGEID_READINFO())) { + $message = "Not enough permission to use $dataset_id"; + $errorcode = GENIRESPONSE_FORBIDDEN; + goto bad; + } + } + else { + # + # The image must be in the current project, or it must + # be global (okay, shared). + # + if (! ($image->global() || + $image->pid() eq $experiment->pid())) { + $message = "Not enough permission to use $dataset_id"; + $errorcode = GENIRESPONSE_FORBIDDEN; + goto bad; + } + } + # This needs to be name, not id. + push(@attributes, ["dataset", $image->versname(), 0]); + + if (!$image->size()) { + $message = "No local size info for $dataset_id"; + goto bad; + } + $size = int(($image->lba_high() - + $image->lba_low() + 1) / + (1024 / $image->lba_size())) . "KB"; + } + else { + $size = GeniXML::GetText("size", $blockref); + + if (!defined($size)) { + $message = "Missing blockstore size for $bsname"; + goto bad; + } } $size = Blockstore::ConvertToMebi($size); if ($size < 0) { @@ -6859,8 +6935,6 @@ sub HandleBlockstore($$$$$$@) goto bad; } } - # Build up list of attributes. - my @attributes = (); push(@attributes, ["mountpoint", $mount, 0]); push(@attributes, ["placement", "ANY", 0]); push(@attributes, ["readonly", $readonly, 0]); diff --git a/protogeni/lib/GeniCMV2.pm.in b/protogeni/lib/GeniCMV2.pm.in index 41072a6e796771e7b7127d618fb73f3663db433a..5a4228f1d1b92ccc25dd136e2b94718d5e1fbc69 100755 --- a/protogeni/lib/GeniCMV2.pm.in +++ b/protogeni/lib/GeniCMV2.pm.in @@ -2419,6 +2419,7 @@ sub CreateImage($) my $imagename = $argref->{'imagename'}; my $sliver_urn = $argref->{'sliver_urn'}; my $wholedisk = 0; + my $bsname; require Image; require WebTask; @@ -2432,7 +2433,12 @@ sub CreateImage($) } $wholedisk = 1 if (exists($argref->{'wholedisk'}) && $argref->{'wholedisk'}); - + # Optional blockstore name. + if (exists($argref->{'bsname'})) { + $bsname = $argref->{'bsname'}; + return GeniResponse->MalformedArgsResponse("Improper bsname argument") + if ($bsname !~ /^[-\w\.\+]*$/); + } my ($credential,$speaksfor) = GeniStd::CheckCredentials($credentials); return $credential if (GeniResponse::IsResponse($credential)); @@ -2504,9 +2510,23 @@ sub CreateImage($) "Not allowed to shadow system images; ". "use a different name for your image"); } - # See if it already exists. + # See if it already exists and check ownership permission. my $image = Image->Lookup($experiment->pid(), $imagename); - + if (defined($image)) { + if (!defined($image->creator_urn()) || + $image->creator_urn() ne $user->urn()) { + return GeniResponse->Create(GENIRESPONSE_FORBIDDEN, undef, + "This is not your image to overwrite"); + } + if (defined($bsname) && !$image->isdataset()) { + return GeniResponse->Create(GENIRESPONSE_FORBIDDEN, undef, + "This image is not a dataset"); + } + } + elsif (defined($bsname)) { + return GeniResponse->Create(GENIRESPONSE_SEARCHFAILED, undef, + "Dataset for $bsname does not exist"); + } if (!defined(GeniCM::FlipToUser($slice, $user))) { return GeniResponse->Create(GENIRESPONSE_ERROR, undef, "FlipToUser failed"); @@ -2518,6 +2538,12 @@ sub CreateImage($) if ($slice->Lock() != 0) { return GeniResponse->BusyResponse(); } + # + # We do not version datasets yet, + # + if (defined($image) && $image->isdataset()) { + goto dataset; + } my $opt = ""; $opt .= " -e" if ($wholedisk); @@ -2558,7 +2584,7 @@ sub CreateImage($) # Cause of image versioning. $image = $image->LookupMostRecent(); } - +dataset: # # Form an image URN so the user knows how to request the new image. # @@ -2607,8 +2633,12 @@ sub CreateImage($) # # Do the snapshot. # + $opt = ""; + $opt .= " -b $bsname" if (defined($bsname)); + $output = - GeniUtil::ExecQuiet("$CREATEIMAGE -F -e -f -p $pid $imagename $node_id"); + GeniUtil::ExecQuiet("$CREATEIMAGE -F -e -f $opt ". + " -p $pid $imagename $node_id"); # Not a typical op, so always print debugging info; print STDERR $output; # Return to normal state even if it failed. @@ -3225,23 +3255,20 @@ my $CREATEDATASET = "$TB/bin/createdataset"; my $DELETEDATASET = "$TB/bin/deletelease"; my $EXTENDDATASET = "$TB/bin/extendlease"; my $GRANTDATASET = "$TB/bin/grantlease"; +my $GRANTIMAGE = "$TB/sbin/grantimage"; sub CreateDataset($) { my ($argref) = @_; my $credentials = $argref->{'credentials'}; - my $size = $argref->{'size'}; # MiBs my $dataset = $argref->{'name'}; require Lease; + require Image; require EmulabConstants; - if (! (defined($credentials) && - defined($size) && defined($dataset))) { + if (! (defined($credentials) && defined($dataset))) { return GeniResponse->MalformedArgsResponse("Missing arguments"); } - if ($size !~ /^\d+$/) { - return GeniResponse->MalformedArgsResponse("Bad size, use MiBs"); - } if ($dataset !~ /^[-\w]+$/) { return GeniResponse->MalformedArgsResponse("Bad dataset name"); } @@ -3267,11 +3294,15 @@ sub CreateDataset($) return $group if (GeniResponse::IsResponse($group)); + my $image = Image->Lookup($group->pid(), $dataset); + if (defined($image)) { + GeniResponse->Create(GENIRESPONSE_ALREADYEXISTS); + } my $lease = Lease->Lookup($group->pid(), $dataset); if (defined($lease)) { GeniResponse->Create(GENIRESPONSE_ALREADYEXISTS); } - my $cmd = "$CREATEDATASET -C -b -s $size "; + my $cmd = "$CREATEDATASET -C -b "; if ($PROTOGENI_LOCALUSER) { $cmd = "$WAP $cmd -o ". $user->uid() . " "; # @@ -3280,6 +3311,12 @@ sub CreateDataset($) # GeniUtil::FlipToElabMan(); } + if (exists($argref->{'size'})) { + my $size = $argref->{'size'}; + return GeniResponse->MalformedArgsResponse("Bad size, use MiBs") + if ($size !~ /^\d+$/); + $cmd .= " -s $size"; + } if (exists($argref->{'type'})) { my $type = $argref->{'type'}; return GeniResponse->MalformedArgsResponse("Bad type") @@ -3327,35 +3364,57 @@ sub CreateDataset($) } GeniUtil::FlipToGeniUser() if ($PROTOGENI_LOCALUSER); + + # Stuff to put in the return blob. + my ($state,$uuid,$busy,$msg,$urn); + # - # Grab the lease to see if its been approved, we want to tell - # the user something. + # Grab the lease or image to see if its been created/approved, we want + # to tell the user something. # - $lease = Lease->Lookup($group->pid(), $group->gid(), $dataset); - if (!defined($lease)) { - print STDERR "Could not lookup lease after createdataset\n"; - GeniResponse->Create(GENIRESPONSE_ERROR); - } - # Set the manager URN for same-SA permission checks. - $lease->SetAttribute("manager_urn", $credential->target_urn()); - $lease->SetAttribute("creator_urn", $user->urn()); - - my $msg = undef; - if ($lease->state() eq "unapproved") { - if ($lease->locked()) { - $msg = "Your dataset is being allocated."; + if (exists($argref->{'type'}) && $argref->{'type'} eq "imdataset") { + my $image = Image->Lookup($group->pid(), $dataset); + if (!defined($image)) { + print STDERR "Could not lookup image after createdataset\n"; + GeniResponse->Create(GENIRESPONSE_ERROR); } - else { - $msg = "Your dataset has not been approved yet. Watch for email"; + $image->Update({"creator_urn" => $user->urn()}); + $uuid = $image->image_uuid(); + $state = "new"; + $busy = 0; + $urn = GeniHRN::Generate($OURDOMAIN . ":" . $image->pid(), "dataset", + $image->gid() . "//" . $image->imagename()); + } + else { + my $lease = Lease->Lookup($group->pid(), $group->gid(), $dataset); + if (!defined($lease)) { + print STDERR "Could not lookup lease after createdataset\n"; + GeniResponse->Create(GENIRESPONSE_ERROR); + } + # Set the manager URN for same-SA permission checks. + $lease->SetAttribute("manager_urn", $credential->target_urn()); + $lease->SetAttribute("creator_urn", $user->urn()); + + if ($lease->state() eq "unapproved") { + if ($lease->locked()) { + $msg = "Your dataset is being allocated."; + } + else { + $msg = "Your dataset has not been approved yet. ". + "Watch for email."; + } } + $uuid = $lease->uuid(); + $state = $lease->state(); + $busy = $lease->locked() ? 1 : 0; + $urn = GeniHRN::Generate($OURDOMAIN . ":" . $lease->pid(), "dataset", + $lease->gid() . "//" . $lease->lease_id()); } - my $urn = GeniHRN::Generate($OURDOMAIN . ":" . $lease->pid(), "dataset", - $lease->gid() . "//" . $lease->lease_id()); my $blob = { - "state" => $lease->state(), - "uuid" => $lease->uuid(), + "state" => $state, + "uuid" => $uuid, "urn" => $urn, - "busy" => $lease->locked() ? 1 : 0, + "busy" => $busy, }; return GeniResponse->Create(GENIRESPONSE_SUCCESS, $blob, $msg); } @@ -3364,6 +3423,8 @@ sub DeleteDataset($) my ($argref) = @_; my $credentials = $argref->{'credentials'}; my $dataset = $argref->{'name'}; + my $cmd; + require Image; require Lease; require EmulabConstants; @@ -3392,19 +3453,29 @@ sub DeleteDataset($) if (GeniResponse::IsResponse($group)); my $lease = Lease->Lookup($group->pid(), $group->gid(), $dataset); - if (!defined($lease)) { - return GeniResponse->Create(GENIRESPONSE_SEARCHFAILED); - } - # - # Only the creator until we are using credentials here. - # - my $lease_owner = $lease->GetAttribute("creator_urn"); - return GeniResponse->Create(GENIRESPONSE_FORBIDDEN) - if (! (defined($lease_owner) && $lease_owner eq $user->urn())); - - my $cmd = "$DELETEDATASET -b -f " . - $group->pid() . "/" . $group->gid() . "/" . $dataset; + if (defined($lease)) { + # + # Only the creator until we are using credentials here. + # + my $lease_owner = $lease->GetAttribute("creator_urn"); + return GeniResponse->Create(GENIRESPONSE_FORBIDDEN) + if (! (defined($lease_owner) && $lease_owner eq $user->urn())); + $cmd = "$DELETEDATASET -b -f " . + $group->pid() . "/" . $group->gid() . "/" . $dataset; + } + else { + my $image = Image->Lookup($group->pid(), $dataset); + if (!defined($image)) { + return GeniResponse->Create(GENIRESPONSE_SEARCHFAILED); + } + return GeniResponse->Create(GENIRESPONSE_FORBIDDEN) + if (!$image->isdataset() || + !defined($image->creator_urn()) || + $image->creator_urn() ne $user->urn()); + + $cmd = "$DELETEIMAGE -p ". $image->imageid(); + } if ($PROTOGENI_LOCALUSER) { $cmd = "$WAP $cmd"; # @@ -3426,6 +3497,8 @@ sub ModifyDataset($) my ($argref) = @_; my $credentials = $argref->{'credentials'}; my $dataset = $argref->{'name'}; + my $islease = 0; + require Image; require Lease; require EmulabConstants; @@ -3454,26 +3527,40 @@ sub ModifyDataset($) if (GeniResponse::IsResponse($group)); my $lease = Lease->Lookup($group->pid(), $group->gid(), $dataset); - if (!defined($lease)) { - return GeniResponse->Create(GENIRESPONSE_SEARCHFAILED); + if (defined($lease)) { + # + # Only the creator until we are using credentials here. + # + my $lease_owner = $lease->GetAttribute("creator_urn"); + return GeniResponse->Create(GENIRESPONSE_FORBIDDEN) + if (! (defined($lease_owner) && $lease_owner eq $user->urn())); + + $islease = 1; + } + else { + my $image = Image->Lookup($group->pid(), $dataset); + if (!defined($image)) { + return GeniResponse->Create(GENIRESPONSE_SEARCHFAILED); + } + return GeniResponse->Create(GENIRESPONSE_FORBIDDEN) + if (!$image->isdataset() || + !defined($image->creator_urn()) || + $image->creator_urn() ne $user->urn()); + $islease = 0; } - # - # Only the creator until we are using credentials here. - # - my $lease_owner = $lease->GetAttribute("creator_urn"); - return GeniResponse->Create(GENIRESPONSE_FORBIDDEN) - if (! (defined($lease_owner) && $lease_owner eq $user->urn())); my $cmd; # # All we can handle is extend and modify permission bits. # if (exists($argref->{'extend'})) { + return GeniResponse->Create(GENIRESPONSE_UNSUPPORTED) + if ($islease); $cmd = "$EXTENDDATASET "; } elsif (exists($argref->{'read_access'}) || exists($argref->{'write_access'})) { - $cmd = "$GRANTDATASET "; + $cmd = ($islease ? "$GRANTDATASET " : "$GRANTIMAGE "); if (exists($argref->{'read_access'})) { my $read_access = $argref->{'read_access'}; @@ -3492,7 +3579,12 @@ sub ModifyDataset($) else { return GeniResponse->MalformedArgsResponse("unknown operation"); } - $cmd .= " " . $group->pid() . "/" . $group->gid() . "/" . $dataset; + if ($islease) { + $cmd .= " " . $group->pid() . "/" . $group->gid() . "/" . $dataset; + } + else { + $cmd .= " " . $group->pid() . "," . $dataset; + } if ($PROTOGENI_LOCALUSER) { $cmd = "$WAP $cmd"; # @@ -3521,6 +3613,8 @@ sub DescribeDataset($) my ($argref) = @_; my $credentials = $argref->{'credentials'}; my $dataset = $argref->{'name'}; + my $blob = {}; + require Image; require Lease; require Blockstore; require EmulabConstants; @@ -3550,28 +3644,51 @@ sub DescribeDataset($) if (GeniResponse::IsResponse($group)); my $lease = Lease->Lookup($group->pid(), $group->gid(), $dataset); - if (!defined($lease)) { - return GeniResponse->Create(GENIRESPONSE_SEARCHFAILED); - } - - # - # Only the creator until we are using credentials here. - # - my $lease_owner = $lease->GetAttribute("creator_urn"); - return GeniResponse->Create(GENIRESPONSE_FORBIDDEN) - if (! (defined($lease_owner) && $lease_owner eq $user->urn())); + if (defined($lease)) { + # + # Only the creator until we are using credentials here. + # + my $lease_owner = $lease->GetAttribute("creator_urn"); + return GeniResponse->Create(GENIRESPONSE_FORBIDDEN) + if (! (defined($lease_owner) && $lease_owner eq $user->urn())); - my $blob = {}; - $blob->{'state'} = $lease->state(); - $blob->{'type'} = $lease->type(); - $blob->{"busy"} = $lease->locked() ? 1 : 0; - $blob->{'created'} = emutil::TBDateStringGMT($lease->inception()); - $blob->{'expires'} = emutil::TBDateStringGMT($lease->lease_end()); - $blob->{'lastused'} = emutil::TBDateStringGMT($lease->last_used()); + $blob->{'state'} = $lease->state(); + $blob->{'type'} = $lease->type(); + $blob->{"busy"} = $lease->locked() ? 1 : 0; + $blob->{'created'} = emutil::TBDateStringGMT($lease->inception()); + $blob->{'expires'} = emutil::TBDateStringGMT($lease->lease_end()); + $blob->{'lastused'} = emutil::TBDateStringGMT($lease->last_used()); - my $bstore = Blockstore->LookupByLease($lease->idx()); - if (defined($bstore)) { - $blob->{'size'} = $bstore->total_size(); + my $bstore = Blockstore->LookupByLease($lease->idx()); + if (defined($bstore)) { + $blob->{'size'} = $bstore->total_size(); + } + } + else { + my $image = Image->Lookup($group->pid(), $dataset); + if (!defined($image)) { + return GeniResponse->Create(GENIRESPONSE_SEARCHFAILED); + } + return GeniResponse->Create(GENIRESPONSE_FORBIDDEN) + if (!$image->isdataset() || + !defined($image->creator_urn()) || + $image->creator_urn() ne $user->urn()); + + $blob->{'state'} = ($image->size() ? "valid" : "new"); + $blob->{'type'} = "imdataset"; + $blob->{"busy"} = $image->locked() ? 1 : 0; + $blob->{'size'} = 0; + $blob->{'created'} = emutil::TBDateStringGMT($image->created()); + $blob->{'updated'} = emutil::TBDateStringGMT($image->updated()); + $blob->{'expires'} = ""; + $blob->{'lastused'} = ""; + if ($image->size()) { + my $kbytes = int(($image->lba_high() - + $image->lba_low() + 1) / + (1024 / $image->lba_size())); + $blob->{'size'} = + Blockstore::ConvertToMebi("$kbytes" . "KB"); + } } return GeniResponse->Create(GENIRESPONSE_SUCCESS, $blob); } diff --git a/tbsetup/ns2ir/blockstore.tcl b/tbsetup/ns2ir/blockstore.tcl index 9dbf9cec7dc7aabc2e38ba5d4c3976277bdb138a..00d2803a55249cb432e6501db5f80fc0104a1514 100644 --- a/tbsetup/ns2ir/blockstore.tcl +++ b/tbsetup/ns2ir/blockstore.tcl @@ -1,6 +1,6 @@ # -*- tcl -*- # -# Copyright (c) 2012-2014 University of Utah and the Flux Group. +# Copyright (c) 2012-2015 University of Utah and the Flux Group. # # {{{EMULAB-LICENSE # @@ -171,6 +171,14 @@ Blockstore instproc set-mount-point {newmount} { return } +Blockstore instproc load-dataset {dataset} { + $self instvar attributes + $self instvar node + + set attributes(dataset) $dataset + return +} + Blockstore instproc set-size {newsize} { $self instvar node $self instvar size diff --git a/utils/GNUmakefile.in b/utils/GNUmakefile.in index f0033d5a81321b3f3924c7406da7ae26069b15cb..e637017321a417ce35fd63fa1011844da86447d2 100644 --- a/utils/GNUmakefile.in +++ b/utils/GNUmakefile.in @@ -58,7 +58,7 @@ SBIN_SCRIPTS = vlandiff vlansync withadminprivs export_tables cvsupd.pl \ WEB_SBIN_SCRIPTS= webnewnode webdeletenode webspewconlog webarchive_list \ webwanodecheckin webspewimage webdumpdescriptor \ - webdelete_image websitecheckin webclone_image + webdelete_image websitecheckin webclone_image webgrantimage WEB_BIN_SCRIPTS = webcreate_image websetdest weblinkmon_ctl webspewevents \ webdelay_config webcreatedataset webdeletelease \ webapprovelease webextendlease webmodlease webgrantlease diff --git a/utils/clone_image.in b/utils/clone_image.in index 5c3db0fbb752d3d6c52882d0630c91f4ca654d6b..066a9378e2b324e54362cb56d828fcf3a2d46cf8 100644 --- a/utils/clone_image.in +++ b/utils/clone_image.in @@ -57,7 +57,7 @@ sub usage() " -w Wait for image to be created\n"); exit(-1); } -my $optlist = "densg:wFr:"; +my $optlist = "densg:wFr:b:"; my $debug = 0; my $wholedisk = 0; my $impotent = 0; @@ -67,6 +67,7 @@ my $waitmode = 0; my $nodelta = 0; # To pass to create_image. my $global = 0; my $shared = 0; +my $bsname; # # Configure variables @@ -132,6 +133,16 @@ if (defined($options{"w"})) { if (defined($options{"F"})) { $nodelta = 1; } +if (defined($options{"b"})) { + $bsname = $options{"b"}; + + if ($bsname =~ /^([-\w]+)$/) { + $bsname = $1; + } + else { + fatal("Bad data in $bsname."); + } +} if (defined($options{"g"})) { $global = $options{"g"}; } @@ -213,10 +224,13 @@ if (defined($image)) { my $needdelete = 0; # - # Only EZ images via this interface. + # Only EZ images or Datasets via this interface. # - if (!$image->ezid()) { - fatal("Cannot clone a non-ez image"); + if (!($image->ezid())) { + fatal("Only EZ images on this path. Dataset images not yet"); + } + if ($image->isdataset() && !defined($bsname)) { + fatal("You must provide a blockstore name (-b) for this image!"); } # @@ -321,6 +335,19 @@ if (defined($image)) { } } } + # + # If a wholedisk image was requested, we need to change the + # descriptor, since it might not have started out as a whole disk + # image, but then the user brought in a new partition on the disk + # and wants it made part of the image. When provenance is on, we + # will change just the new version, so the old versions of the + # image will continue to work properly. But without versioning, + # a failure will leave the image descriptor as a whole disk image, + # but the image file will not be, and that will break. + # + if ($wholedisk) { + ; + } $image->Unlock(); if ($nosnapshot) { diff --git a/utils/create_image.in b/utils/create_image.in index 12f123ad7b459912124967df0c476d298cfba5f9..106893e0c46ff27f9a71ed4be2d8d67a23963f31 100644 --- a/utils/create_image.in +++ b/utils/create_image.in @@ -1,6 +1,6 @@ -#!/usr/bin/perl -wT +#!/usr/bin/perl -w # -# Copyright (c) 2000-2014 University of Utah and the Flux Group. +# Copyright (c) 2000-2015 University of Utah and the Flux Group. # # {{{EMULAB-LICENSE # @@ -112,7 +112,7 @@ sub usage() " - nodeid to create the image from\n"); exit(-1); } -my $optlist = "p:wsNdfeDSMA:F"; +my $optlist = "p:wsNdfeDSMA:Fb:"; my $waitmode = 0; my $usessh = 0; my $usenfs = 0; @@ -123,6 +123,7 @@ my $nodelta = 0; my $nomfs = 0; my $signature= 0; my $deltapct = 0; +my $bsname; my $webtask; # @@ -251,6 +252,17 @@ if (defined($options{"f"})) { $foreground = 1; $waitmode = 0; } +if (defined($options{"b"})) { + $bsname = $options{"b"}; + + if ($bsname =~ /^([-\w]+)$/) { + $bsname = $1; + } + else { + die("*** $0:\n". + " Bad data in $bsname.\n"); + } +} if (defined($options{"D"})) { if (!$WITHDELTAS) { print STDERR "Delta image support not enabled\n"; @@ -376,6 +388,12 @@ if ($mereuser && " You do not have permission to use imageid $imageid!\n"); } +# Must have a blockstore name if the image is marked as a dataset. +if ($image->isdataset() && !defined($bsname)) { + die("*** $0:\n". + " You must provide a blockstore name (-b) for this image!\n"); +} + # # Before doing anything else, check for overquota ... lets not waste # our time. Make sure user sees the error by exiting with 1. @@ -544,7 +562,7 @@ else { if ($doprovenance) { $srcimage = $image->Parent(); } - if (!defined($srcimage)) { + if (!defined($srcimage) && !$image->isdataset()) { (undef, $srcimage) = $node->RunningOsImage(); } if (defined($srcimage)) { @@ -612,9 +630,10 @@ else { # Make sure that the directory exists and is writeable for the user. # We test this by creating the file. Its going to get wiped anyway. # -my $filename = $image->path(); -my $isglobal = $image->global(); -my $usepath = 0; +my $filename = $image->path(); +my $isglobal = $image->global(); +my $usepath = 0; +my $isdataset = $image->isdataset(); # # Redirect pathname for global images. See equiv code in clone_image. @@ -707,7 +726,7 @@ if ($image->Lock()) { } $needunlock = 1; -if ($doprovenance && $image->ready()) { +if ($doprovenance && !$isdataset && $image->ready()) { $image->Unlock(); die("*** $0:\n". " $image ready flag is set, this is inconsistent!\n"); @@ -777,7 +796,7 @@ close(FILE) or chmod(0664, $tmp) or fatal("Could not make $tmp group writable: $!"); -if (! ($isvirtnode || $isec2node)) { +if (! ($isvirtnode || $isec2node || $isdataset)) { # # Get the disktype for this node # @@ -811,7 +830,7 @@ if ($isec2node) { # Virtnode images use a version of the old create-image script on the vhost # XXX needs to be fixed. # -elsif ($isvirtnode && (!$doprovenance || !$isxenhost)) { +elsif ($isvirtnode && !$isdataset && (!$doprovenance || !$isxenhost)) { $command = "$ocreateimage"; if ($usefup) { my $id; @@ -857,12 +876,18 @@ elsif (!$doprovenance) { $command .= " -S $BOSSIP -F $id"; } - $startslice = $image->loadpart(); - $loadlength = $image->loadlength(); - if ($startslice || $loadlength == 1) { - $command .= " -s $startslice"; + if ($isdataset) { + # This is not backward compatable, but none of the BS code is. + $command .= " -b $bsname"; + } + else { + $startslice = $image->loadpart(); + $loadlength = $image->loadlength(); + if ($startslice || $loadlength == 1) { + $command .= " -s $startslice"; + } + $command .= " $device"; } - $command .= " $device"; if ($usefup || $usessh) { $command .= " -"; @@ -874,13 +899,13 @@ elsif (!$doprovenance) { # Otherwise, use the new script with different argument syntax. # else { - $command = ($isxenhost ? "$createxenimage" : "$createimage"); + $command = ($isxenhost && !$isdataset ? "$createxenimage" : "$createimage"); # # XEN Hosts cannot do provenance/delta without client side update. # We need to provide these arguments for backwards compat though. # - if ($isxenhost) { + if ($isxenhost && !$isdataset) { $command .= " $node_id"; if ($usefup || $usessh) { $command .= " -"; @@ -931,15 +956,21 @@ else { $zipperopts .= "P=$deltapct"; } - $startslice = $image->loadpart(); - $loadlength = $image->loadlength(); - - if ($startslice || $loadlength == 1) { - $command .= " PART=$startslice"; + if ($isdataset) { + # This is not backward compatable, but none of the BS code is. + $command .= " BSNAME=$bsname"; } - if (!$isxenhost) { - # The XEN host will figure out what device on its own. - $command .= " DISK=$device"; + else { + $startslice = $image->loadpart(); + $loadlength = $image->loadlength(); + + if ($startslice || $loadlength == 1) { + $command .= " PART=$startslice"; + } + if (!$isxenhost) { + # The XEN host will figure out what device on its own. + $command .= " DISK=$device"; + } } if ($zipperopts) { @@ -1067,24 +1098,26 @@ if ($isec2node) { # Virtnodes. # Run on vnode host. # -if ($isvirtnode) { - # - # XEN creates a problem; the physical host cannot actually - # execute a command inside the guest, but we need to run - # reboot_prepare and reboot it. FreeBSD creates an additional - # problem in that shutdown has to run to invoke prepare; reboot - # does not run it, and a shutdown from outside the VM has the - # sae effect; prepare does not run. What a pain. - # +if ($isvirtnode || $isdataset) { my $SAVEUID = $UID; $EUID = $UID = 0; - my $cmd = "$TB/bin/sshtb -n -o ConnectTimeout=10 ". - "-host $node_id $reboot_prep"; - print STDERR "About to: '$cmd'\n" if ($debug); - system($cmd); - fatal("'$cmd' failed") - if ($?); + if (!$isdataset) { + # + # XEN creates a problem; the physical host cannot actually + # execute a command inside the guest, but we need to run + # reboot_prepare and reboot it. FreeBSD creates an additional + # problem in that shutdown has to run to invoke prepare; reboot + # does not run it, and a shutdown from outside the VM has the + # sae effect; prepare does not run. What a pain. + # + my $cmd = "$TB/bin/sshtb -n -o ConnectTimeout=10 ". + "-host $node_id $reboot_prep"; + print STDERR "About to: '$cmd'\n" if ($debug); + system($cmd); + fatal("'$cmd' failed") + if ($?); + } # Mark webtask $webtask->status("imaging") @@ -1219,10 +1252,11 @@ done: # Grab boot log now. Node will reboot and possibly erase it. We should # probably come up with a better way to handle this. my $bootlog; -if ($node->GetBootLog(\$bootlog)) { - $bootlog = undef; +if (!$isdataset) { + if ($node->GetBootLog(\$bootlog)) { + $bootlog = undef; + } } - if (! cleanup()) { fatal("Problem encountered while cleaning up!\n"); } @@ -1375,7 +1409,7 @@ sub cleanup () unlink($hacksigfile); } - if ($isvirtnode || $isec2node) { + if ($isvirtnode || $isec2node || $isdataset) { # # Nothing to do; the clientside script rebooted the container. # diff --git a/utils/createdataset.in b/utils/createdataset.in index 1818d69da90f0379afcafc30ea20caa7a2f3c1f0..683af1fda35e4e18148fb428af7b52cac17a5300 100644 --- a/utils/createdataset.in +++ b/utils/createdataset.in @@ -25,6 +25,8 @@ use strict; use English; use Getopt::Std; use Date::Parse; +use File::Temp qw(tempfile); +use CGI; # # Create a new dataset. @@ -46,7 +48,7 @@ sub usage() print STDERR " name Name (in the form /)\n"; exit(-1); } -my $optlist = "dhUo:s:t:e:a:f:bCR:W:"; +my $optlist = "dhUo:s:t:e:a:f:bCR:W:I:"; my $debug = 0; my $background = 0; my $pid; @@ -70,11 +72,13 @@ my $quota; # Valid dataset types my %descrip = ( "stdataset" => "short-term dataset", - "ltdataset" => "long-term dataset" + "ltdataset" => "long-term dataset", + "imdataset" => "Image backed dataset", ); # Protos sub fatal($); +sub HandleIMDataset(); # # Configure variables @@ -82,6 +86,10 @@ sub fatal($); my $TB = "@prefix@"; my $TBOPS = "@TBOPSEMAIL@"; my $TBBASE = "@TBBASE@"; +my $TBGROUP_DIR = "@GROUPSROOT_DIR@"; +my $TBPROJ_DIR = "@PROJROOT_DIR@"; +my $NEWIMAGEEZ = "$TB/bin/newimageid_ez"; +my $CREATEIMAGE = "$TB/bin/create_image"; # # Testbed Support libraries @@ -89,12 +97,15 @@ my $TBBASE = "@TBBASE@"; use lib "@prefix@/lib"; use EmulabConstants; use libtestbed; +use emutil; use libdb; use Quota; use Lease; use Project; use Group; use User; +use Image; +use Node; # # Turn off line buffering on output @@ -186,8 +197,12 @@ if (defined($options{"W"})) { } } -if (!$size || @ARGV != 1) { - print STDERR "Must specify size and name\n"; +if (@ARGV != 1) { + print STDERR "Must specify dataset name\n"; + usage(); +} +if ($dstype ne "imdataset" && !$size) { + print STDERR "Must specify dataset size\n"; usage(); } @@ -255,6 +270,13 @@ if (!exists($descrip{$dstype})) { exit(1); } +# +# IM dataset handled differently +# +if ($dstype eq "imdataset") { + exit(HandleIMDataset()); +} + # # Fetch default values for the lease type. We use: # @@ -498,6 +520,112 @@ if (!$approveme) { exit(0); +# +# Image backed datasets. Basically create an image and optionally take +# a snapshot from the BSname. +# +sub HandleIMDataset() +{ + my $global = (defined($read_access) && $read_access eq "global" ? 1 : 0); + my $path = ($pid eq $gid || $global ? + "$TBPROJ_DIR/$pid/images/${lname}.ndz" : + "$TBGROUP_DIR/$pid/$gid/images/${lname}.ndz"); + + # + # See if we are going to take a snapshot right away, and verify the + # node and bsname. + # + my ($nodeid,$bsname); + if (defined($options{"I"})) { + ($nodeid,$bsname) = split(",", $options{"I"}); + if (!defined($bsname)) { + fatal("Improper -I option"); + } + my $node = Node->Lookup($nodeid); + if (!defined($node)) { + fatal("No such node: $nodeid"); + } + my $experiment = $node->Reservation(); + if (!defined($experiment) || + $experiment->pid() ne $pid) { + fatal("Node not reserved to an experiment in the same ". + "project as the dataset"); + } + if (!$node->AccessCheck($user, TB_NODEACCESS_LOADIMAGE())) { + fatal("Not enough permission to create dataset from $nodeid"); + } + my $blockstore = $experiment->LookupBlockstore($bsname); + if (!defined($blockstore)) { + fatal("No such blockstore: $bsname"); + } + if ($node->vname() ne $blockstore->fixed()) { + fatal("Blockstore $bsname is not on node $nodeid"); + } + } + + my %xmlfields = ( + "pid" => $pid, + "gid" => $gid, + "imagename" => $lname, + "description" => "This is a dataset, DO NOT DELETE!", + "isdataset" => 1, + "path" => $path, + "global" => $global, + ); + # + # Create the XML file to pass to newimageid_ez. + # + my ($fh, $filename) = tempfile(UNLINK => 1); + fatal("Could not create temporary file") + if (!defined($fh)); + + print $fh "\n"; + foreach my $key (keys(%xmlfields)) { + my $value = $xmlfields{$key}; + + print $fh ""; + print $fh "" . CGI::escapeHTML($value) . ""; + print $fh "\n"; + } + print $fh "\n"; + close($fh); + + my $output = emutil::ExecQuiet("$NEWIMAGEEZ -s -v $filename"); + if ($?) { + print STDERR $output; + my $foo = `cat $filename`; + print STDERR $foo; + fatal("Failed to verify image descriptor from $filename"); + } + $output = emutil::ExecQuiet("$NEWIMAGEEZ -s $filename"); + if ($?) { + print STDERR $output; + my $foo = `cat $filename`; + print STDERR $foo; + fatal("Failed to create image descriptor"); + } + my $image = Image->Lookup($pid, $lname); + if (!defined($image)) { + fatal("Cannot lookup newly created image for $lname"); + } + if (defined($write_access)) { + if ($write_access eq "creator") { + $image->GrantAccess($project, 0); + } + } + if (defined($nodeid)) { + my $output = emutil::ExecQuiet("$CREATEIMAGE ". + "-b $bsname -p $pid $lname $nodeid"); + if ($?) { + $image->Delete(1); # Delete with purge. + print STDERR $output; + fatal("Failed to create image"); + } + print "Image is being created. This can take 15-30 minutes.\n"; + } + return 0; +} + sub fatal($) { my ($mesg) = $_[0]; diff --git a/utils/dumpdescriptor.in b/utils/dumpdescriptor.in index f780ec4db4a91bb11617dc6611a3daa49ffc6926..ec85e98a700fe52dbfbeb4faa1c8c02f9267cb0d 100644 --- a/utils/dumpdescriptor.in +++ b/utils/dumpdescriptor.in @@ -1,6 +1,6 @@ #!/usr/bin/perl -w # -# Copyright (c) 2010-2014 University of Utah and the Flux Group. +# Copyright (c) 2010-2015 University of Utah and the Flux Group. # # {{{EMULAB-LICENSE # @@ -140,6 +140,10 @@ sub DumpImage($) $xmlfields{"hash"} = $image->hash() if ($export && defined($image->hash()) && $image->hash() ne ""); $xmlfields{"mbr_version"} = $image->mbr_version(); + if (0) { + # Not yet, backwards compatability problem. + $xmlfields{"isdataset"} = $image->isdataset(); + } if ($export) { my $imageid = $image->imageid(); diff --git a/utils/grantimage.in b/utils/grantimage.in index 9af57da1cea1eedc4d6d376e8c9f4db78c6e3f80..ab8bd0e365b5c8ef64a050c766f0211566bf90f7 100644 --- a/utils/grantimage.in +++ b/utils/grantimage.in @@ -1,6 +1,6 @@ #!/usr/bin/perl -w # -# Copyright (c) 2003-2012 University of Utah and the Flux Group. +# Copyright (c) 2003-2015 University of Utah and the Flux Group. # # {{{EMULAB-LICENSE # @@ -41,9 +41,12 @@ sub usage() print STDERR " -g Grant access to a specific group (project)\n"; print STDERR " -a Grant global read-only access\n"; print STDERR " -x Also grant access to protogeni users\n"; + print STDERR "Alternate form:\n"; + print STDERR " -R acl Grant project|global read-only access\n"; + print STDERR " -W acl Grant creator|project write access\n"; exit(-1); } -my $optlist = "hg:dnru:wlax"; +my $optlist = "hg:dnru:wlaxR:W:"; my $impotent = 0; my $debug = 0; my $revoke = 0; @@ -54,6 +57,8 @@ my $protogeni= 0; my $gid; my $uid; my $target; +my $read_access; +my $write_access; # Protos sub fatal($); @@ -133,10 +138,22 @@ if (defined($options{g})) { if (defined($options{u})) { $uid = $options{u}; } +if (defined($options{"R"})) { + $read_access = $options{"R"}; + usage() + if ($read_access !~ /^(global|project)$/); +} +if (defined($options{"W"})) { + $write_access = $options{"W"}; + usage() + if ($write_access !~ /^(creator|project)$/); +} + usage() if (@ARGV != 1); usage() - if (! ($listonly || $global || defined($gid) || defined($uid))); + if (! ($listonly || $global || defined($gid) || defined($uid) || + defined($read_access) || defined($write_access))); my $imageid = $ARGV[0]; @@ -188,7 +205,10 @@ if ($listonly) { } exit(0); } -elsif ($global) { +elsif ($global || defined($read_access)) { + if (defined($read_access) && $read_access eq "project") { + $revoke = 1; + } my $val = ($revoke ? 0 : 1); $image->Update({"global" => $val}) == 0 or fatal("Could not update global flag"); @@ -224,6 +244,20 @@ elsif ($revoke) { } } } +elsif (defined($write_access)) { + my $project = $image->GetProject(); + + if (defined($write_access)) { + if ($write_access eq "creator") { + $image->RevokeAccess($project); + $image->GrantAccess($project, 0); + } + else { + $image->RevokeAccess($project); + $image->GrantAccess($project, 1); + } + } +} else { $image->GrantAccess($target, $writable) == 0 or fatal("Could not grant permission for $target"); diff --git a/www/aptui/create-dataset.php b/www/aptui/create-dataset.php index 16aa801309e454e30c9326a7e52ad7f47fa451db..5639a55ab155ecf69fb51047adfdbd958c48821d 100644 --- a/www/aptui/create-dataset.php +++ b/www/aptui/create-dataset.php @@ -25,6 +25,7 @@ chdir(".."); include("defs.php3"); chdir("apt"); include("quickvm_sup.php"); +include("instance_defs.php"); # Must be after quickvm_sup.php since it changes the auth domain. $page_title = "Create Dataset"; @@ -75,6 +76,17 @@ function SPITFORM($formfields, $errors) echo "\n"; + + if (!$embedded) { + $am_array = Instance::DefaultAggregateList(); + $amlist = array(); + while (list($am) = each($am_array)) { + $amlist[] = $am; + } + echo "\n"; + } # FS types. $fstypelist = array(); @@ -118,6 +130,7 @@ if (! isset($create)) { $defaults["dataset_fstype"] = 'ext3'; $defaults["dataset_read"] = 'project'; $defaults["dataset_modify"] = 'creator'; + $defaults["dataset_am"] = ''; # Default project. if (count($projlist) == 1) { $defaults["dataset_pid"] = $projlist[0]; diff --git a/www/aptui/dataset.ajax b/www/aptui/dataset.ajax index 02e34fed980ac1196552df9e6f40a4859dbdd2a3..00a8f528556d99aed7779b6002383d643bc245da 100644 --- a/www/aptui/dataset.ajax +++ b/www/aptui/dataset.ajax @@ -23,9 +23,12 @@ # chdir(".."); include_once("lease_defs.php"); +include_once("imageid_defs.php"); include_once("blockstore_defs.php"); +include_once("node_defs.php"); chdir("apt"); include_once("dataset_defs.php"); +include_once("instance_defs.php"); # # Server side of creating a dataset. @@ -56,8 +59,7 @@ function Do_CreateDataSet() $command = "webmanage_dataset create "; } $required = array("dataset_pid", "dataset_name", "dataset_type", - "dataset_size", "dataset_fstype", "dataset_read", - "dataset_modify"); + "dataset_fstype", "dataset_read", "dataset_modify"); foreach ($required as $field) { if (!isset($formfields[$field]) || $formfields[$field] == "") { @@ -86,7 +88,8 @@ function Do_CreateDataSet() $errors["dataset_name"] = $DBFieldErrstr; } if (! ($formfields["dataset_type"] == "stdataset" || - $formfields["dataset_type"] == "ltdataset")) { + $formfields["dataset_type"] == "ltdataset" || + $formfields["dataset_type"] == "imdataset")) { $errors["dataset_type"] = "Illegal value"; } else { @@ -105,19 +108,88 @@ function Do_CreateDataSet() escapeshellarg($formfields["dataset_expires"]); } } - if (! preg_match('/^(none|ext2|ext3|ext4|ufs|ufs2)$/', - $formfields["dataset_fstype"])) { - $errors["dataset_fstype"] = "Illegal value"; - } - elseif ($formfields["dataset_fstype"] != "none") { - $command .= " -f " . $formfields["dataset_fstype"]; - } - if (! preg_match('/^\d+(MB|GB|TB)$/i', - $formfields["dataset_size"])) { - $errors["dataset_size"] = "Illegal value"; + if ($formfields["dataset_type"] != "imdataset") { + if (! preg_match('/^(none|ext2|ext3|ext4|ufs|ufs2)$/', + $formfields["dataset_fstype"])) { + $errors["dataset_fstype"] = "Illegal value"; + } + elseif ($formfields["dataset_fstype"] != "none") { + $command .= " -f " . $formfields["dataset_fstype"]; + } + if (! (isset($formfields["dataset_size"]) && + $formfields["dataset_size"] != "")) { + $errors["dataset_size"] = "Missing field"; + } + elseif (! preg_match('/^\d+(MB|GB|TB)$/i', + $formfields["dataset_size"])) { + $errors["dataset_size"] = "Illegal value"; + } + else { + $command .= " -s " . $formfields["dataset_size"]; + } } else { - $command .= " -s " . $formfields["dataset_size"]; + # + # Node and BS name are optional, but the aggregate is not. + # + if (! (isset($formfields["dataset_am"]) && + $formfields["dataset_am"] != "")) { + $errors["dataset_am"] = "Must select an aggregate"; + } + $am_array = Instance::DefaultAggregateList(); + if (array_key_exists($formfields["dataset_am"], $am_array)) { + $command .= " -a " . $am_array[$formfields["dataset_am"]]; + } + else { + $errors["dataset_am"] = "Invalid aggregate"; + } + if ((isset($formfields["dataset_node"]) && + $formfields["dataset_node"] != "") || + (isset($formfields["dataset_bsname"]) && + $formfields["dataset_bsname"] != "")) { + if (! (isset($formfields["dataset_node"]) && + $formfields["dataset_node"] != "")) { + $errors["dataset_node"] = "Must also provide a node"; + } + elseif (! (isset($formfields["dataset_bsname"]) && + $formfields["dataset_bsname"] != "")) { + $errors["dataset_bsname"] = "Must also provide bsname"; + } + else { + $nodeid = $formfields["dataset_node"]; + $bsname = $formfields["dataset_bsname"]; + + if (!TBvalid_node_id($nodeid)) { + $errors["dataset_node"] = TBFieldErrorString(); + } + elseif (!TBvalid_vnode_id($bsname)) { + $errors["dataset_bsname"] = TBFieldErrorString(); + } + elseif ($node = Node::Lookup($nodeid)) { + $reservation = $node->Reservation(); + if (!$reservation || + !$reservation->pid() == $pid) { + $errors["dataset_node"] = + "Node not reserved to an experiment in the same ". + "project as your dataset"; + } + else { + $blockstore = $reservation->LookupBlockstore($bsname); + if (!$blockstore) { + $errors["dataset_bsname"] = "No such blockstore"; + } + elseif ($blockstore['fixed'] != $node->VirtName()) { + $errors["dataset_bsname"] = + "Blockstore does not exist on node $nodeid"; + } + } + $command .= " -I ${nodeid},${bsname}"; + } + else { + $errors["dataset_node"] = "No such node"; + } + } + } } # Permission bits. if ($formfields["dataset_read"] != "global" && @@ -151,7 +223,14 @@ function Do_CreateDataSet() return; } if ($embedded) { - $dataset = Lease::LookupByName($project, $formfields["dataset_name"]); + if ($formfields["dataset_type"] == "imdataset") { + $dataset = ImageDataset::LookupByName($project, + $formfields["dataset_name"]); + } + else { + $dataset = Lease::LookupByName($project, + $formfields["dataset_name"]); + } } else { $dataset = Dataset::LookupByName($project, $formfields["dataset_name"]); @@ -173,7 +252,6 @@ function Do_ModifyDataSet() 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(); @@ -199,8 +277,10 @@ function Do_ModifyDataSet() # $dataset = Lease::Lookup($dataset_uuid); if (!$dataset) { - $dataset = Dataset::Lookup($dataset_uuid); - $islease = 0; + $dataset = ImageDataset::Lookup($dataset_uuid); + if (!$dataset) { + $dataset = Dataset::Lookup($dataset_uuid); + } } if (!$dataset) { SPITAJAX_ERROR(1, "Unknown dataset"); @@ -225,6 +305,56 @@ function Do_ModifyDataSet() $errors["dataset_write"] = "Illegal value"; } } + $nodeid = null; + if ($dataset->type() == "imdataset") { + # + # Node and BS name are optional. + # + if ((isset($formfields["dataset_node"]) && + $formfields["dataset_node"] != "") || + (isset($formfields["dataset_bsname"]) && + $formfields["dataset_bsname"] != "")) { + if (! (isset($formfields["dataset_node"]) && + $formfields["dataset_node"] != "")) { + $errors["dataset_node"] = "Must also provide a node"; + } + elseif (! (isset($formfields["dataset_bsname"]) && + $formfields["dataset_bsname"] != "")) { + $errors["dataset_bsname"] = "Must also provide bsname"; + } + else { + $nodeid = $formfields["dataset_node"]; + $bsname = $formfields["dataset_bsname"]; + + if (!TBvalid_node_id($nodeid)) { + $errors["dataset_node"] = TBFieldErrorString(); + } + elseif (!TBvalid_vnode_id($bsname)) { + $errors["dataset_bsname"] = TBFieldErrorString(); + } + elseif ($node = Node::Lookup($nodeid)) { + $reservation = $node->Reservation(); + if (!$reservation || + $reservation->pid() != $dataset->pid()) { + $errors["dataset_node"] = + "Node not reserved to an experiment in the same ". + "project as your dataset"; + } + else { + $blockstore = $reservation->LookupBlockstore($bsname); + if (!$blockstore || + $blockstore['fixed'] != $node->VirtName()) { + $errors["dataset_bsname"] = + "Blockstore does not exist on node $nodeid"; + } + } + } + else { + $errors["dataset_node"] = "No such node"; + } + } + } + } if (count($errors)) { SPITAJAX_ERROR(2, $errors); return; @@ -234,7 +364,7 @@ function Do_ModifyDataSet() return; } $leaseid = $dataset->pid() . "/" . $dataset->id(); - $command = ($islease ? "webgrantlease " : "webmanage_dataset modify "); + $command = $dataset->grantCommand(); if (isset($formfields["dataset_read"])) { $perm = $formfields["dataset_read"]; @@ -256,6 +386,21 @@ function Do_ModifyDataSet() return; } } + if ($dataset->type() == "imdataset" && $nodeid) { + $safe_nodeid = escapeshellarg($nodeid); + $safe_bsname = escapeshellarg($bsname); + $dname = $dataset->id(); + $pid = $dataset->pid(); + + $retval = SUEXEC($this_uid, $dataset->pid(), + "webcreate_image -b $safe_bsname -p $pid ". + "$dname $safe_nodeid", + SUEXEC_ACTION_CONTINUE); + if ($retval) { + SPITAJAX_ERROR(1, $suexec_output); + return; + } + } SPITAJAX_RESPONSE("show-dataset.php?uuid=$dataset_uuid"); } @@ -267,7 +412,6 @@ function Do_DeleteDataset() $this_idx = $this_user->uid_idx(); $this_uid = $this_user->uid(); - $islease = 1; if (!isset($ajax_args["uuid"])) { SPITAJAX_ERROR(1, "Missing uuid"); @@ -278,8 +422,11 @@ function Do_DeleteDataset() # $dataset = Lease::Lookup($ajax_args["uuid"]); if (!$dataset) { - $dataset = Dataset::Lookup($ajax_args["uuid"]); - $islease = 0; + $dataset = ImageDataset::Lookup($ajax_args["uuid"]); + if (!$dataset) { + $dataset = Dataset::Lookup($ajax_args["uuid"]); + $islease = 0; + } } if (!$dataset) { SPITAJAX_ERROR(1, "Unknown dataset"); @@ -289,14 +436,7 @@ function Do_DeleteDataset() SPITAJAX_ERROR(1, "Not enough permission"); return; } - if ($islease) { - $command = "webdeletelease -f -b "; - } - else { - $command = "webmanage_dataset delete "; - } - $command .= $dataset->pid() . "/" . $dataset->id(); - + $command = $dataset->deleteCommand(); # # Invoke backend. # diff --git a/www/aptui/dataset_defs.php b/www/aptui/dataset_defs.php index 31732ca67c0f1b1587a0a5de06f91cae7752192b..a6de6258ce508bae002f91d643c79b69f98fe505 100644 --- a/www/aptui/dataset_defs.php +++ b/www/aptui/dataset_defs.php @@ -114,6 +114,20 @@ class Dataset # This is incomplete. # function AccessCheck($user, $access_type) { + global $LEASE_ACCESS_READINFO; + global $LEASE_ACCESS_MODIFYINFO; + global $LEASE_ACCESS_READ; + global $LEASE_ACCESS_MODIFY; + global $LEASE_ACCESS_DESTROY; + global $LEASE_ACCESS_MIN; + global $LEASE_ACCESS_MAX; + global $TBDB_TRUST_USER; + global $TBDB_TRUST_GROUPROOT; + global $TBDB_TRUST_LOCALROOT; + + $mintrust = $LEASE_ACCESS_READINFO; + $read_access = $this->read_access(); + $write_access = $this->write_access(); # # Admins do whatever they want. # @@ -123,6 +137,36 @@ class Dataset if ($this->creator_uid() == $user->uid()) { return 1; } + if ($read_access == "global") { + if ($access_type == $LEASE_ACCESS_READINFO) { + return 1; + } + } + if ($write_access == "creator") { + if ($access_type > $LEASE_ACCESS_READINFO) { + return 0; + } + } + $pid = $this->pid(); + $gid = $this->gid(); + $uid = $user->uid(); + $uid_idx= $user->uid_idx(); + $pid_idx= $user->uid_idx(); + $gid_idx= $user->uid_idx(); + + # + # Otherwise must have proper trust in the project. + # + if ($access_type == $LEASE_ACCESS_READINFO) { + $mintrust = $TBDB_TRUST_USER; + } + else { + $mintrust = $TBDB_TRUST_LOCALROOT; + } + if (TBMinTrust(TBGrpTrust($uid, $pid, $gid), $mintrust) || + TBMinTrust(TBGrpTrust($uid, $pid, $pid), $TBDB_TRUST_GROUPROOT)) { + return 1; + } return 0; } @@ -132,5 +176,13 @@ class Dataset function URN() { return $this->remote_urn(); } + + function deleteCommand() { + return "webmanage_dataset delete " . $this->pid() . "/" . $this->id(); + } + function grantCommand() { + return "webmanage_dataset modify "; + } } + ?> diff --git a/www/aptui/edit-dataset.php b/www/aptui/edit-dataset.php index 98613ba1b43dd235ec49ce63d641ffeb7f602abb..53cbdc8fedc6b94271dd8694d9ae89fcef7762d8 100644 --- a/www/aptui/edit-dataset.php +++ b/www/aptui/edit-dataset.php @@ -24,6 +24,7 @@ chdir(".."); include("defs.php3"); include("lease_defs.php"); +include("imageid_defs.php"); chdir("apt"); include("quickvm_sup.php"); include("dataset_defs.php"); @@ -47,8 +48,13 @@ $optargs = OptionalPageArguments("create", PAGEARG_STRING, # # Either a local lease or a remote dataset. # -$dataset = Lease::Lookup($uuid); -if (!$dataset) { +if ($embedded) { + $dataset = Lease::Lookup($uuid); + if (!$dataset) { + $dataset = ImageDataset::Lookup($uuid); + } +} +else { $dataset = Dataset::Lookup($uuid); } if (!$dataset) { diff --git a/www/aptui/js/create-dataset.js b/www/aptui/js/create-dataset.js index 696e10d515dfd6d38b35365df421e4816969d77d..5e977ea6cbcc498f85d61df269c8cc980421494c 100644 --- a/www/aptui/js/create-dataset.js +++ b/www/aptui/js/create-dataset.js @@ -1,8 +1,9 @@ require(window.APT_OPTIONS.configObject, ['underscore', 'js/quickvm_sup', 'moment', 'js/lib/text!template/create-dataset.html', + 'js/lib/text!template/dataset-help.html', 'jquery-ui'], -function (_, sup, moment, mainString) +function (_, sup, moment, mainString, helpString) { 'use strict'; @@ -10,6 +11,7 @@ function (_, sup, moment, mainString) var fields = null; var fstypes = null; var projlist = null; + var amlist = null; var editing = false; var isadmin = false; var embedded = 0; @@ -27,6 +29,9 @@ function (_, sup, moment, mainString) projlist = JSON.parse(_.unescape($('#projects-json')[0].textContent)); } + if (! (editing || embedded)) { + amlist = JSON.parse(_.unescape($('#amlist-json')[0].textContent)); + } GeneratePageBody(fields, null); } @@ -41,6 +46,7 @@ function (_, sup, moment, mainString) formfields: formfields, fstypes: fstypes, projects: projlist, + amlist: amlist, title: window.TITLE, embedded: window.EMBEDDED, editing: editing, @@ -55,6 +61,21 @@ function (_, sup, moment, mainString) trigger: 'hover', container: 'body' }); + $('#dataset_help_link').popover({ + html: true, + content: helpString, + trigger: 'manual', + placement:'auto', + container:'body', + }); + $("#dataset_help_link").click(function(event) { + event.preventDefault(); + $('#dataset_help_link').popover('show'); + $('#dataset_popover_close').on('click', function(e) { + $('#dataset_help_link').popover('hide'); + }); + }); + // stdatasets need ro show the expiration date. var needexpire = false; if (formfields["dataset_type"] == "stdataset") { @@ -79,9 +100,21 @@ function (_, sup, moment, mainString) var val = $(this).val(); if (val == "stdataset") { $('#dataset_expires_div').removeClass("hidden"); + $('#dataset_size_div').removeClass("hidden"); + $('#dataset_fstype_div').removeClass("hidden"); + $('#dataset_imageonly_div').addClass("hidden"); + } + else if (val == "ltdataset") { + $('#dataset_expires_div').addClass("hidden"); + $('#dataset_size_div').removeClass("hidden"); + $('#dataset_fstype_div').removeClass("hidden"); + $('#dataset_imageonly_div').addClass("hidden"); } else { $('#dataset_expires_div').addClass("hidden"); + $('#dataset_size_div').addClass("hidden"); + $('#dataset_fstype_div').addClass("hidden"); + $('#dataset_imageonly_div').removeClass("hidden"); } }); } diff --git a/www/aptui/list-datasets.php b/www/aptui/list-datasets.php index dd957df6ef7d0b0b55d0f1e7e6e81a579fb2f934..09ddd5ba67b0f7bd9fbc7598109abe25fb5647ad 100644 --- a/www/aptui/list-datasets.php +++ b/www/aptui/list-datasets.php @@ -25,8 +25,10 @@ chdir(".."); include("defs.php3"); include("lease_defs.php"); include("blockstore_defs.php"); +include("imageid_defs.php"); chdir("apt"); include("quickvm_sup.php"); +include("dataset_defs.php"); $page_title = "My Datasets"; # @@ -60,23 +62,32 @@ echo "\n"; $whereclause1 = "where l.owner_uid='$target_uid'"; -$whereclause2 = "where d.creator_uid='$target_uid'"; +$whereclause2 = "where v.creator='$target_uid' and v.isdataset=1"; +$whereclause3 = "where d.creator_uid='$target_uid'"; $orderclause1 = "order by l.owner_uid"; -$orderclause2 = "order by d.creator_uid"; +$orderclause2 = "order by v.creator"; +$orderclause3 = "order by d.creator_uid"; $joinclause1 = ""; -$joinclause2 = ""; +$joinclause2 = "left join image_versions as v on ". + " v.imageid=i.imageid and v.version=i.version "; +$joinclause3 = ""; if (isset($all)) { if (ISADMIN()) { $whereclause1 = ""; - $whereclause2 = ""; + $whereclause2 = "where v.isdataset=1"; + $whereclause3 = ""; } else { $joinclause1 = "left join group_membership as g on ". " g.uid='$target_uid' and ". " g.pid=l.pid and g.pid_idx=g.gid_idx"; - $joinclause2 = + $joinclause2 .= + "left join group_membership as g on ". + " g.uid='$target_uid' and ". + " g.pid=i.pid and g.pid_idx=g.gid_idx"; + $joinclause3 = "left join group_membership as g on ". " g.uid='$target_uid' and ". " g.pid=d.pid and g.pid_idx=g.gid_idx"; @@ -84,6 +95,9 @@ if (isset($all)) { "where l.owner_uid='$target_uid' or ". " g.uid_idx is not null "; $whereclause2 = + "where (v.creator='$target_uid' or ". + " g.uid_idx is not null) and v.isdataset=1 "; + $whereclause3 = "where d.creator_uid='$target_uid' or ". " g.uid_idx is not null "; } @@ -91,15 +105,19 @@ if (isset($all)) { if ($embedded) { $query_result = - DBQueryFatal("select l.* from project_leases as l ". - "$joinclause1 ". - "$whereclause1 $orderclause1"); + DBQueryFatal("(select l.uuid,'lease' as type from project_leases as l ". + " $joinclause1 ". + " $whereclause1 $orderclause1) ". + "union ". + "(select i.uuid,'image' as type from images as i ". + " $joinclause2 ". + " $whereclause2 $orderclause2)"); } else { $query_result = - DBQueryFatal("select d.* from apt_datasets as d ". - "$joinclause2 ". - "$whereclause2 $orderclause2"); + DBQueryFatal("select d.uuid,'dataset' as type from apt_datasets as d ". + "$joinclause3 ". + "$whereclause3 $orderclause3"); } if (mysql_num_rows($query_result) == 0) { @@ -151,31 +169,29 @@ if (isset($all) && ISADMIN()) { } echo " Project Expires - State \n"; while ($row = mysql_fetch_array($query_result)) { - if ($embedded) { - $uuid = $row["uuid"]; - $idx = $row["lease_idx"]; - $name = $row["lease_id"]; - $pid = $row["pid"]; - $creator = $row["owner_uid"]; - $expires = $row["lease_end"]; - $state = $row["state"]; + $uuid = $row["uuid"]; + $type = $row["type"]; + + if ($type == "image") { + $dataset = ImageDataset::Lookup($uuid); } - else { - $uuid = $row["uuid"]; - $idx = $row["idx"]; - $name = $row["dataset_id"]; - $pid = $row["pid"]; - $creator = $row["creator_uid"]; - $expires = DateStringGMT($row["expires"]); - $state = $row["state"]; + elseif ($type == "lease") { + $dataset = Lease::Lookup($uuid); + } + elseif ($type == "dataset") { + $dataset = Dataset::Lookup($uuid); } - + $idx = $dataset->idx(); + $name = $dataset->id(); + $pid = $dataset->pid(); + $creator = $dataset->owner_uid(); + $expires = $dataset->expires(); + echo " $name\n"; @@ -190,7 +206,6 @@ while ($row = mysql_fetch_array($query_result)) { } echo " $pid $expires - $state \n"; } echo " diff --git a/www/aptui/show-dataset.php b/www/aptui/show-dataset.php index ca07b261d468c02bc964578d74bd7a4c27fa54cc..b3cda4d2965d30464fff1049afdbf38aceadf07a 100644 --- a/www/aptui/show-dataset.php +++ b/www/aptui/show-dataset.php @@ -24,6 +24,7 @@ chdir(".."); include("defs.php3"); include("lease_defs.php"); +include("imageid_defs.php"); chdir("apt"); include("quickvm_sup.php"); include("dataset_defs.php"); @@ -47,8 +48,13 @@ $optargs = RequiredPageArguments("uuid", PAGEARG_UUID); # # Either a local lease or a remote dataset. # -$dataset = Lease::Lookup($uuid); -if (!$dataset) { +if ($embedded) { + $dataset = Lease::Lookup($uuid); + if (!$dataset) { + $dataset = ImageDataset::Lookup($uuid); + } +} +else { $dataset = Dataset::Lookup($uuid); } if (!$dataset) { @@ -69,19 +75,20 @@ $canrefresh = ($dataset->islocal() ? 0 : 1); $fields = array(); if ($dataset->type() == "stdataset") { - $fields["dataset_type"] = "short term"; + $fields["dataset_type_string"] = "short term"; } elseif ($dataset->type() == "ltdataset") { - $fields["dataset_type"] = "long term"; + $fields["dataset_type_string"] = "long term"; } -else { - $fields["dataset_type"] = $dataset->type(); +elseif ($dataset->type() == "imdataset") { + $fields["dataset_type_string"] = "image backed"; } +$fields["dataset_type"] = $dataset->type(); $fields["dataset_creator"] = $dataset->owner_uid(); $fields["dataset_pid"] = $dataset->pid(); $fields["dataset_gid"] = $dataset->gid(); $fields["dataset_name"] = $dataset->id(); -$fields["dataset_size"] = $dataset->size(); +$fields["dataset_size"] = $dataset->size() ? $dataset->size() : "0"; $fields["dataset_fstype"] = ($dataset->fstype() ? $dataset->fstype() : "none"); $fields["dataset_created"] = DateStringGMT($dataset->created()); diff --git a/www/aptui/template/create-dataset.html b/www/aptui/template/create-dataset.html index 4c8d66097522dcf7062f263b54084a386aff5a5e..804d7ccc4dae1ef452922f15f29a43bfe362cc31 100644 --- a/www/aptui/template/create-dataset.html +++ b/www/aptui/template/create-dataset.html @@ -50,20 +50,14 @@ data-label="Name" <% if (editing) { %> readonly <% } %> placeholder="alphanumeric, no spaces" type="text"> - readonly <% } %> - type="text"> <% if (editing) { %> 'Short Term'<% } else { %>'Long Term'<% } %> + value=<% if (formfields.dataset_type == "stdataset") + { %>'Short Term'<% } + else if (formfields.dataset_type == "ltdataset") + { %>'Long Term'<% } else { %>Image Backed'<% } %> class="form-control format-me" data-key="dataset_type" data-label="Type" @@ -72,39 +66,81 @@ <% } else { %>
-
<% } %> +
class='hidden' <% } %> > + readonly <% } %> + type="text"> +
+
class='hidden' <% } %> > + <% if (!editing && amlist) { %> + + <% } %> + + +
class='hidden' <% } %> >
- <% if (editing) { %> - - <% } else { %> - + <% } else { %> + - <% } %> + + <% }); %> + + <% } %> +
@@ -222,8 +262,6 @@
-
- More about Datasets