Merge pull request #341 from democratic-csi/next

Next
This commit is contained in:
Travis Glenn Hansen 2023-11-09 17:07:34 -05:00 committed by GitHub
commit 79f16a0cf6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 706 additions and 2447 deletions

View File

@ -147,6 +147,38 @@ jobs:
TRUENAS_USERNAME: ${{ secrets.SANITY_TRUENAS_USERNAME }}
TRUENAS_PASSWORD: ${{ secrets.SANITY_TRUENAS_PASSWORD }}
csi-sanity-truenas-scale-23_10:
needs:
- build-npm-linux-amd64
strategy:
fail-fast: false
matrix:
config:
- truenas/scale/23.10/scale-iscsi.yaml
- truenas/scale/23.10/scale-nfs.yaml
# 80 char limit
- truenas/scale/23.10/scale-smb.yaml
runs-on:
- self-hosted
- Linux
- X64
#- csi-sanity-truenas
- csi-sanity-zfs-generic
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
with:
name: node-modules-linux-amd64
- name: csi-sanity
run: |
# run tests
ci/bin/run.sh
env:
TEMPLATE_CONFIG_FILE: "./ci/configs/${{ matrix.config }}"
TRUENAS_HOST: ${{ secrets.SANITY_TRUENAS_SCALE_23_10_HOST }}
TRUENAS_USERNAME: ${{ secrets.SANITY_TRUENAS_USERNAME }}
TRUENAS_PASSWORD: ${{ secrets.SANITY_TRUENAS_PASSWORD }}
# ssh-based drivers
csi-sanity-truenas-core-13_0:
needs:
@ -394,6 +426,7 @@ jobs:
- csi-sanity-synology-dsm6
- csi-sanity-synology-dsm7
- csi-sanity-truenas-scale-22_12
- csi-sanity-truenas-scale-23_10
- csi-sanity-truenas-core-13_0
- csi-sanity-zfs-generic
- csi-sanity-client
@ -432,6 +465,7 @@ jobs:
- csi-sanity-synology-dsm6
- csi-sanity-synology-dsm7
- csi-sanity-truenas-scale-22_12
- csi-sanity-truenas-scale-23_10
- csi-sanity-truenas-core-13_0
- csi-sanity-zfs-generic
- csi-sanity-client

View File

