Commit 490d011a authored by Mike Hibler's avatar Mike Hibler

Support for FreeNAS 9.3.

This REPLACES FreeNAS 9.2 support. The two are incompatible.

This new code uses the REST API whereever possible (i.e., when it is
implemented and works). There is some client-side reorg going on too.
parent 45212eb1
#
# Copyright (c) 2000-2014 University of Utah and the Flux Group.
# Copyright (c) 2000-2015 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -41,7 +41,7 @@ include $(TESTBED_SRCDIR)/GNUmakerules
FBSDBASEDIR = freebsd9
OETCDIR = $(DESTDIR)$(CLIENT_ETCDIR)
NETCDIR = $(DESTDIR)/cfg/emulab
NETCDIR = $(DESTDIR)/conf/base/etc/emulab
OBINDIR = $(DESTDIR)$(CLIENT_BINDIR)
NBINDIR = $(DESTDIR)/usr/local/emulab
OVARDIR = $(DESTDIR)$(CLIENT_VARDIR)
......@@ -70,11 +70,21 @@ basefbsd-install:
# filesystem (e.g., "rm -rf /etc" below). So DO NOT remove the destdircheck.
#
install client-install: destdircheck basefbsd-install
echo "Tweaking FreeBSD 8.3 installed files for FreeNAS..."
mkdir -p $(DESTDIR)/cfg $(DESTDIR)/cfg/rc.d $(DESTDIR)/cfg/local
echo "Tweaking FreeBSD 9.3 installed files for FreeNAS..."
mkdir -p $(DESTDIR)/conf/base/etc/rc.d $(DESTDIR)/conf/base/etc/local
mkdir -p $(DESTDIR)/data $(DESTDIR)/local $(DESTDIR)/conf/base/var
rm -f $(OETCDIR)/master.passwd $(OETCDIR)/passwd $(OETCDIR)/group
rm -f $(OETCDIR)/hosts $(OETCDIR)/prepare.sh
mv $(OETCDIR) $(NETCDIR)
rm -f $(ORCDIR)/rc.kname $(ORCDIR)/rc.canaryd $(ORCDIR)/rc.linktest
rm -f $(ORCDIR)/rc.mkelab $(ORCDIR)/rc.motelog $(ORCDIR)/rc.trace
rm -f $(ORCDIR)/rc.inelab $(ORCDIR)/rc.healthd $(ORCDIR)/rc.nodecheck
rm -f $(ORCDIR)/rc.pgeni $(ORCDIR)/rc.tpmsetup $(ORCDIR)/rc.trafgen
rm -f $(ORCDIR)/rc.slothd
rm -f $(OBINDIR)/checknode $(OBINDIR)/checkutils.sh
rm -f $(OBINDIR)/diskcheck $(OBINDIR)/hbis.sh $(OBINDIR)/cpucheck
rm -f $(OBINDIR)/healthd.conf $(OBINDIR)/ixpboot $(OBINDIR)/memcheck
rm -f $(OBINDIR)/niccheck $(OBINDIR)/tdd $(OBINDIR)/timecheck
mv $(OBINDIR) $(DESTDIR)/local/emulab
mv $(OVARDIR) $(NVARDIR)
ln -sf /data/emulab $(DESTDIR)/conf/base/var/emulab
......@@ -84,10 +94,10 @@ install client-install: destdircheck basefbsd-install
rm -rf $(DESTDIR)/boot $(DESTDIR)/etc $(DESTDIR)/root $(DESTDIR)/sbin
rm -rf $(DESTDIR)/usr/* $(DESTDIR)/var
mv $(DESTDIR)/local $(DESTDIR)/usr/local
ln -sf /usr/local/emulab $(DESTDIR)/cfg/local/emulab
ln -sf /usr/local/emulab $(DESTDIR)/conf/base/etc/local/emulab
echo "Installing FreeNAS files..."
$(INSTALL) -m 644 $(SRCDIR)/genvmtype $(NETCDIR)/
$(INSTALL) -m 755 $(SRCDIR)/testbed $(DESTDIR)/cfg/rc.d/
$(INSTALL) -m 755 $(SRCDIR)/testbed $(DESTDIR)/conf/base/etc/rc.d/
$(INSTALL) -m 755 $(SRCDIR)/bscontrol.proxy.pl $(NBINDIR)/bscontrol.proxy
$(INSTALL) -m 755 $(SRCDIR)/freenas-config $(NBINDIR)/
$(INSTALL) -m 644 $(SRCDIR)/libfreenas.pm $(NBINDIR)/
......
......@@ -49,6 +49,13 @@ sub usage()
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";
print STDERR "iSCSI-related debugging commands:\n";
print STDERR " slices Print info about Emulab slices\n";
print STDERR " targets Print info about iSCSI targets\n";
print STDERR " extents Print info about iSCSI extents\n";
print STDERR " assocs Print info about iSCSI target/extent assocs\n";
print STDERR " authinit Print info about iSCSI authorized initiators\n";
print STDERR " nextaitag Print next available initiator tag\n";
exit(-1);
}
my $optlist = "hd";
......@@ -78,6 +85,12 @@ my %cmds = (
"destroy" => \&destroy,
"desnapshot" => \&desnapshot,
"declone" => \&declone,
"slices" => \&slices,
"extents" => \&extents,
"authinit" => \&authinit,
"nextaitag" => \&nexttag,
"targets" => \&targets,
"assocs" => \&assocs,
);
#
......@@ -152,6 +165,124 @@ sub volumes()
return 0;
}
#
# Print uninterpreted Emulab slice info.
#
sub slices()
{
my $eref = freenasSliceList();
foreach my $ext (keys %{$eref}) {
foreach my $key ("pid", "eid", "volname", "bsid", "vnode_id", "size", "type") {
my $val = $eref->{$ext}->{$key};
$val = lc($val)
if ($key eq "type");
print "$key=$val "
if (defined($val));
}
print "\n";
}
return 0;
}
#
# Print uninterpreted iSCSI extent info.
#
sub extents()
{
my $eref = freenasExtentList(0);
foreach my $ext (keys %{$eref}) {
foreach my $key ("id", "name", "path", "type", "blocksize", "filesize", "naa") {
my $val = $eref->{$ext}->{$key};
$val = lc($val)
if ($key eq "type");
print "$key=$val "
if (defined($val));
}
print "\n";
}
return 0;
}
#
# Print uninterpreted iSCSI target info.
#
sub targets()
{
my $airef = freenasTargetList(0);
foreach my $ai (keys %{$airef}) {
foreach my $key ("id", "name", "alias", "serial", "portalgroup", "authgroup", "authtype", "initiatorgroup") {
my $val = $airef->{$ai}->{$key};
print "$key=$val "
if (defined($val));
}
print "\n";
}
return 0;
}
#
# Print uninterpreted iSCSI target/extent association info.
#
sub assocs()
{
my $aref = freenasAssocList();
foreach my $a (keys %{$aref}) {
foreach my $key ("id", "target", "target_name", "extent", "extent_name") {
my $val = $aref->{$a}->{$key};
print "$key=$val "
if (defined($val));
}
print "\n";
}
return 0;
}
#
# Print uninterpreted iSCSI authorized initiator info.
#
sub authinit()
{
my $airef = freenasAuthInitList();
foreach my $ai (keys %{$airef}) {
foreach my $key ("id", "tag", "auth_network", "comment") {
my $val = $airef->{$ai}->{$key};
print "$key=$val "
if (defined($val));
}
print "\n";
}
return 0;
}
#
# Print next available authinit tag
#
sub nexttag() {
my $aiinfo = freenasAuthInitList();
my @taglist = ();
foreach my $ai (keys %{$aiinfo}) {
my $tag = $aiinfo->{$ai}->{'tag'};
if (defined($tag) && $tag =~ /^(\d+)$/) {
push(@taglist, $1);
}
}
my $freetag = 1;
foreach my $curtag (sort {$a <=> $b} @taglist) {
last
if ($freetag < $curtag);
$freetag++;
}
print "nexttag=$freetag\n";
}
sub create($$$;$)
{
my ($pool,$vol,$size,$fstype) = @_;
......
......@@ -24,26 +24,55 @@
# Support functions for the libvnode API and also for bscontrol which is
# a proxy for the blockstore server control program on boss.
#
# Uses the FreeNAS API directly.
#
# XXX things the API cannot do yet:
#
# 1. create a zvol,
# 2. create an iSCSI extent (POST) with type='ZVOL',
# Actually, you can do this if you specify
# iscsi_target_extent_type=='Disk' and
# iscsi_target_extent_disk=='zvol/...'
# 3. create an authorized initiator (POST); always return 302 FOUND
#
# API also does not report an error for:
#
# 1. attempting to remove a snapshot with a dependent clone
#
# So right now we use the API for all listing functions (get volumes,
# get extents, etc.) and for snapshots/clones and destroying "datasets".
#
package libfreenas;
use Exporter;
@ISA = "Exporter";
@EXPORT =
qw(
freenasPoolList freenasVolumeList
freenasPoolList freenasVolumeList freenasSliceList
freenasAuthInitList freenasExtentList freenasTargetList
freenasAssocList
freenasVolumeCreate freenasVolumeDestroy freenasFSCreate
freenasVolumeSnapshot freenasVolumeClone
freenasVolumeDesnapshot freenasVolumeDeclone
freenasRunCmd freenasParseListing
freenasRequest
$FREENAS_CLI_VERB_IFACE $FREENAS_CLI_VERB_IST_EXTENT
$FREENAS_CLI_VERB_IST_AUTHI $FREENAS_CLI_VERB_IST_TARGET
$FREENAS_CLI_VERB_IST_ASSOC $FREENAS_CLI_VERB_VLAN
$FREENAS_CLI_VERB_VOLUME $FREENAS_CLI_VERB_POOL
$FREENAS_CLI_VERB_SNAPSHOT
$FREENAS_API_RESOURCE_IFACE $FREENAS_API_RESOURCE_IST_EXTENT
$FREENAS_API_RESOURCE_IST_AUTHI $FREENAS_API_RESOURCE_IST_TARGET
$FREENAS_API_RESOURCE_IST_ASSOC $FREENAS_API_RESOURCE_VLAN
$FREENAS_API_RESOURCE_VOLUME $FREENAS_API_RESOURCE_SNAPSHOT
);
use strict;
use English;
use HTTP::Tiny;
use JSON::PP;
use MIME::Base64;
use Data::Dumper;
use Socket;
use File::Basename;
......@@ -56,6 +85,17 @@ use libutil;
use libtestbed;
use libsetup;
#
# Exported resources
#
our $FREENAS_API_RESOURCE_IFACE = "network/interface";
our $FREENAS_API_RESOURCE_IST_EXTENT = "services/iscsi/extent";
our $FREENAS_API_RESOURCE_IST_AUTHI = "services/iscsi/authorizedinitiator";
our $FREENAS_API_RESOURCE_IST_TARGET = "services/iscsi/target";
our $FREENAS_API_RESOURCE_IST_ASSOC = "services/iscsi/targettoextent";
our $FREENAS_API_RESOURCE_VLAN = "network/vlan";
our $FREENAS_API_RESOURCE_VOLUME = "storage/volume";
our $FREENAS_API_RESOURCE_SNAPSHOT = "storage/snapshot";
#
# Exported CLI constants
......@@ -90,6 +130,7 @@ my $IFCONFIG = "/sbin/ifconfig";
my $ALIASMASK = "255.255.255.255";
my $LINUX_MKFS = "/usr/local/sbin/mke2fs";
my $FBSD_MKFS = "/sbin/newfs";
my $API_AUTHINFO = "$ETCDIR/freenas-api.auth";
# storageconfig constants
# XXX: should go somewhere more general
......@@ -116,6 +157,7 @@ my %cliverbs = (
# Global variables
#
my $debug = 0;
my $auth;
sub freenasPoolList();
sub freenasVolumeList($;$);
......@@ -136,6 +178,11 @@ sub freenasVolumeDeclone($$);
sub listPools();
sub convertZfsToMebi($);
sub volumeDestroy($$$$);
sub snapshotHasClone($$);
sub getZvolsFromVolinfo($);
sub parseSliceName($);
sub parseSlicePath($);
sub calcSliceSizes($);
#
# Turn off line buffering on output
......@@ -149,6 +196,153 @@ sub setDebug($)
if ($debug);
}
#
# Make a request via the FreeNAS v1.0 API.
# $resourse is the resource path, e.g., "account/users"
# $method is "GET", "PUT", "POST", or "DELETE" (default is "GET")
# $paramp is a reference to a hash of KEY=VALUE URL params (default is ())
# $datap is a reference to a hash of KEY=VALUE input content (default is ())
# $exstat is the expected success status code if not the method default
# $errorp is a reference to a string, used to return error string if !undef
# Return value is the decoded (as a hash) JSON KEY=VALUE returned by request
# Returns undef on failure.
#
sub freenasRequest($;$$$$$)
{
my ($resource,$method,$paramp,$datap,$exstat,$errorp) = @_;
my %data = $datap ? %$datap : ();
my ($datastr,$paramstr);
my %status = (
"GET" => 200,
"PUT" => 200,
"POST" => 201,
"DELETE" => 204
);
# XXX read the authentication info in user:password format
if (!$auth) {
if (!open(FD, "<$API_AUTHINFO")) {
my $msg = "could not open $API_AUTHINFO";
if ($errorp) {
$$errorp = $msg;
} else {
warn("*** ERROR: freenasRequest: $msg");
}
return undef;
}
$auth = <FD>;
close(FD);
chomp $auth;
if ($auth !~ /^(\w+:.*)$/) {
my $msg = " bogus authinfo, wrong format";
if ($errorp) {
$$errorp = $msg;
} else {
warn("*** ERROR: freenasRequest: $msg");
}
return undef;
}
$auth = $1;
}
$method = "GET"
if (!defined($method));
$datastr = encode_json(\%data);
$paramstr = "";
if ($paramp) {
my @params = ();
foreach my $k (keys %$paramp) {
my $v = $paramp->{$k};
push @params, "$k=$v";
}
if (@params) {
$paramstr = "?" . join('&', @params);
}
}
my $url = "http://localhost/api/v1.0/$resource/$paramstr";
print STDERR "freenasRequest: URL: $url\nCONTENT: $datastr\n"
if ($debug);
my %headers = (
"Content-Type" => "application/json",
"Authorization" => "Basic " . MIME::Base64::encode_base64($auth, "")
);
my $http = HTTP::Tiny->new("timeout" => 30);
my %options = ("headers" => \%headers, "content" => $datastr);
my $res = $http->request($method, $url, \%options);
print STDERR "freenasRequest: RESPONSE: ", Dumper($res), "\n"
if ($debug);
$exstat = $status{$method}
if (!defined($exstat));
if ($res->{'success'} && $res->{'status'} == $exstat) {
if (exists($res->{'headers'}{'content-type'}) &&
$res->{'headers'}{'content-type'} eq "application/json") {
return JSON::PP->new->decode($res->{'content'});
}
if (!exists($res->{'content'})) {
return {};
}
if (!ref($res->{'content'})) {
return { "content" => $res->{'content'} };
}
my $msg = "Unparsable content: " . Dumper($res->{'content'});
if ($errorp) {
$$errorp = $msg;
} else {
warn("*** ERROR: freenasRequest: $msg");
}
return undef;
}
if ($res->{'reason'}) {
my $content;
if (exists($res->{'content'}) &&
exists($res->{'headers'}{'content-type'})) {
my $ctype = $res->{'headers'}{'content-type'};
if ($ctype eq "text/plain") {
$content = $res->{'content'};
} elsif ($ctype eq "application/json") {
my $cref =
JSON::PP->new->allow_nonref->decode($res->{'content'});
if ($cref && ref $cref) {
if (exists($cref->{'__all__'})) {
$content = $cref->{'__all__'};
} elsif (exists($cref->{'error'})) {
$content = $cref->{'error'};
}
} elsif ($cref) {
$content = $cref;
} else {
$content = $res->{'content'};
}
}
}
my $msg = "Request failed: " . $res->{'reason'};
if ($content) {
$msg .= "\nFreeNAS error: $content";
}
if ($errorp) {
$$errorp = $msg;
} else {
warn("*** ERROR: freenasRequest: $msg");
}
return undef;
}
my $msg = "Request failed: " . Dumper($res);
if ($errorp) {
$$errorp = $msg;
} else {
warn("*** ERROR: freenasRequest: $msg");
}
return undef;
}
sub freenasVolumeList($;$)
{
my ($inameinfo,$snapinfo) = @_;
......@@ -157,35 +351,38 @@ sub freenasVolumeList($;$)
$inameinfo = 0 if (!defined($inameinfo));
$snapinfo = 0 if (!defined($snapinfo));
# Assorted hack maps
my %inames = (); # volume-name -> slice-name
my %snaps = (); # volume-name -> (snapshot1 snapshot2 ...)
my %clones = (); # clone-volume-name -> snapshot
my %zvolsizes = (); # volume-name -> volsize
#
# Extract blockstores from the freenas volume info and augment
# with slice info where it exists.
#
my %inames = ();
if ($inameinfo) {
my @slist = freenasParseListing($FREENAS_CLI_VERB_IST_EXTENT);
foreach my $slice (@slist) {
if ($slice->{'path'} =~ /^zvol\/([-\w]+\/[-\w+]+)$/) {
$inames{$1} = $slice->{'name'};
my $extinfo = freenasRequest($FREENAS_API_RESOURCE_IST_EXTENT,
"GET", { "limit" => 0 });
foreach my $ext (@$extinfo) {
if ($ext->{'iscsi_target_extent_path'} =~ /^\/dev\/zvol\/([-\w]+\/[-\w+]+)$/) {
$inames{$1} = $ext->{'iscsi_target_extent_name'};
}
}
}
# volume-name -> (snapshot1 snapshot2 ...)
my %snaps = ();
# clone-volume-name -> snapshot
my %clones = ();
if ($snapinfo) {
my @slist = freenasParseListing($FREENAS_CLI_VERB_SNAPSHOT);
my $sinfo = freenasRequest($FREENAS_API_RESOURCE_SNAPSHOT,
"GET", { "limit" => 0 });
my @snames = ();
foreach my $snap (@slist) {
my $vol = $snap->{'vol_name'};
foreach my $snap (@$sinfo) {
my $vol = $snap->{'filesystem'};
next if (!$vol);
# XXX only handle zvols right now
next if ($snap->{'snap_parent'} ne 'volume');
next if ($snap->{'parent_type'} ne 'volume');
if ($snap->{'snap_name'} =~ /^(.*)\/([^\/]+)$/) {
if ($snap->{'fullname'} =~ /^(.*)\/([^\/]+)$/) {
my $sname = $2;
push(@snames, "$1/$2");
$snaps{$vol} = [ ] if (!exists($snaps{$vol}));
......@@ -211,22 +408,50 @@ sub freenasVolumeList($;$)
}
}
my @zvols = freenasParseListing($FREENAS_CLI_VERB_VOLUME);
# XXX unbelievable: the API does not return the volsize of a zvol!
# Gotta do it ourselves...
if (open(ZFS, "$ZFS_CMD get -t volume -o name,value -Hp volsize |")) {
while (my $line = <ZFS>) {
chomp $line;
my ($name, $val) = split(/\s+/, $line);
$zvolsizes{$name} = $val;
}
close(ZFS);
} else {
warn("*** WARNING: could not run 'zfs get' for zvol size info");
}
#
# The FreeNAS API returns pools, filesystems, zvols using "volume"
# so we have to dig the zvols out from there.
#
my $vinfo = freenasRequest($FREENAS_API_RESOURCE_VOLUME,
"GET", {"limit" => 0});
my @zvols = getZvolsFromVolinfo($vinfo);
foreach my $zvol (@zvols) {
my $vol = {};
if ($zvol->{'vol_name'} =~ /^([-\w]+)\/([-\w+]+)$/) {
my $volname = $zvol->{'path'};
if ($volname =~ /^([-\w]+)\/([-\w+]+)$/) {
$vol->{'pool'} = $1;
$vol->{'volume'} = $2;
$vol->{'size'} = convertZfsToMebi($zvol->{'vol_size'});
if ($inameinfo && exists($inames{$zvol->{'vol_name'}})) {
$vol->{'iname'} = $inames{$zvol->{'vol_name'}};
if (exists($zvolsizes{$volname})) {
$vol->{'size'} = convertToMebi($zvolsizes{$volname});
} else {
$vol->{'size'} = 0;
warn("*** WARNING: could not get volume size of $volname");
}
if ($inameinfo && exists($inames{$zvol->{'path'}})) {
$vol->{'iname'} = $inames{$zvol->{'path'}};
}
if ($snapinfo) {
my $sref = $snaps{$zvol->{'vol_name'}};
my $sref = $snaps{$zvol->{'path'}};
if ($sref && @$sref > 0) {
$vol->{'snapshots'} = join(',', @$sref);
}
my $sname = $clones{$zvol->{'vol_name'}};
my $sname = $clones{$zvol->{'path'}};
if ($sname) {
$vol->{'cloneof'} = $sname;
}
......@@ -242,6 +467,11 @@ sub freenasPoolList() {
return listPools();
}
#
# Create a ZFS zvol.
# Unbelievably, the FreeNAS 1.0 API does not support creation of a zvol
# so we have to use the old, hacky interface.
#
sub freenasVolumeCreate($$$)
{
my ($pool, $volname, $size) = @_;
......@@ -277,7 +507,7 @@ sub freenasVolumeCreate($$$)
# Allocate volume in zpool
eval { freenasRunCmd($FREENAS_CLI_VERB_VOLUME,
"add $pool $volname ${size}MB off") };
"add $pool $volname ${size}M off") };
if ($@) {
my $msg = " $@";
$msg =~ s/\\n/\n /g;
......@@ -333,13 +563,11 @@ sub freenasVolumeSnapshot($$;$)
}
# 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");
my $res = freenasRequest($FREENAS_API_RESOURCE_SNAPSHOT, "POST", undef,
{"dataset" => "$pool/$volname",
"name" => "$tstamp"});
if (!$res) {
warn("*** ERROR: freenasVolumeSnapshot: could not create snapshot");
return -1;
}
......@@ -384,32 +612,42 @@ sub freenasVolumeDesnapshot($$;$)
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");
#
# XXX only return an error for this case if we are
# removing a specific snapshot. Otherwise, it causes
# too much drama up the line for something that is
# "normal" (i.e., we are attempting to remove all
# snapshots and some of them are in use).
#
if ($tstamp) {
$rv = -1;
}
} else {
my $msg = " $@";
$msg =~ s/\\n/\n /g;
warn("*** ERROR: freenasVolumeDesnapshot: ".
"'del $pool/$snapshot' failed:\n$msg");
# if it isn't an "in use" error, we really do fail
#
# XXX API does not return an error if you try to remove
# a snapshot that has a clone. So we have to check ourselves.
#
if (snapshotHasClone($sname, $vollist)) {
warn("*** WARNING: freenasVolumeDesnapshot: ".
"snapshot '$sname' in use");
#
# XXX only return an error for this case if we are
# removing a specific snapshot. Otherwise, it causes
# too much drama up the line for something that is
# "normal" (i.e., we are attempting to remove all
# snapshots and some of them are in use).
#
if ($tstamp) {
$rv = -1;
}
next;
}
#
# Otherwise, try to remove the snapshot.
#
my $msg;
my $resource =
"$FREENAS_API_RESOURCE_SNAPSHOT/${pool}\%2F${sname}";
my $res = freenasRequest($resource, "DELETE",
undef, undef, undef, \$msg);
if (!$res) {
warn("*** ERROR: freenasVolumeDesnapshot: ".
"delete of $snapshot failed:\n$msg");
# if it isn't an "in use" error, we really do fail
$rv = -1;
}
}
}
......@@ -495,14 +733,13 @@ sub freenasVolumeClone($$$;$)
$snapshot = "$ovolname\@$tag";
}
# Let's do it!
eval { freenasRunCmd($FREENAS_CLI_VERB_SNAPSHOT,
"clone $pool/$snapshot $pool/$nvolname") };
if ($@) {
my $msg = " $@";
$msg =~ s/\\n/\n /g;
warn("*** ERROR: freenasVolumeClone: ".
"'clone $pool/$snapshot $pool/$nvolname' failed:\n$msg");
my $resource =
"$FREENAS_API_RESOURCE_SNAPSHOT/${pool}\%2F${snapshot}/clone";
my $res = freenasRequest($resource, "POST", undef,
{"name" => "$pool/$nvolname"}, 202);
if (!$res) {
warn("*** ERROR: freenasVolumeClone: could not create clone");
return -1;
}
......@@ -568,19 +805,22 @@ sub volumeDestroy($$$$) {
# Deallocate volume. Wrap in loop to enable retries.
my $count;
for ($count = 1; $count <= $MAX_RETRY_COUNT; $count++) {
eval { freenasRunCmd($FREENAS_CLI_VERB_VOLUME,
"del $pool $volname") };
# Process exceptions thrown during deletion attempt. Retry on
# some errors.
if ($@) {
if ($@ =~ /dataset is busy/) {
my $resource = "$FREENAS_API_RESOURCE_VOLUME/$pool/datasets/$volname";
my $msg;
my $res = freenasRequest($resource, "DELETE", undef, undef,
undef, \$msg);
# Retry on some errors
if (!$res) {
if ($msg =~ /dataset is busy/) {
warn("*** WARNING: $tag: ".
"Volume is busy. ".
"Waiting $VOLUME_BUSY_WAIT seconds before trying again ".
"(count=$count).");
sleep $VOLUME_BUSY_WAIT;
}
elsif ($@ =~ /does not exist/) {
elsif ($msg =~ /does not exist/) {
if ($count < $MAX_RETRY_COUNT) {
warn("*** WARNING: $tag: ".
"Volume seems to be gone, retrying.");
......@@ -596,7 +836,6 @@ sub volumeDestroy($$$$) {
}
}
else {