further truenas 25.10 support

Signed-off-by: Travis Glenn Hansen <travisghansen@yahoo.com>
This commit is contained in:
Travis Glenn Hansen 2025-10-29 19:31:56 -06:00
parent fc7ec358ab
commit 018de54685
3 changed files with 148 additions and 22 deletions

View File

@ -181,10 +181,7 @@ class FreeNASApiDriver extends CsiBaseDriver {
const httpApiClient = await this.getTrueNASHttpApiClient();
const apiVersion = httpClient.getApiVersion();
const zb = await this.getZetabyte();
const truenasVersion = semver.coerce(
await httpApiClient.getSystemVersionMajorMinor(),
{ loose: true }
);
const truenasVersion = await httpApiClient.getSystemVersionSemver();
if (!truenasVersion) {
throw new GrpcError(
@ -506,6 +503,40 @@ class FreeNASApiDriver extends CsiBaseDriver {
}
}
if (isScale && semver.satisfies(truenasVersion, ">=25.10")) {
let topLevelProperties = [
"purpose",
"name",
"path",
"enabled",
"comment",
"readonly",
"browsable",
"access_based_share_enumeration",
"audit",
];
let disallowedOptions = ["abe"];
share.purpose = "LEGACY_SHARE";
share.options = {
purpose: "LEGACY_SHARE",
};
for (const key in share) {
switch (key) {
case "options":
// ignore
break;
default:
if (!topLevelProperties.includes(key)) {
if (!disallowedOptions.includes(key)) {
share.options[key] = share[key];
}
delete share[key];
}
break;
}
}
}
switch (apiVersion) {
case 1:
endpoint = "/sharing/cifs";
@ -2819,13 +2850,13 @@ class FreeNASApiDriver extends CsiBaseDriver {
// set quota
if (this.options.zfs.datasetEnableQuotas) {
setProps = true;
properties.refquota = capacity_bytes;
properties.refquota = Number(capacity_bytes);
}
// set reserve
if (this.options.zfs.datasetEnableReservation) {
setProps = true;
properties.refreservation = capacity_bytes;
properties.refreservation = Number(capacity_bytes);
}
// quota for dataset and all children
@ -2933,7 +2964,7 @@ class FreeNASApiDriver extends CsiBaseDriver {
// this should be already set, but when coming from a volume source
// it may not match that of the source
properties.volsize = capacity_bytes;
properties.volsize = Number(capacity_bytes);
// dedup
// on, off, verify
@ -3221,17 +3252,17 @@ class FreeNASApiDriver extends CsiBaseDriver {
// set quota
if (this.options.zfs.datasetEnableQuotas) {
setProps = true;
properties.refquota = capacity_bytes;
properties.refquota = Number(capacity_bytes);
}
// set reserve
if (this.options.zfs.datasetEnableReservation) {
setProps = true;
properties.refreservation = capacity_bytes;
properties.refreservation = Number(capacity_bytes);
}
break;
case "volume":
properties.volsize = capacity_bytes;
properties.volsize = Number(capacity_bytes);
setProps = true;
// managed automatically for zvols
@ -3532,6 +3563,7 @@ class FreeNASApiDriver extends CsiBaseDriver {
const httpClient = await this.getHttpClient();
const httpApiClient = await this.getTrueNASHttpApiClient();
const zb = await this.getZetabyte();
const truenasVersion = await httpApiClient.getSystemVersionSemver();
let entries = [];
let entries_length = 0;
@ -3693,11 +3725,19 @@ class FreeNASApiDriver extends CsiBaseDriver {
response = await httpClient.get(endpoint, {
"extra.snapshots": 1,
"extra.snapshots_properties": JSON.stringify(zfsProperties),
//"extra.snapshots_properties": "null",
});
if (response.statusCode == 404) {
throw new Error("dataset does not exist");
} else if (response.statusCode == 200) {
for (let snapshot of response.body.snapshots) {
if (semver.satisfies(truenasVersion, ">=25.10")) {
// request the snapshot because fetching properties is broken with the dataset is broken
snapshot.properties = await httpApiClient.SnapshotGet(
snapshot.id,
zfsProperties
);
}
let row = {};
for (let p in snapshot.properties) {
row[p] = snapshot.properties[p].rawvalue;
@ -3716,12 +3756,20 @@ class FreeNASApiDriver extends CsiBaseDriver {
response = await httpClient.get(endpoint, {
"extra.snapshots": 1,
"extra.snapshots_properties": JSON.stringify(zfsProperties),
//"extra.snapshots_properties": "null",
});
if (response.statusCode == 404) {
throw new Error("dataset does not exist");
} else if (response.statusCode == 200) {
for (let child of response.body.children) {
for (let snapshot of child.snapshots) {
if (semver.satisfies(truenasVersion, ">=25.10")) {
// request the snapshot because fetching properties is broken with the dataset is broken
snapshot.properties = await httpApiClient.SnapshotGet(
snapshot.id,
zfsProperties
);
}
let row = {};
for (let p in snapshot.properties) {
row[p] = snapshot.properties[p].rawvalue;

View File

@ -1,4 +1,5 @@
const _ = require("lodash");
const semver = require("semver");
const { sleep, stringify } = require("../../../utils/general");
const { Zetabyte } = require("../../../utils/zfs");
const { Registry } = require("../../../utils/registry");
@ -183,6 +184,12 @@ class Api {
return majorMinor.split(".")[0];
}
async getSystemVersionSemver() {
return semver.coerce(await this.getSystemVersionMajorMinor(), {
loose: true,
});
}
async setVersionInfoCache(versionInfo) {
await this.cache.set(FREENAS_SYSTEM_VERSION_CACHE_KEY, versionInfo, {
ttl: 60 * 1000,
@ -250,7 +257,7 @@ class Api {
let user_properties = {};
for (const property in properties) {
if (this.getIsUserProperty(property)) {
user_properties[property] = properties[property];
user_properties[property] = String(properties[property]);
}
}
@ -271,7 +278,15 @@ class Api {
getPropertiesKeyValueArray(properties) {
let arr = [];
for (const property in properties) {
arr.push({ key: property, value: properties[property] });
let value = properties[property];
if (
this.getIsUserProperty(property) &&
value != null &&
value !== undefined
) {
value = String(value);
}
arr.push({ key: property, value });
}
return arr;
@ -501,10 +516,17 @@ class Api {
async SnapshotSet(snapshotName, properties) {
const httpClient = await this.getHttpClient(false);
const systemVersionSemver = await this.getSystemVersionSemver();
let response;
let endpoint;
endpoint = `/zfs/snapshot/id/${encodeURIComponent(snapshotName)}`;
if (semver.satisfies(systemVersionSemver, ">=25.10")) {
endpoint = `/pool/snapshot/id/${encodeURIComponent(snapshotName)}`;
} else {
endpoint = `/zfs/snapshot/id/${encodeURIComponent(snapshotName)}`;
}
response = await httpClient.put(endpoint, {
//...this.getSystemProperties(properties),
user_properties_update: this.getPropertiesKeyValueArray(
@ -529,10 +551,17 @@ class Api {
*/
async SnapshotGet(snapshotName, properties) {
const httpClient = await this.getHttpClient(false);
const systemVersionSemver = await this.getSystemVersionSemver();
let response;
let endpoint;
endpoint = `/zfs/snapshot/id/${encodeURIComponent(snapshotName)}`;
if (semver.satisfies(systemVersionSemver, ">=25.10")) {
endpoint = `/pool/snapshot/id/${encodeURIComponent(snapshotName)}`;
} else {
endpoint = `/zfs/snapshot/id/${encodeURIComponent(snapshotName)}`;
}
response = await httpClient.get(endpoint);
if (response.statusCode == 200) {
@ -540,7 +569,7 @@ class Api {
}
if (response.statusCode == 404) {
throw new Error("dataset does not exist");
throw new Error("snapshot does not exist");
}
throw new Error(JSON.stringify(response.body));
@ -549,6 +578,7 @@ class Api {
async SnapshotCreate(snapshotName, data = {}) {
const httpClient = await this.getHttpClient(false);
const zb = await this.getZetabyte();
const systemVersionSemver = await this.getSystemVersionSemver();
let response;
let endpoint;
@ -559,7 +589,12 @@ class Api {
data.dataset = dataset;
data.name = snapshot;
endpoint = "/zfs/snapshot";
if (semver.satisfies(systemVersionSemver, ">=25.10")) {
endpoint = "/pool/snapshot";
} else {
endpoint = "/zfs/snapshot";
}
response = await httpClient.post(endpoint, data);
if (response.statusCode == 200) {
@ -579,11 +614,17 @@ class Api {
async SnapshotDelete(snapshotName, data = {}) {
const httpClient = await this.getHttpClient(false);
const zb = await this.getZetabyte();
const systemVersionSemver = await this.getSystemVersionSemver();
let response;
let endpoint;
endpoint = `/zfs/snapshot/id/${encodeURIComponent(snapshotName)}`;
if (semver.satisfies(systemVersionSemver, ">=25.10")) {
endpoint = `/pool/snapshot/id/${encodeURIComponent(snapshotName)}`;
} else {
endpoint = `/zfs/snapshot/id/${encodeURIComponent(snapshotName)}`;
}
response = await httpClient.delete(endpoint, data);
if (response.statusCode == 200) {
@ -607,6 +648,7 @@ class Api {
async CloneCreate(snapshotName, datasetName, data = {}) {
const httpClient = await this.getHttpClient(false);
const zb = await this.getZetabyte();
const systemVersionSemver = await this.getSystemVersionSemver();
let response;
let endpoint;
@ -614,7 +656,12 @@ class Api {
data.snapshot = snapshotName;
data.dataset_dst = datasetName;
endpoint = "/zfs/snapshot/clone";
if (semver.satisfies(systemVersionSemver, ">=25.10")) {
endpoint = "/pool/snapshot/clone";
} else {
endpoint = "/zfs/snapshot/clone";
}
response = await httpClient.post(endpoint, data);
if (response.statusCode == 200) {

View File

@ -269,10 +269,7 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver {
const httpApiClient = await this.getTrueNASHttpApiClient();
const apiVersion = httpClient.getApiVersion();
const zb = await this.getZetabyte();
const truenasVersion = semver.coerce(
await httpApiClient.getSystemVersionMajorMinor(),
{ loose: true }
);
const truenasVersion = await httpApiClient.getSystemVersionSemver();
if (!truenasVersion) {
throw new GrpcError(
@ -594,6 +591,40 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver {
}
}
if (isScale && semver.satisfies(truenasVersion, ">=25.10")) {
let topLevelProperties = [
"purpose",
"name",
"path",
"enabled",
"comment",
"readonly",
"browsable",
"access_based_share_enumeration",
"audit",
];
let disallowedOptions = ["abe"];
share.purpose = "LEGACY_SHARE";
share.options = {
purpose: "LEGACY_SHARE",
};
for (const key in share) {
switch (key) {
case "options":
// ignore
break;
default:
if (!topLevelProperties.includes(key)) {
if (!disallowedOptions.includes(key)) {
share.options[key] = share[key];
}
delete share[key];
}
break;
}
}
}
switch (apiVersion) {
case 1:
endpoint = "/sharing/cifs";