Commit 0f7ee1ad authored by Mike Hibler's avatar Mike Hibler

Finish modifications for snapshots and clones.

parent 0cdcd480
......@@ -45,6 +45,8 @@ sub usage()
print STDERR " Create a clone of <pool>/<vol> called <nvol> from the snapshot at <tstamp> (most recent if not specified)\n";
print STDERR " destroy <pool> <vol>\n";
print STDERR " Destroy <vol> in <pool>\n";
print STDERR " desnapshot <pool> <vol> [ <tstamp> ]\n";
print STDERR " Destroy snapshot <vol>/<pool>@<tstamp>; if <tstamp> is not given, destroy all snapshots\n";
print STDERR " declone <pool> <vol>\n";
print STDERR " Destroy clone <vol> in <pool>; also destroys associated snapshot if this is the last clone\n";
exit(-1);
......@@ -68,13 +70,14 @@ sub fatal($);
# Commands
my %cmds = (
"pools" => \&pools,
"volumes" => \&volumes,
"create" => \&create,
"snapshot" => \&snapshot,
"clone" => \&clone,
"destroy" => \&destroy,
"declone" => \&declone,
"pools" => \&pools,
"volumes" => \&volumes,
"create" => \&create,
"snapshot" => \&snapshot,
"clone" => \&clone,
"destroy" => \&destroy,
"desnapshot" => \&desnapshot,
"declone" => \&declone,
);
#
......@@ -214,6 +217,34 @@ sub snapshot($$$)
return freenasVolumeSnapshot($pool, $vol, $tstamp);
}
sub desnapshot($$$)
{
my ($pool,$vol,$tstamp) = @_;
if (defined($pool) && $pool =~ /^([-\w]+)$/) {
$pool = $1;
} else {
print STDERR "bscontrol_proxy: bogus pool arg\n";
return 1;
}
if (defined($vol) && $vol =~ /^([-\w]+)$/) {
$vol = $1;
} else {
print STDERR "bscontrol_proxy: bogus volume arg\n";
return 1;
}
if (defined($tstamp)) {
if ($tstamp =~ /^(\d+)$/) {
$tstamp = $1;
} else {
print STDERR "bscontrol_proxy: bogus tstamp arg\n";
return 1;
}
}
return freenasVolumeDesnapshot($pool, $vol, $tstamp);
}
sub clone($$$;$)
{
my ($pool,$ovol,$nvol,$tstamp) = @_;
......@@ -227,13 +258,13 @@ sub clone($$$;$)
if (defined($ovol) && $ovol =~ /^([-\w]+)$/) {
$ovol = $1;
} else {
print STDERR "bscontrol_proxy: bogus volume arg\n";
print STDERR "bscontrol_proxy: bogus origin volume arg\n";
return 1;
}
if (defined($nvol) && $nvol =~ /^([-\w]+)$/) {
$nvol = $1;
} else {
print STDERR "bscontrol_proxy: bogus volume arg\n";
print STDERR "bscontrol_proxy: bogus clone volume arg\n";
return 1;
}
if (defined($tstamp)) {
......
......@@ -15,7 +15,7 @@ cache.get_apps()
import django.http
import freenasUI.middleware.notifier
import freenasUI.urls
#import freenasUI.urls
import freenasUI.network.models
import freenasUI.storage.models
import freenasUI.services.models
......
......@@ -32,7 +32,8 @@ use Exporter;
qw(
freenasPoolList freenasVolumeList
freenasVolumeCreate freenasVolumeDestroy freenasFSCreate
freenasVolumeSnapshot freenasVolumeClone freenasVolumeDeclone
freenasVolumeSnapshot freenasVolumeClone
freenasVolumeDesnapshot freenasVolumeDeclone
freenasRunCmd freenasParseListing
$FREENAS_CLI_VERB_IFACE $FREENAS_CLI_VERB_IST_EXTENT
$FREENAS_CLI_VERB_IST_AUTHI $FREENAS_CLI_VERB_IST_TARGET
......@@ -124,12 +125,17 @@ sub freenasFSCreate($$$);
sub freenasRunCmd($$);
sub freenasParseListing($);
sub freenasVolumeSnapshot($$;$);
sub freenasVolumeDesnapshot($$;$);
sub freenasVolumeClone($$$;$);
sub freenasVolumeDeclone($$);
#
# Local Functions
#
sub listPools();
sub convertZfsToMebi($);
sub volumeDestroy($$$);
sub volumeDestroy($$$$);
#
# Turn off line buffering on output
......@@ -283,6 +289,122 @@ sub freenasVolumeCreate($$$)
return 0;
}
sub freenasVolumeSnapshot($$;$)
{
my ($pool, $volname, $tstamp) = @_;
# Untaint arguments that are passed to a command execution
$pool = untaintHostname($pool);
$volname = untaintHostname($volname);
if (defined($tstamp) && $tstamp != 0) {
$tstamp = untaintNumber($tstamp);
} else {
$tstamp = time();
}
if (!$pool || !$volname || !$tstamp) {
warn("*** ERROR: freenasVolumeSnapshot: ".
"Invalid arguments");
return -1;
}
# Get volume and snapshot info
my $vollist = freenasVolumeList(0, 1);
# The base volume must exist
my $vref = $vollist->{$volname};
if (!$vref || $vref->{'pool'} ne $pool) {
warn("*** ERROR: freenasVolumeSnapshot: ".
"Base volume '$volname' does not exist in pool '$pool'");
return -1;
}
# The snapshot must not exist
my $snapshot = "$volname\@$tstamp";
if (exists($vref->{'snapshots'})) {
my @snaps = split(',', $vref->{'snapshots'});
foreach my $sname (@snaps) {
if ($snapshot eq $sname) {
warn("*** ERROR: freenasVolumeSnapshot: ".
"Snapshot '$snapshot' already exists");
return -1;
}
}
}
# Let's do it!
eval { freenasRunCmd($FREENAS_CLI_VERB_SNAPSHOT,
"add $pool/$snapshot") };
if ($@) {
my $msg = " $@";
$msg =~ s/\\n/\n /g;
warn("*** ERROR: freenasVolumeSnapshot: ".
"'add $pool/$snapshot' failed:\n$msg");
return -1;
}
return 0;
}
sub freenasVolumeDesnapshot($$;$)
{
my ($pool, $volname, $tstamp) = @_;
# Untaint arguments that are passed to a command execution
$pool = untaintHostname($pool);
$volname = untaintHostname($volname);
if (defined($tstamp)) {
$tstamp = untaintNumber($tstamp);
} else {
$tstamp = 0;
}
if (!$pool || !$volname || !defined($tstamp)) {
warn("*** ERROR: freenasVolumeSnapshot: ".
"Invalid arguments");
return -1;
}
# Get volume and snapshot info
my $vollist = freenasVolumeList(0, 1);
# The base volume must exist
my $vref = $vollist->{$volname};
if (!$vref || $vref->{'pool'} ne $pool) {
warn("*** ERROR: freenasVolumeDesnapshot: ".
"Base volume '$volname' does not exist in pool '$pool'");
return -1;
}
# Loop through removing snapshots as appropriate.
my $rv = 0;
if (exists($vref->{'snapshots'})) {
my @snaps = split(',', $vref->{'snapshots'});
my $snapshot = "$volname\@$tstamp"
if ($tstamp);
foreach my $sname (@snaps) {
if (!$tstamp || $snapshot eq $sname) {
eval { freenasRunCmd($FREENAS_CLI_VERB_SNAPSHOT,
"del $pool/$sname") };
if ($@) {
if ($@ =~ /has dependent clones/) {
warn("*** WARNING: freenasVolumeDesnapshot: ".
"snapshot '$sname' in use");
} else {
my $msg = " $@";
$msg =~ s/\\n/\n /g;
warn("*** ERROR: freenasVolumeDesnapshot: ".
"'del $pool/$snapshot' failed:\n$msg");
}
$rv = -1;
}
}
}
}
return $rv;
}
#
# Create a clone volume named $nvolname from volume $ovolname.
# The clone will be created from the snapshot $volname-$tag where
......@@ -387,50 +509,7 @@ sub freenasVolumeDeclone($$)
return -1;
}
# Get volume and snapshot info
my $vollist = freenasVolumeList(0, 1);
# Volume must exist and be a clone
my $ovref = $vollist->{$volname};
if (!$ovref || $ovref->{'pool'} ne $pool) {
warn("*** ERROR: freenasVolumeDeclone: ".
"Volume '$volname' does not exist in pool '$pool'");
return -1;
}
if (!exists($ovref->{'cloneof'})) {
warn("*** ERROR: freenasVolumeDeclone: ".
"Volume '$volname' is not a clone, use 'destroy' instead");
return -1;
}
# volume must not itself have snapshots
# XXX this is not strictly necessary as FreeNAS destroy will fail anyway
if (exists($ovref->{'snapshots'})) {
warn("*** ERROR: freenasVolumeDeclone: ".
"Volume '$volname' has clones");
return -1;
}
if (volumeDestroy($pool, $volname, "freenasVolumeDeclone")) {
return -1;
}
# see if we can whack the snapshot
my $snapshot = $ovref->{'cloneof'};
eval { freenasRunCmd($FREENAS_CLI_VERB_SNAPSHOT,
"del $pool/$snapshot") };
if ($@) {
if ($@ =~ /has dependent clones/) {
return 0;
}
my $msg = " $@";
$msg =~ s/\\n/\n /g;
warn("*** ERROR: freenasVolumeDeclone: ".
"'del $pool/$snapshot' failed:\n$msg");
return -1;
}
return 0;
return volumeDestroy($pool, $volname, 1, "freenasVolumeDeclone");
}
sub freenasVolumeDestroy($$)
......@@ -446,18 +525,33 @@ sub freenasVolumeDestroy($$)
return -1;
}
#
# Note that we don't do any sanity checks of our own (ala Declone),
# instead relying on the FreeNAS destroy to fail when appropriate.
#
return volumeDestroy($pool, $volname, "freenasVolumeDestroy");
return volumeDestroy($pool, $volname, 0, "freenasVolumeDestroy");
}
#
# The guts of destroy and declone
sub volumeDestroy($$$) {
my ($pool, $volname, $tag) = @_;
#
sub volumeDestroy($$$$) {
my ($pool, $volname, $declone, $tag) = @_;
# Get volume and snapshot info
my $vollist = freenasVolumeList(0, 1);
# Volume must exist
my $vref = $vollist->{$volname};
if (!$vref || $vref->{'pool'} ne $pool) {
warn("*** ERROR: $tag: ".
"Volume '$volname' does not exist in pool '$pool'");
return -1;
}
# Volume must not have snapshots
if (exists($vref->{'snapshots'})) {
warn("*** ERROR: $tag: ".
"Volume '$volname' has clones, cannot destroy");
return -1;
}
# Deallocate volume. Wrap in loop to enable retries.
my $count;
for ($count = 1; $count <= $MAX_RETRY_COUNT; $count++) {
......@@ -510,6 +604,30 @@ sub volumeDestroy($$$) {
return -1;
}
#
# If decloning, see if we can whack the snapshot
#
if (exists($vref->{'cloneof'})) {
my $snapshot = $vref->{'cloneof'};
if ($declone) {
eval { freenasRunCmd($FREENAS_CLI_VERB_SNAPSHOT,
"del $pool/$snapshot") };
if ($@) {
if ($@ =~ /has dependent clones/) {
return 0;
}
my $msg = " $@";
$msg =~ s/\\n/\n /g;
warn("*** ERROR: freenasVolumeDeclone: ".
"'del $pool/$snapshot' failed:\n$msg");
return -1;
}
} else {
warn("*** WARNING: $tag: ".
"Destroying clone but not origin snapshot '$snapshot'");
}
}
return 0;
}
......
#!/usr/bin/perl -wT
#
# Copyright (c) 2013 University of Utah and the Flux Group.
# Copyright (c) 2013-2015 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -608,7 +608,7 @@ sub calcSliceSizes($) {
my $sliceshash = shift;
# Ugh... Have to look up size via the "volume" list for zvol slices.
my $zvollist = freenasVolumeList(0);
my $zvollist = freenasVolumeList(0, 0);
foreach my $slice (values(%$sliceshash)) {
my $vnode_id = $slice->{'vnode_id'};
......@@ -700,21 +700,32 @@ sub allocSlice($$$$) {
#
# If this is a use of a persistent store, the BSID is a unique
# volume name based on the lease ID. Look up the volume to make
# sure it exists, but do nothing else.
# sure it exists, but do nothing else other than stash away some
# state for exportSlice.
#
if ($bsid =~ /^lease-\d+$/) {
my $volumes = freenasVolumeList(1);
if (exists($volumes->{$bsid})) {
$priv->{'pool'} = $volumes->{$bsid}->{'pool'};
$priv->{'volume'} = $volumes->{$bsid}->{'volume'};
if (exists($volumes->{$bsid}->{'iname'})) {
$priv->{'iname'} = $volumes->{$bsid}->{'iname'};
}
return 0;
# XXX we no longer share mappings so don't need iname info
#my $volumes = freenasVolumeList(1, 1);
my $volumes = freenasVolumeList(0, 1);
my $vref = $volumes->{$bsid};
if (!defined($vref)) {
warn("*** ERROR: blockstore_allocSlice: $volname: ".
"Requested volume not found: $bsid!");
return -1;
}
warn("*** ERROR: blockstore_allocSlice: $volname: ".
"Requested volume not found: $bsid!");
return -1;
# For possible later cloning, remember if it has snapshots
if (exists($vref->{'snapshots'})) {
$priv->{'hassnapshot'} = 1;
}
$priv->{'pool'} = $vref->{'pool'};
$priv->{'volume'} = $vref->{'volume'};
# XXX we no longer share mappings
#if (exists($vref->{'iname'})) {
# $priv->{'iname'} = $vref->{'iname'};
#}
return 0;
}
$priv->{'pool'} = $bsid;
......@@ -785,17 +796,68 @@ sub exportSlice($$$$) {
$perm = "ro";
}
#
# XXX hack temporary support for RO sharing of persistent blockstores.
#
# If the mapping to a persistent store is RO, then we will create
# an ephemeral clone for each such mapping.
#
if ($volume =~ /^lease-\d+$/ && $perm eq "ro") {
#
# If no snapshot exists, create one. VolumeClone must have
# a snapshot to hang the clone on. If a snapshot already exists
# we assume another mapping has already gone through here and
# we just use the same snapshot.
#
# XXX could this race with another vnode setup? If so, we could
# wind up creating multiple snapshots for the same volume.
# That does not matter right now, but something to watch out for.
#
my $tstamp;
if (!exists($priv->{'hassnapshot'})) {
$tstamp = time();
if (freenasVolumeSnapshot($pool, $volume, $tstamp)) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Could not create snapshot for RO mapping");
return -1;
}
}
#
# Create a clone with the same name as an ephemeral blockstore
# would have. Note that it is in a different pool however,
# since snapshot/clone must be in the same pool as the origin.
# Clone will use the most recent snapshot (though there should
# only be one anyway).
#
if (freenasVolumeClone($pool, $volume, $vnode_id)) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Could not create clone for RO mapping");
if ($tstamp) {
freenasVolumeDesnapshot($pool, $volume, $tstamp);
}
return -1;
}
$volume = $vnode_id;
}
# If operating on a lease, we use the lease id as the iSCSI
# initiator group identifier instead of the vnode_id because this
# entry may end up being shared (simultaneous RO use).
my $tag_ident = $vnode_id;
if ($priv->{'volume'} =~ /^lease-\d+$/) {
$tag_ident = $priv->{'volume'};
}
# XXX we no longer share mappings
#if ($priv->{'volume'} =~ /^lease-\d+$/) {
# $tag_ident = $priv->{'volume'};
#}
#
# Go through the whole iSCSI extent/target setup if it hasn't been
# done yet for this volume. (If this is a persistent lease, it
# may already be exported and in use.)
#
# XXX currently iname will never exist since we don't share mappings.
# The "else" code is left just in case we need it again in the future.
#
if (!exists($priv->{'iname'})) {
# Create iSCSI extent.
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_EXTENT,
......@@ -847,6 +909,10 @@ sub exportSlice($$$$) {
# requested perms are RO, and validate that the dataset isn't
# currently in use RW.
else {
# XXX for now
warn("*** ERROR: unexpected arrival in shared dataset code!");
return -1;
# Check requested perms.
if ($perm ne "ro") {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
......@@ -1256,9 +1322,10 @@ sub unexportSlice($$$$) {
# iSCSI auth group identifier instead of the vnode_id because
# this entry may be shared (simultaneous RO use).
my $tag_ident = $vnode_id;
if ($sconf->{'UUID'} =~ /:(lease-\d+)$/) {
$tag_ident = $1; # untaint
}
# XXX we no longer share mappings
#if ($sconf->{'UUID'} =~ /:(lease-\d+)$/) {
# $tag_ident = $1; # untaint
#}
# Fetch the authorized initators entry associated with this slice,
# and yank its network entry out of the list. It this is the last
......@@ -1314,6 +1381,10 @@ sub unexportSlice($$$$) {
# This export is still referenced, so leave export but update the
# authorized initiators list.
else {
# XXX for now
warn("*** ERROR: unexpected arrival in shared dataset code!");
return -1;
my $auth_network_str = join(' ', @pruned_networks);
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_AUTHI,
"edit $authtag ALL '$auth_network_str' ".
......@@ -1350,7 +1421,23 @@ sub deallocSlice($$$$) {
# sure it exists, but do nothing else.
#
if ($bsid =~ /^lease-\d+$/) {
my $volumes = freenasVolumeList(0);
my $volumes = freenasVolumeList(0, 1);
#
# Check for clone volumes. A clone will have our (vnode_id)
# name and be a "cloneof" a snapshot of this lease.
#
if (exists($volumes->{$vnode_id})) {
my $vref = $volumes->{$vnode_id};
my $pool = $vref->{'pool'};
if (exists($vref->{'cloneof'}) &&
$vref->{'cloneof'} =~ /^$bsid\@\d+/) {
return freenasVolumeDeclone($pool, $vnode_id);
}
warn("*** WARNING: blockstore_deallocSlice: $volname: ".
"Found stale ephemeral volume '$pool/$vnode_id'");
}
if (exists($volumes->{$bsid})) {
return 0;
}
......@@ -1359,6 +1446,10 @@ sub deallocSlice($$$$) {
return -1;
}
#
# We use Declone here which will remove the origin snapshot
# as well (if we are the last user) when invoked on a cloned volume.
#
return freenasVolumeDestroy($bsid, $vnode_id);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment