csi-v1.3.0 support

This commit is contained in:
Travis Glenn Hansen 2021-02-09 16:40:54 -07:00
parent ba01d1ca56
commit d0c3f80052
6 changed files with 260 additions and 94 deletions

View File

@ -1,3 +1,9 @@
# v1.1.0
Released 2021-02-XX
- support for csi-v1.3.0
# v1.0.1 # v1.0.1
Released 2021-01-29 Released 2021-01-29

View File

@ -81,7 +81,8 @@ const GeneralUtils = require("../src/utils/general");
if (args.logLevel) { if (args.logLevel) {
logger.level = args.logLevel; logger.level = args.logLevel;
} }
const csiVersion = process.env.CSI_VERSION || "1.2.0";
const csiVersion = process.env.CSI_VERSION || args.csiVersion || "1.2.0";
const PROTO_PATH = __dirname + "/../csi_proto/csi-v" + csiVersion + ".proto"; const PROTO_PATH = __dirname + "/../csi_proto/csi-v" + csiVersion + ".proto";
// Suggested options for similarity to existing grpc.load behavior // Suggested options for similarity to existing grpc.load behavior
@ -101,7 +102,7 @@ logger.info("initializing csi driver: %s", options.driver);
let driver; let driver;
try { try {
driver = require("../src/driver/factory").factory( driver = require("../src/driver/factory").factory(
{ logger, args, cache, package }, { logger, args, cache, package, csiVersion },
options options
); );
} catch (err) { } catch (err) {
@ -239,6 +240,9 @@ function getServer() {
async ValidateVolumeCapabilities(call, callback) { async ValidateVolumeCapabilities(call, callback) {
requestHandlerProxy(call, callback, arguments.callee.name); requestHandlerProxy(call, callback, arguments.callee.name);
}, },
async ControllerGetVolume(call, callback) {
requestHandlerProxy(call, callback, arguments.callee.name);
},
async ListVolumes(call, callback) { async ListVolumes(call, callback) {
requestHandlerProxy(call, callback, arguments.callee.name); requestHandlerProxy(call, callback, arguments.callee.name);
}, },

36
package-lock.json generated
View File

@ -1,15 +1,15 @@
{ {
"name": "democratic-csi", "name": "democratic-csi",
"version": "1.0.0", "version": "1.0.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@babel/code-frame": { "@babel/code-frame": {
"version": "7.12.11", "version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
"integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
"requires": { "requires": {
"@babel/highlight": "^7.10.4" "@babel/highlight": "^7.12.13"
} }
}, },
"@babel/helper-validator-identifier": { "@babel/helper-validator-identifier": {
@ -18,11 +18,11 @@
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==" "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="
}, },
"@babel/highlight": { "@babel/highlight": {
"version": "7.10.4", "version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz",
"integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==",
"requires": { "requires": {
"@babel/helper-validator-identifier": "^7.10.4", "@babel/helper-validator-identifier": "^7.12.11",
"chalk": "^2.0.0", "chalk": "^2.0.0",
"js-tokens": "^4.0.0" "js-tokens": "^4.0.0"
}, },
@ -567,9 +567,9 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
}, },
"eslint": { "eslint": {
"version": "7.18.0", "version": "7.19.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.19.0.tgz",
"integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", "integrity": "sha512-CGlMgJY56JZ9ZSYhJuhow61lMPPjUzWmChFya71Z/jilVos7mR/jPgaEfVGgMBY5DshbKdG8Ezb8FDCHcoMEMg==",
"requires": { "requires": {
"@babel/code-frame": "^7.0.0", "@babel/code-frame": "^7.0.0",
"@eslint/eslintrc": "^0.3.0", "@eslint/eslintrc": "^0.3.0",
@ -681,9 +681,9 @@
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
}, },
"esquery": { "esquery": {
"version": "1.3.1", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
"integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
"requires": { "requires": {
"estraverse": "^5.1.0" "estraverse": "^5.1.0"
}, },
@ -2052,9 +2052,9 @@
}, },
"dependencies": { "dependencies": {
"ajv": { "ajv": {
"version": "7.0.3", "version": "7.0.4",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.3.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.4.tgz",
"integrity": "sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==", "integrity": "sha512-xzzzaqgEQfmuhbhAoqjJ8T/1okb6gAzXn/eQRNpAN1AEUoHJTNF9xCDRTtf/s3SKldtZfa+RJeTs+BQq+eZ/sw==",
"requires": { "requires": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0", "json-schema-traverse": "^1.0.0",

View File

@ -20,12 +20,13 @@
"dependencies": { "dependencies": {
"@grpc/proto-loader": "^0.5.6", "@grpc/proto-loader": "^0.5.6",
"bunyan": "^1.8.15", "bunyan": "^1.8.15",
"eslint": "^7.18.0", "eslint": "^7.19.0",
"grpc-uds": "^0.1.6", "grpc-uds": "^0.1.6",
"handlebars": "^4.7.6", "handlebars": "^4.7.6",
"js-yaml": "^4.0.0", "js-yaml": "^4.0.0",
"lru-cache": "^6.0.0", "lru-cache": "^6.0.0",
"request": "^2.88.2", "request": "^2.88.2",
"semver": "^7.3.4",
"ssh2": "^0.8.9", "ssh2": "^0.8.9",
"uri-js": "^4.4.1", "uri-js": "^4.4.1",
"uuid": "^8.3.2", "uuid": "^8.3.2",

View File

@ -6,6 +6,7 @@ const { Zetabyte, ZfsSshProcessManager } = require("../../utils/zfs");
const Handlebars = require("handlebars"); const Handlebars = require("handlebars");
const uuidv4 = require("uuid").v4; const uuidv4 = require("uuid").v4;
const semver = require("semver");
// zfs common properties // zfs common properties
const MANAGED_PROPERTY_NAME = "democratic-csi:managed_resource"; const MANAGED_PROPERTY_NAME = "democratic-csi:managed_resource";
@ -81,6 +82,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
//"UNKNOWN", //"UNKNOWN",
"CREATE_DELETE_VOLUME", "CREATE_DELETE_VOLUME",
//"PUBLISH_UNPUBLISH_VOLUME", //"PUBLISH_UNPUBLISH_VOLUME",
//"LIST_VOLUMES_PUBLISHED_NODES",
"LIST_VOLUMES", "LIST_VOLUMES",
"GET_CAPACITY", "GET_CAPACITY",
"CREATE_DELETE_SNAPSHOT", "CREATE_DELETE_SNAPSHOT",
@ -88,6 +90,8 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
"CLONE_VOLUME", "CLONE_VOLUME",
//"PUBLISH_READONLY", //"PUBLISH_READONLY",
"EXPAND_VOLUME", "EXPAND_VOLUME",
//"VOLUME_CONDITION", // added in v1.3.0
//"GET_VOLUME", // added in v1.3.0
]; ];
} }
@ -100,7 +104,8 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
//"UNKNOWN", //"UNKNOWN",
"STAGE_UNSTAGE_VOLUME", "STAGE_UNSTAGE_VOLUME",
"GET_VOLUME_STATS", "GET_VOLUME_STATS",
//"EXPAND_VOLUME" //"EXPAND_VOLUME",
//"VOLUME_CONDITION",
]; ];
break; break;
case "volume": case "volume":
@ -109,6 +114,7 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
"STAGE_UNSTAGE_VOLUME", "STAGE_UNSTAGE_VOLUME",
"GET_VOLUME_STATS", "GET_VOLUME_STATS",
"EXPAND_VOLUME", "EXPAND_VOLUME",
//"VOLUME_CONDITION",
]; ];
break; break;
} }
@ -257,6 +263,108 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
return { valid, message }; return { valid, message };
} }
async getVolumeStatus(volume_id) {
const driver = this;
if (!!!semver.satisfies(driver.ctx.csiVersion, ">=1.2.0")) {
return;
}
let abnormal = false;
let message = "OK";
let volume_status = {};
//LIST_VOLUMES_PUBLISHED_NODES
if (
semver.satisfies(driver.ctx.csiVersion, ">=1.2.0") &&
driver.options.service.controller.capabilities.rpc.includes(
"LIST_VOLUMES_PUBLISHED_NODES"
)
) {
// TODO: let drivers fill this in
volume_status.published_node_ids = [];
}
//VOLUME_CONDITION
if (
semver.satisfies(driver.ctx.csiVersion, ">=1.3.0") &&
driver.options.service.controller.capabilities.rpc.includes(
"VOLUME_CONDITION"
)
) {
// TODO: let drivers fill ths in
volume_condition = { abnormal, message };
volume_status.volume_condition = volume_condition;
}
return volume_status;
}
async populateCsiVolumeFromData(row) {
const driver = this;
const zb = await this.getZetabyte();
const driverZfsResourceType = this.getDriverZfsResourceType();
let datasetName = this.getVolumeParentDatasetName();
// ignore rows were csi_name is empty
if (row[MANAGED_PROPERTY_NAME] != "true") {
return;
}
let volume_content_source;
let volume_context = JSON.parse(row[SHARE_VOLUME_CONTEXT_PROPERTY_NAME]);
if (
zb.helpers.isPropertyValueSet(
row[VOLUME_CONTEXT_PROVISIONER_DRIVER_PROPERTY_NAME]
)
) {
volume_context["provisioner_driver"] =
row[VOLUME_CONTEXT_PROVISIONER_DRIVER_PROPERTY_NAME];
}
if (
zb.helpers.isPropertyValueSet(
row[VOLUME_CONTEXT_PROVISIONER_INSTANCE_ID_PROPERTY_NAME]
)
) {
volume_context["provisioner_driver_instance_id"] =
row[VOLUME_CONTEXT_PROVISIONER_INSTANCE_ID_PROPERTY_NAME];
}
if (
zb.helpers.isPropertyValueSet(
row[VOLUME_CONTENT_SOURCE_TYPE_PROPERTY_NAME]
)
) {
volume_content_source = {};
switch (row[VOLUME_CONTENT_SOURCE_TYPE_PROPERTY_NAME]) {
case "snapshot":
volume_content_source.snapshot = {};
volume_content_source.snapshot.snapshot_id =
row[VOLUME_CONTENT_SOURCE_ID_PROPERTY_NAME];
break;
case "volume":
volume_content_source.volume = {};
volume_content_source.volume.volume_id =
row[VOLUME_CONTENT_SOURCE_ID_PROPERTY_NAME];
break;
}
}
let volume = {
// remove parent dataset info
volume_id: row["name"].replace(new RegExp("^" + datasetName + "/"), ""),
capacity_bytes:
driverZfsResourceType == "filesystem"
? row["refquota"]
: row["volsize"],
content_source: volume_content_source,
volume_context,
};
return volume;
}
/** /**
* Ensure sane options are used etc * Ensure sane options are used etc
* true = ready * true = ready
@ -1107,6 +1215,86 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
return { available_capacity: properties.available.value }; return { available_capacity: properties.available.value };
} }
/**
* Get a single volume
*
* @param {*} call
*/
async ControllerGetVolume(call) {
const driver = this;
const driverZfsResourceType = this.getDriverZfsResourceType();
const zb = await this.getZetabyte();
let datasetParentName = this.getVolumeParentDatasetName();
let response;
let name = call.request.volume_id;
if (!datasetParentName) {
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`invalid configuration: missing datasetParentName`
);
}
if (!name) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`volume_id is required`
);
}
const datasetName = datasetParentName + "/" + name;
let types = [];
switch (driverZfsResourceType) {
case "filesystem":
types = ["filesystem"];
break;
case "volume":
types = ["volume"];
break;
}
try {
response = await zb.zfs.list(
datasetName,
[
"name",
"mountpoint",
"refquota",
"avail",
"used",
VOLUME_CSI_NAME_PROPERTY_NAME,
VOLUME_CONTENT_SOURCE_TYPE_PROPERTY_NAME,
VOLUME_CONTENT_SOURCE_ID_PROPERTY_NAME,
"volsize",
MANAGED_PROPERTY_NAME,
SHARE_VOLUME_CONTEXT_PROPERTY_NAME,
SUCCESS_PROPERTY_NAME,
VOLUME_CONTEXT_PROVISIONER_INSTANCE_ID_PROPERTY_NAME,
VOLUME_CONTEXT_PROVISIONER_DRIVER_PROPERTY_NAME,
],
{ types, recurse: false }
);
} catch (err) {
if (err.toString().includes("dataset does not exist")) {
throw new GrpcError(grpc.status.NOT_FOUND, `volume_id is missing`);
}
throw err;
}
driver.ctx.logger.debug("list volumes result: %j", response);
let volume = await driver.populateCsiVolumeFromData(response.indexed[0]);
let status = await driver.getVolumeStatus(datasetName);
let res = { volume };
if (status) {
res.status = status;
}
return res;
}
/** /**
* *
* TODO: check capability to ensure not asking about block volumes * TODO: check capability to ensure not asking about block volumes
@ -1213,68 +1401,20 @@ class ControllerZfsSshBaseDriver extends CsiBaseDriver {
} }
entries = []; entries = [];
response.indexed.forEach((row) => { 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; return;
} }
let volume_content_source; let volume = await driver.populateCsiVolumeFromData(row);
let volume_context = JSON.parse(row[SHARE_VOLUME_CONTEXT_PROPERTY_NAME]); let status = await driver.getVolumeStatus(datasetName);
if (
zb.helpers.isPropertyValueSet(
row[VOLUME_CONTEXT_PROVISIONER_DRIVER_PROPERTY_NAME]
)
) {
volume_context["provisioner_driver"] =
row[VOLUME_CONTEXT_PROVISIONER_DRIVER_PROPERTY_NAME];
}
if (
zb.helpers.isPropertyValueSet(
row[VOLUME_CONTEXT_PROVISIONER_INSTANCE_ID_PROPERTY_NAME]
)
) {
volume_context["provisioner_driver_instance_id"] =
row[VOLUME_CONTEXT_PROVISIONER_INSTANCE_ID_PROPERTY_NAME];
}
if (
zb.helpers.isPropertyValueSet(
row[VOLUME_CONTENT_SOURCE_TYPE_PROPERTY_NAME]
)
) {
volume_content_source = {};
switch (row[VOLUME_CONTENT_SOURCE_TYPE_PROPERTY_NAME]) {
case "snapshot":
volume_content_source.snapshot = {};
volume_content_source.snapshot.snapshot_id =
row[VOLUME_CONTENT_SOURCE_ID_PROPERTY_NAME];
break;
case "volume":
volume_content_source.volume = {};
volume_content_source.volume.volume_id =
row[VOLUME_CONTENT_SOURCE_ID_PROPERTY_NAME];
break;
}
}
entries.push({ entries.push({
volume: { volume,
// remove parent dataset info status,
volume_id: row["name"].replace(
new RegExp("^" + datasetName + "/"),
""
),
capacity_bytes:
driverZfsResourceType == "filesystem"
? row["refquota"]
: row["volsize"],
content_source: volume_content_source,
volume_context,
},
});
}); });
}
if (max_entries && entries.length > max_entries) { if (max_entries && entries.length > max_entries) {
uuid = uuidv4(); uuid = uuidv4();

View File

@ -4,6 +4,7 @@ const { GrpcError, grpc } = require("../utils/grpc");
const { Mount } = require("../utils/mount"); const { Mount } = require("../utils/mount");
const { Filesystem } = require("../utils/filesystem"); const { Filesystem } = require("../utils/filesystem");
const { ISCSI } = require("../utils/iscsi"); const { ISCSI } = require("../utils/iscsi");
const semver = require("semver");
const sleep = require("../utils/general").sleep; const sleep = require("../utils/general").sleep;
/** /**
@ -867,6 +868,7 @@ class CsiBaseDriver {
} }
async NodeGetVolumeStats(call) { async NodeGetVolumeStats(call) {
const driver = this;
const mount = new Mount(); const mount = new Mount();
const filesystem = new Filesystem(); const filesystem = new Filesystem();
let result; let result;
@ -880,6 +882,19 @@ class CsiBaseDriver {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing volume_path`); throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing volume_path`);
} }
let res = {};
//VOLUME_CONDITION
if (
semver.satisfies(driver.ctx.csiVersion, ">=1.3.0") &&
options.service.node.capabilities.rpc.includes("VOLUME_CONDITION")
) {
// TODO: let drivers fill ths in
let abnormal = false;
let message = "OK";
res.volume_condition = { abnormal, message };
}
if ( if (
(await mount.isBindMountedBlockDevice(volume_path)) || (await mount.isBindMountedBlockDevice(volume_path)) ||
(await mount.isBindMountedBlockDevice(block_path)) (await mount.isBindMountedBlockDevice(block_path))
@ -895,33 +910,33 @@ class CsiBaseDriver {
case "mount": case "mount":
result = await mount.getMountDetails(device_path); result = await mount.getMountDetails(device_path);
return { res.usage = [
usage: [
{ {
available: result.avail, available: result.avail,
total: result.size, total: result.size,
used: result.used, used: result.used,
unit: "BYTES", unit: "BYTES",
}, },
], ];
}; break;
case "block": case "block":
result = await filesystem.getBlockDevice(device_path); result = await filesystem.getBlockDevice(device_path);
return { res.usage = [
usage: [
{ {
total: result.size, total: result.size,
unit: "BYTES", unit: "BYTES",
}, },
], ];
}; break;
default: default:
throw new GrpcError( throw new GrpcError(
grpc.status.INVALID_ARGUMENT, grpc.status.INVALID_ARGUMENT,
`unsupported/unknown access_type ${access_type}` `unsupported/unknown access_type ${access_type}`
); );
} }
return res;
} }
/** /**