democratic-csi/src/driver/freenas/index.js

1032 lines
34 KiB
JavaScript

const grpc = require("grpc");
const { ControllerZfsSshBaseDriver } = require("../controller-zfs-ssh");
const { GrpcError } = require("../../utils/grpc");
const HttpClient = require("./http").Client;
// freenas properties
const FREENAS_NFS_SHARE_PROPERTY_NAME = "democratic-csi:freenas_nfs_share_id";
const FREENAS_ISCSI_TARGET_ID_PROPERTY_NAME =
"democratic-csi:freenas_iscsi_target_id";
const FREENAS_ISCSI_EXTENT_ID_PROPERTY_NAME =
"democratic-csi:freenas_iscsi_extent_id";
const FREENAS_ISCSI_TARGETTOEXTENT_ID_PROPERTY_NAME =
"democratic-csi:freenas_iscsi_targettoextent_id";
class FreeNASDriver extends ControllerZfsSshBaseDriver {
/**
* cannot make this a storage class parameter as storage class/etc context is *not* sent
* into various calls such as GetControllerCapabilities etc
*/
getDriverZfsResourceType() {
switch (this.options.driver) {
case "freenas-nfs":
return "filesystem";
case "freenas-iscsi":
return "volume";
default:
throw new Error("unknown driver: " + this.ctx.args.driver);
}
}
getHttpClient() {
const client = new HttpClient(this.options.httpConnection);
client.logger = this.ctx.logger;
return client;
}
getDriverShareType() {
switch (this.options.driver) {
case "freenas-nfs":
return "nfs";
case "freenas-iscsi":
return "iscsi";
default:
throw new Error("unknown driver: " + this.ctx.args.driver);
}
}
async findResourceByProperties(endpoint, match) {
if (!match || Object.keys(match).length < 1) {
return;
}
const httpClient = this.getHttpClient();
let target;
let page = 0;
// loop and find target
let queryParams = {};
// TODO: relax this using getSystemVersion perhaps
// https://jira.ixsystems.com/browse/NAS-103916
if (httpClient.getApiVersion() == 1) {
queryParams.limit = 100;
queryParams.offset = 0;
}
while (!target) {
//Content-Range: items 0-2/3 (full set)
//Content-Range: items 0--1/3 (invalid offset)
if (queryParams.hasOwnProperty("offset")) {
queryParams.offset = queryParams.limit * page;
}
let response = await httpClient.get(endpoint, queryParams);
if (response.statusCode == 200) {
if (response.body.length < 1) {
break;
}
response.body.some(i => {
let isMatch = true;
for (let property in match) {
if (match[property] != i[property]) {
isMatch = false;
break;
}
}
if (isMatch) {
target = i;
return true;
}
return false;
});
} else {
throw new Error(
"FreeNAS http error - code: " +
response.statusCode +
" body: " +
JSON.stringify(response.body)
);
}
page++;
}
return target;
}
/**
* should create any necessary share resources
* should set the SHARE_VOLUME_CONTEXT_PROPERTY_NAME propery
*
* @param {*} datasetName
*/
async createShare(call, datasetName) {
const driverShareType = this.getDriverShareType();
const httpClient = this.getHttpClient();
const apiVersion = httpClient.getApiVersion();
const zb = this.getZetabyte();
let properties;
let response;
let share = {};
switch (driverShareType) {
case "nfs":
properties = await zb.zfs.get(datasetName, [
"mountpoint",
FREENAS_NFS_SHARE_PROPERTY_NAME
]);
properties = properties[datasetName];
this.ctx.logger.debug("zfs props data: %j", properties);
// create nfs share
if (
!zb.helpers.isPropertyValueSet(
properties[FREENAS_NFS_SHARE_PROPERTY_NAME].value
)
) {
switch (apiVersion) {
case 1:
case 2:
switch (apiVersion) {
case 1:
share = {
nfs_paths: [properties.mountpoint.value],
nfs_comment: `democratic-csi (${this.ctx.args.csiName}): ${datasetName}`,
nfs_network: this.options.nfs.shareAllowedNetworks.join(
","
),
nfs_hosts: this.options.nfs.shareAllowedHosts.join(","),
nfs_alldirs: this.options.nfs.shareAlldirs,
nfs_ro: false,
nfs_quiet: false,
nfs_maproot_user: this.options.nfs.shareMaprootUser,
nfs_maproot_group: this.options.nfs.shareMaprootGroup,
nfs_mapall_user: this.options.nfs.shareMapallUser,
nfs_mapall_group: this.options.nfs.shareMapallGroup,
nfs_security: []
};
break;
case 2:
share = {
paths: [properties.mountpoint.value],
comment: `democratic-csi (${this.ctx.args.csiName}): ${datasetName}`,
networks: this.options.nfs.shareAllowedNetworks,
hosts: this.options.nfs.shareAllowedHosts,
alldirs: this.options.nfs.shareAlldirs,
ro: false,
quiet: false,
maproot_user: this.options.nfs.shareMaprootUser,
maproot_group: this.options.nfs.shareMaprootGroup,
mapall_user: this.options.nfs.shareMapallUser,
mapall_group: this.options.nfs.shareMapallGroup,
security: []
};
break;
}
response = await httpClient.post("/sharing/nfs", share);
/**
* v1 = 201
* v2 = 200
*/
if ([200, 201].includes(response.statusCode)) {
//set zfs property
await zb.zfs.set(datasetName, {
[FREENAS_NFS_SHARE_PROPERTY_NAME]: response.body.id
});
} else {
/**
* v1 = 409
* v2 = 422
*/
if (
[409, 422].includes(response.statusCode) &&
JSON.stringify(response.body).includes(
"You can't share same filesystem with all hosts twice."
)
) {
// move along
} else {
throw new GrpcError(
grpc.status.UNKNOWN,
`received error creating nfs share - code: ${response.statusCode} body: ${response.body}`
);
}
}
let volume_context = {
node_attach_driver: "nfs",
server: this.options.nfs.shareHost,
share: properties.mountpoint.value
};
return volume_context;
default:
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`invalid configuration: unknown apiVersion ${apiVersion}`
);
}
} else {
let volume_context = {
node_attach_driver: "nfs",
server: this.options.nfs.shareHost,
share: properties.mountpoint.value
};
return volume_context;
}
break;
case "iscsi":
properties = await zb.zfs.get(datasetName, [
FREENAS_ISCSI_TARGET_ID_PROPERTY_NAME,
FREENAS_ISCSI_EXTENT_ID_PROPERTY_NAME,
FREENAS_ISCSI_TARGETTOEXTENT_ID_PROPERTY_NAME
]);
properties = properties[datasetName];
this.ctx.logger.debug("zfs props data: %j", properties);
let basename;
let iscsiName = zb.helpers.extractLeafName(datasetName);
if (this.options.iscsi.namePrefix) {
iscsiName = this.options.iscsi.namePrefix + iscsiName;
}
if (this.options.iscsi.nameSuffix) {
iscsiName += this.options.iscsi.nameSuffix;
}
iscsiName = iscsiName.toLowerCase();
let extentDiskName = "zvol/" + datasetName;
/**
* limit is a FreeBSD limitation
* https://www.ixsystems.com/documentation/freenas/11.2-U5/storage.html#zfs-zvol-config-opts-tab
*/
if (extentDiskName.length > 63) {
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`extent disk name cannot exceed 63 characters: ${extentDiskName}`
);
}
this.ctx.logger.info(
"FreeNAS creating iscsi assets with name: " + iscsiName
);
const extentInsecureTpc = this.options.iscsi.hasOwnProperty(
"extentInsecureTpc"
)
? this.options.iscsi.extentInsecureTpc
: true;
const extentXenCompat = this.options.iscsi.hasOwnProperty(
"extentXenCompat"
)
? this.options.iscsi.extentXenCompat
: false;
const extentBlocksize = this.options.iscsi.hasOwnProperty(
"extentBlocksize"
)
? this.options.iscsi.extentBlocksize
: 512;
const extentDisablePhysicalBlocksize = this.options.iscsi.hasOwnProperty(
"extentDisablePhysicalBlocksize"
)
? this.options.iscsi.extentDisablePhysicalBlocksize
: true;
const extentRpm = this.options.iscsi.hasOwnProperty("extentRpm")
? this.options.iscsi.extentRpm
: "SSD";
let extentAvailThreshold = this.options.iscsi.hasOwnProperty(
"extentAvailThreshold"
)
? Number(this.options.iscsi.extentAvailThreshold)
: null;
if (!(extentAvailThreshold > 0 && extentAvailThreshold <= 100)) {
extentAvailThreshold = null;
}
switch (apiVersion) {
case 1: {
response = await httpClient.get(
"/services/iscsi/globalconfiguration"
);
if (response.statusCode != 200) {
throw new GrpcError(
grpc.status.UNKNOWN,
`error getting iscsi configuration - code: ${
response.statusCode
} body: ${JSON.stringify(response.body)}`
);
}
basename = response.body.iscsi_basename;
this.ctx.logger.verbose("FreeNAS ISCSI BASENAME: " + basename);
// create target
let target = {
iscsi_target_name: iscsiName,
iscsi_target_alias: ""
};
response = await httpClient.post("/services/iscsi/target", target);
// 409 if invalid
if (response.statusCode != 201) {
target = null;
if (
response.statusCode == 409 &&
JSON.stringify(response.body).includes(
"Target name already exists"
)
) {
target = await this.findResourceByProperties(
"/services/iscsi/target",
{
iscsi_target_name: iscsiName
}
);
} else {
throw new GrpcError(
grpc.status.UNKNOWN,
`received error creating iscsi target - code: ${
response.statusCode
} body: ${JSON.stringify(response.body)}`
);
}
} else {
target = response.body;
}
if (!target) {
throw new GrpcError(
grpc.status.UNKNOWN,
`unknown error creating iscsi target`
);
}
this.ctx.logger.verbose("FreeNAS ISCSI TARGET: %j", target);
// set target.id on zvol
await zb.zfs.set(datasetName, {
[FREENAS_ISCSI_TARGET_ID_PROPERTY_NAME]: target.id
});
// create targetgroup(s)
// targetgroups do have IDs
for (let targetGroupConfig of this.options.iscsi.targetGroups) {
let targetGroup = {
iscsi_target: target.id,
iscsi_target_authgroup: targetGroupConfig.targetGroupAuthGroup,
iscsi_target_authtype: targetGroupConfig.targetGroupAuthType
? targetGroupConfig.targetGroupAuthType
: "None",
iscsi_target_portalgroup:
targetGroupConfig.targetGroupPortalGroup,
iscsi_target_initiatorgroup:
targetGroupConfig.targetGroupInitiatorGroup,
iscsi_target_initialdigest: "Auto"
};
response = await httpClient.post(
"/services/iscsi/targetgroup",
targetGroup
);
// 409 if invalid
if (response.statusCode != 201) {
targetGroup = null;
/**
* 404 gets returned with an unable to process response when the DB is corrupted (has invalid entries in essense)
*
* To resolve properly the DB should be cleaned up
* /usr/local/etc/rc.d/django stop
* /usr/local/etc/rc.d/nginx stop
* sqlite3 /data/freenas-v1.db
*
* // this deletes everything, probably not what you want
* // should have a better query to only find entries where associated assets no longer exist
* DELETE from services_iscsitargetgroups;
*
* /usr/local/etc/rc.d/django restart
* /usr/local/etc/rc.d/nginx restart
*/
if (
response.statusCode == 404 ||
(response.statusCode == 409 &&
JSON.stringify(response.body).includes(
"cannot be duplicated on a target"
))
) {
targetGroup = await this.findResourceByProperties(
"/services/iscsi/targetgroup",
{
iscsi_target: target.id,
iscsi_target_portalgroup:
targetGroupConfig.targetGroupPortalGroup,
iscsi_target_initiatorgroup:
targetGroupConfig.targetGroupInitiatorGroup
}
);
} else {
throw new GrpcError(
grpc.status.UNKNOWN,
`received error creating iscsi targetgroup - code: ${
response.statusCode
} body: ${JSON.stringify(response.body)}`
);
}
} else {
targetGroup = response.body;
}
if (!targetGroup) {
throw new GrpcError(
grpc.status.UNKNOWN,
`unknown error creating iscsi targetgroup`
);
}
this.ctx.logger.verbose(
"FreeNAS ISCSI TARGET_GROUP: %j",
targetGroup
);
}
let extent = {
iscsi_target_extent_comment: "",
iscsi_target_extent_type: "Disk", // Disk/File, after save Disk becomes "ZVOL"
iscsi_target_extent_name: iscsiName,
iscsi_target_extent_insecure_tpc: extentInsecureTpc,
//iscsi_target_extent_naa: "0x3822690834aae6c5",
iscsi_target_extent_disk: extentDiskName,
iscsi_target_extent_xen: extentXenCompat,
iscsi_target_extent_avail_threshold: extentAvailThreshold,
iscsi_target_extent_blocksize: Number(extentBlocksize),
iscsi_target_extent_pblocksize: extentDisablePhysicalBlocksize,
iscsi_target_extent_rpm: isNaN(Number(extentRpm))
? "SSD"
: Number(extentRpm),
iscsi_target_extent_ro: false
};
response = await httpClient.post("/services/iscsi/extent", extent);
// 409 if invalid
if (response.statusCode != 201) {
extent = null;
if (
response.statusCode == 409 &&
JSON.stringify(response.body).includes(
"Extent name must be unique"
)
) {
extent = await this.findResourceByProperties(
"/services/iscsi/extent",
{ iscsi_target_extent_name: iscsiName }
);
} else {
throw new GrpcError(
grpc.status.UNKNOWN,
`received error creating iscsi extent - code: ${
response.statusCode
} body: ${JSON.stringify(response.body)}`
);
}
} else {
extent = response.body;
}
if (!extent) {
throw new GrpcError(
grpc.status.UNKNOWN,
`unknown error creating iscsi extent`
);
}
this.ctx.logger.verbose("FreeNAS ISCSI EXTENT: %j", extent);
await zb.zfs.set(datasetName, {
[FREENAS_ISCSI_EXTENT_ID_PROPERTY_NAME]: extent.id
});
// create targettoextent
let targetToExtent = {
iscsi_target: target.id,
iscsi_extent: extent.id,
iscsi_lunid: 0
};
response = await httpClient.post(
"/services/iscsi/targettoextent",
targetToExtent
);
// 409 if invalid
if (response.statusCode != 201) {
targetToExtent = null;
// LUN ID is already being used for this target.
// Extent is already in this target.
if (
response.statusCode == 409 &&
JSON.stringify(response.body).includes(
"Extent is already in this target."
) &&
JSON.stringify(response.body).includes(
"LUN ID is already being used for this target."
)
) {
targetToExtent = await this.findResourceByProperties(
"/services/iscsi/targettoextent",
{
iscsi_target: target.id,
iscsi_extent: extent.id,
iscsi_lunid: 0
}
);
} else {
throw new GrpcError(
grpc.status.UNKNOWN,
`received error creating iscsi targettoextent - code: ${
response.statusCode
} body: ${JSON.stringify(response.body)}`
);
}
} else {
targetToExtent = response.body;
}
if (!targetToExtent) {
throw new GrpcError(
grpc.status.UNKNOWN,
`unknown error creating iscsi targettoextent`
);
}
this.ctx.logger.verbose(
"FreeNAS ISCSI TARGET_TO_EXTENT: %j",
targetToExtent
);
await zb.zfs.set(datasetName, {
[FREENAS_ISCSI_TARGETTOEXTENT_ID_PROPERTY_NAME]: targetToExtent.id
});
break;
}
case 2:
response = await httpClient.get("/iscsi/global");
if (response.statusCode != 200) {
throw new GrpcError(
grpc.status.UNKNOWN,
`error getting iscsi configuration - code: ${
response.statusCode
} body: ${JSON.stringify(response.body)}`
);
}
basename = response.body.basename;
this.ctx.logger.verbose("FreeNAS ISCSI BASENAME: " + basename);
// create target and targetgroup
//let targetId;
let targetGroups = [];
for (let targetGroupConfig of this.options.iscsi.targetGroups) {
targetGroups.push({
portal: targetGroupConfig.targetGroupPortalGroup,
initiator: targetGroupConfig.targetGroupInitiatorGroup,
auth:
targetGroupConfig.targetGroupAuthGroup > 0
? targetGroupConfig.targetGroupAuthGroup
: null,
authmethod:
targetGroupConfig.targetGroupAuthType.length > 0
? targetGroupConfig.targetGroupAuthType
.toUpperCase()
.replace(" ", "_")
: "NONE"
});
}
let target = {
name: iscsiName,
alias: null, // cannot send "" error: handler error - driver: FreeNASDriver method: CreateVolume error: {"name":"GrpcError","code":2,"message":"received error creating iscsi target - code: 422 body: {\"iscsi_target_create.alias\":[{\"message\":\"Alias already exists\",\"errno\":22}]}"}
mode: "ISCSI",
groups: targetGroups
};
response = await httpClient.post("/iscsi/target", target);
// 409 if invalid
if (response.statusCode != 200) {
target = null;
if (
response.statusCode == 422 &&
JSON.stringify(response.body).includes(
"Target name already exists"
)
) {
target = await this.findResourceByProperties("/iscsi/target", {
name: iscsiName
});
} else {
throw new GrpcError(
grpc.status.UNKNOWN,
`received error creating iscsi target - code: ${
response.statusCode
} body: ${JSON.stringify(response.body)}`
);
}
} else {
target = response.body;
}
if (!target) {
throw new GrpcError(
grpc.status.UNKNOWN,
`unknown error creating iscsi target`
);
}
this.ctx.logger.verbose("FreeNAS ISCSI TARGET: %j", target);
// set target.id on zvol
await zb.zfs.set(datasetName, {
[FREENAS_ISCSI_TARGET_ID_PROPERTY_NAME]: target.id
});
let extent = {
comment: "",
type: "DISK", // Disk/File, after save Disk becomes "ZVOL"
name: iscsiName,
//iscsi_target_extent_naa: "0x3822690834aae6c5",
disk: extentDiskName,
insecure_tpc: extentInsecureTpc,
xen: extentXenCompat,
avail_threshold: extentAvailThreshold,
blocksize: Number(extentBlocksize),
pblocksize: extentDisablePhysicalBlocksize,
rpm: "" + extentRpm, // should be a string
ro: false
};
response = await httpClient.post("/iscsi/extent", extent);
// 409 if invalid
if (response.statusCode != 200) {
extent = null;
if (
response.statusCode == 422 &&
JSON.stringify(response.body).includes(
"Extent name must be unique"
)
) {
extent = await this.findResourceByProperties("/iscsi/extent", {
name: iscsiName
});
} else {
throw new GrpcError(
grpc.status.UNKNOWN,
`received error creating iscsi extent - code: ${
response.statusCode
} body: ${JSON.stringify(response.body)}`
);
}
} else {
extent = response.body;
}
if (!extent) {
throw new GrpcError(
grpc.status.UNKNOWN,
`unknown error creating iscsi extent`
);
}
this.ctx.logger.verbose("FreeNAS ISCSI EXTENT: %j", extent);
await zb.zfs.set(datasetName, {
[FREENAS_ISCSI_EXTENT_ID_PROPERTY_NAME]: extent.id
});
// create targettoextent
let targetToExtent = {
target: target.id,
extent: extent.id,
lunid: 0
};
response = await httpClient.post(
"/iscsi/targetextent",
targetToExtent
);
if (response.statusCode != 200) {
targetToExtent = null;
// LUN ID is already being used for this target.
// Extent is already in this target.
if (
response.statusCode == 422 &&
JSON.stringify(response.body).includes(
"Extent is already in this target."
) &&
JSON.stringify(response.body).includes(
"LUN ID is already being used for this target."
)
) {
targetToExtent = await this.findResourceByProperties(
"/iscsi/targetextent",
{
target: target.id,
extent: extent.id,
lunid: 0
}
);
} else {
throw new GrpcError(
grpc.status.UNKNOWN,
`received error creating iscsi targetextent - code: ${
response.statusCode
} body: ${JSON.stringify(response.body)}`
);
}
} else {
targetToExtent = response.body;
}
if (!targetToExtent) {
throw new GrpcError(
grpc.status.UNKNOWN,
`unknown error creating iscsi targetextent`
);
}
this.ctx.logger.verbose(
"FreeNAS ISCSI TARGET_TO_EXTENT: %j",
targetToExtent
);
await zb.zfs.set(datasetName, {
[FREENAS_ISCSI_TARGETTOEXTENT_ID_PROPERTY_NAME]: targetToExtent.id
});
break;
default:
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`invalid configuration: unknown apiVersion ${apiVersion}`
);
}
// iqn = target
let iqn = basename + ":" + iscsiName;
this.ctx.logger.info("FreeNAS iqn: " + iqn);
// iscsiadm -m discovery -t st -p 172.21.26.81
// iscsiadm -m node -T iqn.2011-03.lan.bitness.istgt:test -p bitness.lan -l
// FROM driver config? no, node attachment should have everything required to remain independent
// portal
// portals
// interface
// chap discovery
// chap session
// FROM context
// iqn
// lun
let volume_context = {
node_attach_driver: "iscsi",
portal: this.options.iscsi.targetPortal,
portals: this.options.iscsi.targetPortals.join(","),
interface: this.options.iscsi.interface || "",
//chapDiscoveryEnabled: this.options.iscsi.chapDiscoveryEnabled,
//chapSessionEnabled: this.options.iscsi.chapSessionEnabled,
iqn: iqn,
lun: 0
};
return volume_context;
default:
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`invalid configuration: unknown driverShareType ${driverShareType}`
);
}
}
async deleteShare(call, datasetName) {
const driverShareType = this.getDriverShareType();
const httpClient = this.getHttpClient();
const apiVersion = httpClient.getApiVersion();
const zb = this.getZetabyte();
let properties;
let response;
let endpoint;
switch (driverShareType) {
case "nfs":
try {
properties = await zb.zfs.get(datasetName, [
FREENAS_NFS_SHARE_PROPERTY_NAME
]);
} catch (err) {
if (err.toString().includes("dataset does not exist")) {
return;
}
throw err;
}
properties = properties[datasetName];
this.ctx.logger.debug("zfs props data: %j", properties);
let shareId = properties[FREENAS_NFS_SHARE_PROPERTY_NAME].value;
// remove nfs share
if (
properties &&
properties[FREENAS_NFS_SHARE_PROPERTY_NAME] &&
properties[FREENAS_NFS_SHARE_PROPERTY_NAME].value != "-"
) {
switch (apiVersion) {
case 1:
case 2:
endpoint = "/sharing/nfs/";
if (apiVersion == 2) {
endpoint += "id/";
}
endpoint += shareId;
response = await httpClient.get(endpoint);
// assume share is gone for now
if ([404, 500].includes(response.statusCode)) {
} else {
response = await httpClient.delete(endpoint);
// returns a 500 if does not exist
// v1 = 204
// v2 = 200
if (![200, 204].includes(response.statusCode)) {
throw new GrpcError(
grpc.status.UNKNOWN,
`received error deleting nfs share - share: ${shareId} code: ${
response.statusCode
} body: ${JSON.stringify(response.body)}`
);
}
}
break;
default:
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`invalid configuration: unknown apiVersion ${apiVersion}`
);
}
}
break;
case "iscsi":
// Delete target
// NOTE: deletting a target inherently deletes associated targetgroup(s) and targettoextent(s)
// Delete extent
try {
properties = await zb.zfs.get(datasetName, [
FREENAS_ISCSI_TARGET_ID_PROPERTY_NAME,
FREENAS_ISCSI_EXTENT_ID_PROPERTY_NAME,
FREENAS_ISCSI_TARGETTOEXTENT_ID_PROPERTY_NAME
]);
} catch (err) {
if (err.toString().includes("dataset does not exist")) {
return;
}
throw err;
}
properties = properties[datasetName];
this.ctx.logger.debug("zfs props data: %j", properties);
let targetId = properties[FREENAS_ISCSI_TARGET_ID_PROPERTY_NAME].value;
let extentId = properties[FREENAS_ISCSI_EXTENT_ID_PROPERTY_NAME].value;
switch (apiVersion) {
case 1:
case 2:
// https://jira.ixsystems.com/browse/NAS-103952
// v1 - /services/iscsi/target/{id}/
// v2 - /iscsi/target/id/{id}
endpoint = "";
if (apiVersion == 1) {
endpoint += "/services";
}
endpoint += "/iscsi/target/";
if (apiVersion == 2) {
endpoint += "id/";
}
endpoint += targetId;
response = await httpClient.get(endpoint);
// assume is gone for now
if ([404, 500].includes(response.statusCode)) {
} else {
response = await httpClient.delete(endpoint);
if (![200, 204].includes(response.statusCode)) {
throw new GrpcError(
grpc.status.UNKNOWN,
`received error deleting iscsi target - extent: ${targetId} code: ${
response.statusCode
} body: ${JSON.stringify(response.body)}`
);
}
}
// v1 - /services/iscsi/targettoextent/{id}/
// v2 - /iscsi/targetextent/id/{id}
if (apiVersion == 1) {
endpoint = "/services/iscsi/extent/";
} else {
endpoint = "/iscsi/extent/id/";
}
endpoint += extentId;
response = await httpClient.get(endpoint);
// assume is gone for now
if ([404, 500].includes(response.statusCode)) {
} else {
response = await httpClient.delete(endpoint);
if (![200, 204].includes(response.statusCode)) {
throw new GrpcError(
grpc.status.UNKNOWN,
`received error deleting iscsi extent - extent: ${extentId} code: ${
response.statusCode
} body: ${JSON.stringify(response.body)}`
);
}
}
break;
default:
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`invalid configuration: unknown apiVersion ${apiVersion}`
);
}
break;
default:
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`invalid configuration: unknown driverShareType ${driverShareType}`
);
}
}
async expandVolume(call, datasetName) {
const driverShareType = this.getDriverShareType();
const sshClient = this.getSshClient();
switch (driverShareType) {
case "iscsi":
this.ctx.logger.verbose("FreeNAS reloading ctld");
await sshClient.exec(
sshClient.buildCommand("/etc/rc.d/ctld", ["reload"])
);
break;
}
}
async getApiVersion() {
const systemVersion = await this.getSystemVersion();
return 1;
}
async getSystemVersion() {
const httpClient = this.getHttpClient();
const endpoint = "/system/version/";
let response;
const startApiVersion = httpClient.getApiVersion();
const versionInfo = {};
httpClient.setApiVersion(2);
/**
* FreeNAS-11.2-U5
*/
try {
response = await httpClient.get(endpoint);
if (response.statusCode == 200) {
versionInfo.v2 = response.body;
}
} catch (e) {}
httpClient.setApiVersion(1);
/**
* {"fullversion": "FreeNAS-9.3-STABLE-201503200528", "name": "FreeNAS", "version": "9.3"}
* {"fullversion": "FreeNAS-11.2-U5 (c129415c52)", "name": "FreeNAS", "version": ""}
*/
try {
response = await httpClient.get(endpoint);
if (response.statusCode == 200) {
versionInfo.v1 = response.body;
}
} catch (e) {}
// reset apiVersion
httpClient.setApiVersion(startApiVersion);
return versionInfo;
}
}
module.exports.FreeNASDriver = FreeNASDriver;