From 66a22d718ffb015098046ca7aed7c1f86b09806c Mon Sep 17 00:00:00 2001 From: Hunter Madsen Date: Mon, 26 Jul 2021 20:08:53 -0600 Subject: [PATCH] synology snapshots, getcapacity, misc --- src/driver/controller-synology/http/index.js | 149 +++++++++++++++++-- src/driver/controller-synology/index.js | 119 ++++++++++++--- 2 files changed, 230 insertions(+), 38 deletions(-) diff --git a/src/driver/controller-synology/http/index.js b/src/driver/controller-synology/http/index.js index 59b632c..53e9496 100644 --- a/src/driver/controller-synology/http/index.js +++ b/src/driver/controller-synology/http/index.js @@ -143,6 +143,111 @@ class SynologyHttpClient { } } + async GetLunIDByName(name) { + const lun_list = { + api: "SYNO.Core.ISCSI.LUN", + version: "1", + method: "list", + }; + + let response = await this.do_request("GET", "entry.cgi", lun_list); + let lun = response.body.data.luns.find((i) => { + return i.name == name; + }); + + if (lun) { + return lun.lun_id; + } + } + + async GetLunByName(name) { + const lun_list = { + api: "SYNO.Core.ISCSI.LUN", + version: "1", + method: "list", + }; + + let response = await this.do_request("GET", "entry.cgi", lun_list); + let lun = response.body.data.luns.find((i) => { + return i.name == name; + }); + + if (lun) { + return lun; + } + } + + async GetSnapshotByLunIDAndName(lun_id, name) { + const get_snapshot_info = { + lid: lun_id, //check? + api: "SYNO.Core.Storage.iSCSILUN", + method: "load_snapshot", + version: 1, + }; + + let response = await this.do_request("GET", "entry.cgi", get_snapshot_info); + + if (response.body.data) { + let snapshot = response.body.data.find((i) => { + return i.desc == name; + }); + + if (snapshot) { + return snapshot; + } + } + } + + async GetSnapshotByLunIDAndSnapshotUUID(lun_id, snapshot_uuid) { + const get_snapshot_info = { + lid: lun_id, //check? + api: "SYNO.Core.Storage.iSCSILUN", + method: "load_snapshot", + version: 1, + }; + + let response = await this.do_request("GET", "entry.cgi", get_snapshot_info); + + if (response.body.data) { + let snapshot = response.body.data.find((i) => { + return i.uuid == snapshot_uuid; + }); + + if (snapshot) { + return snapshot; + } + } + } + + async DeleteSnapshot(snapshot_uuid) { + const iscsi_snapshot_delete = { + api: "SYNO.Core.ISCSI.LUN", + method: "delete_snapshot", + version: 1, + snapshot_uuid: snapshot_uuid, // snapshot_id + deleted_by: "democratic_csi", // ? + }; + + let response = await this.do_request( + "GET", + "entry.cgi", + iscsi_snapshot_delete + ); + // return? + } + + async GetVolumeInfo(volume_path) { + let data = { + api: "SYNO.Core.Storage.Volume", + method: "get", + version: "1", + //volume_path: "/volume1", + volume_path, + }; + + return await this.do_request("GET", "entry.cgi", data); + } + async GetTargetByTargetID(target_id) { let targets = await this.ListTargets(); let target = targets.find((i) => { @@ -237,18 +342,30 @@ class SynologyHttpClient { //is_soft_feas_ignored: false, is_soft_feas_ignored: true, }; - try { - await this.do_request("GET", "entry.cgi", iscsi_lun_delete); - } catch (err) { - /** - * 18990710 = already gone - * LUN_BAD_LUN_UUID = 18990505 - * LUN_NO_SUCH_SNAPSHOT = 18990532 - */ - if (![18990505].includes(err.body.error.code)) { - throw err; - } - } + + await this.do_request("GET", "entry.cgi", iscsi_lun_delete); + + // } catch (err) { + // /** + // * 18990710 = already gone + // * LUN_BAD_LUN_UUID = 18990505 + // * LUN_NO_SUCH_SNAPSHOT = 18990532 + // *//* + // if (![18990505].includes(err.body.error.code)) { + // throw err; + // } + // } + // */ + } + + async CreateSnapshot(data) { + data = Object.assign({}, data, { + api: "SYNO.Core.ISCSI.LUN", + method: "take_snapshot", + version: 1, + }); + + return await this.do_request("GET", "entry.cgi", data); } async CreateTarget(data = {}) { @@ -311,9 +428,9 @@ class SynologyHttpClient { /** * 18990710 = non-existant */ - if (![18990710].includes(err.body.error.code)) { - throw err; - } + //if (![18990710].includes(err.body.error.code)) { + throw err; + //} } } @@ -324,7 +441,7 @@ class SynologyHttpClient { version: 1, }; - await this.do_request( + return await this.do_request( "GET", "entry.cgi", Object.assign({}, iscsi_lun_extend, { uuid: uuid, new_size: size }) diff --git a/src/driver/controller-synology/index.js b/src/driver/controller-synology/index.js index 889e21b..6beefd9 100644 --- a/src/driver/controller-synology/index.js +++ b/src/driver/controller-synology/index.js @@ -53,8 +53,8 @@ class ControllerSynologyDriver extends CsiBaseDriver { "CREATE_DELETE_VOLUME", //"PUBLISH_UNPUBLISH_VOLUME", //"LIST_VOLUMES", - //"GET_CAPACITY", - //"CREATE_DELETE_SNAPSHOT", + "GET_CAPACITY", + "CREATE_DELETE_SNAPSHOT", //"LIST_SNAPSHOTS", //"CLONE_VOLUME", //"PUBLISH_READONLY", @@ -69,7 +69,7 @@ class ControllerSynologyDriver extends CsiBaseDriver { //"UNKNOWN", "STAGE_UNSTAGE_VOLUME", "GET_VOLUME_STATS", - //"EXPAND_VOLUME" + "EXPAND_VOLUME", ]; } } @@ -407,13 +407,15 @@ class ControllerSynologyDriver extends CsiBaseDriver { let iscsiName = driver.buildIscsiName(name); let iqn = driver.options.iscsi.baseiqn + iscsiName; - response = await httpClient.GetTargetByIQN(iqn); - if (response) { - await httpClient.DeleteTarget(response.target_id); + let target = await httpClient.GetTargetByIQN(iqn); + if (target) { + await httpClient.DeleteTarget(target.target_id); } - response = await httpClient.GetLunUUIDByName(iscsiName); - await httpClient.DeleteLun(response); + let lun_uuid = await httpClient.GetLunUUIDByName(iscsiName); + if (lun_uuid) { + await httpClient.DeleteLun(lun_uuid); + } break; default: throw new GrpcError( @@ -523,10 +525,33 @@ class ControllerSynologyDriver extends CsiBaseDriver { * @param {*} call */ async GetCapacity(call) { - throw new GrpcError( - grpc.status.UNIMPLEMENTED, - `operation not supported by driver` + // throw new GrpcError( + // grpc.status.UNIMPLEMENTED, + // `operation not supported by driver` + // ); + + const driver = this; + const httpClient = await driver.getHttpClient(); + + if (!driver.options.synology.location) { + throw new GrpcError( + grpc.status.FAILED_PRECONDITION, + `invalid configuration: missing location` + ); + } + + if (call.request.volume_capabilities) { + const result = this.assertCapabilities(call.request.volume_capabilities); + + if (result.valid !== true) { + return { available_capacity: 0 }; + } + } + + let response = await httpClient.GetVolumeInfo( + driver.options.synology.location ); + return { available_capacity: response.body.data.volume.size_free_byte }; } /** @@ -558,11 +583,8 @@ class ControllerSynologyDriver extends CsiBaseDriver { * @param {*} call */ async CreateSnapshot(call) { - throw new GrpcError( - grpc.status.UNIMPLEMENTED, - `operation not supported by driver` - ); const driver = this; + const httpClient = await driver.getHttpClient(); // both these are required let source_volume_id = call.request.source_volume_id; @@ -596,7 +618,47 @@ class ControllerSynologyDriver extends CsiBaseDriver { ); } - // TODO: create snapshot here + // create snapshot here + + let iscsiName = driver.buildIscsiName(source_volume_id); + let lun = await httpClient.GetLunByName(iscsiName); + + if (!lun) { + throw new GrpcError( + grpc.status.INVALID_ARGUMENT, + `invalid source_volume_id: ${source_volume_id}` + ); + } + + // check for already exists + let snapshot = await httpClient.GetSnapshotByLunIDAndName(lun.lun_id, name); + if (snapshot) { + return { + snapshot: { + /** + * The purpose of this field is to give CO guidance on how much space + * is needed to create a volume from this snapshot. + */ + size_bytes: 0, + snapshot_id: `/lun/${lun.lun_id}/${snapshot.uuid}`, // add shanpshot_uuid //fixme + source_volume_id: source_volume_id, + //https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/timestamp.proto + creation_time: { + seconds: snapshot.time, + nanos: 0, + }, + ready_to_use: true, + }, + }; + } + + let data = Object.assign({}, driver.options.iscsi.lunSnapshotAttributes, { + src_lun_uuid: lun.uuid, + taken_by: "democratic-csi", + description: name, //check + }); + + let response = await httpClient.CreateSnapshot(data); return { snapshot: { @@ -605,7 +667,7 @@ class ControllerSynologyDriver extends CsiBaseDriver { * is needed to create a volume from this snapshot. */ size_bytes: 0, - snapshot_id, + snapshot_id: `/lun/${lun.lun_id}/${response.body.data.snapshot_uuid}`, source_volume_id: source_volume_id, //https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/timestamp.proto creation_time: { @@ -624,12 +686,13 @@ class ControllerSynologyDriver extends CsiBaseDriver { * @param {*} call */ async DeleteSnapshot(call) { - throw new GrpcError( - grpc.status.UNIMPLEMENTED, - `operation not supported by driver` - ); + // throw new GrpcError( + // grpc.status.UNIMPLEMENTED, + // `operation not supported by driver` + // ); const driver = this; + const httpClient = await driver.getHttpClient(); const snapshot_id = call.request.snapshot_id; @@ -640,7 +703,19 @@ class ControllerSynologyDriver extends CsiBaseDriver { ); } - // TODO: delete snapshot here + let parts = snapshot_id.split("/"); + let lun_id = parts[2]; + let snapshot_uuid = parts[3]; + + // TODO: delete snapshot + let snapshot = await httpClient.GetSnapshotByLunIDAndSnapshotUUID( + lun_id, + snapshot_uuid + ); + + if (snapshot) { + await httpClient.DeleteSnapshot(snapshot.uuid); + } return {}; }