new generic driver for zfs (support nfs share via prop, iscsi via targetcli)

This commit is contained in:
Travis Glenn Hansen 2019-12-16 10:59:39 -07:00
parent 0e3f97877b
commit 1f8bb9322c
5 changed files with 366 additions and 4 deletions

View File

@ -0,0 +1,60 @@
driver: zfs-generic-iscsi
sshConnection:
host: server address
port: 22
username: root
# use either password or key
password: ""
privateKey: |
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
service:
identity: {}
controller: {}
node: {}
zfs:
datasetParentName: tank/k8s/test
detachedSnapshotsDatasetParentName: tanks/k8s/test-snapshots
# "" (inherit), lz4, gzip-9, etc
zvolCompression:
# "" (inherit), on, off, verify
zvolDedup:
zvolEnableReservation: false
# 512, 1K, 2K, 4K, 8K, 16K, 64K, 128K default is 16K
zvolBlocksize:
iscsi:
shareStrategy: "targetCli"
# https://kifarunix.com/how-to-install-and-configure-iscsi-storage-server-on-ubuntu-18-04/
# https://kifarunix.com/how-install-and-configure-iscsi-storage-server-on-centos-7/
# https://linuxlasse.net/linux/howtos/ISCSI_and_ZFS_ZVOL
# http://www.linux-iscsi.org/wiki/ISCSI
# https://bugzilla.redhat.com/show_bug.cgi?id=1659195
# http://atodorov.org/blog/2015/04/07/how-to-configure-iscsi-target-on-red-hat-enterprise-linux-7/
shareStragetyTargetCli:
basename: "iqn.2003-01.org.linux-iscsi.ubuntu-19.x8664"
tpg:
attributes:
# set to 1 to enable CHAP
authentication: 0
# this is required currently as we do not register all node iqns
# the effective outcome of this is, allow all iqns to connect
generate_node_acls: 1
cache_dynamic_acls: 1
# if generate_node_acls is 1 then must turn this off as well (assuming you want write ability)
demo_mode_write_protect: 0
auth:
# CHAP
#userid: "foo"
#password: "bar"
# mutual CHAP
#mutual_userid: "baz"
#mutual_password: "bar"
targetPortal: "server address"
targetPortals: []
interface: ""
namePrefix:
nameSuffix:

View File

@ -0,0 +1,33 @@
driver: zfs-generic-nfs
sshConnection:
host: server address
port: 22
username: root
# use either password or key
password: ""
privateKey: |
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
service:
identity: {}
controller: {}
node: {}
zfs:
datasetParentName: tank/k8s/test
detachedSnapshotsDatasetParentName: tanks/k8s/test-snapshots
datasetEnableQuotas: true
datasetEnableReservation: false
datasetPermissionsMode: "0777"
datasetPermissionsUser: root
datasetPermissionsGroup: root
nfs:
# https://docs.oracle.com/cd/E23824_01/html/821-1448/gayne.html
# https://www.hiroom2.com/2016/05/18/ubuntu-16-04-share-zfs-storage-via-nfs-smb/
shareStrategy: "setDatasetProperties"
shareStrategySetDatasetProperties:
properties:
sharenfs: "on"
# share: ""
shareHost: "server address"

View File

@ -0,0 +1,263 @@
const grpc = require("grpc");
const { ControllerZfsSshBaseDriver } = require("../controller-zfs-ssh");
const { GrpcError } = require("../../utils/grpc");
class ControllerZfsGenericDriver extends ControllerZfsSshBaseDriver {
/**
* cannot make this a storage class parameter as storage class/etc context is *not* sent
* into various calls such as GetControllerCapabilities etc
*/
getDriverZfsResourceType() {
switch (this.options.driver) {
case "zfs-generic-nfs":
return "filesystem";
case "zfs-generic-iscsi":
return "volume";
default:
throw new Error("unknown driver: " + this.ctx.args.driver);
}
}
/**
* should create any necessary share resources
* should set the SHARE_VOLUME_CONTEXT_PROPERTY_NAME propery
*
* @param {*} datasetName
*/
async createShare(call, datasetName) {
const zb = this.getZetabyte();
const sshClient = this.getSshClient();
let properties;
let response;
let share = {};
let volume_context = {};
switch (this.options.driver) {
case "zfs-generic-nfs":
switch (this.options.nfs.shareStrategy) {
case "setDatasetProperties":
for (let key of ["share", "sharenfs"]) {
if (
this.options.nfs.shareStrategySetDatasetProperties.properties[
key
]
) {
await zb.zfs.set(datasetName, {
[key]: this.options.nfs.shareStrategySetDatasetProperties
.properties[key]
});
}
}
break;
default:
break;
}
properties = await zb.zfs.get(datasetName, ["mountpoint"]);
properties = properties[datasetName];
this.ctx.logger.debug("zfs props data: %j", properties);
volume_context = {
node_attach_driver: "nfs",
server: this.options.nfs.shareHost,
share: properties.mountpoint.value
};
return volume_context;
case "zfs-generic-iscsi":
let basename;
let iscsiName = zb.helpers.extractLeafName(datasetName);
if (this.options.iscsi.namePrefix) {
iscsiName = this.options.iscsi.namePrefix + iscsiName;
}
if (this.options.iscsi.nameSuffix) {
iscsiName += this.options.iscsi.nameSuffix;
}
iscsiName = iscsiName.toLowerCase();
let extentDiskName = "zvol/" + datasetName;
/**
* limit is a FreeBSD limitation
* https://www.ixsystems.com/documentation/freenas/11.2-U5/storage.html#zfs-zvol-config-opts-tab
*/
//if (extentDiskName.length > 63) {
// throw new GrpcError(
// grpc.status.FAILED_PRECONDITION,
// `extent disk name cannot exceed 63 characters: ${extentDiskName}`
// );
//}
switch (this.options.iscsi.shareStrategy) {
case "targetCli":
basename = this.options.iscsi.shareStragetyTargetCli.basename;
let setAttributesText = "";
let setAuthText = "";
if (this.options.iscsi.shareStragetyTargetCli.tpg) {
if (this.options.iscsi.shareStragetyTargetCli.tpg.attributes) {
for (const attributeName in this.options.iscsi
.shareStragetyTargetCli.tpg.attributes) {
const attributeValue = this.options.iscsi
.shareStragetyTargetCli.tpg.attributes[attributeName];
setAttributesText += "\n";
setAttributesText += `set attribute ${attributeName}=${attributeValue}`;
}
}
if (this.options.iscsi.shareStragetyTargetCli.tpg.auth) {
for (const attributeName in this.options.iscsi
.shareStragetyTargetCli.tpg.auth) {
const attributeValue = this.options.iscsi
.shareStragetyTargetCli.tpg.auth[attributeName];
setAttributesText += "\n";
setAttributesText += `set auth ${attributeName}=${attributeValue}`;
}
}
}
response = await this.targetCliCommand(
`
# create target
cd /iscsi
create ${basename}:${iscsiName}
# setup tpg
cd /iscsi/${basename}:${iscsiName}/tpg1
${setAttributesText}
${setAuthText}
# create extent
cd /backstores/block
create ${iscsiName} /dev/${extentDiskName}
# add extent to target/tpg
cd /iscsi/${basename}:${iscsiName}/tpg1/luns
create /backstores/block/${iscsiName}
`
);
break;
default:
break;
}
// iqn = target
let iqn = basename + ":" + iscsiName;
this.ctx.logger.info("iqn: " + iqn);
volume_context = {
node_attach_driver: "iscsi",
portal: this.options.iscsi.targetPortal,
portals: this.options.iscsi.targetPortals.join(","),
interface: this.options.iscsi.interface,
iqn: iqn,
lun: 0
};
return volume_context;
default:
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`invalid configuration: unknown driver ${this.options.driver}`
);
}
}
async deleteShare(call, datasetName) {
const zb = this.getZetabyte();
const sshClient = this.getSshClient();
let response;
switch (this.options.driver) {
case "zfs-generic-nfs":
switch (this.options.nfs.shareStrategy) {
case "setDatasetProperties":
break;
default:
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`invalid configuration: unknown shareStrategy ${this.options.nfs.shareStrategy}`
);
}
break;
case "zfs-generic-iscsi":
let basename;
let iscsiName = zb.helpers.extractLeafName(datasetName);
if (this.options.iscsi.namePrefix) {
iscsiName = this.options.iscsi.namePrefix + iscsiName;
}
if (this.options.iscsi.nameSuffix) {
iscsiName += this.options.iscsi.nameSuffix;
}
iscsiName = iscsiName.toLowerCase();
switch (this.options.iscsi.shareStrategy) {
case "targetCli":
basename = this.options.iscsi.shareStragetyTargetCli.basename;
response = await this.targetCliCommand(
`
cd /iscsi
delete ${basename}:${iscsiName}
cd /backstores/block
delete ${iscsiName}
`
);
break;
default:
break;
}
break;
default:
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`invalid configuration: unknown driver ${this.options.driver}`
);
}
return {};
}
async expandVolume(call, datasetName) {
switch (this.options.driver) {
case "zfs-generic-nfs":
break;
case "zfs-generic-iscsi":
switch (this.options.iscsi.shareStrategy) {
case "targetCli":
// nothing required, just need to rescan on the node
break;
default:
break;
}
break;
default:
break;
}
}
async targetCliCommand(data) {
const sshClient = this.getSshClient();
data = data.trim();
let args = ["-c"];
let command = [];
command.push(`echo "${data}"`.trim());
command.push("|");
command.push("targetcli");
args.push("'" + command.join(" ") + "'");
return sshClient.exec(sshClient.buildCommand("sh", args));
}
}
module.exports.ControllerZfsGenericDriver = ControllerZfsGenericDriver;

View File

@ -708,10 +708,12 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
const res = { const res = {
volume: { volume: {
volume_id: name, volume_id: name,
capacity_bytes: capacity_bytes, // kubernetes currently pukes if capacity is returned as 0 //capacity_bytes: capacity_bytes, // kubernetes currently pukes if capacity is returned as 0
//capacity_bytes: this.options.zfs.datasetEnableQuotas capacity_bytes:
// ? capacity_bytes this.options.zfs.datasetEnableQuotas ||
// : 0, driverZfsResourceType == "volume"
? capacity_bytes
: 0,
content_source: volume_content_source, content_source: volume_content_source,
volume_context volume_context
} }

View File

@ -1,10 +1,14 @@
const { FreeNASDriver } = require("./freenas"); const { FreeNASDriver } = require("./freenas");
const { ControllerZfsGenericDriver } = require("./controller-zfs-generic");
function factory(ctx, options) { function factory(ctx, options) {
switch (options.driver) { switch (options.driver) {
case "freenas-nfs": case "freenas-nfs":
case "freenas-iscsi": case "freenas-iscsi":
return new FreeNASDriver(ctx, options); return new FreeNASDriver(ctx, options);
case "zfs-generic-nfs":
case "zfs-generic-iscsi":
return new ControllerZfsGenericDriver(ctx, options);
default: default:
throw new Error("invalid csi driver: " + options.driver); throw new Error("invalid csi driver: " + options.driver);
} }