@ -1,6 +1,16 @@
# v1.8.4
Released 2023-11-09
- allow templatized `volume_id` (dangerous, only use if you *really* know what you are doing)
- fix SCALE iscsi resize issue
- SCALE 23.10 support
- minor improvements/fixes throughout
- dependency updates
# v1.8.3
Released 2023-04-02
Released 2023-04-05
- fix invalid `access_mode` logic (see #287)

View File

@ -29,3 +29,10 @@ iscsi:
targetGroupAuthGroup:
# 0-100 (0 == ignore)
extentAvailThreshold: 0
# https://github.com/SCST-project/scst/blob/master/scst/src/dev_handlers/scst_vdisk.c#L203
_private:
csi:
volume:
idHash:
strategy: crc16

View File

@ -0,0 +1,38 @@
driver: freenas-api-iscsi
httpConnection:
protocol: http
host: ${TRUENAS_HOST}
port: 80
#apiKey:
username: ${TRUENAS_USERNAME}
password: ${TRUENAS_PASSWORD}
zfs:
datasetParentName: tank/ci/${CI_BUILD_KEY}/v
detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s
zvolCompression:
zvolDedup:
zvolEnableReservation: false
zvolBlocksize:
iscsi:
targetPortal: ${TRUENAS_HOST}
interface: ""
namePrefix: "csi-ci-${CI_BUILD_KEY}-"
nameSuffix: ""
targetGroups:
- targetGroupPortalGroup: 1
targetGroupInitiatorGroup: 1
targetGroupAuthType: None
targetGroupAuthGroup:
# 0-100 (0 == ignore)
extentAvailThreshold: 0
# https://github.com/SCST-project/scst/blob/master/scst/src/dev_handlers/scst_vdisk.c#L203
_private:
csi:
volume:
idHash:
strategy: crc16

View File

@ -0,0 +1,29 @@
driver: freenas-api-nfs
httpConnection:
protocol: http
host: ${TRUENAS_HOST}
port: 80
#apiKey:
username: ${TRUENAS_USERNAME}
password: ${TRUENAS_PASSWORD}
zfs:
datasetParentName: tank/ci/${CI_BUILD_KEY}/v
detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s
datasetEnableQuotas: true
datasetEnableReservation: false
datasetPermissionsMode: "0777"
datasetPermissionsUser: 0
datasetPermissionsGroup: 0
nfs:
shareHost: ${TRUENAS_HOST}
shareAlldirs: false
shareAllowedHosts: []
shareAllowedNetworks: []
shareMaprootUser: root
shareMaprootGroup: root
shareMapallUser: ""
shareMapallGroup: ""

View File

@ -0,0 +1,50 @@
driver: freenas-api-smb
httpConnection:
protocol: http
host: ${TRUENAS_HOST}
port: 80
#apiKey:
username: ${TRUENAS_USERNAME}
password: ${TRUENAS_PASSWORD}
zfs:
datasetParentName: tank/ci/${CI_BUILD_KEY}/v
detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s
datasetEnableQuotas: true
datasetEnableReservation: false
datasetPermissionsMode: "0770"
datasetPermissionsUser: 1001
datasetPermissionsGroup: 1001
smb:
shareHost: ${TRUENAS_HOST}
#nameTemplate: ""
namePrefix: "csi-ci-${CI_BUILD_KEY}-"
nameSuffix: ""
shareAuxiliaryConfigurationTemplate: |
#guest ok = yes
#guest only = yes
shareHome: false
shareAllowedHosts: []
shareDeniedHosts: []
#shareDefaultPermissions: true
shareGuestOk: false
#shareGuestOnly: true
#shareShowHiddenFiles: true
shareRecycleBin: false
shareBrowsable: false
shareAccessBasedEnumeration: true
shareTimeMachine: false
#shareStorageTask:
node:
mount:
mount_flags: "username=smbroot,password=smbroot"
_private:
csi:
volume:
idHash:
strategy: crc16

View File

@ -8,10 +8,20 @@
_private:
csi:
volume:
derivedContext:
volumeContext:
# driver left blank is used to auto select
driver: memory # strictly to facilitate testing
#driver: kubernetes
# THIS IS UNSUPPORTED, BAD THINGS WILL HAPPEN IF NOT CONFIGURED PROPERLY
#
# note the volume length must *always* be the same for every call for the same volume by the CO
# the length must NOT execeed 128 characters
# must start with an alphanumeric character
# must only contain alphnumeric characters or `-` or `_`
idTemplate: "{{ parameters.[csi.storage.k8s.io/pvc/namespace] }}-{{ parameters.[csi.storage.k8s.io/pvc/name] }}"
# if set, this hash is applied *after* the templating above
idHash:
strategy: crc16
#strategy: crc32

2596
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "democratic-csi",
"version": "1.8.3",
"version": "1.8.4",
"description": "kubernetes csi driver framework",
"main": "bin/democratic-csi",
"scripts": {
@ -24,6 +24,7 @@
"async-mutex": "^0.4.0",
"axios": "^1.1.3",
"bunyan": "^1.8.15",
"crc": "^4.3.2",
"fs-extra": "^11.1.0",
"handlebars": "^4.7.7",
"js-yaml": "^4.0.0",

View File

@ -443,16 +443,9 @@ class ControllerClientCommonDriver extends CsiBaseDriver {
const driver = this;
let config_key = this.getConfigKey();
let name = call.request.name;
let volume_id = await driver.getVolumeIdFromCall(call);
let volume_content_source = call.request.volume_content_source;
if (!name) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`volume name is required`
);
}
if (
call.request.volume_capabilities &&
call.request.volume_capabilities.length > 0
@ -513,7 +506,7 @@ class ControllerClientCommonDriver extends CsiBaseDriver {
);
}
const volume_path = driver.getControllerVolumePath(name);
const volume_path = driver.getControllerVolumePath(volume_id);
let response;
let source_path;
@ -596,7 +589,7 @@ class ControllerClientCommonDriver extends CsiBaseDriver {
}
}
let volume_context = driver.getVolumeContext(name);
let volume_context = driver.getVolumeContext(volume_id);
volume_context["provisioner_driver"] = driver.options.driver;
if (driver.options.instance_id) {
@ -611,7 +604,7 @@ class ControllerClientCommonDriver extends CsiBaseDriver {
const res = {
volume: {
volume_id: name,
volume_id,
//capacity_bytes: capacity_bytes, // kubernetes currently pukes if capacity is returned as 0
capacity_bytes: 0,
content_source: volume_content_source,
@ -634,16 +627,16 @@ class ControllerClientCommonDriver extends CsiBaseDriver {
async DeleteVolume(call) {
const driver = this;
let name = call.request.volume_id;
let volume_id = call.request.volume_id;
if (!name) {
if (!volume_id) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`volume_id is required`
);
}
const volume_path = driver.getControllerVolumePath(name);
const volume_path = driver.getControllerVolumePath(volume_id);
await driver.deleteDir(volume_path);
return {};

View File

@ -44,12 +44,11 @@ class ControllerLocalHostpathDriver extends ControllerClientCommonDriver {
return "local-hostpath";
}
getVolumeContext(name) {
getVolumeContext(volume_id) {
const driver = this;
const config_key = driver.getConfigKey();
return {
node_attach_driver: "hostpath",
path: driver.getShareVolumePath(name),
path: driver.getShareVolumePath(volume_id),
};
}

View File

@ -13,13 +13,13 @@ class ControllerLustreClientDriver extends ControllerClientCommonDriver {
return "lustre";
}
getVolumeContext(name) {
getVolumeContext(volume_id) {
const driver = this;
const config_key = driver.getConfigKey();
return {
node_attach_driver: "lustre",
server: this.options[config_key].shareHost,
share: driver.getShareVolumePath(name),
share: driver.getShareVolumePath(volume_id),
};
}

View File

@ -13,13 +13,13 @@ class ControllerNfsClientDriver extends ControllerClientCommonDriver {
return "nfs";
}
getVolumeContext(name) {
getVolumeContext(volume_id) {
const driver = this;
const config_key = driver.getConfigKey();
return {
node_attach_driver: "nfs",
server: this.options[config_key].shareHost,
share: driver.getShareVolumePath(name),
share: driver.getShareVolumePath(volume_id),
};
}

View File

@ -13,13 +13,13 @@ class ControllerSmbClientDriver extends ControllerClientCommonDriver {
return "smb";
}
getVolumeContext(name) {
getVolumeContext(volume_id) {
const driver = this;
const config_key = driver.getConfigKey();
return {
node_attach_driver: "smb",
server: this.options[config_key].shareHost,
share: driver.stripLeadingSlash(driver.getShareVolumePath(name)),
share: driver.stripLeadingSlash(driver.getShareVolumePath(volume_id)),
};
}

View File

@ -176,8 +176,8 @@ class ControllerSynologyDriver extends CsiBaseDriver {
}
}
buildIscsiName(name) {
let iscsiName = name;
buildIscsiName(volume_id) {
let iscsiName = volume_id;
if (this.options.iscsi.namePrefix) {
iscsiName = this.options.iscsi.namePrefix + iscsiName;
}
@ -324,16 +324,9 @@ class ControllerSynologyDriver extends CsiBaseDriver {
const driver = this;
const httpClient = await driver.getHttpClient();
let name = call.request.name;
let volume_id = await driver.getVolumeIdFromCall(call);
let volume_content_source = call.request.volume_content_source;
if (!name) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`volume name is required`
);
}
if (
call.request.volume_capabilities &&
call.request.volume_capabilities.length > 0
@ -414,7 +407,7 @@ class ControllerSynologyDriver extends CsiBaseDriver {
);
break;
case "iscsi":
let iscsiName = driver.buildIscsiName(name);
let iscsiName = driver.buildIscsiName(volume_id);
let lunTemplate;
let targetTemplate;
let data;
@ -670,7 +663,7 @@ class ControllerSynologyDriver extends CsiBaseDriver {
const res = {
volume: {
volume_id: name,
volume_id,
capacity_bytes, // kubernetes currently pukes if capacity is returned as 0
content_source: volume_content_source,
volume_context,
@ -689,9 +682,9 @@ class ControllerSynologyDriver extends CsiBaseDriver {
const driver = this;
const httpClient = await driver.getHttpClient();
let name = call.request.volume_id;
let volume_id = call.request.volume_id;
if (!name) {
if (!volume_id) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`volume_id is required`
@ -718,7 +711,7 @@ class ControllerSynologyDriver extends CsiBaseDriver {
case "iscsi":
//await httpClient.DeleteAllLuns();
let iscsiName = driver.buildIscsiName(name);
let iscsiName = driver.buildIscsiName(volume_id);
let iqn = driver.options.iscsi.baseiqn + iscsiName;
let target = await httpClient.GetTargetByIQN(iqn);
@ -786,9 +779,9 @@ class ControllerSynologyDriver extends CsiBaseDriver {
const driver = this;
const httpClient = await driver.getHttpClient();
let name = call.request.volume_id;
let volume_id = call.request.volume_id;
if (!name) {
if (!volume_id) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`volume_id is required`
@ -850,7 +843,7 @@ class ControllerSynologyDriver extends CsiBaseDriver {
break;
case "iscsi":
node_expansion_required = true;
let iscsiName = driver.buildIscsiName(name);
let iscsiName = driver.buildIscsiName(volume_id);
response = await httpClient.GetLunUUIDByName(iscsiName);
await httpClient.ExpandISCSILun(response, capacity_bytes);

View File

@ -644,7 +644,7 @@ class ControllerZfsBaseDriver extends CsiBaseDriver {
let snapshotParentDatasetName = this.getDetachedSnapshotParentDatasetName();
let zvolBlocksize = this.options.zfs.zvolBlocksize || "16K";
let name = call.request.name;
let volume_id = await driver.getVolumeIdFromName(name);
let volume_id = await driver.getVolumeIdFromCall(call);
let volume_content_source = call.request.volume_content_source;
if (!datasetParentName) {
@ -654,13 +654,6 @@ class ControllerZfsBaseDriver extends CsiBaseDriver {
);
}
if (!name) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`volume name is required`
);
}
if (
call.request.volume_capabilities &&
call.request.volume_capabilities.length > 0

View File

@ -265,6 +265,11 @@ class FreeNASApiDriver extends CsiBaseDriver {
break;
}
if (isScale && semver.satisfies(truenasVersion, ">=23.10")) {
delete share.quiet;
delete share.nfs_quiet;
}
if (isScale && semver.satisfies(truenasVersion, ">=22.12")) {
share.path = share.paths[0];
delete share.paths;
@ -680,6 +685,7 @@ class FreeNASApiDriver extends CsiBaseDriver {
// According to RFC3270, 'Each iSCSI node, whether an initiator or target, MUST have an iSCSI name. Initiators and targets MUST support the receipt of iSCSI names of up to the maximum length of 223 bytes.'
// https://kb.netapp.com/Advice_and_Troubleshooting/Miscellaneous/What_is_the_maximum_length_of_a_iSCSI_iqn_name
// https://tools.ietf.org/html/rfc3720
// https://github.com/SCST-project/scst/blob/master/scst/src/dev_handlers/scst_vdisk.c#L203
iscsiName = iscsiName.toLowerCase();
let extentDiskName = "zvol/" + datasetName;
@ -697,6 +703,14 @@ class FreeNASApiDriver extends CsiBaseDriver {
);
}
// https://github.com/SCST-project/scst/blob/master/scst/src/dev_handlers/scst_vdisk.c#L203
if (isScale && iscsiName.length > 64) {
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`extent name cannot exceed 64 characters: ${iscsiName}`
);
}
this.ctx.logger.info(
"FreeNAS creating iscsi assets with name: " + iscsiName
);
@ -2207,7 +2221,7 @@ class FreeNASApiDriver extends CsiBaseDriver {
let snapshotParentDatasetName = this.getDetachedSnapshotParentDatasetName();
let zvolBlocksize = this.options.zfs.zvolBlocksize || "16K";
let name = call.request.name;
let volume_id = await driver.getVolumeIdFromName(name);
let volume_id = await driver.getVolumeIdFromCall(call);
let volume_content_source = call.request.volume_content_source;
let minimum_volume_size = await driver.getMinimumVolumeSize();
let default_required_bytes = 1073741824;
@ -2219,13 +2233,6 @@ class FreeNASApiDriver extends CsiBaseDriver {
);
}
if (!name) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`volume name is required`
);
}
if (
call.request.volume_capabilities &&
call.request.volume_capabilities.length > 0

View File

@ -698,15 +698,16 @@ class Api {
// wait for job to finish
do {
currentTime = Date.now() / 1000;
if (timeout > 0 && currentTime > startTime + timeout) {
throw new Error("timeout waiting for job to complete");
}
if (job) {
await sleep(check_interval);
}
job = await this.CoreGetJobs({ id: job_id });
job = job[0];
currentTime = Date.now() / 1000;
if (timeout > 0 && currentTime > startTime + timeout) {
throw new Error("timeout waiting for job to complete");
}
} while (!["SUCCESS", "ABORTED", "FAILED"].includes(job.state));
return job;

View File

@ -131,15 +131,18 @@ class Client {
delete options.httpAgent;
delete options.httpsAgent;
this.logger.debug("FREENAS HTTP REQUEST: " + stringify(options));
let duration = parseFloat((Math.round((_.get(response, 'duration', 0) + Number.EPSILON) * 100) / 100) / 1000).toFixed(2);
this.logger.debug("FREENAS HTTP REQUEST DETAILS: " + stringify(options));
this.logger.debug("FREENAS HTTP REQUEST DURATION: " + duration + "s");
this.logger.debug("FREENAS HTTP ERROR: " + error);
this.logger.debug(
"FREENAS HTTP STATUS: " + _.get(response, "statusCode", "")
"FREENAS HTTP RESPONSE STATUS CODE: " + _.get(response, "statusCode", "")
);
this.logger.debug(
"FREENAS HTTP HEADERS: " + stringify(_.get(response, "headers", ""))
"FREENAS HTTP RESPONSE HEADERS: " + stringify(_.get(response, "headers", ""))
);
this.logger.debug("FREENAS HTTP BODY: " + stringify(body));
this.logger.debug("FREENAS HTTP RESPONSE BODY: " + stringify(body));
}
async get(endpoint, data) {

View File

@ -314,6 +314,11 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver {
break;
}
if (isScale && semver.satisfies(truenasVersion, ">=23.10")) {
delete share.quiet;
delete share.nfs_quiet;
}
if (isScale && semver.satisfies(truenasVersion, ">=22.12")) {
share.path = share.paths[0];
delete share.paths;
@ -728,6 +733,7 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver {
// According to RFC3270, 'Each iSCSI node, whether an initiator or target, MUST have an iSCSI name. Initiators and targets MUST support the receipt of iSCSI names of up to the maximum length of 223 bytes.'
// https://kb.netapp.com/Advice_and_Troubleshooting/Miscellaneous/What_is_the_maximum_length_of_a_iSCSI_iqn_name
// https://tools.ietf.org/html/rfc3720
// https://github.com/SCST-project/scst/blob/master/scst/src/dev_handlers/scst_vdisk.c#L203
iscsiName = iscsiName.toLowerCase();
let extentDiskName = "zvol/" + datasetName;
@ -742,7 +748,15 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver {
if (extentDiskName.length > maxZvolNameLength) {
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`extent disk name cannot exceed ${maxZvolNameLength} characters: ${extentDiskName}`
`extent disk name cannot exceed ${maxZvolNameLength} characters: ${extentDiskName}`
);
}
// https://github.com/SCST-project/scst/blob/master/scst/src/dev_handlers/scst_vdisk.c#L203
if (isScale && iscsiName.length > 64) {
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`extent name cannot exceed 64 characters: ${iscsiName}`
);
}
@ -1982,6 +1996,9 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver {
this.ctx.logger.debug("zfs props data: %j", properties);
let iscsiName =
properties[FREENAS_ISCSI_ASSETS_NAME_PROPERTY_NAME].value;
// name correlates to the extent NOT the target
let kName = iscsiName.replaceAll(".", "_");
/**
* command = execClient.buildCommand("systemctl", ["reload", "scst"]);
@ -1998,7 +2015,7 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver {
*/
command = execClient.buildCommand("sh", [
"-c",
`echo 1 > /sys/kernel/scst_tgt/devices/${iscsiName}/resync_size`,
`echo 1 > /sys/kernel/scst_tgt/devices/${kName}/resync_size`,
]);
reload = true;
} else {

View File

@ -5,6 +5,7 @@ const fs = require("fs");
const CsiProxyClient = require("../utils/csi_proxy_client").CsiProxyClient;
const k8s = require("@kubernetes/client-node");
const { GrpcError, grpc } = require("../utils/grpc");
const Handlebars = require("handlebars");
const { Mount } = require("../utils/mount");
const { OneClient } = require("../utils/oneclient");
const { Filesystem } = require("../utils/filesystem");
@ -14,7 +15,6 @@ const registry = require("../utils/registry");
const semver = require("semver");
const GeneralUtils = require("../utils/general");
const { Zetabyte } = require("../utils/zfs");
const { transport } = require("winston");
const __REGISTRY_NS__ = "CsiBaseDriver";
@ -366,26 +366,120 @@ class CsiBaseDriver {
* the value of `volume_id` to play nicely with scenarios that do not support
* long names (ie: smb share, etc)
*
* @param {*} name
* per csi, strings have a max size of 128 bytes, volume_id should NOT
* execeed this limit
*
* Any Unicode string that conforms to the length limit is allowed
* except those containing the following banned characters:
* U+0000-U+0008, U+000B, U+000C, U+000E-U+001F, U+007F-U+009F.
* (These are control characters other than commonly used whitespace.)
*
* https://github.com/container-storage-interface/spec/blob/master/spec.md#size-limits
* https://docs.oracle.com/cd/E26505_01/html/E37384/gbcpt.html
*
* @param {*} call
* @returns
*/
async getVolumeIdFromName(name) {
async getVolumeIdFromCall(call) {
const driver = this;
const strategy = _.get(
let volume_id = call.request.name;
if (!volume_id) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`volume name is required`
);
}
const idTemplate = _.get(
driver.options,
"_private.csi.volume.idTemplate",
""
);
if (idTemplate) {
volume_id = Handlebars.compile(idTemplate)({
name: call.request.name,
parameters: call.request.parameters,
});
if (!volume_id) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`generated volume_id is empty, idTemplate may be invalid`
);
}
}
const hash_strategy = _.get(
driver.options,
"_private.csi.volume.idHash.strategy",
""
);
switch (strategy.toLowerCase()) {
case "md5":
return GeneralUtils.md5(name);
case "crc32":
return GeneralUtils.crc32(name);
case "crc16":
return GeneralUtils.crc16(name);
default:
return name;
if (hash_strategy) {
switch (hash_strategy.toLowerCase()) {
case "md5":
volume_id = GeneralUtils.md5(volume_id);
break;
case "crc8":
volume_id = GeneralUtils.crc8(volume_id);
break;
case "crc16":
volume_id = GeneralUtils.crc16(volume_id);
break;
case "crc32":
volume_id = GeneralUtils.crc32(volume_id);
break;
default:
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`unkown hash strategy: ${hash_strategy}`
);
}
}
volume_id = String(volume_id);
if (volume_id.length > 128) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`generated volume_id '${volume_id}' is too large`
);
}
if (volume_id.length < 1) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`generated volume_id '${volume_id}' is too small`
);
}
/**
* technically zfs allows `:` and `.` in addition to `_` and `-`
*/
let invalid_chars;
invalid_chars = volume_id.match(/[^a-z0-9_\-]/gi);
if (invalid_chars) {
invalid_chars = String.prototype.concat(
...new Set(invalid_chars.join(""))
);
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`generated volume_id '${volume_id}' contains invalid characters: '${invalid_chars}'`
);
}
/**
* Dataset names must begin with an alphanumeric character.
*/
if (!/^[a-z0-9]/gi.test(volume_id)) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`generated volume_id '${volume_id}' must begin with alphanumeric character`
);
}
return volume_id;
}
async GetPluginInfo(call) {

View File

@ -2,6 +2,32 @@ const _ = require("lodash");
const axios = require("axios");
const crypto = require("crypto");
const dns = require("dns");
const crc = require("crc");
axios.interceptors.request.use(
function (config) {
config.metadata = { startTime: new Date() };
return config;
},
function (error) {
return Promise.reject(error);
}
);
axios.interceptors.response.use(
function (response) {
response.config.metadata.endTime = new Date();
response.duration =
response.config.metadata.endTime - response.config.metadata.startTime;
return response;
},
function (error) {
error.config.metadata.endTime = new Date();
error.duration =
error.config.metadata.endTime - error.config.metadata.startTime;
return Promise.reject(error);
}
);
function sleep(ms) {
return new Promise((resolve) => {
@ -24,58 +50,16 @@ function md5(val) {
return crypto.createHash("md5").update(val).digest("hex");
}
function crc32(val) {
for (var a, o = [], c = 0; c < 256; c++) {
a = c;
for (var f = 0; f < 8; f++) a = 1 & a ? 3988292384 ^ (a >>> 1) : a >>> 1;
o[c] = a;
}
for (var n = -1, t = 0; t < val.length; t++)
n = (n >>> 8) ^ o[255 & (n ^ val.charCodeAt(t))];
return (-1 ^ n) >>> 0;
function crc8(data) {
return crc.crc8(data);
}
const crctab16 = new Uint16Array([
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48,
0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108,
0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb,
0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399,
0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e,
0xfae7, 0xc87c, 0xd9f5, 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e,
0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd,
0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285,
0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44,
0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 0x6306, 0x728f, 0x4014,
0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5,
0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3,
0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862,
0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e,
0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1,
0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483,
0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50,
0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710,
0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7,
0x6e6e, 0x5cf5, 0x4d7c, 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1,
0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72,
0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e,
0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf,
0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 0xf78f, 0xe606, 0xd49d,
0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c,
0x3de3, 0x2c6a, 0x1ef1, 0x0f78,
]);
// calculate the 16-bit CRC of data with predetermined length.
function crc16(data) {
var res = 0x0ffff;
return crc.crc16(data);
}
for (let b of data) {
res = ((res >> 8) & 0x0ff) ^ crctab16[(res ^ b) & 0xff];
}
return ~res & 0x0ffff;
function crc32(data) {
return crc.crc32(data);
}
function lockKeysFromRequest(call, serviceMethodName) {
@ -278,6 +262,7 @@ module.exports.sleep = sleep;
module.exports.md5 = md5;
module.exports.crc32 = crc32;
module.exports.crc16 = crc16;
module.exports.crc8 = crc8;
module.exports.lockKeysFromRequest = lockKeysFromRequest;
module.exports.getLargestNumber = getLargestNumber;
module.exports.stringify = stringify;

View File

@ -29,6 +29,9 @@ class NVMEoF {
nvmeof.logger = nvmeof.options.logger;
} else {
nvmeof.logger = console;
console.verbose = function() {
console.log(...arguments);
}
}
}
@ -273,11 +276,33 @@ class NVMEoF {
};
}
async pathExists(path) {
const nvmeof = this;
try {
await nvmeof.exec("stat", [
path,
]);
return true;
} catch (err) {
return false;
}
}
async nativeMultipathEnabled() {
const nvmeof = this;
let result = await nvmeof.exec("cat", [
"/sys/module/nvme_core/parameters/multipath",
]);
let result;
try {
result = await nvmeof.exec("cat", [
"/sys/module/nvme_core/parameters/multipath",
]);
} catch (err) {
if (err.code == 1 && err.stderr.includes("No such file or directory")) {
return false;
}
throw err;
}
return result.stdout.trim() == "Y";
}