Commit 9b6e1a59 authored by Kirk Webb's avatar Kirk Webb

Backend support for simultaneous read-only dataset access.

Any number of users/experiments can mount a given dataset (given that
they have permission) in read-only mode.  Attempts to mount RW will
fail if the dataset is currently in use.  Attempts to mount RO while
the dataset is in use RW are also prohibited.

Under the hood, iSCSI lease exports (targets) are now managed per-lease
instead of per-experiment.  The set of authorized initiators (based
on network) is manipulated as consumers come and go.  When the last
consumer goes, the export is torn down. Likewise, if there are no
current consumers, a new consumer will cause an iSCSI export to be
created for the lease.

Also included in this commit is a small tweak to implicit lease permissions.
parent bedcb609
......@@ -643,10 +643,13 @@ sub allocSlice($$$$) {
# sure it exists, but do nothing else.
#
if ($bsid =~ /^lease-\d+$/) {
my $volumes = freenasVolumeList(0);
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;
}
warn("*** ERROR: blockstore_allocSlice: $volname: ".
......@@ -673,7 +676,8 @@ sub exportSlice($$$$) {
"Error calculating ip network information.");
return -1;
}
my $bsid = $sconf->{'BSID'};
my $volname = $sconf->{'VOLNAME'};
$volname = "UNKNOWN" if (!$volname);
......@@ -717,54 +721,132 @@ sub exportSlice($$$$) {
# XXX we lowercase the IQN since the ist_assoc will blow up with caps!
my $iqn = lc($1);
# Create iSCSI extent
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_EXTENT,
"add $iqn $pool/$volume") };
if ($@) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Failed to create iSCSI extent: $@");
return -1;
}
# Create iSCSI auth group
my $tag = getNextAuthITag();
if ($tag !~ /^(\d+)$/) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"bad tag returned from getNextAuthITag: $tag");
return -1;
}
$tag = $1; # untaint.
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_AUTHI,
"add $tag ALL $network/$cmask $vnode_id") };
if ($@) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Failed to create iSCSI auth group: $@");
return -1;
}
my $perm = "rw";
if (exists($sconf->{'PERMS'}) && $sconf->{'PERMS'} eq "RO") {
$perm = "ro";
}
# Create iSCSI target
my $serial = genSerial();
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_TARGET,
"add $iqn $serial $ISCSI_GLOBAL_PORTAL ".
"$tag Auto -1 flags=$perm") };
if ($@) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Failed to create iSCSI target: $@");
return -1;
# 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 ($bsid =~ /^lease-\d+$/) {
$tag_ident = $bsid;
}
# Bind iSCSI target to slice (extent)
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_ASSOC,
"add $iqn $iqn") };
if ($@) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Failed to associate iSCSI target with extent: $@");
return -1;
# 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.)
if (!exists($priv->{'iname'})) {
# Create iSCSI extent.
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_EXTENT,
"add $iqn $pool/$volume") };
if ($@) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Failed to create iSCSI extent: $@");
return -1;
}
# Create iSCSI auth group
my $tag = getNextAuthITag();
if ($tag !~ /^(\d+)$/) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"bad tag returned from getNextAuthITag: $tag");
return -1;
}
$tag = $1; # untaint.
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_AUTHI,
"add $tag ALL $network/$cmask $tag_ident") };
if ($@) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Failed to create iSCSI auth group: $@");
return -1;
}
# Create iSCSI target
my $serial = genSerial();
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_TARGET,
"add $iqn $serial $ISCSI_GLOBAL_PORTAL ".
"$tag Auto -1 flags=$perm") };
if ($@) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Failed to create iSCSI target: $@");
return -1;
}
# Bind iSCSI target to slice (extent)
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_ASSOC,
"add $iqn $iqn") };
if ($@) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Failed to associate iSCSI target with extent: $@");
return -1;
}
}
# The iSCSI target/extent setup is already in place, so just
# modify the authorized networks. Check that the incoming
# requested perms are RO, and validate that the dataset isn't
# currently in use RW.
else {
# Check requested perms.
if ($perm ne "ro") {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Cannot re-export in-use dataset as RW!");
return -1;
}
# 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) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Couldn't find the iSCSI target while attempting re-export!");
return -1;
}
# Grab the current iSCSI initiator group so we can modify it.
my $authent = findAuthITag($tag_ident);
if (!$authent) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Couldn't find the iSCSI auth group entry while attempting re-export!");
return -1;
}
if ($authent->{'tag'} !~ /^(\d+)$/) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Malformed tag returned for current authentication group entry while attempting re-export!");
return -1;
}
my $curtag = $1; # untaint;
my @auth_networks = split(/\s+/, $authent->{'auth_network'});
# The incoming network shouldn't already be there, but let's check.
if (grep(/^$network\/$cmask$/, @auth_networks)) {
warn("*** WARNING: blockstore_exportSlice: $volname: ".
"Our network is already present in iSCSI auth group: $network/$cmask");
} else {
push @auth_networks, "$network/$cmask";
my $auth_network_str = join(' ', @auth_networks);
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_AUTHI,
"edit $curtag ALL ".
"'$auth_network_str' $tag_ident") };
if ($@) {
warn("*** ERROR: blockstore_exportSlice: $volname: ".
"Failed to modify iSCSI auth group (re-export): $@");
return -1;
}
}
}
# All setup and exported!
......@@ -772,12 +854,12 @@ sub exportSlice($$$$) {
}
# Helper function.
# Locate and return tag for given network, if it exists.
# Locate and return tag for given identifier, if it exists.
sub findAuthITag($) {
my ($vnode_id,) = @_;
my ($ident,) = @_;
return undef
if !defined($vnode_id);
if !defined($ident);
my @authentries = freenasParseListing($FREENAS_CLI_VERB_IST_AUTHI);
......@@ -785,8 +867,8 @@ sub findAuthITag($) {
if !@authentries;
foreach my $authent (@authentries) {
if ($authent->{'comment'} eq $vnode_id) {
return $authent->{'tag'};
if ($authent->{'comment'} eq $ident) {
return $authent;
}
}
......@@ -1078,9 +1160,13 @@ sub removeVlanInterface($$) {
return 0;
}
#
# Reverse counterpart to exportSlice().
#
sub unexportSlice($$$$) {
my ($vnode_id, $sconf, $vnconfig, $priv) = @_;
my $bsid = $sconf->{'BSID'};
my $volname = $sconf->{'VOLNAME'};
$volname = "UNKNOWN" if (!$volname);
......@@ -1089,40 +1175,88 @@ sub unexportSlice($$$$) {
$sconf->{'UUID'} =~ /^([-\.:\w]+)$/;
my $iqn = lc($1); # untaint and lowercase.
# Remove iSCSI target to extent mapping.
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_ASSOC,
"del $iqn $iqn") };
if ($@) {
warn("*** WARNING: blockstore_unexportSlice: $volname: ".
"Failed to disassociate iSCSI target with extent: $@");
# There should only be one ifconfig entry - checked earlier.
my $ifcfg = (@{$vnconfig->{'ifconfig'}})[0];
my $nmask = $ifcfg->{'IPMASK'};
my $cmask = libutil::CIDRmask($nmask);
my $network = libutil::ipToNetwork($ifcfg->{'IPADDR'}, $nmask);
if (!$cmask || !$network) {
warn("*** ERROR: blockstore_unexportSlice: ".
"Error calculating ip network information.");
return -1;
}
# If operating on a lease, we use the lease name as the
# iSCSI auth group identifier instead of the vnode_id because
# this entry may be shared (simultaneous RO use).
my $tag_ident = $vnode_id;
if ($bsid =~ /^(lease-\d+)$/) {
$tag_ident = $1; # untaint
}
# Remove iSCSI target.
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_TARGET,
"del $iqn") };
if ($@) {
warn("*** WARNING: blockstore_unexportSlice: $volname: ".
"Failed to remove iSCSI target: $@");
# Fetch the authorized initators entry associated with this slice,
# and yank its network entry out of the list. It this is the last
# remaining entry, we tear down the export. If any network entries
# remain, this tells us that other experiments are still using
# this iSCSI target (simultaneous RO). In that case we only
# modify the set of authorized initiators.
my $authtag = 0;
my @pruned_networks = ();
my $curtag = findAuthITag($tag_ident);
if ($curtag && $curtag->{'tag'} =~ /^(\d+)$/) {
$authtag = $1; # untaint;
@pruned_networks = grep {!/^$network\/$cmask$/} split(/\s+/, $curtag->{'auth_network'});
}
# Remove iSCSI auth group
my $tag = findAuthITag($vnode_id);
if ($tag && $tag =~ /^(\d+)$/) {
$tag = $1; # untaint.
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_AUTHI,
"del $tag") };
# 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 ($@) {
warn("*** WARNING: blockstore_unexportSlice: $volname: ".
"Failed to remove iSCSI auth group: $@");
"Failed to disassociate iSCSI target with extent: $@");
}
# Remove iSCSI target.
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_TARGET,
"del $iqn") };
if ($@) {
warn("*** WARNING: blockstore_unexportSlice: $volname: ".
"Failed to remove iSCSI target: $@");
}
# 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: $@");
}
}
}
# Remove iSCSI extent.
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_EXTENT,
"del $iqn") };
if ($@) {
warn("*** WARNING: blockstore_unexportSlice: $volname: ".
"Failed to remove iSCSI extent: $@");
# Remove iSCSI extent.
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_EXTENT,
"del $iqn") };
if ($@) {
warn("*** WARNING: blockstore_unexportSlice: $volname: ".
"Failed to remove iSCSI extent: $@");
}
}
# This export is still referenced, so leave export but update the
# authorized initiators list.
else {
my $auth_network_str = join(' ', @pruned_networks);
eval { freenasRunCmd($FREENAS_CLI_VERB_IST_AUTHI,
"edit $authtag ALL '$auth_network_str' ".
"$tag_ident") };
if ($@) {
warn("*** ERROR: blockstore_unexportSlice: $volname: ".
"Failed to modify iSCSI auth group (re-export): $@");
return -1;
}
}
# All torn down and unexported!
......
......@@ -1426,15 +1426,17 @@ sub AccessCheck($$$) {
if ($gid eq "");
my $group = Group->Lookup($pid, $gid);
# Project managers can do anything to a lease that is attributed
# to their project.
if (TBMinTrust($group->Trust($user), PROJMEMBERTRUST_GROUPROOT())) {
# Members of the owning project have some implicit permissions, depending
# on their project trust.
my $gtrust = $group->Trust($user);
if (TBMinTrust($gtrust, PROJMEMBERTRUST_GROUPROOT())) {
return 1;
}
# If the user is a member of the owning project, then they can at
# least grab the lease's info.
if (TBMinTrust($group->Trust($user), PROJMEMBERTRUST_USER())) {
# XXX: Need to decide what the right thing to do is here.
#elsif (TBMinTrust($gtrust, PROJMEMBERTRUST_LOCALROOT())) {
# $user_access = LEASE_ACCESS_READ();
#}
elsif (TBMinTrust($gtrust, PROJMEMBERTRUST_USER())) {
$user_access = LEASE_ACCESS_READINFO();
}
......
......@@ -4545,7 +4545,7 @@ sendstoreconf(int sock, int tcp, tmcdreq_t *reqp, char *bscmd, char *vname,
char iqn[BS_IQN_MAXSIZE];
char *mynodeid;
char *class, *protocol, *placement, *mountpoint, *lease;
int nrows, nattrs, ro;
int nrows, nattrs, ro, slen;
/* Remember the nodeid we care about up front. */
mynodeid = reqp->isvnode ? reqp->vnodeid : reqp->nodeid;
......@@ -4590,10 +4590,20 @@ sendstoreconf(int sock, int tcp, tmcdreq_t *reqp, char *bscmd, char *vname,
/* iSCSI blockstore */
if ((strcmp(class, BS_CLASS_SAN) == 0) &&
(strcmp(protocol, BS_PROTO_ISCSI) == 0)) {
/* Construct IQN string. */
if (snprintf(iqn, sizeof(iqn), "%s:%s:%s:%s",
BS_IQN_PREFIX, reqp->pid,
reqp->eid, vname) >= sizeof(iqn)) {
/*
* Construct IQN string. Leases have a static IQN,
* whereas ephemeral blockstores have IQNs based on
* experiment-specific data.
*/
if (strlen(lease) && atoi(lease) != 0) {
slen = snprintf(iqn, sizeof(iqn), "%s:lease-%s",
BS_IQN_PREFIX, lease);
} else {
slen = snprintf(iqn, sizeof(iqn), "%s:%s:%s:%s",
BS_IQN_PREFIX, reqp->pid,
reqp->eid, vname);
}
if (slen >= sizeof(iqn)) {
error("STORAGECONFIG: %s: Not enough room in "
"IQN string buffer", mynodeid);
mysql_free_result(res);
......
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