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"
|
session: "democratic-csi"
|
||||||
serialize: true
|
serialize: true
|
||||||
|
|
||||||
synology:
|
# choose the default volume for your system. The default value is /volume1.
|
||||||
# choose the proper volume for your system
|
# This can also be overridden by StorageClasses
|
||||||
volume: /volume1
|
# synology:
|
||||||
|
# volume: /volume1
|
||||||
|
|
||||||
iscsi:
|
iscsi:
|
||||||
targetPortal: "server[:port]"
|
targetPortal: "server[:port]"
|
||||||
|
|
@ -27,63 +28,5 @@ iscsi:
|
||||||
# full iqn limit is 223 bytes, plan accordingly
|
# full iqn limit is 223 bytes, plan accordingly
|
||||||
namePrefix: ""
|
namePrefix: ""
|
||||||
nameSuffix: ""
|
nameSuffix: ""
|
||||||
|
# LUN options and CHAP authentication can be configured using StorageClasses.
|
||||||
# documented below are several blocks
|
# See https://github.com/democratic-csi/democratic-csi/blob/master/docs/storage-class-parameters.md
|
||||||
# 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
|
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
const create_cloned_volume = {
|
||||||
api: "SYNO.Core.ISCSI.LUN",
|
api: "SYNO.Core.ISCSI.LUN",
|
||||||
version: 1,
|
version: 1,
|
||||||
method: "clone",
|
method: "clone",
|
||||||
src_lun_uuid: JSON.stringify(src_lun_uuid), // src lun uuid
|
src_lun_uuid: JSON.stringify(src_lun_uuid), // src lun uuid
|
||||||
dst_lun_name: dst_lun_name, // dst lun name
|
dst_lun_name: dst_lun_name, // dst lun name
|
||||||
|
dst_location: dst_location,
|
||||||
is_same_pool: true, // always true? string?
|
is_same_pool: true, // always true? string?
|
||||||
clone_type: "democratic-csi", // check
|
clone_type: "democratic-csi", // check
|
||||||
};
|
};
|
||||||
|
if (description) {
|
||||||
|
create_cloned_volume.description = description;
|
||||||
|
}
|
||||||
return await this.do_request("GET", "entry.cgi", create_cloned_volume);
|
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) {
|
buildIscsiName(name) {
|
||||||
let iscsiName = name;
|
let iscsiName = name;
|
||||||
if (this.options.iscsi.namePrefix) {
|
if (this.options.iscsi.namePrefix) {
|
||||||
|
|
@ -155,6 +173,25 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
||||||
return iscsiName.toLowerCase();
|
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) {
|
assertCapabilities(capabilities) {
|
||||||
const driverResourceType = this.getDriverResourceType();
|
const driverResourceType = this.getDriverResourceType();
|
||||||
this.ctx.logger.verbose("validating capabilities: %j", capabilities);
|
this.ctx.logger.verbose("validating capabilities: %j", capabilities);
|
||||||
|
|
@ -310,6 +347,7 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
let volume_context = {};
|
let volume_context = {};
|
||||||
|
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
|
||||||
|
|
@ -425,7 +463,7 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
||||||
`invalid volume_id: ${volume_content_source.volume.volume_id}`
|
`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;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -446,9 +484,59 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
||||||
// create lun
|
// create lun
|
||||||
data = Object.assign({}, driver.options.iscsi.lunTemplate, {
|
data = Object.assign({}, driver.options.iscsi.lunTemplate, {
|
||||||
name: iscsiName,
|
name: iscsiName,
|
||||||
location: driver.options.synology.volume,
|
location: driver.getLocation(normalizedParameters),
|
||||||
size: capacity_bytes,
|
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);
|
lun_uuid = await httpClient.CreateLun(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -458,6 +546,60 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
||||||
name: iscsiName,
|
name: iscsiName,
|
||||||
iqn,
|
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);
|
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);
|
||||||
|
|
@ -737,8 +879,10 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
||||||
async GetCapacity(call) {
|
async GetCapacity(call) {
|
||||||
const driver = this;
|
const driver = this;
|
||||||
const httpClient = await driver.getHttpClient();
|
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(
|
throw new GrpcError(
|
||||||
grpc.status.FAILED_PRECONDITION,
|
grpc.status.FAILED_PRECONDITION,
|
||||||
`invalid configuration: missing volume`
|
`invalid configuration: missing volume`
|
||||||
|
|
@ -753,9 +897,7 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = await httpClient.GetVolumeInfo(
|
let response = await httpClient.GetVolumeInfo(location);
|
||||||
driver.options.synology.volume
|
|
||||||
);
|
|
||||||
return { available_capacity: response.body.data.volume.size_free_byte };
|
return { available_capacity: response.body.data.volume.size_free_byte };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -850,11 +992,25 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
||||||
let snapshot;
|
let snapshot;
|
||||||
snapshot = await httpClient.GetSnapshotByLunIDAndName(lun.lun_id, name);
|
snapshot = await httpClient.GetSnapshotByLunIDAndName(lun.lun_id, name);
|
||||||
if (!snapshot) {
|
if (!snapshot) {
|
||||||
|
const normalizedParameters = driver.getNormalizedParameters(call.request.parameters);
|
||||||
let data = Object.assign({}, driver.options.iscsi.lunSnapshotTemplate, {
|
let data = Object.assign({}, driver.options.iscsi.lunSnapshotTemplate, {
|
||||||
src_lun_uuid: lun.uuid,
|
src_lun_uuid: lun.uuid,
|
||||||
taken_by: "democratic-csi",
|
taken_by: "democratic-csi",
|
||||||
description: name, //check
|
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);
|
await httpClient.CreateSnapshot(data);
|
||||||
snapshot = await httpClient.GetSnapshotByLunIDAndName(lun.lun_id, name);
|
snapshot = await httpClient.GetSnapshotByLunIDAndName(lun.lun_id, name);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue