csi-test conformance, relax iscsi name length
Signed-off-by: Travis Glenn Hansen <travisghansen@yahoo.com>
This commit is contained in:
parent
109d493cfc
commit
855f48d3af
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -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
|
||||
|
||||
Released 2021-09-09
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ non-`root` user when connecting to the FreeNAS server:
|
|||
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`)
|
||||
account user update id=<id> sudo=true
|
||||
account user update id=<id> sudo_nopasswd=true
|
||||
# optional if you want to disable password
|
||||
#account user update id=<id> password_disabled=true
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ zfs:
|
|||
# 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
|
||||
# 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
|
||||
# do NOT make datasetParentName and detachedSnapshotsDatasetParentName overlap
|
||||
# they may be siblings, but neither should be nested in the other
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ zfs:
|
|||
# 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
|
||||
# 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
|
||||
# do NOT make datasetParentName and detachedSnapshotsDatasetParentName overlap
|
||||
# they may be siblings, but neither should be nested in the other
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "democratic-csi",
|
||||
"version": "1.3.2",
|
||||
"version": "1.4.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "democratic-csi",
|
||||
"version": "1.3.2",
|
||||
"version": "1.4.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.3.6",
|
||||
|
|
@ -40,9 +40,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.14.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz",
|
||||
"integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==",
|
||||
"version": "7.15.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
|
||||
"integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
|
|
@ -197,9 +197,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@grpc/proto-loader": {
|
||||
"version": "0.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.4.tgz",
|
||||
"integrity": "sha512-7xvDvW/vJEcmLUltCUGOgWRPM8Oofv0eCFSVMuKqaqWJaXSzmB+m9hiyqe34QofAl4WAzIKUZZlinIF9FOHyTQ==",
|
||||
"version": "0.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.5.tgz",
|
||||
"integrity": "sha512-GZdzyVQI1Bln/kCzIYgTKu+rQJ5dno0gVrfmLe4jqQu7T2e7svSwJzpCBqVU5hhBSJP3peuPjOMWsj5GR61YmQ==",
|
||||
"dependencies": {
|
||||
"@types/long": "^4.0.1",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
|
|
@ -311,9 +311,9 @@
|
|||
"integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w=="
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.0.tgz",
|
||||
"integrity": "sha512-nmP+VR4oT0pJUPFbKE4SXj3Yb4Q/kz3M9dSAO1GGMebRKWHQxLfDNmU/yh3xxCJha3N60nQ/JwXWwOE/ZSEVag=="
|
||||
"version": "16.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.6.tgz",
|
||||
"integrity": "sha512-YHUZhBOMTM3mjFkXVcK+WwAcYmyhe1wL4lfqNtzI0b3qAy7yuSetnM7QJazgE5PFmgVTNGiLOgRFfJMqW7XpSQ=="
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "7.4.1",
|
||||
|
|
@ -361,9 +361,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
|
|
@ -2793,9 +2793,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/table/node_modules/ajv": {
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz",
|
||||
"integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==",
|
||||
"version": "8.6.3",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz",
|
||||
"integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
|
|
@ -3108,9 +3108,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.14.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz",
|
||||
"integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==",
|
||||
"version": "7.15.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
|
||||
"integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/highlight": {
|
||||
|
|
@ -3239,9 +3239,9 @@
|
|||
}
|
||||
},
|
||||
"@grpc/proto-loader": {
|
||||
"version": "0.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.4.tgz",
|
||||
"integrity": "sha512-7xvDvW/vJEcmLUltCUGOgWRPM8Oofv0eCFSVMuKqaqWJaXSzmB+m9hiyqe34QofAl4WAzIKUZZlinIF9FOHyTQ==",
|
||||
"version": "0.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.5.tgz",
|
||||
"integrity": "sha512-GZdzyVQI1Bln/kCzIYgTKu+rQJ5dno0gVrfmLe4jqQu7T2e7svSwJzpCBqVU5hhBSJP3peuPjOMWsj5GR61YmQ==",
|
||||
"requires": {
|
||||
"@types/long": "^4.0.1",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
|
|
@ -3343,9 +3343,9 @@
|
|||
"integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w=="
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.0.tgz",
|
||||
"integrity": "sha512-nmP+VR4oT0pJUPFbKE4SXj3Yb4Q/kz3M9dSAO1GGMebRKWHQxLfDNmU/yh3xxCJha3N60nQ/JwXWwOE/ZSEVag=="
|
||||
"version": "16.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.6.tgz",
|
||||
"integrity": "sha512-YHUZhBOMTM3mjFkXVcK+WwAcYmyhe1wL4lfqNtzI0b3qAy7yuSetnM7QJazgE5PFmgVTNGiLOgRFfJMqW7XpSQ=="
|
||||
},
|
||||
"acorn": {
|
||||
"version": "7.4.1",
|
||||
|
|
@ -3378,9 +3378,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
|
|
@ -5240,9 +5240,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"ajv": {
|
||||
"version": "8.6.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz",
|
||||
"integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==",
|
||||
"version": "8.6.3",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz",
|
||||
"integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "democratic-csi",
|
||||
"version": "1.3.2",
|
||||
"version": "1.4.0",
|
||||
"description": "kubernetes csi driver framework",
|
||||
"main": "bin/democratic-csi",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const { CsiBaseDriver } = require("../index");
|
||||
const { GrpcError, grpc } = require("../../utils/grpc");
|
||||
const cp = require("child_process");
|
||||
const fs = require("fs");
|
||||
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:
|
||||
* 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);
|
||||
if (result.valid !== true) {
|
||||
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 (
|
||||
|
|
@ -431,6 +453,13 @@ class ControllerClientCommonDriver extends CsiBaseDriver {
|
|||
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);
|
||||
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 snapshot_path = driver.getControllerSnapshotPath(snapshot_id);
|
||||
|
||||
// do NOT overwrite existing snapshot
|
||||
if (!(await driver.directoryExists(snapshot_path))) {
|
||||
await driver.cloneDir(volume_path, snapshot_path);
|
||||
}
|
||||
|
||||
return {
|
||||
snapshot: {
|
||||
|
|
@ -681,6 +713,25 @@ class ControllerClientCommonDriver extends CsiBaseDriver {
|
|||
*/
|
||||
async ValidateVolumeCapabilities(call) {
|
||||
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);
|
||||
|
||||
if (result.valid !== true) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
const lun_list = {
|
||||
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) {
|
||||
const get_snapshot_info = {
|
||||
lid: lun_id, //check?
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
if (result.valid !== true) {
|
||||
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 (
|
||||
|
|
@ -314,17 +331,69 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
let lun_uuid;
|
||||
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) {
|
||||
let src_lun_uuid;
|
||||
let src_lun_id;
|
||||
switch (volume_content_source.type) {
|
||||
case "snapshot":
|
||||
let parts = volume_content_source.snapshot.snapshot_id.split("/");
|
||||
|
||||
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];
|
||||
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);
|
||||
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);
|
||||
if (!existingLun) {
|
||||
await httpClient.CreateVolumeFromSnapshot(
|
||||
|
|
@ -340,8 +409,20 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
let srcLunName = driver.buildIscsiName(
|
||||
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);
|
||||
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);
|
||||
}
|
||||
break;
|
||||
|
|
@ -740,8 +821,6 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
);
|
||||
}
|
||||
|
||||
// create snapshot here
|
||||
|
||||
let iscsiName = driver.buildIscsiName(source_volume_id);
|
||||
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
|
||||
let snapshot = await httpClient.GetSnapshotByLunIDAndName(lun.lun_id, name);
|
||||
if (snapshot) {
|
||||
|
|
@ -827,7 +919,14 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
|
||||
let parts = snapshot_id.split("/");
|
||||
let lun_id = parts[2];
|
||||
if (!lun_id) {
|
||||
return {};
|
||||
}
|
||||
|
||||
let snapshot_uuid = parts[3];
|
||||
if (!snapshot_uuid) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// TODO: delete snapshot
|
||||
let snapshot = await httpClient.GetSnapshotByLunIDAndSnapshotUUID(
|
||||
|
|
@ -848,8 +947,55 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
*/
|
||||
async ValidateVolumeCapabilities(call) {
|
||||
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) {
|
||||
return { message: result.message };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -392,6 +392,62 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
|
|||
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
|
||||
* 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);
|
||||
if (result.valid !== true) {
|
||||
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 (
|
||||
|
|
@ -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
|
||||
* 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.freebsd.org/cgi/man.cgi?query=devfs
|
||||
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=238112
|
||||
*/
|
||||
if (driverZfsResourceType == "volume") {
|
||||
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(
|
||||
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) {
|
||||
if (err.toString().includes("dataset does not exist")) {
|
||||
throw new GrpcError(
|
||||
grpc.status.FAILED_PRECONDITION,
|
||||
grpc.status.NOT_FOUND,
|
||||
`snapshot source_snapshot_id ${volume_content_source_snapshot_id} does not exist`
|
||||
);
|
||||
}
|
||||
|
|
@ -700,7 +830,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
|
|||
} catch (err) {
|
||||
if (err.toString().includes("dataset does not exist")) {
|
||||
throw new GrpcError(
|
||||
grpc.status.FAILED_PRECONDITION,
|
||||
grpc.status.NOT_FOUND,
|
||||
"dataset does not exists"
|
||||
);
|
||||
}
|
||||
|
|
@ -720,7 +850,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
|
|||
} catch (err) {
|
||||
if (err.toString().includes("dataset does not exist")) {
|
||||
throw new GrpcError(
|
||||
grpc.status.FAILED_PRECONDITION,
|
||||
grpc.status.NOT_FOUND,
|
||||
`snapshot source_snapshot_id ${volume_content_source_snapshot_id} does not exist`
|
||||
);
|
||||
}
|
||||
|
|
@ -766,7 +896,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
|
|||
} catch (err) {
|
||||
if (err.toString().includes("dataset does not exist")) {
|
||||
throw new GrpcError(
|
||||
grpc.status.FAILED_PRECONDITION,
|
||||
grpc.status.NOT_FOUND,
|
||||
"dataset does not exists"
|
||||
);
|
||||
}
|
||||
|
|
@ -816,7 +946,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
|
|||
} catch (err) {
|
||||
if (err.toString().includes("dataset does not exist")) {
|
||||
throw new GrpcError(
|
||||
grpc.status.FAILED_PRECONDITION,
|
||||
grpc.status.NOT_FOUND,
|
||||
"dataset does not exists"
|
||||
);
|
||||
}
|
||||
|
|
@ -1274,10 +1404,20 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
|
|||
const datasetName = datasetParentName;
|
||||
|
||||
let properties;
|
||||
try {
|
||||
properties = await zb.zfs.get(datasetName, ["avail"]);
|
||||
properties = properties[datasetName];
|
||||
|
||||
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_length = 0;
|
||||
let next_token;
|
||||
let uuid, page, next_page;
|
||||
let uuid;
|
||||
let response;
|
||||
|
||||
const max_entries = call.request.max_entries;
|
||||
|
|
@ -1385,15 +1525,18 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
|
|||
if (starting_token) {
|
||||
let parts = starting_token.split(":");
|
||||
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}`);
|
||||
if (entries) {
|
||||
entries = JSON.parse(JSON.stringify(entries));
|
||||
entries_length = entries.length;
|
||||
entries = entries.splice((page - 1) * max_entries, max_entries);
|
||||
if (page * max_entries < entries_length) {
|
||||
next_page = page + 1;
|
||||
next_token = `${uuid}:${next_page}`;
|
||||
entries = entries.slice(start_position, end_position);
|
||||
if (max_entries > 0 && end_position > entries_length) {
|
||||
next_token = `${uuid}:${end_position}`;
|
||||
} else {
|
||||
next_token = null;
|
||||
}
|
||||
|
|
@ -1404,7 +1547,10 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
|
|||
|
||||
return data;
|
||||
} 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) {
|
||||
// ignore rows were csi_name is empty
|
||||
if (row[MANAGED_PROPERTY_NAME] != "true") {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
let volume = await driver.populateCsiVolumeFromData(row);
|
||||
|
|
@ -1487,8 +1633,8 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
|
|||
`ListVolumes:result:${uuid}`,
|
||||
JSON.parse(JSON.stringify(entries))
|
||||
);
|
||||
next_token = `${uuid}:2`;
|
||||
entries = entries.splice(0, max_entries);
|
||||
next_token = `${uuid}:${max_entries}`;
|
||||
entries = entries.slice(0, max_entries);
|
||||
}
|
||||
|
||||
const data = {
|
||||
|
|
@ -1511,7 +1657,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
|
|||
let entries = [];
|
||||
let entries_length = 0;
|
||||
let next_token;
|
||||
let uuid, page, next_page;
|
||||
let uuid;
|
||||
|
||||
const max_entries = call.request.max_entries;
|
||||
const starting_token = call.request.starting_token;
|
||||
|
|
@ -1526,15 +1672,18 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
|
|||
if (starting_token) {
|
||||
let parts = starting_token.split(":");
|
||||
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}`);
|
||||
if (entries) {
|
||||
entries = JSON.parse(JSON.stringify(entries));
|
||||
entries_length = entries.length;
|
||||
entries = entries.splice((page - 1) * max_entries, max_entries);
|
||||
if (page * max_entries < entries_length) {
|
||||
next_page = page + 1;
|
||||
next_token = `${uuid}:${next_page}`;
|
||||
entries = entries.slice(start_position, end_position);
|
||||
if (max_entries > 0 && end_position > entries_length) {
|
||||
next_token = `${uuid}:${end_position}`;
|
||||
} else {
|
||||
next_token = null;
|
||||
}
|
||||
|
|
@ -1545,7 +1694,10 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
|
|||
|
||||
return data;
|
||||
} 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;
|
||||
case 2:
|
||||
message = `source_volume_id ${source_volume_id} does not exist`;
|
||||
continue;
|
||||
break;
|
||||
case 3:
|
||||
message = `snapshot_id ${snapshot_id} does not exist`;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
throw new GrpcError(grpc.status.NOT_FOUND, message);
|
||||
|
|
@ -1720,8 +1874,8 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
|
|||
`ListSnapshots:result:${uuid}`,
|
||||
JSON.parse(JSON.stringify(entries))
|
||||
);
|
||||
next_token = `${uuid}:2`;
|
||||
entries = entries.splice(0, max_entries);
|
||||
next_token = `${uuid}:${max_entries}`;
|
||||
entries = entries.slice(0, max_entries);
|
||||
}
|
||||
|
||||
const data = {
|
||||
|
|
@ -1822,6 +1976,54 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
|
|||
|
||||
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 snapshotDatasetName;
|
||||
let tmpSnapshotName;
|
||||
|
|
@ -2043,6 +2245,42 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
|
|||
*/
|
||||
async ValidateVolumeCapabilities(call) {
|
||||
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);
|
||||
|
||||
if (result.valid !== true) {
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
* @param {*} datasetName
|
||||
*/
|
||||
async createShare(call, datasetName) {
|
||||
const driver = this;
|
||||
const driverShareType = this.getDriverShareType();
|
||||
const httpClient = await this.getHttpClient();
|
||||
const httpApiClient = await this.getTrueNASHttpApiClient();
|
||||
|
|
@ -515,7 +516,7 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
}
|
||||
|
||||
//set zfs property
|
||||
await zb.zfs.set(datasetName, {
|
||||
await httpApiClient.DatasetSet(datasetName, {
|
||||
[FREENAS_SMB_SHARE_PROPERTY_NAME]: response.body.id,
|
||||
});
|
||||
} else {
|
||||
|
|
@ -620,15 +621,17 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
iscsiName = iscsiName.toLowerCase();
|
||||
|
||||
let extentDiskName = "zvol/" + datasetName;
|
||||
let maxZvolNameLength = await driver.getMaxZvolNameLength();
|
||||
driver.ctx.logger.debug("max zvol name length: %s", maxZvolNameLength);
|
||||
|
||||
/**
|
||||
* limit is a FreeBSD limitation
|
||||
* https://www.ixsystems.com/documentation/freenas/11.2-U5/storage.html#zfs-zvol-config-opts-tab
|
||||
*/
|
||||
if (extentDiskName.length > 63) {
|
||||
if (extentDiskName.length > maxZvolNameLength) {
|
||||
throw new GrpcError(
|
||||
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
|
||||
// due to id re-use by FreeNAS/TrueNAS
|
||||
await zb.zfs.inherit(
|
||||
await httpApiClient.DatasetInherit(
|
||||
datasetName,
|
||||
FREENAS_SMB_SHARE_PROPERTY_NAME
|
||||
);
|
||||
|
|
@ -1922,6 +1925,32 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
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
|
||||
* 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);
|
||||
if (result.valid !== true) {
|
||||
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 (
|
||||
|
|
@ -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
|
||||
* 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") {
|
||||
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(
|
||||
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 {
|
||||
await httpApiClient.SnapshotCreate(fullSnapshotName);
|
||||
} 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(
|
||||
grpc.status.FAILED_PRECONDITION,
|
||||
grpc.status.NOT_FOUND,
|
||||
`snapshot source_snapshot_id ${volume_content_source_snapshot_id} does not exist`
|
||||
);
|
||||
}
|
||||
|
|
@ -2235,9 +2341,12 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
}
|
||||
);
|
||||
} 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(
|
||||
grpc.status.FAILED_PRECONDITION,
|
||||
grpc.status.NOT_FOUND,
|
||||
"dataset does not exists"
|
||||
);
|
||||
}
|
||||
|
|
@ -2253,9 +2362,12 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
defer: true,
|
||||
});
|
||||
} 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(
|
||||
grpc.status.FAILED_PRECONDITION,
|
||||
grpc.status.NOT_FOUND,
|
||||
`snapshot source_snapshot_id ${volume_content_source_snapshot_id} does not exist`
|
||||
);
|
||||
}
|
||||
|
|
@ -2299,9 +2411,12 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
try {
|
||||
response = await httpApiClient.SnapshotCreate(fullSnapshotName);
|
||||
} 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(
|
||||
grpc.status.FAILED_PRECONDITION,
|
||||
grpc.status.NOT_FOUND,
|
||||
"dataset does not exists"
|
||||
);
|
||||
}
|
||||
|
|
@ -2393,9 +2508,12 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
}
|
||||
);
|
||||
} 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(
|
||||
grpc.status.FAILED_PRECONDITION,
|
||||
grpc.status.NOT_FOUND,
|
||||
"dataset does not exists"
|
||||
);
|
||||
}
|
||||
|
|
@ -2942,7 +3060,7 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
let entries = [];
|
||||
let entries_length = 0;
|
||||
let next_token;
|
||||
let uuid, page, next_page;
|
||||
let uuid;
|
||||
let response;
|
||||
let endpoint;
|
||||
|
||||
|
|
@ -2953,15 +3071,18 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
if (starting_token) {
|
||||
let parts = starting_token.split(":");
|
||||
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}`);
|
||||
if (entries) {
|
||||
entries = JSON.parse(JSON.stringify(entries));
|
||||
entries_length = entries.length;
|
||||
entries = entries.splice((page - 1) * max_entries, max_entries);
|
||||
if (page * max_entries < entries_length) {
|
||||
next_page = page + 1;
|
||||
next_token = `${uuid}:${next_page}`;
|
||||
entries = entries.slice(start_position, end_position);
|
||||
if (max_entries > 0 && end_position > entries_length) {
|
||||
next_token = `${uuid}:${end_position}`;
|
||||
} else {
|
||||
next_token = null;
|
||||
}
|
||||
|
|
@ -2972,7 +3093,10 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
|
||||
return data;
|
||||
} 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) {
|
||||
// ignore rows were csi_name is empty
|
||||
if (row[MANAGED_PROPERTY_NAME] != "true") {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
let volume_id = row["name"].replace(
|
||||
|
|
@ -3054,8 +3178,8 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
`ListVolumes:result:${uuid}`,
|
||||
JSON.parse(JSON.stringify(entries))
|
||||
);
|
||||
next_token = `${uuid}:2`;
|
||||
entries = entries.splice(0, max_entries);
|
||||
next_token = `${uuid}:${max_entries}`;
|
||||
entries = entries.slice(0, max_entries);
|
||||
}
|
||||
|
||||
const data = {
|
||||
|
|
@ -3080,7 +3204,7 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
let entries = [];
|
||||
let entries_length = 0;
|
||||
let next_token;
|
||||
let uuid, page, next_page;
|
||||
let uuid;
|
||||
|
||||
const max_entries = call.request.max_entries;
|
||||
const starting_token = call.request.starting_token;
|
||||
|
|
@ -3095,15 +3219,18 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
if (starting_token) {
|
||||
let parts = starting_token.split(":");
|
||||
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}`);
|
||||
if (entries) {
|
||||
entries = JSON.parse(JSON.stringify(entries));
|
||||
entries_length = entries.length;
|
||||
entries = entries.splice((page - 1) * max_entries, max_entries);
|
||||
if (page * max_entries < entries_length) {
|
||||
next_page = page + 1;
|
||||
next_token = `${uuid}:${next_page}`;
|
||||
entries = entries.slice(start_position, end_position);
|
||||
if (max_entries > 0 && end_position > entries_length) {
|
||||
next_token = `${uuid}:${end_position}`;
|
||||
} else {
|
||||
next_token = null;
|
||||
}
|
||||
|
|
@ -3114,7 +3241,10 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
|
||||
return data;
|
||||
} 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;
|
||||
case 2:
|
||||
message = `source_volume_id ${source_volume_id} does not exist`;
|
||||
continue;
|
||||
break;
|
||||
case 3:
|
||||
message = `snapshot_id ${snapshot_id} does not exist`;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
throw new GrpcError(grpc.status.NOT_FOUND, message);
|
||||
|
|
@ -3447,8 +3579,8 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
`ListSnapshots:result:${uuid}`,
|
||||
JSON.parse(JSON.stringify(entries))
|
||||
);
|
||||
next_token = `${uuid}:2`;
|
||||
entries = entries.splice(0, max_entries);
|
||||
next_token = `${uuid}:${max_entries}`;
|
||||
entries = entries.slice(0, max_entries);
|
||||
}
|
||||
|
||||
const data = {
|
||||
|
|
@ -3466,6 +3598,7 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
async CreateSnapshot(call) {
|
||||
const driver = this;
|
||||
const driverZfsResourceType = this.getDriverZfsResourceType();
|
||||
const httpClient = await this.getHttpClient();
|
||||
const httpApiClient = await this.getTrueNASHttpApiClient();
|
||||
const zb = await this.getZetabyte();
|
||||
|
||||
|
|
@ -3550,6 +3683,80 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
|
||||
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 snapshotDatasetName;
|
||||
let tmpSnapshotName;
|
||||
|
|
@ -3671,7 +3878,10 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
properties: snapshotProperties,
|
||||
});
|
||||
} 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(
|
||||
grpc.status.FAILED_PRECONDITION,
|
||||
`snapshot source_volume_id ${source_volume_id} does not exist`
|
||||
|
|
@ -3840,7 +4050,42 @@ class FreeNASApiDriver extends CsiBaseDriver {
|
|||
*/
|
||||
async ValidateVolumeCapabilities(call) {
|
||||
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) {
|
||||
return { message: result.message };
|
||||
|
|
|
|||
|
|
@ -167,7 +167,9 @@ class FreeNASSshDriver extends ControllerZfsSshBaseDriver {
|
|||
* @param {*} datasetName
|
||||
*/
|
||||
async createShare(call, datasetName) {
|
||||
const driver = this;
|
||||
const driverShareType = this.getDriverShareType();
|
||||
const sshClient = this.getSshClient();
|
||||
const httpClient = await this.getHttpClient();
|
||||
const apiVersion = httpClient.getApiVersion();
|
||||
const zb = await this.getZetabyte();
|
||||
|
|
@ -617,15 +619,18 @@ class FreeNASSshDriver extends ControllerZfsSshBaseDriver {
|
|||
iscsiName = iscsiName.toLowerCase();
|
||||
|
||||
let extentDiskName = "zvol/" + datasetName;
|
||||
let maxZvolNameLength = await driver.getMaxZvolNameLength();
|
||||
driver.ctx.logger.debug("max zvol name length: %s", maxZvolNameLength);
|
||||
|
||||
/**
|
||||
* limit is a FreeBSD limitation
|
||||
* https://www.ixsystems.com/documentation/freenas/11.2-U5/storage.html#zfs-zvol-config-opts-tab
|
||||
*/
|
||||
if (extentDiskName.length > 63) {
|
||||
|
||||
if (extentDiskName.length > maxZvolNameLength) {
|
||||
throw new GrpcError(
|
||||
grpc.status.FAILED_PRECONDITION,
|
||||
`extent disk name cannot exceed 63 characters: ${extentDiskName}`
|
||||
`extent disk name cannot exceed ${maxZvolNameLength} characters: ${extentDiskName}`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -280,8 +280,20 @@ class CsiBaseDriver {
|
|||
let device;
|
||||
|
||||
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;
|
||||
if (!staging_target_path) {
|
||||
throw new GrpcError(
|
||||
grpc.status.INVALID_ARGUMENT,
|
||||
`missing staging_target_path`
|
||||
);
|
||||
}
|
||||
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 volume_context = call.request.volume_context;
|
||||
let fs_type;
|
||||
|
|
@ -360,6 +372,24 @@ class CsiBaseDriver {
|
|||
break;
|
||||
case "smb":
|
||||
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;
|
||||
case "iscsi":
|
||||
let portals = [];
|
||||
|
|
@ -547,6 +577,10 @@ class CsiBaseDriver {
|
|||
switch (node_attach_driver) {
|
||||
// block specific logic
|
||||
case "iscsi":
|
||||
if (!fs_type) {
|
||||
fs_type = "ext4";
|
||||
}
|
||||
|
||||
if (await filesystem.isBlockDevice(device)) {
|
||||
// format
|
||||
result = await filesystem.deviceIsFormatted(device);
|
||||
|
|
@ -591,6 +625,24 @@ class CsiBaseDriver {
|
|||
|
||||
result = await mount.deviceIsMountedAtPath(device, staging_target_path);
|
||||
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(
|
||||
device,
|
||||
staging_target_path,
|
||||
|
|
@ -697,18 +749,20 @@ class CsiBaseDriver {
|
|||
let access_type = "mount";
|
||||
|
||||
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 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) {
|
||||
throw new GrpcError(
|
||||
grpc.status.INVALID_ARGUMENT,
|
||||
`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(staging_target_path)
|
||||
|
|
@ -910,9 +964,18 @@ class CsiBaseDriver {
|
|||
let result;
|
||||
|
||||
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 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;
|
||||
if (!capability || Object.keys(capability).length === 0) {
|
||||
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing capability`);
|
||||
}
|
||||
const access_type = capability.access_type || "mount";
|
||||
let mount_flags;
|
||||
let volume_mount_group;
|
||||
|
|
@ -1044,7 +1107,13 @@ class CsiBaseDriver {
|
|||
let result;
|
||||
|
||||
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;
|
||||
if (!target_path) {
|
||||
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing target_path`);
|
||||
}
|
||||
const umount_args = [];
|
||||
const umount_force_extra_args = ["--force", "--lazy"];
|
||||
|
||||
|
|
@ -1119,6 +1188,9 @@ class CsiBaseDriver {
|
|||
let device_path;
|
||||
let access_type;
|
||||
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 block_path = volume_path + "/block_device";
|
||||
|
||||
|
|
@ -1152,6 +1224,12 @@ class CsiBaseDriver {
|
|||
|
||||
switch (access_type) {
|
||||
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, [
|
||||
"avail",
|
||||
"size",
|
||||
|
|
@ -1168,6 +1246,12 @@ class CsiBaseDriver {
|
|||
];
|
||||
break;
|
||||
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);
|
||||
|
||||
res.usage = [
|
||||
|
|
@ -1208,14 +1292,16 @@ class CsiBaseDriver {
|
|||
let is_device_mapper = false;
|
||||
|
||||
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 block_path = volume_path + "/block_device";
|
||||
const capacity_range = call.request.capacity_range;
|
||||
const volume_capability = call.request.volume_capability;
|
||||
|
||||
if (!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 (
|
||||
(await mount.isBindMountedBlockDevice(volume_path)) ||
|
||||
|
|
@ -1235,7 +1321,7 @@ class CsiBaseDriver {
|
|||
} catch (err) {
|
||||
if (err.code == 1) {
|
||||
throw new GrpcError(
|
||||
grpc.status.FAILED_PRECONDITION,
|
||||
grpc.status.NOT_FOUND,
|
||||
`volume_path ${volume_path} is not currently mounted`
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1153,14 +1153,15 @@ class Zetabyte {
|
|||
|
||||
args.push("'" + command.join(" ") + "'");
|
||||
|
||||
zb.exec("/bin/sh", args, { timeout: zb.options.timeout }, function (
|
||||
error,
|
||||
stdout,
|
||||
stderr
|
||||
) {
|
||||
zb.exec(
|
||||
"/bin/sh",
|
||||
args,
|
||||
{ timeout: zb.options.timeout },
|
||||
function (error, stdout, stderr) {
|
||||
if (error) return reject(zb.helpers.zfsError(error, stderr));
|
||||
return resolve(stdout);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -1230,6 +1231,7 @@ class Zetabyte {
|
|||
* filesystem|volume|snapshot...
|
||||
*
|
||||
* @param {*} dataset
|
||||
* @param {*} properties
|
||||
* @param {*} options
|
||||
*/
|
||||
list: function (dataset, properties, options = {}) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue