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
|
# v1.3.2
|
||||||
|
|
||||||
Released 2021-09-09
|
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
|
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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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?
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
@ -751,6 +830,19 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
||||||
`invalid source_volume_id: ${source_volume_id}`
|
`invalid source_volume_id: ${source_volume_id}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
|
|
|
||||||
|
|
@ -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}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 = {}) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue