csi-test conformance, relax iscsi name length

Signed-off-by: Travis Glenn Hansen <travisghansen@yahoo.com>
This commit is contained in:
Travis Glenn Hansen 2021-09-21 21:36:14 -06:00
parent 109d493cfc
commit 855f48d3af
14 changed files with 951 additions and 129 deletions

View File

@ -1,3 +1,16 @@
# v1.4.0
Released 2021-09-21
- more advanced logic for iscsi naming limits (allowing > 63 chars in certain
circumstances, SCALE, linux, FreeBSD 13+)
- various updates to support running the csi-test tool and conform to expected
responses/behaviors (full conformance for several drivers!)
- default `fs_type` during `NodeStageVolume` when omitted by `CO`
- automatcally add `guest` mount option to `cifs` shares when creds are absent
- fix `ListVolumes` and `ListSnapshot` behavior on various `zfs-generic-*` and
`freenas-*` drivers
# v1.3.2 # v1.3.2
Released 2021-09-09 Released 2021-09-09

View File

@ -201,6 +201,7 @@ non-`root` user when connecting to the FreeNAS server:
account user query select=id,username,uid,sudo_nopasswd account user query select=id,username,uid,sudo_nopasswd
# find the `id` of the user you want to update (note, this is distinct from the `uid`) # find the `id` of the user you want to update (note, this is distinct from the `uid`)
account user update id=<id> sudo=true
account user update id=<id> sudo_nopasswd=true account user update id=<id> sudo_nopasswd=true
# optional if you want to disable password # optional if you want to disable password
#account user update id=<id> password_disabled=true #account user update id=<id> password_disabled=true

View File

@ -37,7 +37,7 @@ zfs:
# total volume name (zvol/<datasetParentName>/<pvc name>) length cannot exceed 63 chars # total volume name (zvol/<datasetParentName>/<pvc name>) length cannot exceed 63 chars
# https://www.ixsystems.com/documentation/freenas/11.2-U5/storage.html#zfs-zvol-config-opts-tab # https://www.ixsystems.com/documentation/freenas/11.2-U5/storage.html#zfs-zvol-config-opts-tab
# standard volume naming overhead is 46 chars # standard volume naming overhead is 46 chars
# datasetParentName should therefore be 17 chars or less # datasetParentName should therefore be 17 chars or less when using TrueNAS 12 or below
datasetParentName: tank/k8s/b/vols datasetParentName: tank/k8s/b/vols
# do NOT make datasetParentName and detachedSnapshotsDatasetParentName overlap # do NOT make datasetParentName and detachedSnapshotsDatasetParentName overlap
# they may be siblings, but neither should be nested in the other # they may be siblings, but neither should be nested in the other

View File

@ -47,7 +47,7 @@ zfs:
# total volume name (zvol/<datasetParentName>/<pvc name>) length cannot exceed 63 chars # total volume name (zvol/<datasetParentName>/<pvc name>) length cannot exceed 63 chars
# https://www.ixsystems.com/documentation/freenas/11.2-U5/storage.html#zfs-zvol-config-opts-tab # https://www.ixsystems.com/documentation/freenas/11.2-U5/storage.html#zfs-zvol-config-opts-tab
# standard volume naming overhead is 46 chars # standard volume naming overhead is 46 chars
# datasetParentName should therefore be 17 chars or less # datasetParentName should therefore be 17 chars or less when using TrueNAS 12 or below
datasetParentName: tank/k8s/b/vols datasetParentName: tank/k8s/b/vols
# do NOT make datasetParentName and detachedSnapshotsDatasetParentName overlap # do NOT make datasetParentName and detachedSnapshotsDatasetParentName overlap
# they may be siblings, but neither should be nested in the other # they may be siblings, but neither should be nested in the other

64
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "democratic-csi", "name": "democratic-csi",
"version": "1.3.2", "version": "1.4.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "democratic-csi", "name": "democratic-csi",
"version": "1.3.2", "version": "1.4.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@grpc/grpc-js": "^1.3.6", "@grpc/grpc-js": "^1.3.6",
@ -40,9 +40,9 @@
} }
}, },
"node_modules/@babel/helper-validator-identifier": { "node_modules/@babel/helper-validator-identifier": {
"version": "7.14.9", "version": "7.15.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
"integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@ -197,9 +197,9 @@
} }
}, },
"node_modules/@grpc/proto-loader": { "node_modules/@grpc/proto-loader": {
"version": "0.6.4", "version": "0.6.5",
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.4.tgz", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.5.tgz",
"integrity": "sha512-7xvDvW/vJEcmLUltCUGOgWRPM8Oofv0eCFSVMuKqaqWJaXSzmB+m9hiyqe34QofAl4WAzIKUZZlinIF9FOHyTQ==", "integrity": "sha512-GZdzyVQI1Bln/kCzIYgTKu+rQJ5dno0gVrfmLe4jqQu7T2e7svSwJzpCBqVU5hhBSJP3peuPjOMWsj5GR61YmQ==",
"dependencies": { "dependencies": {
"@types/long": "^4.0.1", "@types/long": "^4.0.1",
"lodash.camelcase": "^4.3.0", "lodash.camelcase": "^4.3.0",
@ -311,9 +311,9 @@
"integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w=="
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "16.9.0", "version": "16.9.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.0.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.6.tgz",
"integrity": "sha512-nmP+VR4oT0pJUPFbKE4SXj3Yb4Q/kz3M9dSAO1GGMebRKWHQxLfDNmU/yh3xxCJha3N60nQ/JwXWwOE/ZSEVag==" "integrity": "sha512-YHUZhBOMTM3mjFkXVcK+WwAcYmyhe1wL4lfqNtzI0b3qAy7yuSetnM7QJazgE5PFmgVTNGiLOgRFfJMqW7XpSQ=="
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "7.4.1", "version": "7.4.1",
@ -361,9 +361,9 @@
} }
}, },
"node_modules/ansi-regex": { "node_modules/ansi-regex": {
"version": "5.0.0", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -2793,9 +2793,9 @@
} }
}, },
"node_modules/table/node_modules/ajv": { "node_modules/table/node_modules/ajv": {
"version": "8.6.2", "version": "8.6.3",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz",
"integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
@ -3108,9 +3108,9 @@
} }
}, },
"@babel/helper-validator-identifier": { "@babel/helper-validator-identifier": {
"version": "7.14.9", "version": "7.15.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
"integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
"dev": true "dev": true
}, },
"@babel/highlight": { "@babel/highlight": {
@ -3239,9 +3239,9 @@
} }
}, },
"@grpc/proto-loader": { "@grpc/proto-loader": {
"version": "0.6.4", "version": "0.6.5",
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.4.tgz", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.5.tgz",
"integrity": "sha512-7xvDvW/vJEcmLUltCUGOgWRPM8Oofv0eCFSVMuKqaqWJaXSzmB+m9hiyqe34QofAl4WAzIKUZZlinIF9FOHyTQ==", "integrity": "sha512-GZdzyVQI1Bln/kCzIYgTKu+rQJ5dno0gVrfmLe4jqQu7T2e7svSwJzpCBqVU5hhBSJP3peuPjOMWsj5GR61YmQ==",
"requires": { "requires": {
"@types/long": "^4.0.1", "@types/long": "^4.0.1",
"lodash.camelcase": "^4.3.0", "lodash.camelcase": "^4.3.0",
@ -3343,9 +3343,9 @@
"integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w=="
}, },
"@types/node": { "@types/node": {
"version": "16.9.0", "version": "16.9.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.0.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.6.tgz",
"integrity": "sha512-nmP+VR4oT0pJUPFbKE4SXj3Yb4Q/kz3M9dSAO1GGMebRKWHQxLfDNmU/yh3xxCJha3N60nQ/JwXWwOE/ZSEVag==" "integrity": "sha512-YHUZhBOMTM3mjFkXVcK+WwAcYmyhe1wL4lfqNtzI0b3qAy7yuSetnM7QJazgE5PFmgVTNGiLOgRFfJMqW7XpSQ=="
}, },
"acorn": { "acorn": {
"version": "7.4.1", "version": "7.4.1",
@ -3378,9 +3378,9 @@
"dev": true "dev": true
}, },
"ansi-regex": { "ansi-regex": {
"version": "5.0.0", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
}, },
"ansi-styles": { "ansi-styles": {
"version": "4.3.0", "version": "4.3.0",
@ -5240,9 +5240,9 @@
}, },
"dependencies": { "dependencies": {
"ajv": { "ajv": {
"version": "8.6.2", "version": "8.6.3",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz",
"integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==",
"dev": true, "dev": true,
"requires": { "requires": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",

View File

@ -1,6 +1,6 @@
{ {
"name": "democratic-csi", "name": "democratic-csi",
"version": "1.3.2", "version": "1.4.0",
"description": "kubernetes csi driver framework", "description": "kubernetes csi driver framework",
"main": "bin/democratic-csi", "main": "bin/democratic-csi",
"scripts": { "scripts": {

View File

@ -1,6 +1,7 @@
const { CsiBaseDriver } = require("../index"); const { CsiBaseDriver } = require("../index");
const { GrpcError, grpc } = require("../../utils/grpc"); const { GrpcError, grpc } = require("../../utils/grpc");
const cp = require("child_process"); const cp = require("child_process");
const fs = require("fs");
const semver = require("semver"); const semver = require("semver");
/** /**
@ -329,6 +330,10 @@ class ControllerClientCommonDriver extends CsiBaseDriver {
]); ]);
} }
async directoryExists(path) {
return fs.existsSync(path);
}
/** /**
* Create a volume doing in essence the following: * Create a volume doing in essence the following:
* 1. create directory * 1. create directory
@ -353,11 +358,28 @@ class ControllerClientCommonDriver extends CsiBaseDriver {
); );
} }
if (call.request.volume_capabilities) { if (
call.request.volume_capabilities &&
call.request.volume_capabilities.length > 0
) {
const result = this.assertCapabilities(call.request.volume_capabilities); const result = this.assertCapabilities(call.request.volume_capabilities);
if (result.valid !== true) { if (result.valid !== true) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, result.message); throw new GrpcError(grpc.status.INVALID_ARGUMENT, result.message);
} }
} else {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
"missing volume_capabilities"
);
}
if (
!call.request.capacity_range ||
Object.keys(call.request.capacity_range).length === 0
) {
call.request.capacity_range = {
required_bytes: 1073741824, // meaningless
};
} }
if ( if (
@ -431,6 +453,13 @@ class ControllerClientCommonDriver extends CsiBaseDriver {
break; break;
} }
if (!(await driver.directoryExists(source_path))) {
throw new GrpcError(
grpc.status.NOT_FOUND,
`invalid volume_content_source path: ${source_path}`
);
}
driver.ctx.logger.debug("controller source path: %s", source_path); driver.ctx.logger.debug("controller source path: %s", source_path);
response = await driver.cloneDir(source_path, volume_path); response = await driver.cloneDir(source_path, volume_path);
} }
@ -630,7 +659,10 @@ class ControllerClientCommonDriver extends CsiBaseDriver {
const volume_path = driver.getControllerVolumePath(source_volume_id); const volume_path = driver.getControllerVolumePath(source_volume_id);
const snapshot_path = driver.getControllerSnapshotPath(snapshot_id); const snapshot_path = driver.getControllerSnapshotPath(snapshot_id);
await driver.cloneDir(volume_path, snapshot_path); // do NOT overwrite existing snapshot
if (!(await driver.directoryExists(snapshot_path))) {
await driver.cloneDir(volume_path, snapshot_path);
}
return { return {
snapshot: { snapshot: {
@ -681,6 +713,25 @@ class ControllerClientCommonDriver extends CsiBaseDriver {
*/ */
async ValidateVolumeCapabilities(call) { async ValidateVolumeCapabilities(call) {
const driver = this; const driver = this;
const volume_id = call.request.volume_id;
if (!volume_id) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing volume_id`);
}
const capabilities = call.request.volume_capabilities;
if (!capabilities || capabilities.length === 0) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing capabilities`);
}
const volume_path = driver.getControllerVolumePath(volume_id);
if (!(await driver.directoryExists(volume_path))) {
throw new GrpcError(
grpc.status.NOT_FOUND,
`invalid volume_id: ${volume_id}`
);
}
const result = this.assertCapabilities(call.request.volume_capabilities); const result = this.assertCapabilities(call.request.volume_capabilities);
if (result.valid !== true) { if (result.valid !== true) {

View File

@ -146,6 +146,17 @@ class SynologyHttpClient {
}); });
} }
async GetLuns() {
const lun_list = {
api: "SYNO.Core.ISCSI.LUN",
version: "1",
method: "list",
};
let response = await this.do_request("GET", "entry.cgi", lun_list);
return response.body.data.luns;
}
async GetLunUUIDByName(name) { async GetLunUUIDByName(name) {
const lun_list = { const lun_list = {
api: "SYNO.Core.ISCSI.LUN", api: "SYNO.Core.ISCSI.LUN",
@ -214,6 +225,30 @@ class SynologyHttpClient {
} }
} }
async GetSnapshots() {
let luns = await this.GetLuns();
let snapshots = [];
for (let lun of luns) {
const get_snapshot_info = {
api: "SYNO.Core.ISCSI.LUN",
method: "list_snapshot",
version: 1,
src_lun_uuid: JSON.stringify(lun.uuid),
};
let response = await this.do_request(
"GET",
"entry.cgi",
get_snapshot_info
);
snapshots = snapshots.concat(response.body.data.snapshots);
}
return snapshots;
}
async GetSnapshotByLunIDAndName(lun_id, name) { async GetSnapshotByLunIDAndName(lun_id, name) {
const get_snapshot_info = { const get_snapshot_info = {
lid: lun_id, //check? lid: lun_id, //check?

View File

@ -247,11 +247,28 @@ class ControllerSynologyDriver extends CsiBaseDriver {
); );
} }
if (call.request.volume_capabilities) { if (
call.request.volume_capabilities &&
call.request.volume_capabilities.length > 0
) {
const result = this.assertCapabilities(call.request.volume_capabilities); const result = this.assertCapabilities(call.request.volume_capabilities);
if (result.valid !== true) { if (result.valid !== true) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, result.message); throw new GrpcError(grpc.status.INVALID_ARGUMENT, result.message);
} }
} else {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
"missing volume_capabilities"
);
}
if (
!call.request.capacity_range ||
Object.keys(call.request.capacity_range).length === 0
) {
call.request.capacity_range = {
required_bytes: 1073741824,
};
} }
if ( if (
@ -314,17 +331,69 @@ class ControllerSynologyDriver extends CsiBaseDriver {
let lun_uuid; let lun_uuid;
let existingLun; let existingLun;
// ensure volumes with the same name being requested a 2nd time but with a different size fails
try {
let lun = await httpClient.GetLunByName(iscsiName);
if (lun) {
let size = lun.size;
let check = true;
if (check) {
if (
(call.request.capacity_range.required_bytes &&
call.request.capacity_range.required_bytes > 0 &&
size < call.request.capacity_range.required_bytes) ||
(call.request.capacity_range.limit_bytes &&
call.request.capacity_range.limit_bytes > 0 &&
size > call.request.capacity_range.limit_bytes)
) {
throw new GrpcError(
grpc.status.ALREADY_EXISTS,
`volume has already been created with a different size, existing size: ${size}, required_bytes: ${call.request.capacity_range.required_bytes}, limit_bytes: ${call.request.capacity_range.limit_bytes}`
);
}
}
}
} catch (err) {
throw err;
}
if (volume_content_source) { if (volume_content_source) {
let src_lun_uuid; let src_lun_uuid;
let src_lun_id; let src_lun_id;
switch (volume_content_source.type) { switch (volume_content_source.type) {
case "snapshot": case "snapshot":
let parts = volume_content_source.snapshot.snapshot_id.split("/"); let parts = volume_content_source.snapshot.snapshot_id.split("/");
src_lun_id = parts[2]; src_lun_id = parts[2];
if (!src_lun_id) {
throw new GrpcError(
grpc.status.NOT_FOUND,
`invalid snapshot_id: ${volume_content_source.snapshot.snapshot_id}`
);
}
let snapshot_uuid = parts[3]; let snapshot_uuid = parts[3];
if (!snapshot_uuid) {
throw new GrpcError(
grpc.status.NOT_FOUND,
`invalid snapshot_id: ${volume_content_source.snapshot.snapshot_id}`
);
}
let src_lun = await httpClient.GetLunByID(src_lun_id); let src_lun = await httpClient.GetLunByID(src_lun_id);
src_lun_uuid = src_lun.uuid; src_lun_uuid = src_lun.uuid;
let snapshot = await httpClient.GetSnapshotByLunIDAndSnapshotUUID(
src_lun_id,
snapshot_uuid
);
if (!snapshot) {
throw new GrpcError(
grpc.status.NOT_FOUND,
`invalid snapshot_id: ${volume_content_source.snapshot.snapshot_id}`
);
}
existingLun = await httpClient.GetLunByName(iscsiName); existingLun = await httpClient.GetLunByName(iscsiName);
if (!existingLun) { if (!existingLun) {
await httpClient.CreateVolumeFromSnapshot( await httpClient.CreateVolumeFromSnapshot(
@ -340,8 +409,20 @@ class ControllerSynologyDriver extends CsiBaseDriver {
let srcLunName = driver.buildIscsiName( let srcLunName = driver.buildIscsiName(
volume_content_source.volume.volume_id volume_content_source.volume.volume_id
); );
if (!srcLunName) {
throw new GrpcError(
grpc.status.NOT_FOUND,
`invalid volume_id: ${volume_content_source.volume.volume_id}`
);
}
src_lun_uuid = await httpClient.GetLunUUIDByName(srcLunName); src_lun_uuid = await httpClient.GetLunUUIDByName(srcLunName);
if (!src_lun_uuid) {
throw new GrpcError(
grpc.status.NOT_FOUND,
`invalid volume_id: ${volume_content_source.volume.volume_id}`
);
}
await httpClient.CreateClonedVolume(src_lun_uuid, iscsiName); await httpClient.CreateClonedVolume(src_lun_uuid, iscsiName);
} }
break; break;
@ -740,8 +821,6 @@ class ControllerSynologyDriver extends CsiBaseDriver {
); );
} }
// create snapshot here
let iscsiName = driver.buildIscsiName(source_volume_id); let iscsiName = driver.buildIscsiName(source_volume_id);
let lun = await httpClient.GetLunByName(iscsiName); let lun = await httpClient.GetLunByName(iscsiName);
@ -752,6 +831,19 @@ class ControllerSynologyDriver extends CsiBaseDriver {
); );
} }
// check for other snapshopts with the same name on other volumes and fail as appropriate
// TODO: technically this should only be checking lun/snapshots relevant to this specific install of the driver
// but alas an isolation/namespacing mechanism does not exist in synology
let snapshots = await httpClient.GetSnapshots();
for (let snapshot of snapshots) {
if (snapshot.description == name && snapshot.parent_uuid != lun.uuid) {
throw new GrpcError(
grpc.status.ALREADY_EXISTS,
`snapshot name: ${name} is incompatible with source_volume_id: ${source_volume_id} due to being used with another source_volume_id`
);
}
}
// check for already exists // check for already exists
let snapshot = await httpClient.GetSnapshotByLunIDAndName(lun.lun_id, name); let snapshot = await httpClient.GetSnapshotByLunIDAndName(lun.lun_id, name);
if (snapshot) { if (snapshot) {
@ -827,7 +919,14 @@ class ControllerSynologyDriver extends CsiBaseDriver {
let parts = snapshot_id.split("/"); let parts = snapshot_id.split("/");
let lun_id = parts[2]; let lun_id = parts[2];
if (!lun_id) {
return {};
}
let snapshot_uuid = parts[3]; let snapshot_uuid = parts[3];
if (!snapshot_uuid) {
return {};
}
// TODO: delete snapshot // TODO: delete snapshot
let snapshot = await httpClient.GetSnapshotByLunIDAndSnapshotUUID( let snapshot = await httpClient.GetSnapshotByLunIDAndSnapshotUUID(
@ -848,8 +947,55 @@ class ControllerSynologyDriver extends CsiBaseDriver {
*/ */
async ValidateVolumeCapabilities(call) { async ValidateVolumeCapabilities(call) {
const driver = this; const driver = this;
const result = this.assertCapabilities(call.request.volume_capabilities); const httpClient = await driver.getHttpClient();
let response;
const volume_id = call.request.volume_id;
if (!volume_id) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing volume_id`);
}
const capabilities = call.request.volume_capabilities;
if (!capabilities || capabilities.length === 0) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing capabilities`);
}
switch (driver.getDriverShareType()) {
case "nfs":
// TODO: expand volume here
throw new GrpcError(
grpc.status.UNIMPLEMENTED,
`operation not supported by driver`
);
break;
case "smb":
// TODO: expand volume here
throw new GrpcError(
grpc.status.UNIMPLEMENTED,
`operation not supported by driver`
);
break;
case "iscsi":
let iscsiName = driver.buildIscsiName(volume_id);
response = await httpClient.GetLunUUIDByName(iscsiName);
if (!response) {
throw new GrpcError(
grpc.status.NOT_FOUND,
`invalid volume_id: ${volume_id}`
);
}
break;
default:
throw new GrpcError(
grpc.status.UNIMPLEMENTED,
`operation not supported by driver`
);
break;
}
const result = this.assertCapabilities(call.request.volume_capabilities);
if (result.valid !== true) { if (result.valid !== true) {
return { message: result.message }; return { message: result.message };
} }

View File

@ -392,6 +392,62 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
return volume; return volume;
} }
/**
* Get the max size a zvol name can be
*
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=238112
* https://svnweb.freebsd.org/base?view=revision&revision=343485
*/
async getMaxZvolNameLength() {
const driver = this;
const sshClient = driver.getSshClient();
let response;
let command;
let kernel;
let kernel_release;
// get kernel
command = "uname -s";
driver.ctx.logger.verbose("uname command: %s", command);
response = await sshClient.exec(command);
if (response.code !== 0) {
throw new Error("failed to run uname to determine max zvol name length");
} else {
kernel = response.stdout.trim();
}
switch (kernel.toLowerCase().trim()) {
// Linux is 255 (probably larger 4096) but scst may have a 255 limit
// https://ngelinux.com/what-is-the-maximum-file-name-length-in-linux-and-how-to-see-this-is-this-really-255-characters-answer-is-no/
// https://github.com/dmeister/scst/blob/master/iscsi-scst/include/iscsi_scst.h#L28
case "linux":
return 255;
case "freebsd":
// get kernel_release
command = "uname -r";
driver.ctx.logger.verbose("uname command: %s", command);
response = await sshClient.exec(command);
if (response.code !== 0) {
throw new Error(
"failed to run uname to determine max zvol name length"
);
} else {
kernel_release = response.stdout;
let parts = kernel_release.split(".");
let kernel_release_major = parts[0];
if (kernel_release_major >= 13) {
return 255;
} else {
return 63;
}
}
default:
throw new Error(`unknown kernel: ${kernel}`);
}
}
/** /**
* Ensure sane options are used etc * Ensure sane options are used etc
* true = ready * true = ready
@ -498,11 +554,28 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
); );
} }
if (call.request.volume_capabilities) { if (
call.request.volume_capabilities &&
call.request.volume_capabilities.length > 0
) {
const result = this.assertCapabilities(call.request.volume_capabilities); const result = this.assertCapabilities(call.request.volume_capabilities);
if (result.valid !== true) { if (result.valid !== true) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, result.message); throw new GrpcError(grpc.status.INVALID_ARGUMENT, result.message);
} }
} else {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
"missing volume_capabilities"
);
}
if (
!call.request.capacity_range ||
Object.keys(call.request.capacity_range).length === 0
) {
call.request.capacity_range = {
required_bytes: 1073741824,
};
} }
if ( if (
@ -554,18 +627,75 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
); );
} }
// ensure volumes with the same name being requested a 2nd time but with a different size fails
try {
let properties = await zb.zfs.get(datasetName, ["volsize", "refquota"]);
properties = properties[datasetName];
let size;
switch (driverZfsResourceType) {
case "volume":
size = properties["volsize"].value;
break;
case "filesystem":
size = properties["refquota"].value;
break;
default:
throw new Error(
`unknown zfs resource type: ${driverZfsResourceType}`
);
}
let check = false;
if (driverZfsResourceType == "volume") {
check = true;
}
if (
driverZfsResourceType == "filesystem" &&
this.options.zfs.datasetEnableQuotas
) {
check = true;
}
if (check) {
if (
(call.request.capacity_range.required_bytes &&
call.request.capacity_range.required_bytes > 0 &&
size < call.request.capacity_range.required_bytes) ||
(call.request.capacity_range.limit_bytes &&
call.request.capacity_range.limit_bytes > 0 &&
size > call.request.capacity_range.limit_bytes)
) {
throw new GrpcError(
grpc.status.ALREADY_EXISTS,
`volume has already been created with a different size, existing size: ${size}, required_bytes: ${call.request.capacity_range.required_bytes}, limit_bytes: ${call.request.capacity_range.limit_bytes}`
);
}
}
} catch (err) {
if (err.toString().includes("dataset does not exist")) {
// does NOT already exist
} else {
throw err;
}
}
/** /**
* This is specifically a FreeBSD limitation, not sure what linux limit is * This is specifically a FreeBSD limitation, not sure what linux limit is
* https://www.ixsystems.com/documentation/freenas/11.2-U5/storage.html#zfs-zvol-config-opts-tab * https://www.ixsystems.com/documentation/freenas/11.2-U5/storage.html#zfs-zvol-config-opts-tab
* https://www.ixsystems.com/documentation/freenas/11.3-BETA1/intro.html#path-and-name-lengths * https://www.ixsystems.com/documentation/freenas/11.3-BETA1/intro.html#path-and-name-lengths
* https://www.freebsd.org/cgi/man.cgi?query=devfs * https://www.freebsd.org/cgi/man.cgi?query=devfs
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=238112
*/ */
if (driverZfsResourceType == "volume") { if (driverZfsResourceType == "volume") {
let extentDiskName = "zvol/" + datasetName; let extentDiskName = "zvol/" + datasetName;
if (extentDiskName.length > 63) { let maxZvolNameLength = await driver.getMaxZvolNameLength();
driver.ctx.logger.debug("max zvol name length: %s", maxZvolNameLength);
if (extentDiskName.length > maxZvolNameLength) {
throw new GrpcError( throw new GrpcError(
grpc.status.FAILED_PRECONDITION, grpc.status.FAILED_PRECONDITION,
`extent disk name cannot exceed 63 characters: ${extentDiskName}` `extent disk name cannot exceed ${maxZvolNameLength} characters: ${extentDiskName}`
); );
} }
} }
@ -658,7 +788,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
} catch (err) { } catch (err) {
if (err.toString().includes("dataset does not exist")) { if (err.toString().includes("dataset does not exist")) {
throw new GrpcError( throw new GrpcError(
grpc.status.FAILED_PRECONDITION, grpc.status.NOT_FOUND,
`snapshot source_snapshot_id ${volume_content_source_snapshot_id} does not exist` `snapshot source_snapshot_id ${volume_content_source_snapshot_id} does not exist`
); );
} }
@ -700,7 +830,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
} catch (err) { } catch (err) {
if (err.toString().includes("dataset does not exist")) { if (err.toString().includes("dataset does not exist")) {
throw new GrpcError( throw new GrpcError(
grpc.status.FAILED_PRECONDITION, grpc.status.NOT_FOUND,
"dataset does not exists" "dataset does not exists"
); );
} }
@ -720,7 +850,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
} catch (err) { } catch (err) {
if (err.toString().includes("dataset does not exist")) { if (err.toString().includes("dataset does not exist")) {
throw new GrpcError( throw new GrpcError(
grpc.status.FAILED_PRECONDITION, grpc.status.NOT_FOUND,
`snapshot source_snapshot_id ${volume_content_source_snapshot_id} does not exist` `snapshot source_snapshot_id ${volume_content_source_snapshot_id} does not exist`
); );
} }
@ -766,7 +896,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
} catch (err) { } catch (err) {
if (err.toString().includes("dataset does not exist")) { if (err.toString().includes("dataset does not exist")) {
throw new GrpcError( throw new GrpcError(
grpc.status.FAILED_PRECONDITION, grpc.status.NOT_FOUND,
"dataset does not exists" "dataset does not exists"
); );
} }
@ -816,7 +946,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
} catch (err) { } catch (err) {
if (err.toString().includes("dataset does not exist")) { if (err.toString().includes("dataset does not exist")) {
throw new GrpcError( throw new GrpcError(
grpc.status.FAILED_PRECONDITION, grpc.status.NOT_FOUND,
"dataset does not exists" "dataset does not exists"
); );
} }
@ -1274,10 +1404,20 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
const datasetName = datasetParentName; const datasetName = datasetParentName;
let properties; let properties;
properties = await zb.zfs.get(datasetName, ["avail"]); try {
properties = properties[datasetName]; properties = await zb.zfs.get(datasetName, ["avail"]);
properties = properties[datasetName];
return { available_capacity: properties.available.value }; return { available_capacity: properties.available.value };
} catch (err) {
throw err;
// gracefully handle csi-test suite when parent dataset does not yet exist
if (err.toString().includes("dataset does not exist")) {
return { available_capacity: 0 };
} else {
throw err;
}
}
} }
/** /**
@ -1375,7 +1515,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
let entries = []; let entries = [];
let entries_length = 0; let entries_length = 0;
let next_token; let next_token;
let uuid, page, next_page; let uuid;
let response; let response;
const max_entries = call.request.max_entries; const max_entries = call.request.max_entries;
@ -1385,15 +1525,18 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
if (starting_token) { if (starting_token) {
let parts = starting_token.split(":"); let parts = starting_token.split(":");
uuid = parts[0]; uuid = parts[0];
page = parseInt(parts[1]); let start_position = parseInt(parts[1]);
let end_position;
if (max_entries > 0) {
end_position = start_position + max_entries;
}
entries = this.ctx.cache.get(`ListVolumes:result:${uuid}`); entries = this.ctx.cache.get(`ListVolumes:result:${uuid}`);
if (entries) { if (entries) {
entries = JSON.parse(JSON.stringify(entries)); entries = JSON.parse(JSON.stringify(entries));
entries_length = entries.length; entries_length = entries.length;
entries = entries.splice((page - 1) * max_entries, max_entries); entries = entries.slice(start_position, end_position);
if (page * max_entries < entries_length) { if (max_entries > 0 && end_position > entries_length) {
next_page = page + 1; next_token = `${uuid}:${end_position}`;
next_token = `${uuid}:${next_page}`;
} else { } else {
next_token = null; next_token = null;
} }
@ -1404,7 +1547,10 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
return data; return data;
} else { } else {
// TODO: throw error / cache expired throw new GrpcError(
grpc.status.ABORTED,
`invalid starting_token: ${starting_token}`
);
} }
} }
@ -1469,7 +1615,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
for (let row of response.indexed) { for (let row of response.indexed) {
// ignore rows were csi_name is empty // ignore rows were csi_name is empty
if (row[MANAGED_PROPERTY_NAME] != "true") { if (row[MANAGED_PROPERTY_NAME] != "true") {
return; continue;
} }
let volume = await driver.populateCsiVolumeFromData(row); let volume = await driver.populateCsiVolumeFromData(row);
@ -1487,8 +1633,8 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
`ListVolumes:result:${uuid}`, `ListVolumes:result:${uuid}`,
JSON.parse(JSON.stringify(entries)) JSON.parse(JSON.stringify(entries))
); );
next_token = `${uuid}:2`; next_token = `${uuid}:${max_entries}`;
entries = entries.splice(0, max_entries); entries = entries.slice(0, max_entries);
} }
const data = { const data = {
@ -1511,7 +1657,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
let entries = []; let entries = [];
let entries_length = 0; let entries_length = 0;
let next_token; let next_token;
let uuid, page, next_page; let uuid;
const max_entries = call.request.max_entries; const max_entries = call.request.max_entries;
const starting_token = call.request.starting_token; const starting_token = call.request.starting_token;
@ -1526,15 +1672,18 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
if (starting_token) { if (starting_token) {
let parts = starting_token.split(":"); let parts = starting_token.split(":");
uuid = parts[0]; uuid = parts[0];
page = parseInt(parts[1]); let start_position = parseInt(parts[1]);
let end_position;
if (max_entries > 0) {
end_position = start_position + max_entries;
}
entries = this.ctx.cache.get(`ListSnapshots:result:${uuid}`); entries = this.ctx.cache.get(`ListSnapshots:result:${uuid}`);
if (entries) { if (entries) {
entries = JSON.parse(JSON.stringify(entries)); entries = JSON.parse(JSON.stringify(entries));
entries_length = entries.length; entries_length = entries.length;
entries = entries.splice((page - 1) * max_entries, max_entries); entries = entries.slice(start_position, end_position);
if (page * max_entries < entries_length) { if (max_entries > 0 && end_position > entries_length) {
next_page = page + 1; next_token = `${uuid}:${end_position}`;
next_token = `${uuid}:${next_page}`;
} else { } else {
next_token = null; next_token = null;
} }
@ -1545,7 +1694,10 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
return data; return data;
} else { } else {
// TODO: throw error / cache expired throw new GrpcError(
grpc.status.ABORTED,
`invalid starting_token: ${starting_token}`
);
} }
} }
@ -1639,9 +1791,11 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
break; break;
case 2: case 2:
message = `source_volume_id ${source_volume_id} does not exist`; message = `source_volume_id ${source_volume_id} does not exist`;
continue;
break; break;
case 3: case 3:
message = `snapshot_id ${snapshot_id} does not exist`; message = `snapshot_id ${snapshot_id} does not exist`;
continue;
break; break;
} }
throw new GrpcError(grpc.status.NOT_FOUND, message); throw new GrpcError(grpc.status.NOT_FOUND, message);
@ -1720,8 +1874,8 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
`ListSnapshots:result:${uuid}`, `ListSnapshots:result:${uuid}`,
JSON.parse(JSON.stringify(entries)) JSON.parse(JSON.stringify(entries))
); );
next_token = `${uuid}:2`; next_token = `${uuid}:${max_entries}`;
entries = entries.splice(0, max_entries); entries = entries.slice(0, max_entries);
} }
const data = { const data = {
@ -1822,6 +1976,54 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
driver.ctx.logger.verbose("cleansed snapshot name: %s", name); driver.ctx.logger.verbose("cleansed snapshot name: %s", name);
// check for other snapshopts with the same name on other volumes and fail as appropriate
{
try {
let datasets = [];
datasets = await zb.zfs.list(
this.getDetachedSnapshotParentDatasetName(),
[],
{ recurse: true, types }
);
for (let dataset of datasets.indexed) {
let parts = dataset.name.split("/").slice(-2);
if (parts[1] != name) {
continue;
}
if (parts[0] != source_volume_id) {
throw new GrpcError(
grpc.status.ALREADY_EXISTS,
`snapshot name: ${name} is incompatible with source_volume_id: ${source_volume_id} due to being used with another source_volume_id`
);
}
}
} catch (err) {
if (!err.toString().includes("dataset does not exist")) {
throw err;
}
}
let snapshots = [];
snapshots = await zb.zfs.list(this.getVolumeParentDatasetName(), [], {
recurse: true,
types,
});
for (let snapshot of snapshots.indexed) {
let parts = zb.helpers.extractLeafName(snapshot.name).split("@");
if (parts[1] != name) {
continue;
}
if (parts[0] != source_volume_id) {
throw new GrpcError(
grpc.status.ALREADY_EXISTS,
`snapshot name: ${name} is incompatible with source_volume_id: ${source_volume_id} due to being used with another source_volume_id`
);
}
}
}
let fullSnapshotName; let fullSnapshotName;
let snapshotDatasetName; let snapshotDatasetName;
let tmpSnapshotName; let tmpSnapshotName;
@ -2043,6 +2245,42 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
*/ */
async ValidateVolumeCapabilities(call) { async ValidateVolumeCapabilities(call) {
const driver = this; const driver = this;
const zb = await this.getZetabyte();
const volume_id = call.request.volume_id;
if (!volume_id) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing volume_id`);
}
const capabilities = call.request.volume_capabilities;
if (!capabilities || capabilities.length === 0) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing capabilities`);
}
let datasetParentName = this.getVolumeParentDatasetName();
let name = volume_id;
if (!datasetParentName) {
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`invalid configuration: missing datasetParentName`
);
}
const datasetName = datasetParentName + "/" + name;
try {
await zb.zfs.get(datasetName, []);
} catch (err) {
if (err.toString().includes("dataset does not exist")) {
throw new GrpcError(
grpc.status.NOT_FOUND,
`invalid volume_id: ${volume_id}`
);
} else {
throw err;
}
}
const result = this.assertCapabilities(call.request.volume_capabilities); const result = this.assertCapabilities(call.request.volume_capabilities);
if (result.valid !== true) { if (result.valid !== true) {

View File

@ -170,6 +170,7 @@ class FreeNASApiDriver extends CsiBaseDriver {
* @param {*} datasetName * @param {*} datasetName
*/ */
async createShare(call, datasetName) { async createShare(call, datasetName) {
const driver = this;
const driverShareType = this.getDriverShareType(); const driverShareType = this.getDriverShareType();
const httpClient = await this.getHttpClient(); const httpClient = await this.getHttpClient();
const httpApiClient = await this.getTrueNASHttpApiClient(); const httpApiClient = await this.getTrueNASHttpApiClient();
@ -515,7 +516,7 @@ class FreeNASApiDriver extends CsiBaseDriver {
} }
//set zfs property //set zfs property
await zb.zfs.set(datasetName, { await httpApiClient.DatasetSet(datasetName, {
[FREENAS_SMB_SHARE_PROPERTY_NAME]: response.body.id, [FREENAS_SMB_SHARE_PROPERTY_NAME]: response.body.id,
}); });
} else { } else {
@ -620,15 +621,17 @@ class FreeNASApiDriver extends CsiBaseDriver {
iscsiName = iscsiName.toLowerCase(); iscsiName = iscsiName.toLowerCase();
let extentDiskName = "zvol/" + datasetName; let extentDiskName = "zvol/" + datasetName;
let maxZvolNameLength = await driver.getMaxZvolNameLength();
driver.ctx.logger.debug("max zvol name length: %s", maxZvolNameLength);
/** /**
* limit is a FreeBSD limitation * limit is a FreeBSD limitation
* https://www.ixsystems.com/documentation/freenas/11.2-U5/storage.html#zfs-zvol-config-opts-tab * https://www.ixsystems.com/documentation/freenas/11.2-U5/storage.html#zfs-zvol-config-opts-tab
*/ */
if (extentDiskName.length > 63) { if (extentDiskName.length > maxZvolNameLength) {
throw new GrpcError( throw new GrpcError(
grpc.status.FAILED_PRECONDITION, grpc.status.FAILED_PRECONDITION,
`extent disk name cannot exceed 63 characters: ${extentDiskName}` `extent disk name cannot exceed ${maxZvolNameLength} characters: ${extentDiskName}`
); );
} }
@ -1431,7 +1434,7 @@ class FreeNASApiDriver extends CsiBaseDriver {
// remove property to prevent delete race conditions // remove property to prevent delete race conditions
// due to id re-use by FreeNAS/TrueNAS // due to id re-use by FreeNAS/TrueNAS
await zb.zfs.inherit( await httpApiClient.DatasetInherit(
datasetName, datasetName,
FREENAS_SMB_SHARE_PROPERTY_NAME FREENAS_SMB_SHARE_PROPERTY_NAME
); );
@ -1922,6 +1925,32 @@ class FreeNASApiDriver extends CsiBaseDriver {
return { valid, message }; return { valid, message };
} }
/**
* Get the max size a zvol name can be
*
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=238112
* https://svnweb.freebsd.org/base?view=revision&revision=343485
* https://www.ixsystems.com/documentation/freenas/11.3-BETA1/intro.html#path-and-name-lengths
*/
async getMaxZvolNameLength() {
const driver = this;
const httpApiClient = await driver.getTrueNASHttpApiClient();
// Linux is 255 (probably larger 4096) but scst may have a 255 limit
// https://ngelinux.com/what-is-the-maximum-file-name-length-in-linux-and-how-to-see-this-is-this-really-255-characters-answer-is-no/
// https://github.com/dmeister/scst/blob/master/iscsi-scst/include/iscsi_scst.h#L28
if (await httpApiClient.getIsScale()) {
return 255;
}
let major = await httpApiClient.getSystemVersionMajor();
if (parseInt(major) >= 13) {
return 255;
} else {
return 63;
}
}
/** /**
* Ensure sane options are used etc * Ensure sane options are used etc
* true = ready * true = ready
@ -1989,11 +2018,28 @@ class FreeNASApiDriver extends CsiBaseDriver {
); );
} }
if (call.request.volume_capabilities) { if (
call.request.volume_capabilities &&
call.request.volume_capabilities.length > 0
) {
const result = this.assertCapabilities(call.request.volume_capabilities); const result = this.assertCapabilities(call.request.volume_capabilities);
if (result.valid !== true) { if (result.valid !== true) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, result.message); throw new GrpcError(grpc.status.INVALID_ARGUMENT, result.message);
} }
} else {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
"missing volume_capabilities"
);
}
if (
!call.request.capacity_range ||
Object.keys(call.request.capacity_range).length === 0
) {
call.request.capacity_range = {
required_bytes: 1073741824,
};
} }
if ( if (
@ -2045,6 +2091,61 @@ class FreeNASApiDriver extends CsiBaseDriver {
); );
} }
// ensure volumes with the same name being requested a 2nd time but with a different size fails
try {
let properties = await httpApiClient.DatasetGet(datasetName, [
"volsize",
"refquota",
]);
let size;
switch (driverZfsResourceType) {
case "volume":
size = properties["volsize"].value;
break;
case "filesystem":
size = properties["refquota"].value;
break;
default:
throw new Error(
`unknown zfs resource type: ${driverZfsResourceType}`
);
}
let check = false;
if (driverZfsResourceType == "volume") {
check = true;
}
if (
driverZfsResourceType == "filesystem" &&
this.options.zfs.datasetEnableQuotas
) {
check = true;
}
if (check) {
if (
(call.request.capacity_range.required_bytes &&
call.request.capacity_range.required_bytes > 0 &&
size < call.request.capacity_range.required_bytes) ||
(call.request.capacity_range.limit_bytes &&
call.request.capacity_range.limit_bytes > 0 &&
size > call.request.capacity_range.limit_bytes)
) {
throw new GrpcError(
grpc.status.ALREADY_EXISTS,
`volume has already been created with a different size, existing size: ${size}, required_bytes: ${call.request.capacity_range.required_bytes}, limit_bytes: ${call.request.capacity_range.limit_bytes}`
);
}
}
} catch (err) {
if (err.toString().includes("dataset does not exist")) {
// does NOT already exist
} else {
throw err;
}
}
/** /**
* This is specifically a FreeBSD limitation, not sure what linux limit is * This is specifically a FreeBSD limitation, not sure what linux limit is
* https://www.ixsystems.com/documentation/freenas/11.2-U5/storage.html#zfs-zvol-config-opts-tab * https://www.ixsystems.com/documentation/freenas/11.2-U5/storage.html#zfs-zvol-config-opts-tab
@ -2053,10 +2154,12 @@ class FreeNASApiDriver extends CsiBaseDriver {
*/ */
if (driverZfsResourceType == "volume") { if (driverZfsResourceType == "volume") {
let extentDiskName = "zvol/" + datasetName; let extentDiskName = "zvol/" + datasetName;
if (extentDiskName.length > 63) { let maxZvolNameLength = await driver.getMaxZvolNameLength();
driver.ctx.logger.debug("max zvol name length: %s", maxZvolNameLength);
if (extentDiskName.length > maxZvolNameLength) {
throw new GrpcError( throw new GrpcError(
grpc.status.FAILED_PRECONDITION, grpc.status.FAILED_PRECONDITION,
`extent disk name cannot exceed 63 characters: ${extentDiskName}` `extent disk name cannot exceed ${maxZvolNameLength} characters: ${extentDiskName}`
); );
} }
} }
@ -2147,9 +2250,12 @@ class FreeNASApiDriver extends CsiBaseDriver {
try { try {
await httpApiClient.SnapshotCreate(fullSnapshotName); await httpApiClient.SnapshotCreate(fullSnapshotName);
} catch (err) { } catch (err) {
if (err.toString().includes("dataset does not exist")) { if (
err.toString().includes("dataset does not exist") ||
err.toString().includes("not found")
) {
throw new GrpcError( throw new GrpcError(
grpc.status.FAILED_PRECONDITION, grpc.status.NOT_FOUND,
`snapshot source_snapshot_id ${volume_content_source_snapshot_id} does not exist` `snapshot source_snapshot_id ${volume_content_source_snapshot_id} does not exist`
); );
} }
@ -2235,9 +2341,12 @@ class FreeNASApiDriver extends CsiBaseDriver {
} }
); );
} catch (err) { } catch (err) {
if (err.toString().includes("dataset does not exist")) { if (
err.toString().includes("dataset does not exist") ||
err.toString().includes("not found")
) {
throw new GrpcError( throw new GrpcError(
grpc.status.FAILED_PRECONDITION, grpc.status.NOT_FOUND,
"dataset does not exists" "dataset does not exists"
); );
} }
@ -2253,9 +2362,12 @@ class FreeNASApiDriver extends CsiBaseDriver {
defer: true, defer: true,
}); });
} catch (err) { } catch (err) {
if (err.toString().includes("dataset does not exist")) { if (
err.toString().includes("dataset does not exist") ||
err.toString().includes("not found")
) {
throw new GrpcError( throw new GrpcError(
grpc.status.FAILED_PRECONDITION, grpc.status.NOT_FOUND,
`snapshot source_snapshot_id ${volume_content_source_snapshot_id} does not exist` `snapshot source_snapshot_id ${volume_content_source_snapshot_id} does not exist`
); );
} }
@ -2299,9 +2411,12 @@ class FreeNASApiDriver extends CsiBaseDriver {
try { try {
response = await httpApiClient.SnapshotCreate(fullSnapshotName); response = await httpApiClient.SnapshotCreate(fullSnapshotName);
} catch (err) { } catch (err) {
if (err.toString().includes("dataset does not exist")) { if (
err.toString().includes("dataset does not exist") ||
err.toString().includes("not found")
) {
throw new GrpcError( throw new GrpcError(
grpc.status.FAILED_PRECONDITION, grpc.status.NOT_FOUND,
"dataset does not exists" "dataset does not exists"
); );
} }
@ -2393,9 +2508,12 @@ class FreeNASApiDriver extends CsiBaseDriver {
} }
); );
} catch (err) { } catch (err) {
if (err.toString().includes("dataset does not exist")) { if (
err.toString().includes("dataset does not exist") ||
err.toString().includes("not found")
) {
throw new GrpcError( throw new GrpcError(
grpc.status.FAILED_PRECONDITION, grpc.status.NOT_FOUND,
"dataset does not exists" "dataset does not exists"
); );
} }
@ -2942,7 +3060,7 @@ class FreeNASApiDriver extends CsiBaseDriver {
let entries = []; let entries = [];
let entries_length = 0; let entries_length = 0;
let next_token; let next_token;
let uuid, page, next_page; let uuid;
let response; let response;
let endpoint; let endpoint;
@ -2953,15 +3071,18 @@ class FreeNASApiDriver extends CsiBaseDriver {
if (starting_token) { if (starting_token) {
let parts = starting_token.split(":"); let parts = starting_token.split(":");
uuid = parts[0]; uuid = parts[0];
page = parseInt(parts[1]); let start_position = parseInt(parts[1]);
let end_position;
if (max_entries > 0) {
end_position = start_position + max_entries;
}
entries = this.ctx.cache.get(`ListVolumes:result:${uuid}`); entries = this.ctx.cache.get(`ListVolumes:result:${uuid}`);
if (entries) { if (entries) {
entries = JSON.parse(JSON.stringify(entries)); entries = JSON.parse(JSON.stringify(entries));
entries_length = entries.length; entries_length = entries.length;
entries = entries.splice((page - 1) * max_entries, max_entries); entries = entries.slice(start_position, end_position);
if (page * max_entries < entries_length) { if (max_entries > 0 && end_position > entries_length) {
next_page = page + 1; next_token = `${uuid}:${end_position}`;
next_token = `${uuid}:${next_page}`;
} else { } else {
next_token = null; next_token = null;
} }
@ -2972,7 +3093,10 @@ class FreeNASApiDriver extends CsiBaseDriver {
return data; return data;
} else { } else {
// TODO: throw error / cache expired throw new GrpcError(
grpc.status.ABORTED,
`invalid starting_token: ${starting_token}`
);
} }
} }
@ -3031,7 +3155,7 @@ class FreeNASApiDriver extends CsiBaseDriver {
for (let row of rows) { for (let row of rows) {
// ignore rows were csi_name is empty // ignore rows were csi_name is empty
if (row[MANAGED_PROPERTY_NAME] != "true") { if (row[MANAGED_PROPERTY_NAME] != "true") {
return; continue;
} }
let volume_id = row["name"].replace( let volume_id = row["name"].replace(
@ -3054,8 +3178,8 @@ class FreeNASApiDriver extends CsiBaseDriver {
`ListVolumes:result:${uuid}`, `ListVolumes:result:${uuid}`,
JSON.parse(JSON.stringify(entries)) JSON.parse(JSON.stringify(entries))
); );
next_token = `${uuid}:2`; next_token = `${uuid}:${max_entries}`;
entries = entries.splice(0, max_entries); entries = entries.slice(0, max_entries);
} }
const data = { const data = {
@ -3080,7 +3204,7 @@ class FreeNASApiDriver extends CsiBaseDriver {
let entries = []; let entries = [];
let entries_length = 0; let entries_length = 0;
let next_token; let next_token;
let uuid, page, next_page; let uuid;
const max_entries = call.request.max_entries; const max_entries = call.request.max_entries;
const starting_token = call.request.starting_token; const starting_token = call.request.starting_token;
@ -3095,15 +3219,18 @@ class FreeNASApiDriver extends CsiBaseDriver {
if (starting_token) { if (starting_token) {
let parts = starting_token.split(":"); let parts = starting_token.split(":");
uuid = parts[0]; uuid = parts[0];
page = parseInt(parts[1]); let start_position = parseInt(parts[1]);
let end_position;
if (max_entries > 0) {
end_position = start_position + max_entries;
}
entries = this.ctx.cache.get(`ListSnapshots:result:${uuid}`); entries = this.ctx.cache.get(`ListSnapshots:result:${uuid}`);
if (entries) { if (entries) {
entries = JSON.parse(JSON.stringify(entries)); entries = JSON.parse(JSON.stringify(entries));
entries_length = entries.length; entries_length = entries.length;
entries = entries.splice((page - 1) * max_entries, max_entries); entries = entries.slice(start_position, end_position);
if (page * max_entries < entries_length) { if (max_entries > 0 && end_position > entries_length) {
next_page = page + 1; next_token = `${uuid}:${end_position}`;
next_token = `${uuid}:${next_page}`;
} else { } else {
next_token = null; next_token = null;
} }
@ -3114,7 +3241,10 @@ class FreeNASApiDriver extends CsiBaseDriver {
return data; return data;
} else { } else {
// TODO: throw error / cache expired throw new GrpcError(
grpc.status.ABORTED,
`invalid starting_token: ${starting_token}`
);
} }
} }
@ -3364,9 +3494,11 @@ class FreeNASApiDriver extends CsiBaseDriver {
break; break;
case 2: case 2:
message = `source_volume_id ${source_volume_id} does not exist`; message = `source_volume_id ${source_volume_id} does not exist`;
continue;
break; break;
case 3: case 3:
message = `snapshot_id ${snapshot_id} does not exist`; message = `snapshot_id ${snapshot_id} does not exist`;
continue;
break; break;
} }
throw new GrpcError(grpc.status.NOT_FOUND, message); throw new GrpcError(grpc.status.NOT_FOUND, message);
@ -3447,8 +3579,8 @@ class FreeNASApiDriver extends CsiBaseDriver {
`ListSnapshots:result:${uuid}`, `ListSnapshots:result:${uuid}`,
JSON.parse(JSON.stringify(entries)) JSON.parse(JSON.stringify(entries))
); );
next_token = `${uuid}:2`; next_token = `${uuid}:${max_entries}`;
entries = entries.splice(0, max_entries); entries = entries.slice(0, max_entries);
} }
const data = { const data = {
@ -3466,6 +3598,7 @@ class FreeNASApiDriver extends CsiBaseDriver {
async CreateSnapshot(call) { async CreateSnapshot(call) {
const driver = this; const driver = this;
const driverZfsResourceType = this.getDriverZfsResourceType(); const driverZfsResourceType = this.getDriverZfsResourceType();
const httpClient = await this.getHttpClient();
const httpApiClient = await this.getTrueNASHttpApiClient(); const httpApiClient = await this.getTrueNASHttpApiClient();
const zb = await this.getZetabyte(); const zb = await this.getZetabyte();
@ -3550,6 +3683,80 @@ class FreeNASApiDriver extends CsiBaseDriver {
driver.ctx.logger.verbose("cleansed snapshot name: %s", name); driver.ctx.logger.verbose("cleansed snapshot name: %s", name);
// check for other snapshopts with the same name on other volumes and fail as appropriate
{
let endpoint;
let response;
let datasets = [];
endpoint = `/pool/dataset/id/${encodeURIComponent(
this.getDetachedSnapshotParentDatasetName()
)}`;
response = await httpClient.get(endpoint);
switch (response.statusCode) {
case 200:
for (let child of response.body.children) {
datasets = datasets.concat(child.children);
}
//console.log(datasets);
for (let dataset of datasets) {
let parts = dataset.name.split("/").slice(-2);
if (parts[1] != name) {
continue;
}
if (parts[0] != source_volume_id) {
throw new GrpcError(
grpc.status.ALREADY_EXISTS,
`snapshot name: ${name} is incompatible with source_volume_id: ${source_volume_id} due to being used with another source_volume_id`
);
}
}
break;
case 404:
break;
default:
throw new Error(JSON.stringify(response.body));
}
// get all snapshot recursively from the parent dataset
let snapshots = [];
endpoint = `/pool/dataset/id/${encodeURIComponent(
this.getVolumeParentDatasetName()
)}`;
response = await httpClient.get(endpoint, {
"extra.snapshots": 1,
//"extra.snapshots_properties": JSON.stringify(zfsProperties),
});
switch (response.statusCode) {
case 200:
for (let child of response.body.children) {
snapshots = snapshots.concat(child.snapshots);
}
//console.log(snapshots);
for (let snapshot of snapshots) {
let parts = zb.helpers.extractLeafName(snapshot.name).split("@");
if (parts[1] != name) {
continue;
}
if (parts[0] != source_volume_id) {
throw new GrpcError(
grpc.status.ALREADY_EXISTS,
`snapshot name: ${name} is incompatible with source_volume_id: ${source_volume_id} due to being used with another source_volume_id`
);
}
}
break;
case 404:
break;
default:
throw new Error(JSON.stringify(response.body));
}
}
let fullSnapshotName; let fullSnapshotName;
let snapshotDatasetName; let snapshotDatasetName;
let tmpSnapshotName; let tmpSnapshotName;
@ -3671,7 +3878,10 @@ class FreeNASApiDriver extends CsiBaseDriver {
properties: snapshotProperties, properties: snapshotProperties,
}); });
} catch (err) { } catch (err) {
if (err.toString().includes("dataset does not exist")) { if (
err.toString().includes("dataset does not exist") ||
err.toString().includes("not found")
) {
throw new GrpcError( throw new GrpcError(
grpc.status.FAILED_PRECONDITION, grpc.status.FAILED_PRECONDITION,
`snapshot source_volume_id ${source_volume_id} does not exist` `snapshot source_volume_id ${source_volume_id} does not exist`
@ -3840,7 +4050,42 @@ class FreeNASApiDriver extends CsiBaseDriver {
*/ */
async ValidateVolumeCapabilities(call) { async ValidateVolumeCapabilities(call) {
const driver = this; const driver = this;
const result = this.assertCapabilities(call.request.volume_capabilities); const httpApiClient = await this.getTrueNASHttpApiClient();
const volume_id = call.request.volume_id;
if (!volume_id) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing volume_id`);
}
const capabilities = call.request.volume_capabilities;
if (!capabilities || capabilities.length === 0) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing capabilities`);
}
let datasetParentName = this.getVolumeParentDatasetName();
let name = volume_id;
if (!datasetParentName) {
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`invalid configuration: missing datasetParentName`
);
}
const datasetName = datasetParentName + "/" + name;
try {
await httpApiClient.DatasetGet(datasetName, []);
} catch (err) {
if (err.toString().includes("dataset does not exist")) {
throw new GrpcError(
grpc.status.NOT_FOUND,
`invalid volume_id: ${volume_id}`
);
} else {
throw err;
}
}
const result = this.assertCapabilities(capabilities);
if (result.valid !== true) { if (result.valid !== true) {
return { message: result.message }; return { message: result.message };

View File

@ -167,7 +167,9 @@ class FreeNASSshDriver extends ControllerZfsSshBaseDriver {
* @param {*} datasetName * @param {*} datasetName
*/ */
async createShare(call, datasetName) { async createShare(call, datasetName) {
const driver = this;
const driverShareType = this.getDriverShareType(); const driverShareType = this.getDriverShareType();
const sshClient = this.getSshClient();
const httpClient = await this.getHttpClient(); const httpClient = await this.getHttpClient();
const apiVersion = httpClient.getApiVersion(); const apiVersion = httpClient.getApiVersion();
const zb = await this.getZetabyte(); const zb = await this.getZetabyte();
@ -617,15 +619,18 @@ class FreeNASSshDriver extends ControllerZfsSshBaseDriver {
iscsiName = iscsiName.toLowerCase(); iscsiName = iscsiName.toLowerCase();
let extentDiskName = "zvol/" + datasetName; let extentDiskName = "zvol/" + datasetName;
let maxZvolNameLength = await driver.getMaxZvolNameLength();
driver.ctx.logger.debug("max zvol name length: %s", maxZvolNameLength);
/** /**
* limit is a FreeBSD limitation * limit is a FreeBSD limitation
* https://www.ixsystems.com/documentation/freenas/11.2-U5/storage.html#zfs-zvol-config-opts-tab * https://www.ixsystems.com/documentation/freenas/11.2-U5/storage.html#zfs-zvol-config-opts-tab
*/ */
if (extentDiskName.length > 63) {
if (extentDiskName.length > maxZvolNameLength) {
throw new GrpcError( throw new GrpcError(
grpc.status.FAILED_PRECONDITION, grpc.status.FAILED_PRECONDITION,
`extent disk name cannot exceed 63 characters: ${extentDiskName}` `extent disk name cannot exceed ${maxZvolNameLength} characters: ${extentDiskName}`
); );
} }

View File

@ -280,8 +280,20 @@ class CsiBaseDriver {
let device; let device;
const volume_id = call.request.volume_id; const volume_id = call.request.volume_id;
if (!volume_id) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing volume_id`);
}
const staging_target_path = call.request.staging_target_path; const staging_target_path = call.request.staging_target_path;
if (!staging_target_path) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`missing staging_target_path`
);
}
const capability = call.request.volume_capability; const capability = call.request.volume_capability;
if (!capability || Object.keys(capability).length === 0) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing capability`);
}
const access_type = capability.access_type || "mount"; const access_type = capability.access_type || "mount";
const volume_context = call.request.volume_context; const volume_context = call.request.volume_context;
let fs_type; let fs_type;
@ -360,6 +372,24 @@ class CsiBaseDriver {
break; break;
case "smb": case "smb":
device = `//${volume_context.server}/${volume_context.share}`; device = `//${volume_context.server}/${volume_context.share}`;
// if not present add guest
let has_username = mount_flags.some((element) => {
element = element.trim().toLowerCase();
return element.startsWith("username=");
});
// prevents driver from hanging on stdin waiting for a password to be entered at the cli
if (!has_username) {
let has_guest = mount_flags.some((element) => {
element = element.trim().toLowerCase();
return element === "guest";
});
if (!has_guest) {
mount_flags.push("guest");
}
}
break; break;
case "iscsi": case "iscsi":
let portals = []; let portals = [];
@ -547,6 +577,10 @@ class CsiBaseDriver {
switch (node_attach_driver) { switch (node_attach_driver) {
// block specific logic // block specific logic
case "iscsi": case "iscsi":
if (!fs_type) {
fs_type = "ext4";
}
if (await filesystem.isBlockDevice(device)) { if (await filesystem.isBlockDevice(device)) {
// format // format
result = await filesystem.deviceIsFormatted(device); result = await filesystem.deviceIsFormatted(device);
@ -591,6 +625,24 @@ class CsiBaseDriver {
result = await mount.deviceIsMountedAtPath(device, staging_target_path); result = await mount.deviceIsMountedAtPath(device, staging_target_path);
if (!result) { if (!result) {
if (!fs_type) {
switch (node_attach_driver) {
case "nfs":
fs_type = "nfs";
break;
case "lustre":
fs_type = "lustre";
break;
case "smb":
fs_type = "cifs";
break;
case "iscsi":
fs_type = "ext4";
break;
default:
break;
}
}
await mount.mount( await mount.mount(
device, device,
staging_target_path, staging_target_path,
@ -697,18 +749,20 @@ class CsiBaseDriver {
let access_type = "mount"; let access_type = "mount";
const volume_id = call.request.volume_id; const volume_id = call.request.volume_id;
if (!volume_id) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing volume_id`);
}
const staging_target_path = call.request.staging_target_path; const staging_target_path = call.request.staging_target_path;
const block_path = staging_target_path + "/block_device";
let normalized_staging_path = staging_target_path;
const umount_args = [];
const umount_force_extra_args = ["--force", "--lazy"];
if (!staging_target_path) { if (!staging_target_path) {
throw new GrpcError( throw new GrpcError(
grpc.status.INVALID_ARGUMENT, grpc.status.INVALID_ARGUMENT,
`missing staging_target_path` `missing staging_target_path`
); );
} }
const block_path = staging_target_path + "/block_device";
let normalized_staging_path = staging_target_path;
const umount_args = [];
const umount_force_extra_args = ["--force", "--lazy"];
//result = await mount.pathIsMounted(block_path); //result = await mount.pathIsMounted(block_path);
//result = await mount.pathIsMounted(staging_target_path) //result = await mount.pathIsMounted(staging_target_path)
@ -910,9 +964,18 @@ class CsiBaseDriver {
let result; let result;
const volume_id = call.request.volume_id; const volume_id = call.request.volume_id;
if (!volume_id) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing volume_id`);
}
const staging_target_path = call.request.staging_target_path || ""; const staging_target_path = call.request.staging_target_path || "";
const target_path = call.request.target_path; const target_path = call.request.target_path;
if (!target_path) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing target_path`);
}
const capability = call.request.volume_capability; const capability = call.request.volume_capability;
if (!capability || Object.keys(capability).length === 0) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing capability`);
}
const access_type = capability.access_type || "mount"; const access_type = capability.access_type || "mount";
let mount_flags; let mount_flags;
let volume_mount_group; let volume_mount_group;
@ -1044,7 +1107,13 @@ class CsiBaseDriver {
let result; let result;
const volume_id = call.request.volume_id; const volume_id = call.request.volume_id;
if (!volume_id) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing volume_id`);
}
const target_path = call.request.target_path; const target_path = call.request.target_path;
if (!target_path) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing target_path`);
}
const umount_args = []; const umount_args = [];
const umount_force_extra_args = ["--force", "--lazy"]; const umount_force_extra_args = ["--force", "--lazy"];
@ -1119,6 +1188,9 @@ class CsiBaseDriver {
let device_path; let device_path;
let access_type; let access_type;
const volume_id = call.request.volume_id; const volume_id = call.request.volume_id;
if (!volume_id) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing volume_id`);
}
const volume_path = call.request.volume_path; const volume_path = call.request.volume_path;
const block_path = volume_path + "/block_device"; const block_path = volume_path + "/block_device";
@ -1152,6 +1224,12 @@ class CsiBaseDriver {
switch (access_type) { switch (access_type) {
case "mount": case "mount":
if (!(await mount.pathIsMounted(device_path))) {
throw new GrpcError(
grpc.status.NOT_FOUND,
`nothing mounted at path: ${device_path}`
);
}
result = await mount.getMountDetails(device_path, [ result = await mount.getMountDetails(device_path, [
"avail", "avail",
"size", "size",
@ -1168,6 +1246,12 @@ class CsiBaseDriver {
]; ];
break; break;
case "block": case "block":
if (!(await filesystem.pathExists(device_path))) {
throw new GrpcError(
grpc.status.NOT_FOUND,
`nothing mounted at path: ${device_path}`
);
}
result = await filesystem.getBlockDevice(device_path); result = await filesystem.getBlockDevice(device_path);
res.usage = [ res.usage = [
@ -1208,14 +1292,16 @@ class CsiBaseDriver {
let is_device_mapper = false; let is_device_mapper = false;
const volume_id = call.request.volume_id; const volume_id = call.request.volume_id;
if (!volume_id) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing volume_id`);
}
const volume_path = call.request.volume_path; const volume_path = call.request.volume_path;
const block_path = volume_path + "/block_device";
const capacity_range = call.request.capacity_range;
const volume_capability = call.request.volume_capability;
if (!volume_path) { if (!volume_path) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing volume_path`); throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing volume_path`);
} }
const block_path = volume_path + "/block_device";
const capacity_range = call.request.capacity_range;
const volume_capability = call.request.volume_capability;
if ( if (
(await mount.isBindMountedBlockDevice(volume_path)) || (await mount.isBindMountedBlockDevice(volume_path)) ||
@ -1235,7 +1321,7 @@ class CsiBaseDriver {
} catch (err) { } catch (err) {
if (err.code == 1) { if (err.code == 1) {
throw new GrpcError( throw new GrpcError(
grpc.status.FAILED_PRECONDITION, grpc.status.NOT_FOUND,
`volume_path ${volume_path} is not currently mounted` `volume_path ${volume_path} is not currently mounted`
); );
} }

View File

@ -1153,14 +1153,15 @@ class Zetabyte {
args.push("'" + command.join(" ") + "'"); args.push("'" + command.join(" ") + "'");
zb.exec("/bin/sh", args, { timeout: zb.options.timeout }, function ( zb.exec(
error, "/bin/sh",
stdout, args,
stderr { timeout: zb.options.timeout },
) { function (error, stdout, stderr) {
if (error) return reject(zb.helpers.zfsError(error, stderr)); if (error) return reject(zb.helpers.zfsError(error, stderr));
return resolve(stdout); return resolve(stdout);
}); }
);
}); });
}, },
@ -1230,6 +1231,7 @@ class Zetabyte {
* filesystem|volume|snapshot... * filesystem|volume|snapshot...
* *
* @param {*} dataset * @param {*} dataset
* @param {*} properties
* @param {*} options * @param {*} options
*/ */
list: function (dataset, properties, options = {}) { list: function (dataset, properties, options = {}) {