1061 lines
31 KiB
JavaScript
1061 lines
31 KiB
JavaScript
const _ = require("lodash");
|
|
const { ControllerZfsBaseDriver } = require("../controller-zfs");
|
|
const { GrpcError, grpc } = require("../../utils/grpc");
|
|
const GeneralUtils = require("../../utils/general");
|
|
const registry = require("../../utils/registry");
|
|
const LocalCliExecClient =
|
|
require("../../utils/zfs_local_exec_client").LocalCliClient;
|
|
const SshClient = require("../../utils/zfs_ssh_exec_client").SshClient;
|
|
const { Zetabyte, ZfsSshProcessManager } = require("../../utils/zfs");
|
|
|
|
const Handlebars = require("handlebars");
|
|
|
|
const ISCSI_ASSETS_NAME_PROPERTY_NAME = "democratic-csi:iscsi_assets_name";
|
|
const NVMEOF_ASSETS_NAME_PROPERTY_NAME = "democratic-csi:nvmeof_assets_name";
|
|
const __REGISTRY_NS__ = "ControllerZfsGenericDriver";
|
|
class ControllerZfsGenericDriver extends ControllerZfsBaseDriver {
|
|
getExecClient() {
|
|
return registry.get(`${__REGISTRY_NS__}:exec_client`, () => {
|
|
if (this.options.sshConnection) {
|
|
return new SshClient({
|
|
logger: this.ctx.logger,
|
|
connection: this.options.sshConnection,
|
|
});
|
|
} else {
|
|
return new LocalCliExecClient({
|
|
logger: this.ctx.logger,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
async getZetabyte() {
|
|
return registry.getAsync(`${__REGISTRY_NS__}:zb`, async () => {
|
|
const execClient = this.getExecClient();
|
|
const options = {};
|
|
if (this.options.sshConnection) {
|
|
options.executor = new ZfsSshProcessManager(execClient);
|
|
} else {
|
|
options.executor = execClient;
|
|
}
|
|
options.idempotent = true;
|
|
|
|
if (
|
|
this.options.zfs.hasOwnProperty("cli") &&
|
|
this.options.zfs.cli &&
|
|
this.options.zfs.cli.hasOwnProperty("paths")
|
|
) {
|
|
options.paths = this.options.zfs.cli.paths;
|
|
}
|
|
|
|
options.sudo = _.get(this.options, "zfs.cli.sudoEnabled", false);
|
|
|
|
if (typeof this.setZetabyteCustomOptions === "function") {
|
|
await this.setZetabyteCustomOptions(options);
|
|
}
|
|
|
|
return new Zetabyte(options);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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":
|
|
case "zfs-generic-smb":
|
|
return "filesystem";
|
|
case "zfs-generic-iscsi":
|
|
case "zfs-generic-nvmeof":
|
|
return "volume";
|
|
default:
|
|
throw new Error("unknown driver: " + this.ctx.args.driver);
|
|
}
|
|
}
|
|
|
|
generateSmbShareName(datasetName) {
|
|
const driver = this;
|
|
|
|
driver.ctx.logger.verbose(
|
|
`generating smb share name for dataset: ${datasetName}`
|
|
);
|
|
|
|
let name = datasetName || "";
|
|
name = name.replaceAll("/", "_");
|
|
name = name.replaceAll("-", "_");
|
|
|
|
driver.ctx.logger.verbose(
|
|
`generated smb share name for dataset (${datasetName}): ${name}`
|
|
);
|
|
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* should create any necessary share resources
|
|
* should set the SHARE_VOLUME_CONTEXT_PROPERTY_NAME propery
|
|
*
|
|
* @param {*} datasetName
|
|
*/
|
|
async createShare(call, datasetName) {
|
|
const driver = this;
|
|
const zb = await this.getZetabyte();
|
|
const execClient = this.getExecClient();
|
|
|
|
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-smb":
|
|
let share;
|
|
switch (this.options.smb.shareStrategy) {
|
|
case "setDatasetProperties":
|
|
for (let key of ["share", "sharesmb"]) {
|
|
if (
|
|
this.options.smb.shareStrategySetDatasetProperties.properties[
|
|
key
|
|
]
|
|
) {
|
|
await zb.zfs.set(datasetName, {
|
|
[key]:
|
|
this.options.smb.shareStrategySetDatasetProperties
|
|
.properties[key],
|
|
});
|
|
}
|
|
}
|
|
|
|
share = driver.generateSmbShareName(datasetName);
|
|
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: "smb",
|
|
server: this.options.smb.shareHost,
|
|
share,
|
|
};
|
|
return volume_context;
|
|
|
|
case "zfs-generic-iscsi": {
|
|
let basename;
|
|
let assetName;
|
|
|
|
if (this.options.iscsi.nameTemplate) {
|
|
assetName = Handlebars.compile(this.options.iscsi.nameTemplate)({
|
|
name: call.request.name,
|
|
parameters: call.request.parameters,
|
|
});
|
|
} else {
|
|
assetName = zb.helpers.extractLeafName(datasetName);
|
|
}
|
|
|
|
if (this.options.iscsi.namePrefix) {
|
|
assetName = this.options.iscsi.namePrefix + assetName;
|
|
}
|
|
|
|
if (this.options.iscsi.nameSuffix) {
|
|
assetName += this.options.iscsi.nameSuffix;
|
|
}
|
|
|
|
assetName = assetName.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.shareStrategyTargetCli.basename;
|
|
let setAttributesText = "";
|
|
let setAuthText = "";
|
|
let setBlockAttributesText = "";
|
|
|
|
if (this.options.iscsi.shareStrategyTargetCli.block) {
|
|
if (this.options.iscsi.shareStrategyTargetCli.block.attributes) {
|
|
for (const attributeName in this.options.iscsi
|
|
.shareStrategyTargetCli.block.attributes) {
|
|
const attributeValue =
|
|
this.options.iscsi.shareStrategyTargetCli.block.attributes[
|
|
attributeName
|
|
];
|
|
setBlockAttributesText += "\n";
|
|
setBlockAttributesText += `set attribute ${attributeName}=${attributeValue}`;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.options.iscsi.shareStrategyTargetCli.tpg) {
|
|
if (this.options.iscsi.shareStrategyTargetCli.tpg.attributes) {
|
|
for (const attributeName in this.options.iscsi
|
|
.shareStrategyTargetCli.tpg.attributes) {
|
|
const attributeValue =
|
|
this.options.iscsi.shareStrategyTargetCli.tpg.attributes[
|
|
attributeName
|
|
];
|
|
setAttributesText += "\n";
|
|
setAttributesText += `set attribute ${attributeName}=${attributeValue}`;
|
|
}
|
|
}
|
|
|
|
if (this.options.iscsi.shareStrategyTargetCli.tpg.auth) {
|
|
for (const attributeName in this.options.iscsi
|
|
.shareStrategyTargetCli.tpg.auth) {
|
|
const attributeValue =
|
|
this.options.iscsi.shareStrategyTargetCli.tpg.auth[
|
|
attributeName
|
|
];
|
|
setAttributesText += "\n";
|
|
setAttributesText += `set auth ${attributeName}=${attributeValue}`;
|
|
}
|
|
}
|
|
}
|
|
|
|
await GeneralUtils.retry(
|
|
3,
|
|
2000,
|
|
async () => {
|
|
await this.targetCliCommand(
|
|
`
|
|
# create target
|
|
cd /iscsi
|
|
create ${basename}:${assetName}
|
|
|
|
# setup tpg
|
|
cd /iscsi/${basename}:${assetName}/tpg1
|
|
${setAttributesText}
|
|
${setAuthText}
|
|
|
|
# create extent
|
|
cd /backstores/block
|
|
create ${assetName} /dev/${extentDiskName}
|
|
cd /backstores/block/${assetName}
|
|
${setBlockAttributesText}
|
|
|
|
# add extent to target/tpg
|
|
cd /iscsi/${basename}:${assetName}/tpg1/luns
|
|
create /backstores/block/${assetName}
|
|
`
|
|
);
|
|
},
|
|
{
|
|
retryCondition: (err) => {
|
|
if (err.stdout && err.stdout.includes("Ran out of input")) {
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
}
|
|
);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// iqn = target
|
|
let iqn = basename + ":" + assetName;
|
|
this.ctx.logger.info("iqn: " + iqn);
|
|
|
|
// store this off to make delete process more bullet proof
|
|
await zb.zfs.set(datasetName, {
|
|
[ISCSI_ASSETS_NAME_PROPERTY_NAME]: assetName,
|
|
});
|
|
|
|
volume_context = {
|
|
node_attach_driver: "iscsi",
|
|
portal: this.options.iscsi.targetPortal || "",
|
|
portals: this.options.iscsi.targetPortals
|
|
? this.options.iscsi.targetPortals.join(",")
|
|
: "",
|
|
interface: this.options.iscsi.interface || "",
|
|
iqn: iqn,
|
|
lun: 0,
|
|
};
|
|
return volume_context;
|
|
}
|
|
|
|
case "zfs-generic-nvmeof": {
|
|
let basename;
|
|
let assetName;
|
|
|
|
if (this.options.nvmeof.nameTemplate) {
|
|
assetName = Handlebars.compile(this.options.nvmeof.nameTemplate)({
|
|
name: call.request.name,
|
|
parameters: call.request.parameters,
|
|
});
|
|
} else {
|
|
assetName = zb.helpers.extractLeafName(datasetName);
|
|
}
|
|
|
|
if (this.options.nvmeof.namePrefix) {
|
|
assetName = this.options.nvmeof.namePrefix + assetName;
|
|
}
|
|
|
|
if (this.options.nvmeof.nameSuffix) {
|
|
assetName += this.options.nvmeof.nameSuffix;
|
|
}
|
|
|
|
assetName = assetName.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}`
|
|
// );
|
|
//}
|
|
|
|
let namespace = 1;
|
|
|
|
switch (this.options.nvmeof.shareStrategy) {
|
|
case "nvmetCli":
|
|
{
|
|
basename = this.options.nvmeof.shareStrategyNvmetCli.basename;
|
|
let savefile = _.get(
|
|
this.options,
|
|
"nvmeof.shareStrategyNvmetCli.configPath",
|
|
""
|
|
);
|
|
if (savefile) {
|
|
savefile = `savefile=${savefile}`;
|
|
}
|
|
let setSubsystemAttributesText = "";
|
|
if (this.options.nvmeof.shareStrategyNvmetCli.subsystem) {
|
|
if (
|
|
this.options.nvmeof.shareStrategyNvmetCli.subsystem.attributes
|
|
) {
|
|
for (const attributeName in this.options.nvmeof
|
|
.shareStrategyNvmetCli.subsystem.attributes) {
|
|
const attributeValue =
|
|
this.options.nvmeof.shareStrategyNvmetCli.subsystem
|
|
.attributes[attributeName];
|
|
setSubsystemAttributesText += "\n";
|
|
setSubsystemAttributesText += `set attr ${attributeName}=${attributeValue}`;
|
|
}
|
|
}
|
|
}
|
|
|
|
let portCommands = "";
|
|
this.options.nvmeof.shareStrategyNvmetCli.ports.forEach(
|
|
(port) => {
|
|
portCommands += `
|
|
cd /ports/${port}/subsystems
|
|
create ${basename}:${assetName}
|
|
`;
|
|
}
|
|
);
|
|
|
|
await GeneralUtils.retry(
|
|
3,
|
|
2000,
|
|
async () => {
|
|
await this.nvmetCliCommand(
|
|
`
|
|
# create subsystem
|
|
cd /subsystems
|
|
create ${basename}:${assetName}
|
|
cd ${basename}:${assetName}
|
|
${setSubsystemAttributesText}
|
|
|
|
# create subsystem namespace
|
|
cd namespaces
|
|
create ${namespace}
|
|
cd ${namespace}
|
|
set device path=/dev/${extentDiskName}
|
|
enable
|
|
|
|
# associate subsystem/target to port(al)
|
|
${portCommands}
|
|
|
|
saveconfig ${savefile}
|
|
`
|
|
);
|
|
},
|
|
{
|
|
retryCondition: (err) => {
|
|
if (err.stdout && err.stdout.includes("Ran out of input")) {
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
}
|
|
);
|
|
}
|
|
break;
|
|
|
|
case "spdkCli":
|
|
{
|
|
basename = this.options.nvmeof.shareStrategySpdkCli.basename;
|
|
let bdevAttributesText = "";
|
|
if (this.options.nvmeof.shareStrategySpdkCli.bdev) {
|
|
if (this.options.nvmeof.shareStrategySpdkCli.bdev.attributes) {
|
|
for (const attributeName in this.options.nvmeof
|
|
.shareStrategySpdkCli.bdev.attributes) {
|
|
const attributeValue =
|
|
this.options.nvmeof.shareStrategySpdkCli.bdev.attributes[
|
|
attributeName
|
|
];
|
|
bdevAttributesText += `${attributeName}=${attributeValue}`;
|
|
}
|
|
}
|
|
}
|
|
|
|
let subsystemAttributesText = "";
|
|
if (this.options.nvmeof.shareStrategySpdkCli.subsystem) {
|
|
if (
|
|
this.options.nvmeof.shareStrategySpdkCli.subsystem.attributes
|
|
) {
|
|
for (const attributeName in this.options.nvmeof
|
|
.shareStrategySpdkCli.subsystem.attributes) {
|
|
const attributeValue =
|
|
this.options.nvmeof.shareStrategySpdkCli.subsystem
|
|
.attributes[attributeName];
|
|
subsystemAttributesText += `${attributeName}=${attributeValue}`;
|
|
}
|
|
}
|
|
}
|
|
|
|
let listenerCommands = `cd /nvmf/subsystem/${basename}:${assetName}/listen_addresses\n`;
|
|
this.options.nvmeof.shareStrategySpdkCli.listeners.forEach(
|
|
(listener) => {
|
|
let listenerAttributesText = "";
|
|
for (const attributeName in listener) {
|
|
const attributeValue = listener[attributeName];
|
|
listenerAttributesText += ` ${attributeName}=${attributeValue} `;
|
|
}
|
|
listenerCommands += `
|
|
create ${listenerAttributesText}
|
|
`;
|
|
}
|
|
);
|
|
|
|
await GeneralUtils.retry(
|
|
3,
|
|
2000,
|
|
async () => {
|
|
await this.spdkCliCommand(
|
|
`
|
|
# create bdev
|
|
cd /bdevs/${this.options.nvmeof.shareStrategySpdkCli.bdev.type}
|
|
create filename=/dev/${extentDiskName} name=${basename}:${assetName} ${bdevAttributesText}
|
|
|
|
# create subsystem
|
|
cd /nvmf/subsystem
|
|
create nqn=${basename}:${assetName} ${subsystemAttributesText}
|
|
cd ${basename}:${assetName}
|
|
|
|
# create namespace
|
|
cd /nvmf/subsystem/${basename}:${assetName}/namespaces
|
|
create bdev_name=${basename}:${assetName} nsid=${namespace}
|
|
|
|
# add listener
|
|
${listenerCommands}
|
|
|
|
cd /
|
|
save_config filename=${this.options.nvmeof.shareStrategySpdkCli.configPath}
|
|
`
|
|
);
|
|
},
|
|
{
|
|
retryCondition: (err) => {
|
|
if (err.stdout && err.stdout.includes("Ran out of input")) {
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
}
|
|
);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// iqn = target
|
|
let nqn = basename + ":" + assetName;
|
|
this.ctx.logger.info("nqn: " + nqn);
|
|
|
|
// store this off to make delete process more bullet proof
|
|
await zb.zfs.set(datasetName, {
|
|
[NVMEOF_ASSETS_NAME_PROPERTY_NAME]: assetName,
|
|
});
|
|
|
|
volume_context = {
|
|
node_attach_driver: "nvmeof",
|
|
transport: this.options.nvmeof.transport || "",
|
|
transports: this.options.nvmeof.transports
|
|
? this.options.nvmeof.transports.join(",")
|
|
: "",
|
|
nqn,
|
|
nsid: namespace,
|
|
};
|
|
return volume_context;
|
|
}
|
|
|
|
default:
|
|
throw new GrpcError(
|
|
grpc.status.FAILED_PRECONDITION,
|
|
`invalid configuration: unknown driver ${this.options.driver}`
|
|
);
|
|
}
|
|
}
|
|
|
|
async deleteShare(call, datasetName) {
|
|
const zb = await this.getZetabyte();
|
|
const execClient = this.getExecClient();
|
|
|
|
let response;
|
|
let properties;
|
|
|
|
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
|
|
]
|
|
) {
|
|
try {
|
|
await zb.zfs.inherit(datasetName, key);
|
|
} catch (err) {
|
|
if (err.toString().includes("dataset does not exist")) {
|
|
// do nothing
|
|
} else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
await GeneralUtils.sleep(2000); // let things settle
|
|
break;
|
|
default:
|
|
throw new GrpcError(
|
|
grpc.status.FAILED_PRECONDITION,
|
|
`invalid configuration: unknown shareStrategy ${this.options.nfs.shareStrategy}`
|
|
);
|
|
}
|
|
break;
|
|
|
|
case "zfs-generic-smb":
|
|
switch (this.options.smb.shareStrategy) {
|
|
case "setDatasetProperties":
|
|
for (let key of ["share", "sharesmb"]) {
|
|
if (
|
|
this.options.smb.shareStrategySetDatasetProperties.properties[
|
|
key
|
|
]
|
|
) {
|
|
try {
|
|
await zb.zfs.inherit(datasetName, key);
|
|
} catch (err) {
|
|
if (err.toString().includes("dataset does not exist")) {
|
|
// do nothing
|
|
} else {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
await GeneralUtils.sleep(2000); // let things settle
|
|
break;
|
|
default:
|
|
throw new GrpcError(
|
|
grpc.status.FAILED_PRECONDITION,
|
|
`invalid configuration: unknown shareStrategy ${this.options.smb.shareStrategy}`
|
|
);
|
|
}
|
|
break;
|
|
|
|
case "zfs-generic-iscsi": {
|
|
let basename;
|
|
let assetName;
|
|
|
|
// Delete iscsi assets
|
|
try {
|
|
properties = await zb.zfs.get(datasetName, [
|
|
ISCSI_ASSETS_NAME_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);
|
|
|
|
assetName = properties[ISCSI_ASSETS_NAME_PROPERTY_NAME].value;
|
|
|
|
if (zb.helpers.isPropertyValueSet(assetName)) {
|
|
//do nothing
|
|
} else {
|
|
assetName = zb.helpers.extractLeafName(datasetName);
|
|
|
|
if (this.options.iscsi.namePrefix) {
|
|
assetName = this.options.iscsi.namePrefix + assetName;
|
|
}
|
|
|
|
if (this.options.iscsi.nameSuffix) {
|
|
assetName += this.options.iscsi.nameSuffix;
|
|
}
|
|
}
|
|
|
|
assetName = assetName.toLowerCase();
|
|
switch (this.options.iscsi.shareStrategy) {
|
|
case "targetCli":
|
|
basename = this.options.iscsi.shareStrategyTargetCli.basename;
|
|
await GeneralUtils.retry(
|
|
3,
|
|
2000,
|
|
async () => {
|
|
await this.targetCliCommand(
|
|
`
|
|
# delete target
|
|
cd /iscsi
|
|
delete ${basename}:${assetName}
|
|
|
|
# delete extent
|
|
cd /backstores/block
|
|
delete ${assetName}
|
|
`
|
|
);
|
|
},
|
|
{
|
|
retryCondition: (err) => {
|
|
if (err.stdout && err.stdout.includes("Ran out of input")) {
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
}
|
|
);
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case "zfs-generic-nvmeof": {
|
|
let basename;
|
|
let assetName;
|
|
|
|
// Delete nvmeof assets
|
|
try {
|
|
properties = await zb.zfs.get(datasetName, [
|
|
NVMEOF_ASSETS_NAME_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);
|
|
|
|
assetName = properties[NVMEOF_ASSETS_NAME_PROPERTY_NAME].value;
|
|
|
|
if (zb.helpers.isPropertyValueSet(assetName)) {
|
|
//do nothing
|
|
} else {
|
|
assetName = zb.helpers.extractLeafName(datasetName);
|
|
|
|
if (this.options.nvmeof.namePrefix) {
|
|
assetName = this.options.nvmeof.namePrefix + assetName;
|
|
}
|
|
|
|
if (this.options.nvmeof.nameSuffix) {
|
|
assetName += this.options.nvmeof.nameSuffix;
|
|
}
|
|
}
|
|
|
|
assetName = assetName.toLowerCase();
|
|
switch (this.options.nvmeof.shareStrategy) {
|
|
case "nvmetCli":
|
|
{
|
|
basename = this.options.nvmeof.shareStrategyNvmetCli.basename;
|
|
let savefile = _.get(
|
|
this.options,
|
|
"nvmeof.shareStrategyNvmetCli.configPath",
|
|
""
|
|
);
|
|
if (savefile) {
|
|
savefile = `savefile=${savefile}`;
|
|
}
|
|
let portCommands = "";
|
|
this.options.nvmeof.shareStrategyNvmetCli.ports.forEach(
|
|
(port) => {
|
|
portCommands += `
|
|
cd /ports/${port}/subsystems
|
|
delete ${basename}:${assetName}
|
|
`;
|
|
}
|
|
);
|
|
await GeneralUtils.retry(
|
|
3,
|
|
2000,
|
|
async () => {
|
|
await this.nvmetCliCommand(
|
|
`
|
|
# delete subsystem from port
|
|
${portCommands}
|
|
|
|
# delete subsystem
|
|
cd /subsystems
|
|
delete ${basename}:${assetName}
|
|
|
|
saveconfig ${savefile}
|
|
`
|
|
);
|
|
},
|
|
{
|
|
retryCondition: (err) => {
|
|
if (err.stdout && err.stdout.includes("Ran out of input")) {
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
}
|
|
);
|
|
}
|
|
break;
|
|
case "spdkCli":
|
|
{
|
|
basename = this.options.nvmeof.shareStrategySpdkCli.basename;
|
|
await GeneralUtils.retry(
|
|
3,
|
|
2000,
|
|
async () => {
|
|
await this.spdkCliCommand(
|
|
`
|
|
# delete subsystem
|
|
cd /nvmf/subsystem/
|
|
delete subsystem_nqn=${basename}:${assetName}
|
|
|
|
# delete bdev
|
|
cd /bdevs/${this.options.nvmeof.shareStrategySpdkCli.bdev.type}
|
|
delete name=${basename}:${assetName}
|
|
|
|
cd /
|
|
save_config filename=${this.options.nvmeof.shareStrategySpdkCli.configPath}
|
|
`
|
|
);
|
|
},
|
|
{
|
|
retryCondition: (err) => {
|
|
if (err.stdout && err.stdout.includes("Ran out of input")) {
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
}
|
|
);
|
|
}
|
|
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 execClient = this.getExecClient();
|
|
const driver = this;
|
|
|
|
data = data.trim();
|
|
|
|
let command = "sh";
|
|
let args = ["-c"];
|
|
|
|
let cliArgs = ["targetcli"];
|
|
if (
|
|
_.get(this.options, "iscsi.shareStrategyTargetCli.sudoEnabled", false)
|
|
) {
|
|
cliArgs.unshift("sudo");
|
|
}
|
|
|
|
let cliCommand = [];
|
|
cliCommand.push(`echo "${data}"`.trim());
|
|
cliCommand.push("|");
|
|
cliCommand.push(cliArgs.join(" "));
|
|
args.push("'" + cliCommand.join(" ") + "'");
|
|
|
|
let logCommandTmp = command + " " + args.join(" ");
|
|
let logCommand = "";
|
|
|
|
logCommandTmp.split("\n").forEach((line) => {
|
|
if (line.startsWith("set auth password=")) {
|
|
logCommand += "set auth password=<redacted>";
|
|
} else if (line.startsWith("set auth mutual_password=")) {
|
|
logCommand += "set auth mutual_password=<redacted>";
|
|
} else {
|
|
logCommand += line;
|
|
}
|
|
|
|
logCommand += "\n";
|
|
});
|
|
|
|
driver.ctx.logger.verbose("TargetCLI command: " + logCommand);
|
|
|
|
// https://github.com/democratic-csi/democratic-csi/issues/127
|
|
// https://bugs.launchpad.net/ubuntu/+source/python-configshell-fb/+bug/1776761
|
|
// can apply the linked patch with some modifications to overcome the
|
|
// KeyErrors or we can simply start a fake tty which does not seem to have
|
|
// a detrimental effect, only affects Ubuntu 18.04 and older
|
|
let options = {
|
|
pty: true,
|
|
};
|
|
let response = await execClient.exec(
|
|
execClient.buildCommand(command, args),
|
|
options
|
|
);
|
|
driver.ctx.logger.verbose(
|
|
"TargetCLI response: " + JSON.stringify(response)
|
|
);
|
|
if (response.code != 0) {
|
|
throw response;
|
|
}
|
|
return response;
|
|
}
|
|
|
|
async nvmetCliCommand(data) {
|
|
const execClient = this.getExecClient();
|
|
const driver = this;
|
|
|
|
if (
|
|
_.get(
|
|
this.options,
|
|
"nvmeof.shareStrategyNvmetCli.configIsImportedFilePath"
|
|
)
|
|
) {
|
|
try {
|
|
let response = await execClient.exec(
|
|
execClient.buildCommand("test", [
|
|
"-f",
|
|
_.get(
|
|
this.options,
|
|
"nvmeof.shareStrategyNvmetCli.configIsImportedFilePath"
|
|
),
|
|
])
|
|
);
|
|
} catch (err) {
|
|
throw new Error("nvmet has not been fully configured");
|
|
}
|
|
}
|
|
|
|
data = data.trim();
|
|
|
|
let command = "sh";
|
|
let args = ["-c"];
|
|
|
|
let cliArgs = [
|
|
_.get(
|
|
this.options,
|
|
"nvmeof.shareStrategyNvmetCli.nvmetcliPath",
|
|
"nvmetcli"
|
|
),
|
|
];
|
|
if (
|
|
_.get(this.options, "nvmeof.shareStrategyNvmetCli.sudoEnabled", false)
|
|
) {
|
|
cliArgs.unshift("sudo");
|
|
}
|
|
|
|
let cliCommand = [];
|
|
cliCommand.push(`echo "${data}"`.trim());
|
|
cliCommand.push("|");
|
|
cliCommand.push(cliArgs.join(" "));
|
|
args.push("'" + cliCommand.join(" ") + "'");
|
|
|
|
let logCommandTmp = command + " " + args.join(" ");
|
|
let logCommand = "";
|
|
|
|
logCommandTmp.split("\n").forEach((line) => {
|
|
if (line.startsWith("set auth password=")) {
|
|
logCommand += "set auth password=<redacted>";
|
|
} else if (line.startsWith("set auth mutual_password=")) {
|
|
logCommand += "set auth mutual_password=<redacted>";
|
|
} else {
|
|
logCommand += line;
|
|
}
|
|
|
|
logCommand += "\n";
|
|
});
|
|
|
|
driver.ctx.logger.verbose("nvmetCLI command: " + logCommand);
|
|
//process.exit(0);
|
|
|
|
// https://github.com/democratic-csi/democratic-csi/issues/127
|
|
// https://bugs.launchpad.net/ubuntu/+source/python-configshell-fb/+bug/1776761
|
|
// can apply the linked patch with some modifications to overcome the
|
|
// KeyErrors or we can simply start a fake tty which does not seem to have
|
|
// a detrimental effect, only affects Ubuntu 18.04 and older
|
|
let options = {
|
|
pty: true,
|
|
};
|
|
let response = await execClient.exec(
|
|
execClient.buildCommand(command, args),
|
|
options
|
|
);
|
|
driver.ctx.logger.verbose("nvmetCLI response: " + JSON.stringify(response));
|
|
if (response.code != 0) {
|
|
throw response;
|
|
}
|
|
return response;
|
|
}
|
|
|
|
async spdkCliCommand(data) {
|
|
const execClient = this.getExecClient();
|
|
const driver = this;
|
|
|
|
data = data.trim();
|
|
|
|
let command = "sh";
|
|
let args = ["-c"];
|
|
|
|
let cliArgs = [
|
|
_.get(this.options, "nvmeof.shareStrategySpdkCli.spdkcliPath", "spdkcli"),
|
|
];
|
|
if (_.get(this.options, "nvmeof.shareStrategySpdkCli.sudoEnabled", false)) {
|
|
cliArgs.unshift("sudo");
|
|
}
|
|
|
|
let cliCommand = [];
|
|
cliCommand.push(`echo "${data}"`.trim());
|
|
cliCommand.push("|");
|
|
cliCommand.push(cliArgs.join(" "));
|
|
args.push("'" + cliCommand.join(" ") + "'");
|
|
|
|
let logCommandTmp = command + " " + args.join(" ");
|
|
let logCommand = "";
|
|
|
|
logCommandTmp.split("\n").forEach((line) => {
|
|
if (line.startsWith("set auth password=")) {
|
|
logCommand += "set auth password=<redacted>";
|
|
} else if (line.startsWith("set auth mutual_password=")) {
|
|
logCommand += "set auth mutual_password=<redacted>";
|
|
} else {
|
|
logCommand += line;
|
|
}
|
|
|
|
logCommand += "\n";
|
|
});
|
|
|
|
driver.ctx.logger.verbose("spdkCLI command: " + logCommand);
|
|
//process.exit(0);
|
|
|
|
// https://github.com/democratic-csi/democratic-csi/issues/127
|
|
// https://bugs.launchpad.net/ubuntu/+source/python-configshell-fb/+bug/1776761
|
|
// can apply the linked patch with some modifications to overcome the
|
|
// KeyErrors or we can simply start a fake tty which does not seem to have
|
|
// a detrimental effect, only affects Ubuntu 18.04 and older
|
|
let options = {
|
|
pty: true,
|
|
};
|
|
let response = await execClient.exec(
|
|
execClient.buildCommand(command, args),
|
|
options
|
|
);
|
|
driver.ctx.logger.verbose("spdkCLI response: " + JSON.stringify(response));
|
|
if (response.code != 0) {
|
|
throw response;
|
|
}
|
|
return response;
|
|
}
|
|
}
|
|
|
|
module.exports.ControllerZfsGenericDriver = ControllerZfsGenericDriver;
|