Add StorageClass Parameters for Synology
This commit is contained in:
parent
c76750a303
commit
bd620025a0
|
|
@ -0,0 +1,100 @@
|
|||
# Storage Class Parameters
|
||||
|
||||
Some drivers support different settings for volumes. These can be configured via the driver configuration and/or storage classes.
|
||||
|
||||
## `synology-iscsi`
|
||||
The `synology-iscsi` driver supports several storage class parameters. Note however that not all parameters/values are supported for all backing file systems and LUN type. The following options are available:
|
||||
|
||||
### Configure Storage Classes
|
||||
```yaml
|
||||
apiVersion: storage.k8s.io/v1
|
||||
kind: StorageClass
|
||||
metadata:
|
||||
name: synology-iscsi
|
||||
parameters:
|
||||
fsType: ext4
|
||||
# The following options affect the LUN representing the volume
|
||||
volume: /volume2 # Optional. Override the volume on which the LUN will be created.
|
||||
lunType: BLUN # Btrfs thin provisioning
|
||||
lunType: BLUN_THICK # Btrfs thick provisioning
|
||||
lunType: THIN # Ext4 thin provisioning
|
||||
lunType: ADV # Ext4 thin provisioning with legacy advanced feature set
|
||||
lunType: FILE # Ext4 thick provisioning
|
||||
lunDescription: Some Description
|
||||
hardwareAssistedZeroing: true
|
||||
hardwareAssistedLocking: true
|
||||
hardwareAssistedDataTransfer: true
|
||||
spaceReclamation: true
|
||||
allowSnapshots: true
|
||||
enableFuaWrite: false
|
||||
enableSyncCache: false
|
||||
ioPolicy: Buffered # or Direct
|
||||
# The following options affect the iSCSI target
|
||||
headerDigenst: false
|
||||
dataDigest: false
|
||||
maxSessions: 1 # Note that this option requires a compatible filesystem
|
||||
maxRecieveSegmentBytes: 262144
|
||||
maxSendSegmentBytes: 262144
|
||||
...
|
||||
```
|
||||
|
||||
About extended features:
|
||||
- For `BLUN_THICK` volumes only hardware assisted zeroing and locking can be configured.
|
||||
- For `THIN` volumes none of the extended features can be configured.
|
||||
- For `ADV` volumes only space reclamation can be configured.
|
||||
- For `FILE` volumes only hardware assisted locking can be configured.
|
||||
- `ioPolicy` is only available for thick provisioned volumes.
|
||||
|
||||
### Configure Snapshot Classes
|
||||
`synology-iscsi` can also configure different parameters on snapshot classes:
|
||||
|
||||
```yaml
|
||||
apiVersion: snapshot.storage.k8s.io/v1
|
||||
kind: VolumeSnapshotClass
|
||||
metadata:
|
||||
name: synology-iscsi-snapshot
|
||||
parameters:
|
||||
isLocked: true
|
||||
# https://kb.synology.com/en-me/DSM/tutorial/What_is_file_system_consistent_snapshot
|
||||
consistency: AppConsistent # Or CrashConsistent
|
||||
...
|
||||
```
|
||||
|
||||
### Enabling CHAP Authentication
|
||||
You can enable CHAP Authentication for `StorageClass`es by supplying an appropriate `StorageClass` secret (see the [documentation](https://kubernetes-csi.github.io/docs/secrets-and-credentials-storage-class.html) for more details). You can use the same password for alle volumes of a `StorageClass` or use different passwords per volume.
|
||||
|
||||
```yaml
|
||||
apiVersion: storage.k8s.io/v1
|
||||
kind: StorageClass
|
||||
metadata:
|
||||
name: synology-iscsi-chap
|
||||
parameters:
|
||||
fsType: ext4
|
||||
lunType: BLUN
|
||||
lunDescription: iSCSI volumes with CHAP Authentication
|
||||
secrets:
|
||||
# Use this to configure a single set of credentials for all volumes of this StorageClass
|
||||
csi.storage.k8s.io/provisioner-secret-name: chap-secret
|
||||
csi.storage.k8s.io/provisioner-secret-namespace: default
|
||||
# Use substitutions to use different credentials for volumes based on the PVC
|
||||
csi.storage.k8s.io/provisioner-secret-name: "${pvc.name}-chap-secret"
|
||||
csi.storage.k8s.io/provisioner-secret-namespace: "${pvc.namespace}"
|
||||
...
|
||||
---
|
||||
# Use a secret like this to supply CHAP credentials.
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: chap-secret
|
||||
stringData:
|
||||
# Client Credentials
|
||||
user: client
|
||||
password: MySecretPassword
|
||||
# Mutual CHAP Credentials. If these are specified mutual CHAP will be enabled.
|
||||
mutualUser: server
|
||||
password: MyOtherPassword
|
||||
```
|
||||
|
||||
Note that CHAP authentication will only be enabled if the secret is correctly configured. If e.g. a password is missing CHAP authentication will not be enabled (but the volume will still be created). You cannot automatically enable/disable CHAP or change the password after the volume has been created.
|
||||
|
||||
If the secret itself is referenced but not present, the volume will not be created.
|
||||
|
|
@ -10,9 +10,10 @@ httpConnection:
|
|||
session: "democratic-csi"
|
||||
serialize: true
|
||||
|
||||
synology:
|
||||
# choose the proper volume for your system
|
||||
volume: /volume1
|
||||
# choose the default volume for your system. The default value is /volume1.
|
||||
# This can also be overridden by StorageClasses
|
||||
# synology:
|
||||
# volume: /volume1
|
||||
|
||||
iscsi:
|
||||
targetPortal: "server[:port]"
|
||||
|
|
@ -27,63 +28,5 @@ iscsi:
|
|||
# full iqn limit is 223 bytes, plan accordingly
|
||||
namePrefix: ""
|
||||
nameSuffix: ""
|
||||
|
||||
# documented below are several blocks
|
||||
# pick the option appropriate for you based on what your backing fs is and desired features
|
||||
# you do not need to alter dev_attribs under normal circumstances but they may be altered in advanced use-cases
|
||||
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
|
||||
# LUN options and CHAP authentication can be configured using StorageClasses.
|
||||
# See https://github.com/democratic-csi/democratic-csi/blob/master/docs/storage-class-parameters.md
|
||||
|
|
|
|||
|
|
@ -612,16 +612,20 @@ class SynologyHttpClient {
|
|||
);
|
||||
}
|
||||
|
||||
async CreateClonedVolume(src_lun_uuid, dst_lun_name) {
|
||||
async CreateClonedVolume(src_lun_uuid, dst_lun_name, dst_location, description) {
|
||||
const create_cloned_volume = {
|
||||
api: "SYNO.Core.ISCSI.LUN",
|
||||
version: 1,
|
||||
method: "clone",
|
||||
src_lun_uuid: JSON.stringify(src_lun_uuid), // src lun uuid
|
||||
dst_lun_name: dst_lun_name, // dst lun name
|
||||
dst_location: dst_location,
|
||||
is_same_pool: true, // always true? string?
|
||||
clone_type: "democratic-csi", // check
|
||||
};
|
||||
if (description) {
|
||||
create_cloned_volume.description = description;
|
||||
}
|
||||
return await this.do_request("GET", "entry.cgi", create_cloned_volume);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -142,6 +142,24 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a boolean value (e.g. from the value of a parameter. This recognizes
|
||||
* strings containing boolean literals as well as the numbers 1 and 0.
|
||||
*
|
||||
* @param {String} value - The value to be parsed.
|
||||
* @returns {boolean} The parsed boolean value.
|
||||
*/
|
||||
parseBoolean(value) {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const parsed = parseInt(value)
|
||||
if (!isNaN(parsed)) {
|
||||
return Boolean(parsed)
|
||||
}
|
||||
return "true".localeCompare(value, undefined, {sensitivity: "accent"}) === 0
|
||||
}
|
||||
|
||||
buildIscsiName(name) {
|
||||
let iscsiName = name;
|
||||
if (this.options.iscsi.namePrefix) {
|
||||
|
|
@ -155,6 +173,25 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
return iscsiName.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value for the 'location' parameter indicating on which volume
|
||||
* a LUN is to be created.
|
||||
*
|
||||
* @param {Object} parameters - Parameters received from a StorageClass
|
||||
* @param {String} parameters.volume - The volume specified by the StorageClass
|
||||
* @returns {String} The location of the volume.
|
||||
*/
|
||||
getLocation({volume}) {
|
||||
let location = volume ?? this.options?.synology?.volume
|
||||
if (location === undefined) {
|
||||
location = "volume1"
|
||||
}
|
||||
if (!location.startsWith('/')) {
|
||||
location = "/" + location
|
||||
}
|
||||
return location
|
||||
}
|
||||
|
||||
assertCapabilities(capabilities) {
|
||||
const driverResourceType = this.getDriverResourceType();
|
||||
this.ctx.logger.verbose("validating capabilities: %j", capabilities);
|
||||
|
|
@ -310,6 +347,7 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
}
|
||||
|
||||
let volume_context = {};
|
||||
const normalizedParameters = driver.getNormalizedParameters(call.request.parameters);
|
||||
switch (driver.getDriverShareType()) {
|
||||
case "nfs":
|
||||
// TODO: create volume here
|
||||
|
|
@ -425,7 +463,7 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
`invalid volume_id: ${volume_content_source.volume.volume_id}`
|
||||
);
|
||||
}
|
||||
await httpClient.CreateClonedVolume(src_lun_uuid, iscsiName);
|
||||
await httpClient.CreateClonedVolume(src_lun_uuid, iscsiName, driver.getLocation(normalizedParameters));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
@ -446,9 +484,59 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
// create lun
|
||||
data = Object.assign({}, driver.options.iscsi.lunTemplate, {
|
||||
name: iscsiName,
|
||||
location: driver.options.synology.volume,
|
||||
size: capacity_bytes,
|
||||
location: driver.getLocation(normalizedParameters),
|
||||
size: capacity_bytes
|
||||
});
|
||||
data.type = normalizedParameters.lunType ?? data.type;
|
||||
if ('lunDescription' in normalizedParameters) {
|
||||
data.description = normalizedParameters.lunDescription;
|
||||
}
|
||||
if (normalizedParameters.ioPolicy === "Direct") {
|
||||
data.direct_io_pattern = 3;
|
||||
} else if (normalizedParameters.ioPolicy === "Buffered") {
|
||||
data.direct_io_pattern = 0;
|
||||
} else if (normalizedParameters.ioPolicy !== undefined) {
|
||||
throw new GrpcError(
|
||||
grpc.status.INVALID_ARGUMENT,
|
||||
`snapshot consistency must be either CrashConsistent or AppConsistent`
|
||||
);
|
||||
}
|
||||
|
||||
const dev_attribs = (data.dev_attribs ?? []).reduce(
|
||||
(obj, item) => Object.assign(obj, {[item.dev_attrib]: driver.parseBoolean(item.enable)}), {}
|
||||
);
|
||||
dev_attribs.emulate_tpws = driver.parseBoolean(normalizedParameters.hardwareAssistedZeroing) ?? dev_attribs.emulate_tpws;
|
||||
dev_attribs.emulate_caw = driver.parseBoolean(normalizedParameters.hardwareAssistedLocking) ?? dev_attribs.emulate_caw;
|
||||
dev_attribs.emulate_3pc = driver.parseBoolean(normalizedParameters.hardwareAssistedDataTransfer) ?? dev_attribs.emulate_3pc;
|
||||
dev_attribs.emulate_tpu = driver.parseBoolean(normalizedParameters.spaceReclamation) ?? dev_attribs.emulate_tpu;
|
||||
dev_attribs.emulate_fua_write = driver.parseBoolean(normalizedParameters.enableFuaWrite) ?? dev_attribs.emulate_fua_write;
|
||||
dev_attribs.emulate_sync_cache = driver.parseBoolean(normalizedParameters.enableSyncCache) ?? dev_attribs.emulate_sync_cache;
|
||||
dev_attribs.can_snapshot = driver.parseBoolean(normalizedParameters.allowSnapshots) ?? dev_attribs.can_snapshot;
|
||||
data.dev_attribs = Object.entries(dev_attribs).filter(
|
||||
e => e[1] !== undefined
|
||||
).map(
|
||||
e => ({dev_attrib: e[0], enable: Number(e[1])})
|
||||
);
|
||||
|
||||
if (["BLUN", "THIN", "ADV"].includes(data.type) && 'direct_io_pattern' in data) {
|
||||
throw new GrpcError(
|
||||
grpc.status.INVALID_ARGUMENT,
|
||||
`ioPolicy can only be used with thick provisioning.`
|
||||
);
|
||||
}
|
||||
if (["BLUN_THICK", "FILE"].includes(data.type) && dev_attribs.emulate_tpu) {
|
||||
throw new GrpcError(
|
||||
grpc.status.INVALID_ARGUMENT,
|
||||
`spaceReclamation can only be used with thin provisioning.`
|
||||
);
|
||||
}
|
||||
if (["BLUN_THICK", "FILE"].includes(data.type) && dev_attribs.can_snapshot) {
|
||||
throw new GrpcError(
|
||||
grpc.status.INVALID_ARGUMENT,
|
||||
`allowSnapshots can only be used with thin provisioning.`
|
||||
);
|
||||
}
|
||||
|
||||
lun_uuid = await httpClient.CreateLun(data);
|
||||
}
|
||||
|
||||
|
|
@ -458,6 +546,60 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
name: iscsiName,
|
||||
iqn,
|
||||
});
|
||||
if ('headerChecksum' in normalizedParameters) {
|
||||
data.has_data_checksum = normalizedParameters['headerChecksum'];
|
||||
}
|
||||
if ('dataChecksum' in normalizedParameters) {
|
||||
data.has_data_checksum = normalizedParameters['dataChecksum'];
|
||||
}
|
||||
if ('maxSessions' in normalizedParameters) {
|
||||
data.max_sessions = Number(normalizedParameters['maxSessions']);
|
||||
if (isNaN(data.max_sessions)) {
|
||||
throw new GrpcError(
|
||||
grpc.status.INVALID_ARGUMENT,
|
||||
`maxSessions must be a number.`
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!('multi_sessions' in data) && 'max_sessions' in data) {
|
||||
data.multi_sessions = data.max_sessions == 1;
|
||||
}
|
||||
if ('maxReceiveSegmentBytes' in normalizedParameters) {
|
||||
data.max_recv_seg_bytes = Number(normalizedParameters['maxReceiveSegmentBytes']);
|
||||
if (isNaN(data.max_recv_seg_bytes)) {
|
||||
throw new GrpcError(
|
||||
grpc.status.INVALID_ARGUMENT,
|
||||
`maxReceiveSegmentBytes must be a number.`
|
||||
);
|
||||
}
|
||||
}
|
||||
if ('maxSendSegmentBytes' in normalizedParameters) {
|
||||
data.max_send_seg_bytes = Number(normalizedParameters['maxSendSegmentBytes']);
|
||||
if (isNaN(data.max_send_seg_bytes)) {
|
||||
throw new GrpcError(
|
||||
grpc.status.INVALID_ARGUMENT,
|
||||
`maxSendSegmentBytes must be a number.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
data.auth_type ??= 0;
|
||||
data.chap ??= false;
|
||||
}
|
||||
let target_id = await httpClient.CreateTarget(data);
|
||||
//target = await httpClient.GetTargetByTargetID(target_id);
|
||||
target = await httpClient.GetTargetByIQN(iqn);
|
||||
|
|
@ -737,8 +879,10 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
async GetCapacity(call) {
|
||||
const driver = this;
|
||||
const httpClient = await driver.getHttpClient();
|
||||
const normalizedParameters = driver.getNormalizedParameters(call.request.parameters)
|
||||
const location = driver.getLocation(normalizedParameters);
|
||||
|
||||
if (!driver.options.synology.volume) {
|
||||
if (!location) {
|
||||
throw new GrpcError(
|
||||
grpc.status.FAILED_PRECONDITION,
|
||||
`invalid configuration: missing volume`
|
||||
|
|
@ -753,9 +897,7 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
}
|
||||
}
|
||||
|
||||
let response = await httpClient.GetVolumeInfo(
|
||||
driver.options.synology.volume
|
||||
);
|
||||
let response = await httpClient.GetVolumeInfo(location);
|
||||
return { available_capacity: response.body.data.volume.size_free_byte };
|
||||
}
|
||||
|
||||
|
|
@ -850,11 +992,25 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
let snapshot;
|
||||
snapshot = await httpClient.GetSnapshotByLunIDAndName(lun.lun_id, name);
|
||||
if (!snapshot) {
|
||||
const normalizedParameters = driver.getNormalizedParameters(call.request.parameters);
|
||||
let data = Object.assign({}, driver.options.iscsi.lunSnapshotTemplate, {
|
||||
src_lun_uuid: lun.uuid,
|
||||
taken_by: "democratic-csi",
|
||||
description: name, //check
|
||||
});
|
||||
if ('isLocked' in normalizedParameters) {
|
||||
data['is_locked'] = driver.parseBoolean(normalizedParameters.isLocked);
|
||||
}
|
||||
if (normalizedParameters.consistency === "AppConsistent") {
|
||||
data['is_app_consistent'] = true;
|
||||
} else if (normalizedParameters.consistency === 'CrashConsistent') {
|
||||
data['is_app_consistent'] = false;
|
||||
} else if ('consistency' in normalizedParameters.consistency) {
|
||||
throw new GrpcError(
|
||||
grpc.status.INVALID_ARGUMENT,
|
||||
`snapshot consistency must be either CrashConsistent or AppConsistent`
|
||||
);
|
||||
}
|
||||
|
||||
await httpClient.CreateSnapshot(data);
|
||||
snapshot = await httpClient.GetSnapshotByLunIDAndName(lun.lun_id, name);
|
||||
|
|
|
|||
Loading…
Reference in New Issue