preview support for cifs/smb
This commit is contained in:
parent
f5ba51c9b8
commit
5ae0248ba8
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in New Issue