Merge remote-tracking branch 'origin/dev' into next
This commit is contained in:
commit
49c0c9b16c
|
|
@ -0,0 +1,302 @@
|
|||
const request = require("request");
|
||||
|
||||
const USER_AGENT = "democratic-csi";
|
||||
|
||||
class SynologyHttpClient {
|
||||
constructor(options = {}) {
|
||||
this.options = JSON.parse(JSON.stringify(options));
|
||||
this.logger = console;
|
||||
|
||||
setInterval(() => {
|
||||
console.log("WIPING OUT SYNOLOGY SID");
|
||||
this.sid = null;
|
||||
}, 60 * 1000);
|
||||
}
|
||||
|
||||
async login() {
|
||||
if (!this.sid) {
|
||||
const data = {
|
||||
api: "SYNO.API.Auth",
|
||||
version: "2",
|
||||
method: "login",
|
||||
account: this.options.username,
|
||||
passwd: this.options.password,
|
||||
session: this.options.session,
|
||||
format: "sid",
|
||||
};
|
||||
|
||||
this.authenticating = true;
|
||||
let response = await this.do_request("GET", "auth.cgi", data);
|
||||
this.sid = response.body.data.sid;
|
||||
this.authenticating = false;
|
||||
}
|
||||
}
|
||||
|
||||
log_response(error, response, body, options) {
|
||||
this.logger.debug("SYNOLOGY HTTP REQUEST: " + JSON.stringify(options));
|
||||
this.logger.debug("SYNOLOGY HTTP ERROR: " + error);
|
||||
this.logger.debug("SYNOLOGY HTTP STATUS: " + response.statusCode);
|
||||
this.logger.debug(
|
||||
"SYNOLOGY HTTP HEADERS: " + JSON.stringify(response.headers)
|
||||
);
|
||||
this.logger.debug("SYNOLOGY HTTP BODY: " + JSON.stringify(body));
|
||||
}
|
||||
|
||||
async do_request(method, path, data = {}) {
|
||||
const client = this;
|
||||
if (!this.authenticating) {
|
||||
await this.login();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (data.api != "SYNO.API.Auth") {
|
||||
data._sid = this.sid;
|
||||
}
|
||||
|
||||
const options = {
|
||||
method: method,
|
||||
url: `${this.options.protocol}://${this.options.host}:${this.options.port}/webapi/${path}`,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"User-Agent": USER_AGENT,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
json: true,
|
||||
agentOptions: {
|
||||
rejectUnauthorized: !!!client.options.allowInsecure,
|
||||
},
|
||||
};
|
||||
|
||||
switch (method) {
|
||||
case "GET":
|
||||
options.qs = data;
|
||||
break;
|
||||
default:
|
||||
options.body = data;
|
||||
break;
|
||||
}
|
||||
|
||||
request(options, function (error, response, body) {
|
||||
client.log_response(...arguments, options);
|
||||
|
||||
if (error) {
|
||||
reject(error);
|
||||
}
|
||||
|
||||
if (response.statusCode > 299 || response.statusCode < 200) {
|
||||
reject(response);
|
||||
}
|
||||
|
||||
if (response.body.success === false) {
|
||||
reject(response);
|
||||
}
|
||||
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async GetLunUUIDByName(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.uuid;
|
||||
}
|
||||
}
|
||||
|
||||
async GetTargetByTargetID(target_id) {
|
||||
let targets = await this.ListTargets();
|
||||
let target = targets.find((i) => {
|
||||
return i.target_id == target_id;
|
||||
});
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
async ListTargets() {
|
||||
const iscsi_target_list = {
|
||||
api: "SYNO.Core.ISCSI.Target",
|
||||
version: "1",
|
||||
path: "entry.cgi",
|
||||
method: "list",
|
||||
additional: '["mapped_lun", "status", "acls", "connected_sessions"]',
|
||||
};
|
||||
let response = await this.do_request("GET", "entry.cgi", iscsi_target_list);
|
||||
return response.body.data.targets;
|
||||
}
|
||||
|
||||
async CreateLun(data = {}) {
|
||||
let response;
|
||||
let iscsi_lun_create = Object.assign(data, {
|
||||
api: "SYNO.Core.ISCSI.LUN",
|
||||
version: "1",
|
||||
method: "create",
|
||||
});
|
||||
|
||||
const lun_list = {
|
||||
api: "SYNO.Core.ISCSI.LUN",
|
||||
version: "1",
|
||||
method: "list",
|
||||
};
|
||||
|
||||
try {
|
||||
response = await this.do_request("GET", "entry.cgi", iscsi_lun_create);
|
||||
return response.body.data.uuid;
|
||||
} catch (err) {
|
||||
if ([18990538].includes(err.body.error.code)) {
|
||||
response = await this.do_request("GET", "entry.cgi", lun_list);
|
||||
let lun = response.body.data.luns.find((i) => {
|
||||
return i.name == iscsi_lun_create.name;
|
||||
});
|
||||
return lun.uuid;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async MapLun(data = {}) {
|
||||
// this is mapping from the perspective of the lun
|
||||
let iscsi_target_map = Object.assign(data, {
|
||||
api: "SYNO.Core.ISCSI.LUN",
|
||||
method: "map_target",
|
||||
version: "1",
|
||||
});
|
||||
iscsi_target_map.target_ids = JSON.stringify(iscsi_target_map.target_ids);
|
||||
|
||||
// this is mapping from the perspective of the target
|
||||
/*
|
||||
iscsi_target_map = Object.assign(data, {
|
||||
api: "SYNO.Core.ISCSI.Target",
|
||||
method: "map_lun",
|
||||
version: "1",
|
||||
});
|
||||
iscsi_target_map.lun_uuids = JSON.stringify(iscsi_target_map.lun_uuids);
|
||||
*/
|
||||
|
||||
await this.do_request("GET", "entry.cgi", iscsi_target_map);
|
||||
}
|
||||
|
||||
async DeleteLun(uuid) {
|
||||
let iscsi_lun_delete = {
|
||||
api: "SYNO.Core.ISCSI.LUN",
|
||||
method: "delete",
|
||||
version: 1,
|
||||
uuid: uuid || "",
|
||||
};
|
||||
try {
|
||||
await this.do_request("GET", "entry.cgi", iscsi_lun_delete);
|
||||
} catch (err) {
|
||||
if (![18990505].includes(err.body.error.code)) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async GetTargetIDByIQN(iqn) {
|
||||
const iscsi_target_list = {
|
||||
api: "SYNO.Core.ISCSI.Target",
|
||||
version: "1",
|
||||
path: "entry.cgi",
|
||||
method: "list",
|
||||
additional: '["mapped_lun", "status", "acls", "connected_sessions"]',
|
||||
};
|
||||
|
||||
let response = await this.do_request("GET", "entry.cgi", iscsi_target_list);
|
||||
let target = response.body.data.targets.find((i) => {
|
||||
return i.iqn == iqn;
|
||||
});
|
||||
|
||||
if (target) {
|
||||
return target.target_id;
|
||||
}
|
||||
}
|
||||
|
||||
async CreateTarget(data = {}) {
|
||||
let iscsi_target_create = Object.assign(data, {
|
||||
api: "SYNO.Core.ISCSI.Target",
|
||||
version: "1",
|
||||
method: "create",
|
||||
});
|
||||
|
||||
let response;
|
||||
|
||||
try {
|
||||
response = await this.do_request("GET", "entry.cgi", iscsi_target_create);
|
||||
|
||||
return response.body.data.target_id;
|
||||
} catch (err) {
|
||||
if ([18990744].includes(err.body.error.code)) {
|
||||
//do lookup
|
||||
const iscsi_target_list = {
|
||||
api: "SYNO.Core.ISCSI.Target",
|
||||
version: "1",
|
||||
path: "entry.cgi",
|
||||
method: "list",
|
||||
additional: '["mapped_lun", "status", "acls", "connected_sessions"]',
|
||||
};
|
||||
|
||||
response = await this.do_request("GET", "entry.cgi", iscsi_target_list);
|
||||
let target = response.body.data.targets.find((i) => {
|
||||
return i.iqn == iscsi_target_create.iqn;
|
||||
});
|
||||
|
||||
let target_id = target.target_id;
|
||||
return target_id;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async DeleteTarget(target_id) {
|
||||
const iscsi_target_delete = {
|
||||
api: "SYNO.Core.ISCSI.Target",
|
||||
method: "delete",
|
||||
version: "1",
|
||||
path: "entry.cgi",
|
||||
};
|
||||
|
||||
try {
|
||||
await this.do_request(
|
||||
"GET",
|
||||
"entry.cgi",
|
||||
Object.assign(iscsi_target_delete, {
|
||||
target_id: JSON.stringify(String(target_id || "")),
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
/**
|
||||
* 18990710 = non-existant
|
||||
*/
|
||||
if (![18990710].includes(err.body.error.code)) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async ExpandISCSILun(uuid, size) {
|
||||
const iscsi_lun_extend = {
|
||||
api: "SYNO.Core.ISCSI.LUN",
|
||||
method: "set",
|
||||
version: 1,
|
||||
};
|
||||
|
||||
await this.do_request(
|
||||
"GET",
|
||||
"entry.cgi",
|
||||
Object.assign(iscsi_lun_extend, { uuid: uuid, new_size: size })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.SynologyHttpClient = SynologyHttpClient;
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
const { CsiBaseDriver } = require("../index");
|
||||
const { GrpcError, grpc } = require("../../utils/grpc");
|
||||
const SynologyHttpClient = require("./http").SynologyHttpClient;
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -57,7 +58,7 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
//"LIST_SNAPSHOTS",
|
||||
//"CLONE_VOLUME",
|
||||
//"PUBLISH_READONLY",
|
||||
//"EXPAND_VOLUME",
|
||||
"EXPAND_VOLUME",
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -73,6 +74,13 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
}
|
||||
}
|
||||
|
||||
async getHttpClient() {
|
||||
if (!this.httpClient) {
|
||||
this.httpClient = new SynologyHttpClient(this.options.httpConnection);
|
||||
}
|
||||
return this.httpClient;
|
||||
}
|
||||
|
||||
getDriverResourceType() {
|
||||
switch (this.options.driver) {
|
||||
case "synology-nfs":
|
||||
|
|
@ -98,6 +106,19 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
}
|
||||
}
|
||||
|
||||
buildIscsiName(name) {
|
||||
let iscsiName = name;
|
||||
if (this.options.iscsi.namePrefix) {
|
||||
iscsiName = this.options.iscsi.namePrefix + iscsiName;
|
||||
}
|
||||
|
||||
if (this.options.iscsi.nameSuffix) {
|
||||
iscsiName += this.options.iscsi.nameSuffix;
|
||||
}
|
||||
|
||||
return iscsiName.toLowerCase();
|
||||
}
|
||||
|
||||
assertCapabilities(capabilities) {
|
||||
const driverResourceType = this.getDriverResourceType();
|
||||
this.ctx.logger.verbose("validating capabilities: %j", capabilities);
|
||||
|
|
@ -176,6 +197,7 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
*/
|
||||
async CreateVolume(call) {
|
||||
const driver = this;
|
||||
const httpClient = await driver.getHttpClient();
|
||||
|
||||
let name = call.request.name;
|
||||
let volume_content_source = call.request.volume_content_source;
|
||||
|
|
@ -230,23 +252,84 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
);
|
||||
}
|
||||
|
||||
let volume_context = {};
|
||||
switch (driver.getDriverShareType()) {
|
||||
case "nfs":
|
||||
// TODO: create volume here
|
||||
throw new GrpcError(
|
||||
grpc.status.UNIMPLEMENTED,
|
||||
`operation not supported by driver`
|
||||
);
|
||||
break;
|
||||
case "smb":
|
||||
// TODO: create volume here
|
||||
throw new GrpcError(
|
||||
grpc.status.UNIMPLEMENTED,
|
||||
`operation not supported by driver`
|
||||
);
|
||||
break;
|
||||
case "iscsi":
|
||||
// TODO: create volume here
|
||||
let iscsiName = driver.buildIscsiName(name);
|
||||
let data;
|
||||
let iqn = driver.options.iscsi.baseiqn + iscsiName;
|
||||
data = Object.assign(driver.options.iscsi.targetAttributes, {
|
||||
name: iscsiName,
|
||||
iqn,
|
||||
});
|
||||
|
||||
let target_id = await httpClient.CreateTarget(data);
|
||||
data = Object.assign(driver.options.iscsi.lunAttributes, {
|
||||
name: iscsiName,
|
||||
location: driver.options.synology.location,
|
||||
size: capacity_bytes,
|
||||
});
|
||||
let lun_uuid = await httpClient.CreateLun(data);
|
||||
let target = await httpClient.GetTargetByTargetID(target_id);
|
||||
|
||||
if (!target) {
|
||||
throw new GrpcError(
|
||||
grpc.status.UNKNOWN,
|
||||
`failed to lookup target: ${target_id}`
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!target.mapped_luns.some((lun) => {
|
||||
return lun.lun_uuid == lun_uuid;
|
||||
})
|
||||
) {
|
||||
data = {
|
||||
uuid: lun_uuid,
|
||||
target_ids: [target_id],
|
||||
};
|
||||
/*
|
||||
data = {
|
||||
lun_uuids: [lun_uuid],
|
||||
target_id: target_id,
|
||||
};
|
||||
*/
|
||||
await httpClient.MapLun(data);
|
||||
}
|
||||
|
||||
volume_context = {
|
||||
node_attach_driver: "iscsi",
|
||||
portal: driver.options.iscsi.targetPortal || "",
|
||||
portals: driver.options.iscsi.targetPortals
|
||||
? driver.options.iscsi.targetPortals.join(",")
|
||||
: "",
|
||||
interface: driver.options.iscsi.interface || "",
|
||||
iqn,
|
||||
lun: 0,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
// throw an error
|
||||
throw new GrpcError(
|
||||
grpc.status.UNIMPLEMENTED,
|
||||
`operation not supported by driver`
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
let volume_context = driver.getVolumeContext(name);
|
||||
|
||||
volume_context["provisioner_driver"] = driver.options.driver;
|
||||
if (driver.options.instance_id) {
|
||||
volume_context["provisioner_driver_instance_id"] =
|
||||
|
|
@ -256,8 +339,7 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
const res = {
|
||||
volume: {
|
||||
volume_id: name,
|
||||
//capacity_bytes: capacity_bytes, // kubernetes currently pukes if capacity is returned as 0
|
||||
capacity_bytes: 0,
|
||||
capacity_bytes, // kubernetes currently pukes if capacity is returned as 0
|
||||
content_source: volume_content_source,
|
||||
volume_context,
|
||||
},
|
||||
|
|
@ -273,6 +355,7 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
*/
|
||||
async DeleteVolume(call) {
|
||||
const driver = this;
|
||||
const httpClient = await driver.getHttpClient();
|
||||
|
||||
let name = call.request.volume_id;
|
||||
|
||||
|
|
@ -283,18 +366,38 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
);
|
||||
}
|
||||
|
||||
let response;
|
||||
|
||||
switch (driver.getDriverShareType()) {
|
||||
case "nfs":
|
||||
// TODO: delete volume here
|
||||
throw new GrpcError(
|
||||
grpc.status.UNIMPLEMENTED,
|
||||
`operation not supported by driver`
|
||||
);
|
||||
break;
|
||||
case "smb":
|
||||
// TODO: delete volume here
|
||||
throw new GrpcError(
|
||||
grpc.status.UNIMPLEMENTED,
|
||||
`operation not supported by driver`
|
||||
);
|
||||
break;
|
||||
case "iscsi":
|
||||
// TODO: delete volume here
|
||||
let iscsiName = driver.buildIscsiName(name);
|
||||
let iqn = driver.options.iscsi.baseiqn + iscsiName;
|
||||
|
||||
response = await httpClient.GetLunUUIDByName(iscsiName);
|
||||
await httpClient.DeleteLun(response);
|
||||
|
||||
response = await httpClient.GetTargetIDByIQN(iqn);
|
||||
await httpClient.DeleteTarget(response);
|
||||
break;
|
||||
default:
|
||||
// throw an error
|
||||
throw new GrpcError(
|
||||
grpc.status.UNIMPLEMENTED,
|
||||
`operation not supported by driver`
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -306,10 +409,90 @@ class ControllerSynologyDriver extends CsiBaseDriver {
|
|||
* @param {*} call
|
||||
*/
|
||||
async ControllerExpandVolume(call) {
|
||||
throw new GrpcError(
|
||||
grpc.status.UNIMPLEMENTED,
|
||||
`operation not supported by driver`
|
||||
);
|
||||
const driver = this;
|
||||
const httpClient = await driver.getHttpClient();
|
||||
|
||||
let name = call.request.volume_id;
|
||||
|
||||
if (!name) {
|
||||
throw new GrpcError(
|
||||
grpc.status.INVALID_ARGUMENT,
|
||||
`volume_id is required`
|
||||
);
|
||||
}
|
||||
|
||||
let capacity_bytes =
|
||||
call.request.capacity_range.required_bytes ||
|
||||
call.request.capacity_range.limit_bytes;
|
||||
if (!capacity_bytes) {
|
||||
//should never happen, value must be set
|
||||
throw new GrpcError(
|
||||
grpc.status.INVALID_ARGUMENT,
|
||||
`volume capacity is required (either required_bytes or limit_bytes)`
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
call.request.capacity_range.required_bytes > 0 &&
|
||||
call.request.capacity_range.limit_bytes > 0 &&
|
||||
call.request.capacity_range.required_bytes >
|
||||
call.request.capacity_range.limit_bytes
|
||||
) {
|
||||
throw new GrpcError(
|
||||
grpc.status.INVALID_ARGUMENT,
|
||||
`required_bytes is greather than limit_bytes`
|
||||
);
|
||||
}
|
||||
|
||||
// ensure *actual* capacity is not greater than limit
|
||||
if (
|
||||
call.request.capacity_range.limit_bytes &&
|
||||
call.request.capacity_range.limit_bytes > 0 &&
|
||||
capacity_bytes > call.request.capacity_range.limit_bytes
|
||||
) {
|
||||
throw new GrpcError(
|
||||
grpc.status.OUT_OF_RANGE,
|
||||
`required volume capacity is greater than limit`
|
||||
);
|
||||
}
|
||||
|
||||
let node_expansion_required = false;
|
||||
let response;
|
||||
|
||||
switch (driver.getDriverShareType()) {
|
||||
case "nfs":
|
||||
// TODO: expand volume here
|
||||
throw new GrpcError(
|
||||
grpc.status.UNIMPLEMENTED,
|
||||
`operation not supported by driver`
|
||||
);
|
||||
break;
|
||||
case "smb":
|
||||
// TODO: expand volume here
|
||||
throw new GrpcError(
|
||||
grpc.status.UNIMPLEMENTED,
|
||||
`operation not supported by driver`
|
||||
);
|
||||
break;
|
||||
case "iscsi":
|
||||
node_expansion_required = true;
|
||||
let iscsiName = driver.buildIscsiName(name);
|
||||
|
||||
response = await httpClient.GetLunUUIDByName(iscsiName);
|
||||
await httpClient.ExpandISCSILun(response, capacity_bytes);
|
||||
break;
|
||||
default:
|
||||
throw new GrpcError(
|
||||
grpc.status.UNIMPLEMENTED,
|
||||
`operation not supported by driver`
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
capacity_bytes,
|
||||
node_expansion_required,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue