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) = @_;
......
This diff is collapsed.
......@@ -134,15 +134,12 @@ my $debug = 0;
# Local Functions
#
sub getSliceList();
sub parseSliceName($);
sub parseSlicePath($);
sub calcSliceSizes($);
sub restartIstgt();
sub getIfConfig($);
sub getVlan($);
sub getNextAuthITag();
sub genSerial();
sub findAuthITag($);
sub findAuthITag($;$);
sub createVlanInterface($$);
sub removeVlanInterface($$);
sub setupIPAlias($;$);
......@@ -286,7 +283,7 @@ sub vnodeCreate($$$$)
# Create vmid from the vnode's name.
my $vmid;
if ($vnode_id =~ /^\w+\d+\-(\d+)$/) {
if ($vnode_id =~ /^\w+\-(\d+)$/) {
$vmid = $1;
} else {
fatal("blockstore_vnodeCreate: ".
......@@ -537,99 +534,8 @@ sub runBlockstoreCmds($$$) {
return 0;
}
# Yank information about blockstore slices out of FreeNAS.
# Note: this is an expensive call - may want to re-visit caching some of
# this later if performance becomes a problem.
sub getSliceList() {
my $sliceshash = {};
# Grab list of slices (iscsi extents) from FreeNAS
my @slist = freenasParseListing($FREENAS_CLI_VERB_IST_EXTENT);
# Just return if there are no slices.
return if !@slist;
# Go through each slice hash, culling out extra info.
# Save hash in global list. Throw out malformed stuff.
foreach my $slice (@slist) {
my ($pid,$eid,$volname) = parseSliceName($slice->{'name'});
my ($bsid, $vnode_id) = parseSlicePath($slice->{'path'});
if (!defined($pid) || !defined($bsid)) {
warn("*** WARNING: blockstore_getSliceList: ".
"malformed slice entry, skipping.");
next;
}
$slice->{'pid'} = $pid;
$slice->{'eid'} = $eid;
$slice->{'volname'} = $volname;
$slice->{'bsid'} = $bsid;
$slice->{'vnode_id'} = $vnode_id;
$sliceshash->{$vnode_id} = $slice;
}
# Do the messy work of getting slice size info into mebibytes.
calcSliceSizes($sliceshash);
return $sliceshash;
}
# helper function.
# Slice names look like: 'iqn.<date>.<tld>.<domain>:<pid>:<eid>:<volname>'
sub parseSliceName($) {
my $name = shift;
my @parts = split(/:/, $name);
if (scalar(@parts) != 4) {
warn("*** WARNING: blockstore_parseSliceName: Bad slice name: $name");
return undef;
}
shift @parts;
return @parts;
}
# helper function.
# Paths look like this: '/mnt/<blockstore_id>/<vnode_id>' for file-based
# extent (slice), and 'zvol/<blockstore_id>/<vnode_id>' for zvol extents.
sub parseSlicePath($) {
my $path = shift;
my @parts = split(/\//, $path);
shift @parts
if (scalar(@parts) == 4 && !$parts[0]); # chomp leading slash part
if (scalar(@parts) != 3 || $parts[0] !~ /^(mnt|zvol)$/i) {
warn("*** WARNING: blockstore_parseSlicePath: ".
"malformed slice path: $path");
return undef;
}
shift @parts;
return @parts;
}
sub calcSliceSizes($) {
my $sliceshash = shift;
# Ugh... Have to look up size via the "volume" list for zvol slices.
my $zvollist = freenasVolumeList(0, 0);
foreach my $slice (values(%$sliceshash)) {
my $vnode_id = $slice->{'vnode_id'};
my $type = lc($slice->{'type'});
if ($type eq "zvol") {
if (!exists($zvollist->{$vnode_id})) {
warn("*** WARNING: blockstore_calcSliceList: ".
"Could not find matching volume entry ($vnode_id) for ".
"zvol slice: $slice->{'name'}");
next;
}
# already converted to Mebi
$slice->{'size'} = $zvollist->{$vnode_id}->{'size'};
} elsif ($type eq "file") {
my $size = $slice->{'filesize'};
$size =~ s/B$/iB/; # re-write with correct units.
$slice->{'size'} = convertToMebi($size);
}
}
return;
return freenasSliceList();
}
# Helper function - restart the ISTGT process to reconfigure iSCSI stuff.
......@@ -872,15 +778,23 @@ sub exportSlice($$$$) {
# 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,
"add $iqn $pool/$volume") };
if ($@) {
#
my $res = freenasRequest($FREENAS_API_RESOURCE_IST_EXTENT,
"POST", undef,
{"iscsi_target_extent_name" => $iqn,
"iscsi_target_extent_type" => "Disk",
"iscsi_target_extent_disk" => "zvol/$pool/$volume"});
if (!$res) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Failed to create iSCSI extent: $@");
return -1;
}
# save the index for making a target association
my $eindex = $res->{'id'};
# Create iSCSI auth group
my $tag = getNextAuthITag();
if ($tag !~ /^(\d+)$/) {
......@@ -889,6 +803,15 @@ sub exportSlice($$$$) {
return -1;
}
$tag = $1; # untaint.
#
# Create an authorized initiator.
#
# XXX sigh...once again the FreeNAS API does not work, it just
# returns status 302 with reason "FOUND" which is normally a
# redirect to a new location (but it returns the same location
# in this case).
#
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_AUTHI,
"add $tag ALL $network/$cmask $tag_ident") };
if ($@) {
......@@ -897,21 +820,39 @@ sub exportSlice($$$$) {
return -1;
}
# Create iSCSI target
#
# Create iSCSI target.
#
# XXX ugh, FreeNAS 9.3 no longer supports RO iSCSI targets?
# This is not just an API thing, you cannot do it through the
# GUI either!
#
my $serial = genSerial();
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_TARGET,
"add $iqn $serial $ISCSI_GLOBAL_PORTAL ".
"$tag Auto -1 flags=$perm") };
if ($@) {
$res = freenasRequest($FREENAS_API_RESOURCE_IST_TARGET,
"POST", undef,
{"iscsi_target_name" => $iqn,
"iscsi_target_portalgroup" => $ISCSI_GLOBAL_PORTAL,
"iscsi_target_initiatorgroup" => $tag,
"iscsi_target_authtype" => "None",
"iscsi_target_serial" => $serial});
if (!$res) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Failed to create iSCSI target: $@");
return -1;
}
if (!exists($res->{'id'})) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"No index for iSCSI target we just created!?");
return -1;
}
my $tindex = $res->{'id'};
# Bind iSCSI target to slice (extent)
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_ASSOC,
"add $iqn $iqn") };
if ($@) {
$res = freenasRequest($FREENAS_API_RESOURCE_IST_ASSOC,
"POST", undef,
{"iscsi_target" => $tindex,
"iscsi_extent" => $eindex});
if (!$res) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Failed to associate iSCSI target with extent: $@");
return -1;
......@@ -935,25 +876,19 @@ sub exportSlice($$$$) {
# Check current use mode - must not be RW. Searching for the
# target entry serves as a sanity check as well.
my $found = 0;
my @ist_list = freenasParseListing($FREENAS_CLI_VERB_IST_TARGET);
foreach my $ist (@ist_list) {
if ($ist->{'name'} eq $iqn) {
if ($ist->{'flags'} ne "ro") {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Cannot re-export in-use RW dataset!");
return -1;
} else {
$found = 1;
last;
}
}
}
if (!$found) {
my $targets = freenasTargetList(1);
if (!exists($targets->{$iqn})) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Couldn't find the iSCSI target while attempting re-export!");
return -1;
}
# XXX should check for RO vs RW, but that is not exposed by
# FreeNAS as of 9.3
if (0) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Cannot re-export in-use RW dataset!");
return -1;
}
# Grab the current iSCSI initiator group so we can modify it.
my $authent = findAuthITag($tag_ident);
......@@ -1001,20 +936,22 @@ sub exportSlice($$$$) {
# Helper function.
# Locate and return tag for given identifier, if it exists.
sub findAuthITag($) {
my ($ident,) = @_;
sub findAuthITag($;$) {
my ($ident,$idxp) = @_;
return undef
if !defined($ident);
my @authentries = freenasParseListing($FREENAS_CLI_VERB_IST_AUTHI);
my $aiinfo = freenasAuthInitList();
return undef
if !@authentries;
foreach my $authent (@authentries) {
if ($authent->{'comment'} eq $ident) {
return $authent;
foreach my $ai (keys %{$aiinfo}) {
my $aient = $aiinfo->{$ai};
if (exists($aient->{'comment'}) && $aient->{'comment'} eq $ident &&
exists($aient->{'tag'})) {
if ($idxp) {
$$idxp = $aient->{'id'};
}
return $aient;
}
}
......@@ -1024,19 +961,21 @@ sub findAuthITag($) {
# Helper function.
# Locate and return next unused tag ID for iSCSI initiator groups.
sub getNextAuthITag() {
my @authentries = freenasParseListing($FREENAS_CLI_VERB_IST_AUTHI);
my $freetag = 1;
return $freetag
if !@authentries;
my $aiinfo = freenasAuthInitList();
foreach my $curtag (sort {$a <=> $b} map {$_->{'tag'}} @authentries) {
next if (!defined($curtag) || $curtag !~ /^\d+$/);
if ($freetag < $curtag) {
last;
my @taglist = ();
foreach my $ai (keys %{$aiinfo}) {
my $tag = $aiinfo->{$ai}->{'tag'};
if (defined($tag) && $tag =~ /^(\d+)$/) {
push(@taglist, $1);
}
$freetag += 1;
}
my $freetag = 1;
foreach my $curtag (sort {$a <=> $b} @taglist) {
last
if ($freetag < $curtag);
$freetag++;
}
return $freetag;
......@@ -1348,7 +1287,8 @@ sub unexportSlice($$$$) {
# modify the set of authorized initiators.
my $authtag = 0;
my @pruned_networks = ();
my $curtag = findAuthITag($tag_ident);
my $authidx;
my $curtag = findAuthITag($tag_ident,\$authidx);
if ($curtag && $curtag->{'tag'} =~ /^(\d+)$/) {
$authtag = $1; # untaint;
@pruned_networks = grep {!/^$network\/$cmask$/} split(/\s+/, $curtag->{'auth_network'});
......@@ -1357,38 +1297,48 @@ sub unexportSlice($$$$) {
# If there are no networks left in the authorized initiators list, then
# we are completely done with this export - tear it down!
if (!@pruned_networks) {
# Remove iSCSI target to extent mapping.
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_ASSOC,
"del $iqn $iqn") };
if ($@) {
my ($associd,$targetid,$extentid,$msg);
my $assocs = freenasAssocList();
foreach my $aid (keys %$assocs) {
if (exists($assocs->{$aid}->{'target_name'}) &&
$assocs->{$aid}->{'target_name'} eq $iqn &&
exists($assocs->{$aid}->{'extent_name'}) &&
$assocs->{$aid}->{'extent_name'} eq $iqn) {
$associd = $aid;
$targetid = $assocs->{$aid}->{'target'};
$extentid = $assocs->{$aid}->{'extent'};
last;
}
}
if (!$associd ||
!freenasRequest("$FREENAS_API_RESOURCE_IST_ASSOC/$associd",
"DELETE", undef, undef, undef, \$msg)) {
warn("*** WARNING: blockstore_unexportSlice: $volname: ".
"Failed to disassociate iSCSI target with extent: $@");
"Failed to disassociate iSCSI target with extent:\n$msg");
}
# Remove iSCSI target.
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_TARGET,
"del $iqn") };
if ($@) {
if (!$targetid ||
!freenasRequest("$FREENAS_API_RESOURCE_IST_TARGET/$targetid",
"DELETE", undef, undef, undef, \$msg)) {
warn("*** WARNING: blockstore_unexportSlice: $volname: ".
"Failed to remove iSCSI target: $@");
"Failed to remove iSCSI target:\n$msg");
}
# Remove iSCSI auth group
if ($authtag) {
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_AUTHI,
"del $authtag") };
if ($@) {
warn("*** WARNING: blockstore_unexportSlice: $volname: ".
"Failed to remove iSCSI auth group: $@");
}
if (!$authidx ||
!freenasRequest("$FREENAS_API_RESOURCE_IST_AUTHI/$authidx",
"DELETE", undef, undef, undef, \$msg)) {
warn("*** WARNING: blockstore_unexportSlice: $volname: ".
"Failed to remove iSCSI auth group:\n$msg");
}
# Remove iSCSI extent.
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_EXTENT,
"del $iqn") };
if ($@) {
if (!$extentid ||
!freenasRequest("$FREENAS_API_RESOURCE_IST_EXTENT/$extentid",
"DELETE", undef, undef, undef, \$msg)) {
warn("*** WARNING: blockstore_unexportSlice: $volname: ".
"Failed to remove iSCSI extent: $@");
"Failed to remove iSCSI extent:\n$msg");
}
}
# This export is still referenced, so leave export but update the
......
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