commit
79f16a0cf6
|
|
@ -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
|
||||
|
|
|
|||
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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: ""
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {};
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue