synology updates, dsm6 and dsm7 in ci

Signed-off-by: Travis Glenn Hansen <travisghansen@yahoo.com>
This commit is contained in:
Travis Glenn Hansen 2022-04-20 14:08:51 -06:00
parent c55f3957ac
commit 9026d5e0d6
7 changed files with 291 additions and 103 deletions

View File

@ -35,14 +35,14 @@ jobs:
path: node_modules.tar.gz path: node_modules.tar.gz
retention-days: 7 retention-days: 7
csi-sanity-synology: csi-sanity-synology-dsm6:
needs: needs:
- build-npm - build-npm
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
config: config:
- synlogy/iscsi.yaml - synlogy/dsm6/iscsi.yaml
runs-on: runs-on:
- self-hosted - self-hosted
- csi-sanity-synology - csi-sanity-synology
@ -57,12 +57,41 @@ jobs:
ci/bin/run.sh ci/bin/run.sh
env: env:
TEMPLATE_CONFIG_FILE: "./ci/configs/${{ matrix.config }}" TEMPLATE_CONFIG_FILE: "./ci/configs/${{ matrix.config }}"
SYNOLOGY_HOST: ${{ secrets.SANITY_SYNOLOGY_HOST }} SYNOLOGY_HOST: ${{ secrets.SANITY_SYNOLOGY_DSM6_HOST }}
SYNOLOGY_PORT: ${{ secrets.SANITY_SYNOLOGY_PORT }} SYNOLOGY_PORT: ${{ secrets.SANITY_SYNOLOGY_DSM6_PORT }}
SYNOLOGY_USERNAME: ${{ secrets.SANITY_SYNOLOGY_USERNAME }} SYNOLOGY_USERNAME: ${{ secrets.SANITY_SYNOLOGY_USERNAME }}
SYNOLOGY_PASSWORD: ${{ secrets.SANITY_SYNOLOGY_PASSWORD }} SYNOLOGY_PASSWORD: ${{ secrets.SANITY_SYNOLOGY_PASSWORD }}
SYNOLOGY_VOLUME: ${{ secrets.SANITY_SYNOLOGY_VOLUME }} SYNOLOGY_VOLUME: ${{ secrets.SANITY_SYNOLOGY_VOLUME }}
csi-sanity-synology-dsm7:
needs:
- build-npm
strategy:
fail-fast: false
matrix:
config:
- synlogy/dsm7/iscsi.yaml
runs-on:
- self-hosted
- csi-sanity-synology
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v2
with:
name: node-modules
- name: csi-sanity
run: |
# run tests
ci/bin/run.sh
env:
TEMPLATE_CONFIG_FILE: "./ci/configs/${{ matrix.config }}"
SYNOLOGY_HOST: ${{ secrets.SANITY_SYNOLOGY_DSM7_HOST }}
SYNOLOGY_PORT: ${{ secrets.SANITY_SYNOLOGY_DSM7_PORT }}
SYNOLOGY_USERNAME: ${{ secrets.SANITY_SYNOLOGY_USERNAME }}
SYNOLOGY_PASSWORD: ${{ secrets.SANITY_SYNOLOGY_PASSWORD }}
SYNOLOGY_VOLUME: ${{ secrets.SANITY_SYNOLOGY_VOLUME }}
# api-based drivers # api-based drivers
csi-sanity-truenas-scale-22_02: csi-sanity-truenas-scale-22_02:
needs: needs:
@ -237,7 +266,8 @@ jobs:
build-docker: build-docker:
needs: needs:
- csi-sanity-synology - csi-sanity-synology-dsm6
- csi-sanity-synology-dsm7
- csi-sanity-truenas-scale-22_02 - csi-sanity-truenas-scale-22_02
- csi-sanity-truenas-core-12_0 - csi-sanity-truenas-core-12_0
- csi-sanity-truenas-core-13_0 - csi-sanity-truenas-core-13_0

View File

@ -0,0 +1,77 @@
driver: synology-iscsi
httpConnection:
protocol: http
host: ${SYNOLOGY_HOST}
port: ${SYNOLOGY_PORT}
username: ${SYNOLOGY_USERNAME}
password: ${SYNOLOGY_PASSWORD}
allowInsecure: true
session: "democratic-csi-${CI_BUILD_KEY}"
serialize: true
synology:
volume: ${SYNOLOGY_VOLUME}
iscsi:
targetPortal: ${SYNOLOGY_HOST}
targetPortals: []
baseiqn: "iqn.2000-01.com.synology:XpenoDsm62x."
namePrefix: "csi-${CI_BUILD_KEY}-"
nameSuffix: "-ci"
lunTemplate:
# btrfs thin provisioning
type: "BLUN"
# tpws = Hardware-assisted zeroing
# caw = Hardware-assisted locking
# 3pc = Hardware-assisted data transfer
# tpu = Space reclamation
# can_snapshot = Snapshot
#dev_attribs:
#- dev_attrib: emulate_tpws
# enable: 1
#- dev_attrib: emulate_caw
# enable: 1
#- dev_attrib: emulate_3pc
# enable: 1
#- dev_attrib: emulate_tpu
# enable: 0
#- dev_attrib: can_snapshot
# enable: 1
# btfs thick provisioning
# only zeroing and locking supported
#type: "BLUN_THICK"
# tpws = Hardware-assisted zeroing
# caw = Hardware-assisted locking
#dev_attribs:
#- dev_attrib: emulate_tpws
# enable: 1
#- dev_attrib: emulate_caw
# enable: 1
# ext4 thinn provisioning UI sends everything with enabled=0
#type: "THIN"
# ext4 thin with advanced legacy features set
# can only alter tpu (all others are set as enabled=1)
#type: "ADV"
#dev_attribs:
#- dev_attrib: emulate_tpu
# enable: 1
# ext4 thick
# can only alter caw
#type: "FILE"
#dev_attribs:
#- dev_attrib: emulate_caw
# enable: 1
lunSnapshotTemplate:
is_locked: true
# https://kb.synology.com/en-me/DSM/tutorial/What_is_file_system_consistent_snapshot
is_app_consistent: true
targetTemplate:
auth_type: 0
max_sessions: 0

View File

@ -34,6 +34,9 @@ iscsi:
# These options can also be configured per storage-class: # These options can also be configured per storage-class:
# See https://github.com/democratic-csi/democratic-csi/blob/master/docs/storage-class-parameters.md # See https://github.com/democratic-csi/democratic-csi/blob/master/docs/storage-class-parameters.md
lunTemplate: lunTemplate:
# can be static value or handlebars template
#description: "{{ parameters.[csi.storage.k8s.io/pvc/namespace] }}-{{ parameters.[csi.storage.k8s.io/pvc/name] }}"
# btrfs thin provisioning # btrfs thin provisioning
type: "BLUN" type: "BLUN"
# tpws = Hardware-assisted zeroing # tpws = Hardware-assisted zeroing

View File

@ -10,17 +10,46 @@ const USER_AGENT = "democratic-csi";
const __REGISTRY_NS__ = "SynologyHttpClient"; const __REGISTRY_NS__ = "SynologyHttpClient";
SYNO_ERRORS = { SYNO_ERRORS = {
18990002: { status: grpc.status.RESOURCE_EXHAUSTED, message: "The synology volume is out of disk space." }, 400: {
18990318: { status: grpc.status.INVALID_ARGUMENT, message: "The requested lun type is incompatible with the Synology filesystem." }, status: grpc.status.UNAUTHENTICATED,
18990538: { status: grpc.status.ALREADY_EXISTS, message: "A LUN with this name already exists." }, message: "Failed to authenticate to the Synology DSM",
18990541: { status: grpc.status.RESOURCE_EXHAUSTED, message: "The maximum number of LUNS has been reached." }, },
18990542: { status: grpc.status.RESOURCE_EXHAUSTED, message: "The maximum number if iSCSI target has been reached." }, 18990002: {
18990744: { status: grpc.status.ALREADY_EXISTS, message: "An iSCSI target with this name already exists." }, status: grpc.status.RESOURCE_EXHAUSTED,
message: "The synology volume is out of disk space.",
},
18990318: {
status: grpc.status.INVALID_ARGUMENT,
message:
"The requested lun type is incompatible with the Synology filesystem.",
},
18990538: {
status: grpc.status.ALREADY_EXISTS,
message: "A LUN with this name already exists.",
},
18990541: {
status: grpc.status.RESOURCE_EXHAUSTED,
message: "The maximum number of LUNS has been reached.",
},
18990542: {
status: grpc.status.RESOURCE_EXHAUSTED,
message: "The maximum number if iSCSI target has been reached.",
},
18990744: {
status: grpc.status.ALREADY_EXISTS,
message: "An iSCSI target with this name already exists.",
},
18990532: { status: grpc.status.NOT_FOUND, message: "No such snapshot." }, 18990532: { status: grpc.status.NOT_FOUND, message: "No such snapshot." },
18990500: { status: grpc.status.INVALID_ARGUMENT, message: "Bad LUN type" }, 18990500: { status: grpc.status.INVALID_ARGUMENT, message: "Bad LUN type" },
18990543: { status: grpc.status.RESOURCE_EXHAUSTED, message: "Maximum number of snapshots reached." }, 18990543: {
18990635: { status: grpc.status.INVALID_ARGUMENT, message: "Invalid ioPolicy." } status: grpc.status.RESOURCE_EXHAUSTED,
} message: "Maximum number of snapshots reached.",
},
18990635: {
status: grpc.status.INVALID_ARGUMENT,
message: "Invalid ioPolicy.",
},
};
class SynologyError extends GrpcError { class SynologyError extends GrpcError {
constructor(code, httpCode = undefined) { constructor(code, httpCode = undefined) {
@ -28,9 +57,11 @@ class SynologyError extends GrpcError {
this.synoCode = code; this.synoCode = code;
this.httpCode = httpCode; this.httpCode = httpCode;
if (code > 0) { if (code > 0) {
const error = SYNO_ERRORS[code] const error = SYNO_ERRORS[code];
this.code = error?.status ?? grpc.status.UNKNOWN; this.code = error?.status ?? grpc.status.UNKNOWN;
this.message = error?.message ?? `An unknown error occurred when executing a synology command (code = ${code}).`; this.message =
error?.message ??
`An unknown error occurred when executing a synology command (code = ${code}).`;
} else { } else {
this.code = grpc.status.UNKNOWN; this.code = grpc.status.UNKNOWN;
this.message = `The synology webserver returned a status code ${httpCode}`; this.message = `The synology webserver returned a status code ${httpCode}`;
@ -95,6 +126,15 @@ class SynologyHttpClient {
_.set(options, prop, "redacted"); _.set(options, prop, "redacted");
} }
prop = "params._sid";
val = _.get(options, prop, false);
if (val) {
_.set(options, prop, "redacted");
}
delete options.httpAgent;
delete options.httpsAgent;
this.logger.debug("SYNOLOGY HTTP REQUEST: " + stringify(options)); this.logger.debug("SYNOLOGY HTTP REQUEST: " + stringify(options));
this.logger.debug("SYNOLOGY HTTP ERROR: " + error); this.logger.debug("SYNOLOGY HTTP ERROR: " + error);
this.logger.debug("SYNOLOGY HTTP STATUS: " + response.statusCode); this.logger.debug("SYNOLOGY HTTP STATUS: " + response.statusCode);
@ -179,7 +219,7 @@ class SynologyHttpClient {
} }
if (response.statusCode > 299 || response.statusCode < 200) { if (response.statusCode > 299 || response.statusCode < 200) {
reject(new SynologyError(null, response.statusCode)) reject(new SynologyError(null, response.statusCode));
} }
if (response.body.success === false) { if (response.body.success === false) {
@ -187,7 +227,9 @@ class SynologyHttpClient {
if (response.body.error.code == 119 && sid == client.sid) { if (response.body.error.code == 119 && sid == client.sid) {
client.sid = null; client.sid = null;
} }
reject(new SynologyError(response.body.error.code, response.statusCode)); reject(
new SynologyError(response.body.error.code, response.statusCode)
);
} }
resolve(response); resolve(response);
@ -602,7 +644,12 @@ class SynologyHttpClient {
); );
} }
async CreateClonedVolume(src_lun_uuid, dst_lun_name, dst_location, description) { async CreateClonedVolume(
src_lun_uuid,
dst_lun_name,
dst_location,
description
) {
const create_cloned_volume = { const create_cloned_volume = {
api: "SYNO.Core.ISCSI.LUN", api: "SYNO.Core.ISCSI.LUN",
version: 1, version: 1,
@ -619,7 +666,12 @@ class SynologyHttpClient {
return await this.do_request("GET", "entry.cgi", create_cloned_volume); return await this.do_request("GET", "entry.cgi", create_cloned_volume);
} }
async CreateVolumeFromSnapshot(src_lun_uuid, snapshot_uuid, cloned_lun_name, description) { async CreateVolumeFromSnapshot(
src_lun_uuid,
snapshot_uuid,
cloned_lun_name,
description
) {
const create_volume_from_snapshot = { const create_volume_from_snapshot = {
api: "SYNO.Core.ISCSI.LUN", api: "SYNO.Core.ISCSI.LUN",
version: 1, version: 1,

View File

@ -1,11 +1,12 @@
const _ = require("lodash");
const { CsiBaseDriver } = require("../index"); const { CsiBaseDriver } = require("../index");
const GeneralUtils = require("../../utils/general");
const { GrpcError, grpc } = require("../../utils/grpc"); const { GrpcError, grpc } = require("../../utils/grpc");
const Handlebars = require("handlebars");
const registry = require("../../utils/registry"); const registry = require("../../utils/registry");
const SynologyHttpClient = require("./http").SynologyHttpClient; const SynologyHttpClient = require("./http").SynologyHttpClient;
const semver = require("semver"); const semver = require("semver");
const sleep = require("../../utils/general").sleep;
const yaml = require("js-yaml"); const yaml = require("js-yaml");
const GeneralUtils = require("../../utils/general");
const __REGISTRY_NS__ = "ControllerSynologyDriver"; const __REGISTRY_NS__ = "ControllerSynologyDriver";
@ -146,19 +147,33 @@ class ControllerSynologyDriver extends CsiBaseDriver {
getObjectFromDevAttribs(list = []) { getObjectFromDevAttribs(list = []) {
if (!list) { if (!list) {
return {} return {};
} }
return list.reduce( return list.reduce(
(obj, item) => Object.assign(obj, {[item.dev_attrib]: item.enable}), {} (obj, item) => Object.assign(obj, { [item.dev_attrib]: item.enable }),
) {}
);
} }
getDevAttribsFromObject(obj, keepNull = false) { getDevAttribsFromObject(obj, keepNull = false) {
return Object.entries(obj).filter( return Object.entries(obj)
e => keepNull || (e[1] != null) .filter((e) => keepNull || e[1] != null)
).map( .map((e) => ({ dev_attrib: e[0], enable: e[1] }));
e => ({dev_attrib: e[0], enable: e[1]}) }
parseParameterYamlData(data, fieldHint = "") {
try {
return yaml.load(data);
} catch {
if (err instanceof yaml.YAMLException) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`${fieldHint} not a valid YAML document.`.trim()
); );
} else {
throw err;
}
}
} }
buildIscsiName(name) { buildIscsiName(name) {
@ -183,14 +198,14 @@ class ControllerSynologyDriver extends CsiBaseDriver {
* @returns {String} The location of the volume. * @returns {String} The location of the volume.
*/ */
getLocation() { getLocation() {
let location = this.options?.synology?.volume; let location = _.get(this.options, "synology.volume");
if (location === undefined) { if (!location) {
location = "volume1" location = "volume1";
} }
if (!location.startsWith('/')) { if (!location.startsWith("/")) {
location = "/" + location location = "/" + location;
} }
return location return location;
} }
assertCapabilities(capabilities) { assertCapabilities(capabilities) {
@ -350,7 +365,9 @@ class ControllerSynologyDriver extends CsiBaseDriver {
} }
let volume_context = {}; let volume_context = {};
const normalizedParameters = driver.getNormalizedParameters(call.request.parameters); const normalizedParameters = driver.getNormalizedParameters(
call.request.parameters
);
switch (driver.getDriverShareType()) { switch (driver.getDriverShareType()) {
case "nfs": case "nfs":
// TODO: create volume here // TODO: create volume here
@ -368,13 +385,53 @@ class ControllerSynologyDriver extends CsiBaseDriver {
break; break;
case "iscsi": case "iscsi":
let iscsiName = driver.buildIscsiName(name); let iscsiName = driver.buildIscsiName(name);
let storageClassTemplate; let lunTemplate;
let targetTemplate;
let data; let data;
let target; let target;
let lun_mapping; let lun_mapping;
let lun_uuid; let lun_uuid;
let existingLun; let existingLun;
lunTemplate = Object.assign(
{},
_.get(driver.options, "iscsi.lunTemplate", {}),
driver.parseParameterYamlData(
_.get(normalizedParameters, "lunTemplate", "{}"),
"parameters.lunTemplate"
),
driver.parseParameterYamlData(
_.get(call.request, "secrets.lunTemplate", "{}"),
"secrets.lunTemplate"
)
);
targetTemplate = Object.assign(
{},
_.get(driver.options, "iscsi.targetTemplate", {}),
driver.parseParameterYamlData(
_.get(normalizedParameters, "targetTemplate", "{}"),
"parameters.targetTemplate"
),
driver.parseParameterYamlData(
_.get(call.request, "secrets.targetTemplate", "{}"),
"secrets.targetTemplate"
)
);
// render the template for description
if (lunTemplate.description) {
lunTemplate.description = Handlebars.compile(lunTemplate.description)(
{
name: call.request.name,
parameters: call.request.parameters,
csi: {
name: this.ctx.args.csiName,
version: this.ctx.args.csiVersion,
},
}
);
}
// ensure volumes with the same name being requested a 2nd time but with a different size fails // ensure volumes with the same name being requested a 2nd time but with a different size fails
try { try {
let lun = await httpClient.GetLunByName(iscsiName); let lun = await httpClient.GetLunByName(iscsiName);
@ -429,7 +486,8 @@ class ControllerSynologyDriver extends CsiBaseDriver {
src_lun_uuid = await httpClient.GetLunByID(src_lun_uuid).uuid; src_lun_uuid = await httpClient.GetLunByID(src_lun_uuid).uuid;
} }
let snapshot = await httpClient.GetSnapshotByLunUUIDAndSnapshotUUID( let snapshot =
await httpClient.GetSnapshotByLunUUIDAndSnapshotUUID(
src_lun_uuid, src_lun_uuid,
snapshot_uuid snapshot_uuid
); );
@ -446,7 +504,7 @@ class ControllerSynologyDriver extends CsiBaseDriver {
src_lun_uuid, src_lun_uuid,
snapshot_uuid, snapshot_uuid,
iscsiName, iscsiName,
normalizedParameters.description lunTemplate.description
); );
} }
break; break;
@ -474,7 +532,7 @@ class ControllerSynologyDriver extends CsiBaseDriver {
src_lun_uuid, src_lun_uuid,
iscsiName, iscsiName,
driver.getLocation(), driver.getLocation(),
normalizedParameters.description lunTemplate.description
); );
} }
break; break;
@ -494,62 +552,22 @@ class ControllerSynologyDriver extends CsiBaseDriver {
} }
} else { } else {
// create lun // create lun
try { data = Object.assign({}, lunTemplate, {
storageClassTemplate = yaml.load(normalizedParameters.lunTemplate ?? "")
const devAttribs = driver.getDevAttribsFromObject(Object.assign(
{},
driver.getObjectFromDevAttribs(driver.options.iscsi.lunTemplate?.dev_attribs),
driver.getObjectFromDevAttribs(storageClassTemplate?.dev_attribs)
))
data = Object.assign({}, driver.options.iscsi.lunTemplate, storageClassTemplate, {
name: iscsiName, name: iscsiName,
location: driver.getLocation(), location: driver.getLocation(),
size: capacity_bytes, size: capacity_bytes,
dev_attribs: devAttribs
}); });
lun_uuid = await httpClient.CreateLun(data); lun_uuid = await httpClient.CreateLun(data);
} catch (err) {
if (err instanceof yaml.YAMLException) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`The lunTemplate on StorageClass is not a valid YAML document.`
);
} else {
throw err
}
}
} }
// create target // create target
let iqn = driver.options.iscsi.baseiqn + iscsiName; let iqn = driver.options.iscsi.baseiqn + iscsiName;
try { data = Object.assign({}, targetTemplate, {
storageClassTemplate = yaml.load(normalizedParameters.targetTemplate ?? "")
} catch (err) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`The targetTemplate on StorageClass is not a valid YAML document.`
);
}
data = Object.assign({}, driver.options.iscsi.targetTemplate, storageClassTemplate, {
name: iscsiName, name: iscsiName,
iqn, iqn,
}); });
if ('user' in call.request.secrets && 'password' in call.request.secrets) {
data.user = call.request.secrets.user;
data.password = call.request.secrets.password;
data.chap = true;
if ('mutualUser' in call.request.secrets && 'mutualPassword' in call.request.secrets) {
data.mutual_user = call.request.secrets.mutualUser;
data.mutual_password = call.request.secrets.mutualPassword;
data.auth_type = 2;
data.mutual_chap = true;
} else {
data.auth_type = 1;
data.mutual_chap = false;
}
}
let target_id = await httpClient.CreateTarget(data); let target_id = await httpClient.CreateTarget(data);
//target = await httpClient.GetTargetByTargetID(target_id); //target = await httpClient.GetTargetByTargetID(target_id);
target = await httpClient.GetTargetByIQN(iqn); target = await httpClient.GetTargetByIQN(iqn);
@ -924,6 +942,24 @@ class ControllerSynologyDriver extends CsiBaseDriver {
); );
} }
const normalizedParameters = driver.getNormalizedParameters(
call.request.parameters
);
let lunSnapshotTemplate;
lunSnapshotTemplate = Object.assign(
{},
_.get(driver.options, "iscsi.lunSnapshotTemplate", {}),
driver.parseParameterYamlData(
_.get(normalizedParameters, "lunSnapshotTemplate", "{}"),
"parameters.lunSnapshotTemplate"
),
driver.parseParameterYamlData(
_.get(call.request, "secrets.lunSnapshotTemplate", "{}"),
"secrets.lunSnapshotTemplate"
)
);
// check for other snapshopts with the same name on other volumes and fail as appropriate // 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 // 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 // but alas an isolation/namespacing mechanism does not exist in synology
@ -939,19 +975,9 @@ class ControllerSynologyDriver extends CsiBaseDriver {
// check for already exists // check for already exists
let snapshot; let snapshot;
let snapshotClassTemplate;
snapshot = await httpClient.GetSnapshotByLunUUIDAndName(lun.uuid, name); snapshot = await httpClient.GetSnapshotByLunUUIDAndName(lun.uuid, name);
if (!snapshot) { if (!snapshot) {
const normalizedParameters = driver.getNormalizedParameters(call.request.parameters); let data = Object.assign({}, lunSnapshotTemplate, {
try {
snapshotClassTemplate = yaml.load(normalizedParameters.lunSnapshotTemplate ?? "");
} catch (err) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`The snapshotTemplate on VolumeSnapshotClass is not a valid YAML document.`
);
}
let data = Object.assign({}, driver.options.iscsi.lunSnapshotTemplate, snapshotClassTemplate, {
src_lun_uuid: lun.uuid, src_lun_uuid: lun.uuid,
taken_by: "democratic-csi", taken_by: "democratic-csi",
description: name, //check description: name, //check

View File

@ -65,7 +65,7 @@ class ControllerZfsGenericDriver extends ControllerZfsBaseDriver {
const driver = this; const driver = this;
driver.ctx.logger.verbose( driver.ctx.logger.verbose(
`generating smb share name for dataset: ${typeof datasetName} ${datasetName}` `generating smb share name for dataset: ${datasetName}`
); );
let name = datasetName || ""; let name = datasetName || "";