preview support for cifs/smb

This commit is contained in:
Travis Glenn Hansen 2020-09-08 10:06:09 -06:00
parent f5ba51c9b8
commit 5ae0248ba8
5 changed files with 293 additions and 31 deletions

View File

@ -116,7 +116,7 @@ You may install multiple deployments of each/any driver. It requires the followi
Install beta (v1.17+) CRDs (once per cluster):
- https://github.com/kubernetes-csi/external-snapshotter/tree/master/config/crd
- https://github.com/kubernetes-csi/external-snapshotter/tree/master/client/config/crd
```
kubectl apply -f snapshot.storage.k8s.io_volumesnapshotclasses.yaml

View File

@ -181,7 +181,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
if (
capability.mount.fs_type &&
!["nfs"].includes(capability.mount.fs_type)
!["nfs", "cifs"].includes(capability.mount.fs_type)
) {
message = `invalid fs_type ${capability.mount.fs_type}`;
return false;
@ -694,9 +694,21 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
response = await sshClient.exec(command);
}
// set acls
// TODO: this is unsfafe approach, make it better
if (this.options.zfs.datasetPermissionsAcls) {
for (const acl of this.options.zfs.datasetPermissionsAcls) {
command = sshClient.buildCommand("setfacl", [
acl,
properties.mountpoint.value,
]);
driver.ctx.logger.verbose("set acl command: %s", command);
response = await sshClient.exec(command);
}
}
break;
case "volume":
// TODO: create all the necessary iscsi stuff
// set properties
// set reserve
setProps = true;

View File

@ -9,8 +9,10 @@ const { ControllerNfsClientDriver } = require("./controller-nfs-client");
function factory(ctx, options) {
switch (options.driver) {
case "freenas-nfs":
case "freenas-smb":
case "freenas-iscsi":
case "truenas-nfs":
case "truenas-smb":
case "truenas-iscsi":
return new FreeNASDriver(ctx, options);
case "zfs-generic-nfs":

View File

@ -6,6 +6,7 @@ const Handlebars = require("handlebars");
// freenas properties
const FREENAS_NFS_SHARE_PROPERTY_NAME = "democratic-csi:freenas_nfs_share_id";
const FREENAS_SMB_SHARE_PROPERTY_NAME = "democratic-csi:freenas_smb_share_id";
const FREENAS_ISCSI_TARGET_ID_PROPERTY_NAME =
"democratic-csi:freenas_iscsi_target_id";
const FREENAS_ISCSI_EXTENT_ID_PROPERTY_NAME =
@ -22,6 +23,8 @@ class FreeNASDriver extends ControllerZfsSshBaseDriver {
switch (this.options.driver) {
case "freenas-nfs":
case "truenas-nfs":
case "freenas-smb":
case "truenas-smb":
return "filesystem";
case "freenas-iscsi":
case "truenas-iscsi":
@ -45,6 +48,9 @@ class FreeNASDriver extends ControllerZfsSshBaseDriver {
case "freenas-iscsi":
case "truenas-iscsi":
return "iscsi";
case "freenas-smb":
case "truenas-smb":
return "smb";
default:
throw new Error("unknown driver: " + this.ctx.args.driver);
}
@ -123,6 +129,7 @@ class FreeNASDriver extends ControllerZfsSshBaseDriver {
const zb = this.getZetabyte();
let properties;
let endpoint;
let response;
let share = {};
@ -207,7 +214,9 @@ class FreeNASDriver extends ControllerZfsSshBaseDriver {
} else {
throw new GrpcError(
grpc.status.UNKNOWN,
`received error creating nfs share - code: ${response.statusCode} body: ${response.body}`
`received error creating nfs share - code: ${
response.statusCode
} body: ${JSON.stringify(response.body)}`
);
}
}
@ -234,6 +243,179 @@ class FreeNASDriver extends ControllerZfsSshBaseDriver {
return volume_context;
}
break;
case "smb":
properties = await zb.zfs.get(datasetName, [
"mountpoint",
FREENAS_SMB_SHARE_PROPERTY_NAME,
]);
properties = properties[datasetName];
this.ctx.logger.debug("zfs props data: %j", properties);
let smbName;
if (this.options.smb.nameTemplate) {
smbName = Handlebars.compile(this.options.smb.nameTemplate)({
name: call.request.name,
parameters: call.request.parameters,
});
} else {
smbName = zb.helpers.extractLeafName(datasetName);
}
if (this.options.smb.namePrefix) {
smbName = this.options.smb.namePrefix + smbName;
}
if (this.options.smb.nameSuffix) {
smbName += this.options.smb.nameSuffix;
}
smbName = smbName.toLowerCase();
this.ctx.logger.info(
"FreeNAS creating smb share with name: " + smbName
);
// create smb share
if (
!zb.helpers.isPropertyValueSet(
properties[FREENAS_SMB_SHARE_PROPERTY_NAME].value
)
) {
/**
* The only required parameters are:
* - path
* - name
*
* Note that over time it appears the list of available parameters has increased
* so in an effort to best support old versions of FreeNAS we should check the
* presense of each parameter in the config and set the corresponding parameter in
* the API request *only* if present in the config.
*/
switch (apiVersion) {
case 1:
case 2:
share = {
name: smbName,
path: properties.mountpoint.value,
};
let propertyMapping = {
shareTemplate: "auxsmbconf",
shareHome: "home",
shareAllowedHosts: "hostsallow",
shareDeniedHosts: "hostsdeny",
shareDefaultPermissions: "default_permissions",
shareGuestOk: "guestok",
shareGuestOnly: "guestonly",
shareShowHiddenFiles: "showhiddenfiles",
shareRecycleBin: "recyclebin",
shareBrowsable: "browsable",
shareAccessBasedEnumeration: "abe",
shareTimeMachine: "timemachine",
shareStorageTask: "storage_task",
};
for (const key in propertyMapping) {
if (this.options.smb.hasOwnProperty(key)) {
let value;
switch (key) {
case "shareTemplate":
value = Handlebars.compile(
this.options.smb.shareTemplate
)({
name: call.request.name,
parameters: call.request.parameters,
});
break;
default:
value = this.options.smb[key];
break;
}
share[propertyMapping[key]] = value;
}
}
switch (apiVersion) {
case 1:
endpoint = "/sharing/cifs";
// rename keys with cifs_ prefix
for (const key in share) {
share["cifs_" + key] = share[key];
delete share[key];
}
// convert to comma-separated list
if (share.cifs_hostsallow) {
share.cifs_hostsallow = share.cifs_hostsallow.join(",");
}
// convert to comma-separated list
if (share.cifs_hostsdeny) {
share.cifs_hostsdeny = share.cifs_hostsdeny.join(",");
}
break;
case 2:
endpoint = "/sharing/smb";
break;
}
response = await httpClient.post(endpoint, share);
/**
* v1 = 201
* v2 = 200
*/
if ([200, 201].includes(response.statusCode)) {
//set zfs property
await zb.zfs.set(datasetName, {
[FREENAS_SMB_SHARE_PROPERTY_NAME]: response.body.id,
});
} else {
/**
* v1 = 409
* v2 = 422
*/
if (
[409, 422].includes(response.statusCode) &&
JSON.stringify(response.body).includes(
"You can't share same filesystem with all hosts twice."
)
) {
// move along
} else {
throw new GrpcError(
grpc.status.UNKNOWN,
`received error creating smb share - code: ${
response.statusCode
} body: ${JSON.stringify(response.body)}`
);
}
}
let volume_context = {
node_attach_driver: "smb",
server: this.options.smb.shareHost,
share: smbName,
};
return volume_context;
default:
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`invalid configuration: unknown apiVersion ${apiVersion}`
);
}
} else {
let volume_context = {
node_attach_driver: "smb",
server: this.options.smb.shareHost,
share: smbName,
};
return volume_context;
}
break;
case "iscsi":
properties = await zb.zfs.get(datasetName, [
FREENAS_ISCSI_TARGET_ID_PROPERTY_NAME,
@ -835,6 +1017,7 @@ class FreeNASDriver extends ControllerZfsSshBaseDriver {
let properties;
let response;
let endpoint;
let shareId;
switch (driverShareType) {
case "nfs":
@ -851,7 +1034,7 @@ class FreeNASDriver extends ControllerZfsSshBaseDriver {
properties = properties[datasetName];
this.ctx.logger.debug("zfs props data: %j", properties);
let shareId = properties[FREENAS_NFS_SHARE_PROPERTY_NAME].value;
shareId = properties[FREENAS_NFS_SHARE_PROPERTY_NAME].value;
// remove nfs share
if (
@ -896,6 +1079,68 @@ class FreeNASDriver extends ControllerZfsSshBaseDriver {
}
}
break;
case "smb":
try {
properties = await zb.zfs.get(datasetName, [
FREENAS_SMB_SHARE_PROPERTY_NAME,
]);
} catch (err) {
if (err.toString().includes("dataset does not exist")) {
return;
}
throw err;
}
properties = properties[datasetName];
this.ctx.logger.debug("zfs props data: %j", properties);
shareId = properties[FREENAS_SMB_SHARE_PROPERTY_NAME].value;
// remove smb share
if (
properties &&
properties[FREENAS_SMB_SHARE_PROPERTY_NAME] &&
properties[FREENAS_SMB_SHARE_PROPERTY_NAME].value != "-"
) {
switch (apiVersion) {
case 1:
case 2:
switch (apiVersion) {
case 1:
endpoint = `/sharing/cifs/${shareId}`;
break;
case 2:
endpoint = `/sharing/smb/id/${shareId}`;
break;
}
response = await httpClient.get(endpoint);
// assume share is gone for now
if ([404, 500].includes(response.statusCode)) {
} else {
response = await httpClient.delete(endpoint);
// returns a 500 if does not exist
// v1 = 204
// v2 = 200
if (![200, 204].includes(response.statusCode)) {
throw new GrpcError(
grpc.status.UNKNOWN,
`received error deleting smb share - share: ${shareId} code: ${
response.statusCode
} body: ${JSON.stringify(response.body)}`
);
}
}
break;
default:
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`invalid configuration: unknown apiVersion ${apiVersion}`
);
}
}
break;
case "iscsi":
// Delete target
// NOTE: deletting a target inherently deletes associated targetgroup(s) and targettoextent(s)

View File

@ -75,14 +75,14 @@ class CsiBaseDriver {
async GetPluginInfo(call) {
return {
name: this.ctx.args.csiName,
vendor_version: this.ctx.args.version
vendor_version: this.ctx.args.version,
};
}
async GetPluginCapabilities(call) {
let capabilities;
const response = {
capabilities: []
capabilities: [],
};
//UNKNOWN = 0;
@ -104,12 +104,12 @@ class CsiBaseDriver {
// accessible from a given node when scheduling workloads.
//VOLUME_ACCESSIBILITY_CONSTRAINTS = 2;
capabilities = this.options.service.identity.capabilities.service || [
"UNKNOWN"
"UNKNOWN",
];
capabilities.forEach(item => {
capabilities.forEach((item) => {
response.capabilities.push({
service: { type: item }
service: { type: item },
});
});
@ -155,9 +155,9 @@ class CsiBaseDriver {
capabilities = this.options.service.identity.capabilities
.volume_expansion || ["UNKNOWN"];
capabilities.forEach(item => {
capabilities.forEach((item) => {
response.capabilities.push({
volume_expansion: { type: item }
volume_expansion: { type: item },
});
});
@ -171,7 +171,7 @@ class CsiBaseDriver {
async ControllerGetCapabilities(call) {
let capabilities;
const response = {
capabilities: []
capabilities: [],
};
//UNKNOWN = 0;
@ -199,12 +199,12 @@ class CsiBaseDriver {
// See VolumeExpansion for details.
//EXPAND_VOLUME = 9;
capabilities = this.options.service.controller.capabilities.rpc || [
"UNKNOWN"
"UNKNOWN",
];
capabilities.forEach(item => {
capabilities.forEach((item) => {
response.capabilities.push({
rpc: { type: item }
rpc: { type: item },
});
});
@ -214,7 +214,7 @@ class CsiBaseDriver {
async NodeGetCapabilities(call) {
let capabilities;
const response = {
capabilities: []
capabilities: [],
};
//UNKNOWN = 0;
@ -227,9 +227,9 @@ class CsiBaseDriver {
//EXPAND_VOLUME = 3;
capabilities = this.options.service.node.capabilities.rpc || ["UNKNOWN"];
capabilities.forEach(item => {
capabilities.forEach((item) => {
response.capabilities.push({
rpc: { type: item }
rpc: { type: item },
});
});
@ -239,7 +239,7 @@ class CsiBaseDriver {
async NodeGetInfo(call) {
return {
node_id: process.env.CSI_NODE_ID || os.hostname(),
max_volumes_per_node: 0
max_volumes_per_node: 0,
};
}
@ -296,12 +296,15 @@ class CsiBaseDriver {
case "nfs":
device = `${volume_context.server}:${volume_context.share}`;
break;
case "smb":
device = `//${volume_context.server}/${volume_context.share}`;
break;
case "iscsi":
// create DB entry
// https://library.netapp.com/ecmdocs/ECMP1654943/html/GUID-8EC685B4-8CB6-40D8-A8D5-031A3899BCDC.html
// put these options in place to force targets managed by csi to be explicitly attached (in the case of unclearn shutdown etc)
let nodeDB = {
"node.startup": "manual"
"node.startup": "manual",
};
const nodeDBKeyPrefix = "node-db.";
const normalizedSecrets = this.getNormalizedParameters(
@ -420,7 +423,7 @@ class CsiBaseDriver {
await mount.bindMount(device, block_path, [
"-o",
bind_mount_flags.join(",")
bind_mount_flags.join(","),
]);
}
break;
@ -506,7 +509,7 @@ class CsiBaseDriver {
session.attached_scsi_devices.host.devices
) {
is_attached_to_session = session.attached_scsi_devices.host.devices.some(
device => {
(device) => {
if (device.attached_scsi_disk == block_device_info.name) {
return true;
}
@ -525,7 +528,7 @@ class CsiBaseDriver {
while (!loggedOut) {
try {
await iscsi.iscsiadm.logout(session.target, [
session.persistent_portal
session.persistent_portal,
]);
loggedOut = true;
} catch (err) {
@ -659,7 +662,7 @@ class CsiBaseDriver {
if (!result) {
await mount.bindMount(normalized_staging_path, target_path, [
"-o",
bind_mount_flags.join(",")
bind_mount_flags.join(","),
]);
} else {
// if is mounted, ensure proper source
@ -760,9 +763,9 @@ class CsiBaseDriver {
available: result.avail,
total: result.size,
used: result.used,
unit: "BYTES"
}
]
unit: "BYTES",
},
],
};
case "block":
result = await filesystem.getBlockDevice(device_path);
@ -771,9 +774,9 @@ class CsiBaseDriver {
usage: [
{
total: result.size,
unit: "BYTES"
}
]
unit: "BYTES",
},
],
};
default:
throw new GrpcError(