upgrade node version, objectivefs support, SCALE 24.04 support

Signed-off-by: Travis Glenn Hansen <travisghansen@yahoo.com>
This commit is contained in:
Travis Glenn Hansen 2024-02-24 23:37:55 -07:00
parent 79f16a0cf6
commit 15b120c3cc
17 changed files with 1555 additions and 136 deletions

View File

@ -115,38 +115,6 @@ jobs:
SYNOLOGY_PASSWORD: ${{ secrets.SANITY_SYNOLOGY_PASSWORD }}
SYNOLOGY_VOLUME: ${{ secrets.SANITY_SYNOLOGY_VOLUME }}
csi-sanity-truenas-scale-22_12:
needs:
- build-npm-linux-amd64
strategy:
fail-fast: false
matrix:
config:
- truenas/scale/22.12/scale-iscsi.yaml
- truenas/scale/22.12/scale-nfs.yaml
# 80 char limit
- truenas/scale/22.12/scale-smb.yaml
runs-on:
- self-hosted
- Linux
- X64
#- csi-sanity-truenas
- csi-sanity-zfs-generic
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
with:
name: node-modules-linux-amd64
- name: csi-sanity
run: |
# run tests
ci/bin/run.sh
env:
TEMPLATE_CONFIG_FILE: "./ci/configs/${{ matrix.config }}"
TRUENAS_HOST: ${{ secrets.SANITY_TRUENAS_SCALE_22_12_HOST }}
TRUENAS_USERNAME: ${{ secrets.SANITY_TRUENAS_USERNAME }}
TRUENAS_PASSWORD: ${{ secrets.SANITY_TRUENAS_PASSWORD }}
csi-sanity-truenas-scale-23_10:
needs:
- build-npm-linux-amd64
@ -179,6 +147,38 @@ jobs:
TRUENAS_USERNAME: ${{ secrets.SANITY_TRUENAS_USERNAME }}
TRUENAS_PASSWORD: ${{ secrets.SANITY_TRUENAS_PASSWORD }}
csi-sanity-truenas-scale-24_04:
needs:
- build-npm-linux-amd64
strategy:
fail-fast: false
matrix:
config:
- truenas/scale/24.04/scale-iscsi.yaml
- truenas/scale/24.04/scale-nfs.yaml
# 80 char limit
- truenas/scale/24.04/scale-smb.yaml
runs-on:
- self-hosted
- Linux
- X64
#- csi-sanity-truenas
- csi-sanity-zfs-generic
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
with:
name: node-modules-linux-amd64
- name: csi-sanity
run: |
# run tests
ci/bin/run.sh
env:
TEMPLATE_CONFIG_FILE: "./ci/configs/${{ matrix.config }}"
TRUENAS_HOST: ${{ secrets.SANITY_TRUENAS_SCALE_24_04_HOST }}
TRUENAS_USERNAME: ${{ secrets.SANITY_TRUENAS_USERNAME }}
TRUENAS_PASSWORD: ${{ secrets.SANITY_TRUENAS_PASSWORD }}
# ssh-based drivers
csi-sanity-truenas-core-13_0:
needs:
@ -244,6 +244,41 @@ jobs:
SERVER_USERNAME: ${{ secrets.SANITY_ZFS_GENERIC_USERNAME }}
SERVER_PASSWORD: ${{ secrets.SANITY_ZFS_GENERIC_PASSWORD }}
# client drivers
csi-sanity-objectivefs:
needs:
- build-npm-linux-amd64
strategy:
fail-fast: false
matrix:
config:
- objectivefs/objectivefs.yaml
runs-on:
- self-hosted
- Linux
- X64
- csi-sanity-client
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
with:
name: node-modules-linux-amd64
- name: csi-sanity
run: |
# run tests
ci/bin/run.sh
env:
TEMPLATE_CONFIG_FILE: "./ci/configs/${{ matrix.config }}"
OBJECTIVEFS_POOL: ${{ secrets.SANITY_OBJECTIVEFS_POOL }}
OBJECTIVEFS_LICENSE: ${{ secrets.SANITY_OBJECTIVEFS_LICENSE }}
OBJECTIVEFS_OBJECTSTORE: ${{ secrets.SANITY_OBJECTIVEFS_OBJECTSTORE }}
OBJECTIVEFS_ENDPOINT: ${{ secrets.SANITY_OBJECTIVEFS_ENDPOINT }}
OBJECTIVEFS_SECRET_KEY: ${{ secrets.SANITY_OBJECTIVEFS_SECRET_KEY }}
OBJECTIVEFS_ACCESS_KEY: ${{ secrets.SANITY_OBJECTIVEFS_ACCESS_KEY }}
OBJECTIVEFS_PASSPHRASE: ${{ secrets.SANITY_OBJECTIVEFS_PASSPHRASE }}
CSI_SANITY_SKIP: "should fail when requesting to create a snapshot with already existing name and different source volume ID|should fail when requesting to create a volume with already existing name and different capacity"
# client drivers
csi-sanity-client:
needs:
@ -425,10 +460,11 @@ jobs:
- determine-image-tag
- csi-sanity-synology-dsm6
- csi-sanity-synology-dsm7
- csi-sanity-truenas-scale-22_12
- csi-sanity-truenas-scale-23_10
- csi-sanity-truenas-scale-24_04
- csi-sanity-truenas-core-13_0
- csi-sanity-zfs-generic
- csi-sanity-objectivefs
- csi-sanity-client
- csi-sanity-client-windows
- csi-sanity-zfs-local
@ -464,10 +500,11 @@ jobs:
needs:
- csi-sanity-synology-dsm6
- csi-sanity-synology-dsm7
- csi-sanity-truenas-scale-22_12
- csi-sanity-truenas-scale-23_10
- csi-sanity-truenas-scale-24_04
- csi-sanity-truenas-core-13_0
- csi-sanity-zfs-generic
- csi-sanity-objectivefs
- csi-sanity-client
- csi-sanity-client-windows
- csi-sanity-zfs-local

View File

@ -12,7 +12,7 @@ RUN apt-get update && apt-get install -y locales && rm -rf /var/lib/apt/lists/*
&& localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8
ENV LANG=en_US.utf8
ENV NODE_VERSION=v16.18.0
ENV NODE_VERSION=v20.11.1
ENV NODE_ENV=production
# install build deps
@ -75,7 +75,7 @@ COPY --from=build /usr/local/lib/nodejs/bin/node /usr/local/bin/node
# netbase is required by rpcbind/rpcinfo to work properly
# /etc/{services,rpc} are required
RUN apt-get update && \
apt-get install -y netbase socat e2fsprogs exfatprogs xfsprogs btrfs-progs fatresize dosfstools ntfs-3g nfs-common cifs-utils fdisk gdisk cloud-guest-utils sudo rsync procps util-linux nvme-cli && \
apt-get install -y wget netbase socat e2fsprogs exfatprogs xfsprogs btrfs-progs fatresize dosfstools ntfs-3g nfs-common cifs-utils fdisk gdisk cloud-guest-utils sudo rsync procps util-linux nvme-cli fuse && \
rm -rf /var/lib/apt/lists/*
# controller requirements
@ -83,6 +83,11 @@ RUN apt-get update && \
# apt-get install -y ansible && \
# rm -rf /var/lib/apt/lists/*
# install objectivefs
ENV OBJECTIVEFS_VERSION=7.1
ADD docker/objectivefs-installer.sh /usr/local/sbin
RUN chmod +x /usr/local/sbin/objectivefs-installer.sh && objectivefs-installer.sh
# install wrappers
ADD docker/iscsiadm /usr/local/sbin
RUN chmod +x /usr/local/sbin/iscsiadm

View File

@ -397,10 +397,58 @@ logger.info(
bindSocket
);
[`SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`].forEach(
const signalMapping = {
1: "SIGHUP",
2: "SIGINT",
3: "SIGQUIT",
4: "SIGILL",
5: "SIGTRAP",
6: "SIGABRT",
7: "SIGEMT",
8: "SIGFPE",
9: "SIGKILL",
10: "SIGBUS",
11: "SIGSEGV",
12: "SIGSYS",
13: "SIGPIPE",
14: "SIGALRM",
15: "SIGTERM",
16: "SIGURG",
17: "SIGSTOP",
18: "SIGTSTP",
19: "SIGCONT",
20: "SIGCHLD",
21: "SIGTTIN",
22: "SIGTTOU",
23: "SIGIO",
24: "SIGXCPU",
25: "SIGXFSZ",
26: "SIGVTALRM",
27: "SIGPROF",
28: "SIGWINCH",
29: "SIGINFO",
30: "SIGUSR1",
31: "SIGUSR2",
};
[(`SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`)].forEach(
(eventType) => {
process.on(eventType, async (code) => {
console.log(`running server shutdown, exit code: ${code}`);
let codeNumber = null;
let codeName = null;
if (code > 0) {
codeNumber = code;
codeName = signalMapping[code];
} else {
codeNumber = Object.keys(signalMapping).find(
(key) => signalMapping[key] === code
);
codeName = code;
}
console.log(
`running server shutdown, exit code: ${codeNumber} (${codeName})`
);
// attempt clean shutdown of in-flight requests
try {
@ -431,7 +479,7 @@ logger.info(
}
console.log("server fully shutdown, exiting");
process.exit(code);
process.exit(codeNumber);
});
}
);

View File

@ -0,0 +1,19 @@
driver: objectivefs
objectivefs:
pool: ${OBJECTIVEFS_POOL}
cli:
sudoEnabled: false
env:
OBJECTIVEFS_LICENSE: ${OBJECTIVEFS_LICENSE}
OBJECTSTORE: ${OBJECTIVEFS_OBJECTSTORE}
ENDPOINT: ${OBJECTIVEFS_ENDPOINT}
SECRET_KEY: ${OBJECTIVEFS_SECRET_KEY}
ACCESS_KEY: ${OBJECTIVEFS_ACCESS_KEY}
OBJECTIVEFS_PASSPHRASE: ${OBJECTIVEFS_PASSPHRASE}
_private:
csi:
volume:
idHash:
strategy: crc32

View File

@ -0,0 +1,38 @@
driver: freenas-api-iscsi
httpConnection:
protocol: http
host: ${TRUENAS_HOST}
port: 80
#apiKey:
username: ${TRUENAS_USERNAME}
password: ${TRUENAS_PASSWORD}
zfs:
datasetParentName: tank/ci/${CI_BUILD_KEY}/v
detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s
zvolCompression:
zvolDedup:
zvolEnableReservation: false
zvolBlocksize:
iscsi:
targetPortal: ${TRUENAS_HOST}
interface: ""
namePrefix: "csi-ci-${CI_BUILD_KEY}-"
nameSuffix: ""
targetGroups:
- targetGroupPortalGroup: 1
targetGroupInitiatorGroup: 1
targetGroupAuthType: None
targetGroupAuthGroup:
# 0-100 (0 == ignore)
extentAvailThreshold: 0
# https://github.com/SCST-project/scst/blob/master/scst/src/dev_handlers/scst_vdisk.c#L203
_private:
csi:
volume:
idHash:
strategy: crc16

View File

@ -0,0 +1,29 @@
driver: freenas-api-nfs
httpConnection:
protocol: http
host: ${TRUENAS_HOST}
port: 80
#apiKey:
username: ${TRUENAS_USERNAME}
password: ${TRUENAS_PASSWORD}
zfs:
datasetParentName: tank/ci/${CI_BUILD_KEY}/v
detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s
datasetEnableQuotas: true
datasetEnableReservation: false
datasetPermissionsMode: "0777"
datasetPermissionsUser: 0
datasetPermissionsGroup: 0
nfs:
shareHost: ${TRUENAS_HOST}
shareAlldirs: false
shareAllowedHosts: []
shareAllowedNetworks: []
shareMaprootUser: root
shareMaprootGroup: root
shareMapallUser: ""
shareMapallGroup: ""

View File

@ -0,0 +1,50 @@
driver: freenas-api-smb
httpConnection:
protocol: http
host: ${TRUENAS_HOST}
port: 80
#apiKey:
username: ${TRUENAS_USERNAME}
password: ${TRUENAS_PASSWORD}
zfs:
datasetParentName: tank/ci/${CI_BUILD_KEY}/v
detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s
datasetEnableQuotas: true
datasetEnableReservation: false
datasetPermissionsMode: "0770"
datasetPermissionsUser: 1001
datasetPermissionsGroup: 1001
smb:
shareHost: ${TRUENAS_HOST}
#nameTemplate: ""
namePrefix: "csi-ci-${CI_BUILD_KEY}-"
nameSuffix: ""
shareAuxiliaryConfigurationTemplate: |
#guest ok = yes
#guest only = yes
shareHome: false
shareAllowedHosts: []
shareDeniedHosts: []
#shareDefaultPermissions: true
shareGuestOk: false
#shareGuestOnly: true
#shareShowHiddenFiles: true
shareRecycleBin: false
shareBrowsable: false
shareAccessBasedEnumeration: true
shareTimeMachine: false
#shareStorageTask:
node:
mount:
mount_flags: "username=smbroot,password=smbroot"
_private:
csi:
volume:
idHash:
strategy: crc16

34
docker/objectivefs-installer.sh Executable file
View File

@ -0,0 +1,34 @@
#!/bin/bash
set -e
set -x
PLATFORM_TYPE=${1}
if [[ "${PLATFORM_TYPE}" == "build" ]]; then
PLATFORM=$BUILDPLATFORM
else
PLATFORM=$TARGETPLATFORM
fi
if [[ "x${PLATFORM}" == "x" ]]; then
PLATFORM="linux/amd64"
fi
# these come from the --platform option of buildx, indirectly from DOCKER_BUILD_PLATFORM in main.yaml
if [ "$PLATFORM" = "linux/amd64" ]; then
export OBJECTIVEFS_ARCH="amd64"
elif [ "$PLATFORM" = "linux/arm64" ]; then
export OBJECTIVEFS_ARCH="arm64"
else
echo "unsupported/unknown PLATFORM ${PLATFORM}"
fi
export DEB_FILE="objectivefs_${OBJECTIVEFS_VERSION}_${OBJECTIVEFS_ARCH}.deb"
echo "I am installing objectivefs $OBJECTIVEFS_VERSION"
wget "https://objectivefs.com/user/download/ac24htfht/${DEB_FILE}"
dpkg -i "${DEB_FILE}"
rm "${DEB_FILE}"

186
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "democratic-csi",
"version": "1.8.4",
"version": "1.9.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "democratic-csi",
"version": "1.8.4",
"version": "1.9.0",
"license": "MIT",
"dependencies": {
"@grpc/grpc-js": "^1.8.4",
@ -85,9 +85,9 @@
}
},
"node_modules/@eslint/eslintrc": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz",
"integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
"integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
@ -108,18 +108,18 @@
}
},
"node_modules/@eslint/js": {
"version": "8.53.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz",
"integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==",
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
"integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@grpc/grpc-js": {
"version": "1.9.9",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.9.tgz",
"integrity": "sha512-vQ1qwi/Kiyprt+uhb1+rHMpyk4CVRMTGNUGGPRGS7pLNfWkdCHrGEnT6T3/JyC2VZgoOX/X1KwdoU0WYQAeYcQ==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.1.tgz",
"integrity": "sha512-55ONqFytZExfOIjF1RjXPcVmT/jJqFzbbDqxK9jmRV4nxiYWtL9hENSW1Jfx0SdZfrvoqd44YJ/GJTqfRrawSQ==",
"dependencies": {
"@grpc/proto-loader": "^0.7.8",
"@types/node": ">=12.12.47"
@ -146,13 +146,13 @@
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.13",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
"integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"dev": true,
"dependencies": {
"@humanwhocodes/object-schema": "^2.0.1",
"debug": "^4.1.1",
"@humanwhocodes/object-schema": "^2.0.2",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
},
"engines": {
@ -173,9 +173,9 @@
}
},
"node_modules/@humanwhocodes/object-schema": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
"integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
"dev": true
},
"node_modules/@kubernetes/client-node": {
@ -205,9 +205,9 @@
}
},
"node_modules/@kubernetes/client-node/node_modules/@types/node": {
"version": "18.18.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.9.tgz",
"integrity": "sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ==",
"version": "18.19.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.18.tgz",
"integrity": "sha512-80CP7B8y4PzZF0GWx15/gVWRrB5y/bIjNI84NK3cmQJu0WZwvmj2WMA5LcofQFVfLqqCSp545+U2LsrVzX36Zg==",
"dependencies": {
"undici-types": "~5.26.4"
}
@ -312,9 +312,9 @@
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="
},
"node_modules/@types/node": {
"version": "20.9.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz",
"integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==",
"version": "20.11.20",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz",
"integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==",
"dependencies": {
"undici-types": "~5.26.4"
}
@ -341,9 +341,9 @@
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="
},
"node_modules/@types/ws": {
"version": "8.5.9",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz",
"integrity": "sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==",
"version": "8.5.10",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
"integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
"dependencies": {
"@types/node": "*"
}
@ -355,9 +355,9 @@
"dev": true
},
"node_modules/acorn": {
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
@ -439,9 +439,9 @@
"integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g=="
},
"node_modules/async-mutex": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz",
"integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==",
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.1.tgz",
"integrity": "sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA==",
"dependencies": {
"tslib": "^2.4.0"
}
@ -465,11 +465,11 @@
"integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg=="
},
"node_modules/axios": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz",
"integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==",
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
"integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
"dependencies": {
"follow-redirects": "^1.15.0",
"follow-redirects": "^1.15.4",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
@ -818,9 +818,9 @@
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
},
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
"engines": {
"node": ">=6"
}
@ -838,16 +838,16 @@
}
},
"node_modules/eslint": {
"version": "8.53.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz",
"integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==",
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.3",
"@eslint/js": "8.53.0",
"@humanwhocodes/config-array": "^0.11.13",
"@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.57.0",
"@humanwhocodes/config-array": "^0.11.14",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0",
@ -1017,9 +1017,9 @@
"dev": true
},
"node_modules/fastq": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
"integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
"dev": true,
"dependencies": {
"reusify": "^1.0.4"
@ -1059,9 +1059,9 @@
}
},
"node_modules/flat-cache": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz",
"integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
"integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
"dev": true,
"dependencies": {
"flatted": "^3.2.9",
@ -1069,13 +1069,13 @@
"rimraf": "^3.0.2"
},
"engines": {
"node": ">=12.0.0"
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/flatted": {
"version": "3.2.9",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz",
"integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
"dev": true
},
"node_modules/fn.name": {
@ -1084,9 +1084,9 @@
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
},
"node_modules/follow-redirects": {
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
"funding": [
{
"type": "individual",
@ -1124,9 +1124,9 @@
}
},
"node_modules/fs-extra": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz",
"integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==",
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
"integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
@ -1211,9 +1211,9 @@
}
},
"node_modules/globals": {
"version": "13.23.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz",
"integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==",
"version": "13.24.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
"integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
"dev": true,
"dependencies": {
"type-fest": "^0.20.2"
@ -1301,9 +1301,9 @@
}
},
"node_modules/ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
"dev": true,
"engines": {
"node": ">= 4"
@ -1688,9 +1688,9 @@
}
},
"node_modules/moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"optional": true,
"engines": {
"node": "*"
@ -1817,12 +1817,12 @@
}
},
"node_modules/openid-client": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.1.tgz",
"integrity": "sha512-PtrWsY+dXg6y8mtMPyL/namZSYVz8pjXz3yJiBNZsEdCnu9miHLB4ELVC85WvneMKo2Rg62Ay7NkuCpM0bgiLQ==",
"version": "5.6.4",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.4.tgz",
"integrity": "sha512-T1h3B10BRPKfcObdBklX639tVz+xh34O7GjofqrqiAQdm7eHsQ00ih18x6wuJ/E6FxdtS2u3FmUGPDeEcMwzNA==",
"optional": true,
"dependencies": {
"jose": "^4.15.1",
"jose": "^4.15.4",
"lru-cache": "^6.0.0",
"object-hash": "^2.2.0",
"oidc-token-hash": "^5.0.3"
@ -1982,9 +1982,9 @@
}
},
"node_modules/protobufjs": {
"version": "7.2.5",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz",
"integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==",
"version": "7.2.6",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz",
"integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==",
"hasInstallScript": true,
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
@ -2243,9 +2243,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dependencies": {
"lru-cache": "^6.0.0"
},
@ -2305,9 +2305,9 @@
}
},
"node_modules/ssh2": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.14.0.tgz",
"integrity": "sha512-AqzD1UCqit8tbOKoj6ztDDi1ffJZ2rV2SwlgrVVrHPkV5vWqGJOVp5pmtj18PunkPJAuKQsnInyKV+/Nb2bUnA==",
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.15.0.tgz",
"integrity": "sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==",
"hasInstallScript": true,
"dependencies": {
"asn1": "^0.2.6",
@ -2317,8 +2317,8 @@
"node": ">=10.16.0"
},
"optionalDependencies": {
"cpu-features": "~0.0.8",
"nan": "^2.17.0"
"cpu-features": "~0.0.9",
"nan": "^2.18.0"
}
},
"node_modules/sshpk": {
@ -2644,9 +2644,9 @@
}
},
"node_modules/winston-transport": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.6.0.tgz",
"integrity": "sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==",
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.0.tgz",
"integrity": "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==",
"dependencies": {
"logform": "^2.3.2",
"readable-stream": "^3.6.0",
@ -2691,9 +2691,9 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "8.14.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
"integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==",
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
"engines": {
"node": ">=10.0.0"
},

View File

@ -1,6 +1,6 @@
{
"name": "democratic-csi",
"version": "1.8.4",
"version": "1.9.0",
"description": "kubernetes csi driver framework",
"main": "bin/democratic-csi",
"scripts": {

View File

@ -0,0 +1,740 @@
const _ = require("lodash");
const { CsiBaseDriver } = require("../index");
const { GrpcError, grpc } = require("../../utils/grpc");
const GeneralUtils = require("../../utils/general");
const { ObjectiveFS } = require("../../utils/objectivefs");
const registry = require("../../utils/registry");
const semver = require("semver");
const uuidv4 = require("uuid").v4;
const __REGISTRY_NS__ = "ControllerZfsLocalDriver";
const MAX_VOLUME_NAME_LENGTH = 63;
class ControllerObjectiveFSDriver extends CsiBaseDriver {
constructor(ctx, options) {
super(...arguments);
options = options || {};
options.service = options.service || {};
options.service.identity = options.service.identity || {};
options.service.controller = options.service.controller || {};
options.service.node = options.service.node || {};
options.service.identity.capabilities =
options.service.identity.capabilities || {};
options.service.controller.capabilities =
options.service.controller.capabilities || {};
options.service.node.capabilities = options.service.node.capabilities || {};
if (!("service" in options.service.identity.capabilities)) {
this.ctx.logger.debug("setting default identity service caps");
options.service.identity.capabilities.service = [
//"UNKNOWN",
"CONTROLLER_SERVICE",
//"VOLUME_ACCESSIBILITY_CONSTRAINTS"
];
}
if (!("volume_expansion" in options.service.identity.capabilities)) {
this.ctx.logger.debug("setting default identity volume_expansion caps");
options.service.identity.capabilities.volume_expansion = [
//"UNKNOWN",
//"ONLINE",
//"OFFLINE"
];
}
if (!("rpc" in options.service.controller.capabilities)) {
this.ctx.logger.debug("setting default controller caps");
options.service.controller.capabilities.rpc = [
//"UNKNOWN",
"CREATE_DELETE_VOLUME",
//"PUBLISH_UNPUBLISH_VOLUME",
"LIST_VOLUMES",
//"GET_CAPACITY",
//"CREATE_DELETE_SNAPSHOT",
//"LIST_SNAPSHOTS",
//"CLONE_VOLUME",
//"PUBLISH_READONLY",
//"EXPAND_VOLUME",
];
if (semver.satisfies(this.ctx.csiVersion, ">=1.3.0")) {
options.service.controller.capabilities.rpc
.push
//"VOLUME_CONDITION",
//"GET_VOLUME"
();
}
if (semver.satisfies(this.ctx.csiVersion, ">=1.5.0")) {
options.service.controller.capabilities.rpc.push(
"SINGLE_NODE_MULTI_WRITER"
);
}
}
if (!("rpc" in options.service.node.capabilities)) {
this.ctx.logger.debug("setting default node caps");
options.service.node.capabilities.rpc = [
//"UNKNOWN",
"STAGE_UNSTAGE_VOLUME",
"GET_VOLUME_STATS",
//"EXPAND_VOLUME"
];
if (semver.satisfies(this.ctx.csiVersion, ">=1.3.0")) {
//options.service.node.capabilities.rpc.push("VOLUME_CONDITION");
}
if (semver.satisfies(this.ctx.csiVersion, ">=1.5.0")) {
options.service.node.capabilities.rpc.push("SINGLE_NODE_MULTI_WRITER");
/**
* This is for volumes that support a mount time gid such as smb or fat
*/
//options.service.node.capabilities.rpc.push("VOLUME_MOUNT_GROUP");
}
}
}
async getObjectiveFSClient() {
const driver = this;
return registry.getAsync(
`${__REGISTRY_NS__}:objectivefsclient`,
async () => {
const options = {};
options.sudo = _.get(
driver.options,
"objectivefs.cli.sudoEnabled",
false
);
return new ObjectiveFS({
...options,
env: _.get(driver.options, "objectivefs.env", {}),
});
}
);
}
/**
*
* @returns Array
*/
getAccessModes(capability) {
let access_modes = _.get(this.options, "csi.access_modes", null);
if (access_modes !== null) {
return access_modes;
}
access_modes = [
"UNKNOWN",
"SINGLE_NODE_WRITER",
"SINGLE_NODE_SINGLE_WRITER", // added in v1.5.0
"SINGLE_NODE_MULTI_WRITER", // added in v1.5.0
"SINGLE_NODE_READER_ONLY",
"MULTI_NODE_READER_ONLY",
"MULTI_NODE_SINGLE_WRITER",
"MULTI_NODE_MULTI_WRITER",
];
if (
capability.access_type == "block" &&
!access_modes.includes("MULTI_NODE_MULTI_WRITER")
) {
access_modes.push("MULTI_NODE_MULTI_WRITER");
}
return access_modes;
}
getFsTypes() {
return ["fuse.objectivefs", "objectivefs"];
}
assertCapabilities(capabilities) {
const driver = this;
this.ctx.logger.verbose("validating capabilities: %j", capabilities);
let message = null;
let fs_types = driver.getFsTypes();
const valid = capabilities.every((capability) => {
if (capability.access_type != "mount") {
message = `invalid access_type ${capability.access_type}`;
return false;
}
if (
capability.mount.fs_type &&
!fs_types.includes(capability.mount.fs_type)
) {
message = `invalid fs_type ${capability.mount.fs_type}`;
return false;
}
if (
!this.getAccessModes(capability).includes(capability.access_mode.mode)
) {
message = `invalid access_mode, ${capability.access_mode.mode}`;
return false;
}
return true;
});
return { valid, message };
}
async getVolumeStatus(entry) {
const driver = this;
const object_store = _.get(driver.options, "objectivefs.env.OBJECTSTORE");
const volume_id = entry.NAME.replace(object_store, "").split("/")[1];
if (!!!semver.satisfies(driver.ctx.csiVersion, ">=1.2.0")) {
return;
}
let abnormal = false;
let message = "OK";
let volume_status = {};
//LIST_VOLUMES_PUBLISHED_NODES
if (
semver.satisfies(driver.ctx.csiVersion, ">=1.2.0") &&
driver.options.service.controller.capabilities.rpc.includes(
"LIST_VOLUMES_PUBLISHED_NODES"
)
) {
// TODO: let drivers fill this in
volume_status.published_node_ids = [];
}
//VOLUME_CONDITION
if (
semver.satisfies(driver.ctx.csiVersion, ">=1.3.0") &&
driver.options.service.controller.capabilities.rpc.includes(
"VOLUME_CONDITION"
)
) {
// TODO: let drivers fill ths in
volume_condition = { abnormal, message };
volume_status.volume_condition = volume_condition;
}
return volume_status;
}
async populateCsiVolumeFromData(entry) {
const driver = this;
const object_store = _.get(driver.options, "objectivefs.env.OBJECTSTORE");
let filesystem = entry.NAME.replace(object_store, "");
let volume_content_source;
let volume_context = {
provisioner_driver: driver.options.driver,
node_attach_driver: "objectivefs",
filesystem,
object_store,
"env.OBJECTSTORE": object_store,
};
if (driver.options.instance_id) {
volume_context["provisioner_driver_instance_id"] =
driver.options.instance_id;
}
let accessible_topology;
let volume = {
volume_id: filesystem.split("/")[1],
capacity_bytes: 0,
content_source: volume_content_source,
volume_context,
accessible_topology,
};
return volume;
}
/**
* Ensure sane options are used etc
* true = ready
* false = not ready, but progressiong towards ready
* throw error = faulty setup
*
* @param {*} call
*/
async Probe(call) {
const driver = this;
const pool = _.get(driver.options, "objectivefs.pool");
const object_store = _.get(driver.options, "objectivefs.env.OBJECTSTORE");
if (driver.ctx.args.csiMode.includes("controller")) {
if (!pool) {
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`objectivefs.pool not configured`
);
}
if (!object_store) {
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`env.OBJECTSTORE not configured`
);
}
return { ready: { value: true } };
} else {
return { ready: { value: true } };
}
}
/**
* Create an objectivefs filesystem as a new volume
*
* @param {*} call
*/
async CreateVolume(call) {
const driver = this;
const ofsClient = await driver.getObjectiveFSClient();
const pool = _.get(driver.options, "objectivefs.pool");
const object_store = _.get(driver.options, "objectivefs.env.OBJECTSTORE");
const parameters = call.request.parameters;
if (!pool) {
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`objectivefs.pool not configured`
);
}
if (!object_store) {
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`env.OBJECTSTORE not configured`
);
}
const context_env = {};
for (const key in parameters) {
if (key.startsWith("env.")) {
context_env[key] = parameters[key];
}
}
context_env["env.OBJECTSTORE"] = object_store;
// filesystem names are always lower-cased by ofs
let volume_id = await driver.getVolumeIdFromCall(call);
let volume_content_source = call.request.volume_content_source;
volume_id = volume_id.toLowerCase();
const filesystem = `${pool}/${volume_id}`;
if (volume_id.length >= MAX_VOLUME_NAME_LENGTH) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`derived volume_id ${volume_id} is too long for objectivefs`
);
}
if (
call.request.volume_capabilities &&
call.request.volume_capabilities.length > 0
) {
const result = this.assertCapabilities(call.request.volume_capabilities);
if (result.valid !== true) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, result.message);
}
} else {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
"missing volume_capabilities"
);
}
if (
!call.request.capacity_range ||
Object.keys(call.request.capacity_range).length === 0
) {
call.request.capacity_range = {
required_bytes: 1073741824, // meaningless
};
}
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.OUT_OF_RANGE,
`required_bytes is greather than limit_bytes`
);
}
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)`
);
}
// 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`
);
}
if (volume_content_source) {
//should never happen, cannot clone with this driver
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`cloning is not enabled`
);
}
await ofsClient.create({}, filesystem, ["-f"]);
let volume_context = {
provisioner_driver: driver.options.driver,
node_attach_driver: "objectivefs",
filesystem,
...context_env,
};
if (driver.options.instance_id) {
volume_context["provisioner_driver_instance_id"] =
driver.options.instance_id;
}
const res = {
volume: {
volume_id,
//capacity_bytes: capacity_bytes, // kubernetes currently pukes if capacity is returned as 0
capacity_bytes: 0,
content_source: volume_content_source,
volume_context,
},
};
return res;
}
/**
* Delete a volume
*
* Deleting a volume consists of the following steps:
* 1. delete directory
*
* @param {*} call
*/
async DeleteVolume(call) {
const driver = this;
const ofsClient = await driver.getObjectiveFSClient();
const pool = _.get(driver.options, "objectivefs.pool");
let volume_id = call.request.volume_id;
if (!volume_id) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`volume_id is required`
);
}
volume_id = volume_id.toLowerCase();
const filesystem = `${pool}/${volume_id}`;
await ofsClient.destroy({}, filesystem, []);
return {};
}
/**
*
* @param {*} call
*/
async ControllerExpandVolume(call) {
throw new GrpcError(
grpc.status.UNIMPLEMENTED,
`operation not supported by driver`
);
}
/**
* TODO: consider volume_capabilities?
*
* @param {*} call
*/
async GetCapacity(call) {
throw new GrpcError(
grpc.status.UNIMPLEMENTED,
`operation not supported by driver`
);
}
/**
*
* TODO: check capability to ensure not asking about block volumes
*
* @param {*} call
*/
async ListVolumes(call) {
const driver = this;
const ofsClient = await driver.getObjectiveFSClient();
const pool = _.get(driver.options, "objectivefs.pool");
let entries = [];
let entries_length = 0;
let next_token;
let uuid;
let response;
const max_entries = call.request.max_entries;
const starting_token = call.request.starting_token;
// get data from cache and return immediately
if (starting_token) {
let parts = starting_token.split(":");
uuid = parts[0];
let start_position = parseInt(parts[1]);
let end_position;
if (max_entries > 0) {
end_position = start_position + max_entries;
}
entries = this.ctx.cache.get(`ListVolumes:result:${uuid}`);
if (entries) {
entries_length = entries.length;
entries = entries.slice(start_position, end_position);
if (max_entries > 0 && end_position > entries_length) {
next_token = `${uuid}:${end_position}`;
} else {
next_token = null;
}
const data = {
entries: entries,
next_token: next_token,
};
return data;
} else {
throw new GrpcError(
grpc.status.ABORTED,
`invalid starting_token: ${starting_token}`
);
}
}
entries = [];
const list_entries = await ofsClient.list({});
for (const entry of list_entries) {
if (entry.KIND != "ofs") {
continue;
}
let volume = await driver.populateCsiVolumeFromData(entry);
if (volume) {
let status = await driver.getVolumeStatus(entry);
entries.push({
volume,
status,
});
}
}
if (max_entries && entries.length > max_entries) {
uuid = uuidv4();
this.ctx.cache.set(`ListVolumes:result:${uuid}`, entries);
next_token = `${uuid}:${max_entries}`;
entries = entries.slice(0, max_entries);
}
const data = {
entries: entries,
next_token: next_token,
};
return data;
}
/**
*
* @param {*} call
*/
async ListSnapshots(call) {
throw new GrpcError(
grpc.status.UNIMPLEMENTED,
`operation not supported by driver`
);
}
/**
*
* @param {*} call
*/
async CreateSnapshot(call) {
throw new GrpcError(
grpc.status.UNIMPLEMENTED,
`operation not supported by driver`
);
const driver = this;
// both these are required
let source_volume_id = call.request.source_volume_id;
let name = call.request.name;
if (!source_volume_id) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`snapshot source_volume_id is required`
);
}
source_volume_id = source_volume_id.toLowerCase();
if (!name) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`snapshot name is required`
);
}
driver.ctx.logger.verbose("requested snapshot name: %s", name);
let invalid_chars;
invalid_chars = name.match(/[^a-z0-9_\-:.+]+/gi);
if (invalid_chars) {
invalid_chars = String.prototype.concat(
...new Set(invalid_chars.join(""))
);
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`snapshot name contains invalid characters: ${invalid_chars}`
);
}
// https://stackoverflow.com/questions/32106243/regex-to-remove-all-non-alpha-numeric-and-replace-spaces-with/32106277
name = name.replace(/[^a-z0-9_\-:.+]+/gi, "");
driver.ctx.logger.verbose("cleansed snapshot name: %s", name);
const snapshot_id = `${source_volume_id}-${name}`;
const volume_path = driver.getControllerVolumePath(source_volume_id);
const snapshot_path = driver.getControllerSnapshotPath(snapshot_id);
// do NOT overwrite existing snapshot
if (!(await driver.directoryExists(snapshot_path))) {
await driver.cloneDir(volume_path, snapshot_path);
}
let size_bytes = await driver.getDirectoryUsage(snapshot_path);
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,
snapshot_id,
source_volume_id: source_volume_id,
//https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/timestamp.proto
creation_time: {
seconds: Math.round(new Date().getTime() / 1000),
nanos: 0,
},
ready_to_use: true,
},
};
}
/**
* In addition, if clones have been created from a snapshot, then they must
* be destroyed before the snapshot can be destroyed.
*
* @param {*} call
*/
async DeleteSnapshot(call) {
throw new GrpcError(
grpc.status.UNIMPLEMENTED,
`operation not supported by driver`
);
const driver = this;
const snapshot_id = call.request.snapshot_id;
if (!snapshot_id) {
throw new GrpcError(
grpc.status.INVALID_ARGUMENT,
`snapshot_id is required`
);
}
return {};
}
/**
*
* @param {*} call
*/
async ValidateVolumeCapabilities(call) {
const driver = this;
const ofsClient = await driver.getObjectiveFSClient();
const pool = _.get(driver.options, "objectivefs.pool");
const volume_id = call.request.volume_id;
if (!volume_id) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing volume_id`);
}
const filesystem = `${pool}/${volume_id}`;
const entries = await ofsClient.list({}, filesystem);
const exists = entries.some((entry) => {
return entry.NAME.endsWith(filesystem) && entry.KIND == "ofs";
});
if (!exists) {
throw new GrpcError(
grpc.status.NOT_FOUND,
`invalid volume_id: ${volume_id}`
);
}
const capabilities = call.request.volume_capabilities;
if (!capabilities || capabilities.length === 0) {
throw new GrpcError(grpc.status.INVALID_ARGUMENT, `missing capabilities`);
}
const result = this.assertCapabilities(call.request.volume_capabilities);
if (result.valid !== true) {
return { message: result.message };
}
return {
confirmed: {
volume_context: call.request.volume_context,
volume_capabilities: call.request.volume_capabilities, // TODO: this is a bit crude, should return *ALL* capabilities, not just what was requested
parameters: call.request.parameters,
},
};
}
}
module.exports.ControllerObjectiveFSDriver = ControllerObjectiveFSDriver;

View File

@ -12,6 +12,7 @@ const {
const { ControllerNfsClientDriver } = require("./controller-nfs-client");
const { ControllerSmbClientDriver } = require("./controller-smb-client");
const { ControllerLustreClientDriver } = require("./controller-lustre-client");
const { ControllerObjectiveFSDriver } = require("./controller-objectivefs");
const { ControllerSynologyDriver } = require("./controller-synology");
const { NodeManualDriver } = require("./node-manual");
@ -50,6 +51,8 @@ function factory(ctx, options) {
return new ControllerLocalHostpathDriver(ctx, options);
case "lustre-client":
return new ControllerLustreClientDriver(ctx, options);
case "objectivefs":
return new ControllerObjectiveFSDriver(ctx, options);
case "node-manual":
return new NodeManualDriver(ctx, options);
default:

View File

@ -183,8 +183,17 @@ class FreeNASApiDriver extends CsiBaseDriver {
const apiVersion = httpClient.getApiVersion();
const zb = await this.getZetabyte();
const truenasVersion = semver.coerce(
await httpApiClient.getSystemVersionMajorMinor()
await httpApiClient.getSystemVersionMajorMinor(),
{ loose: true }
);
if (!truenasVersion) {
throw new GrpcError(
grpc.status.UNKNOWN,
`unable to detect TrueNAS version`
);
}
const isScale = await httpApiClient.getIsScale();
let volume_context;

View File

@ -231,8 +231,17 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver {
const apiVersion = httpClient.getApiVersion();
const zb = await this.getZetabyte();
const truenasVersion = semver.coerce(
await httpApiClient.getSystemVersionMajorMinor()
await httpApiClient.getSystemVersionMajorMinor(),
{ loose: true }
);
if (!truenasVersion) {
throw new GrpcError(
grpc.status.UNKNOWN,
`unable to detect TrueNAS version`
);
}
const isScale = await httpApiClient.getIsScale();
let volume_context;
@ -1996,7 +2005,7 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver {
this.ctx.logger.debug("zfs props data: %j", properties);
let iscsiName =
properties[FREENAS_ISCSI_ASSETS_NAME_PROPERTY_NAME].value;
// name correlates to the extent NOT the target
let kName = iscsiName.replaceAll(".", "_");

View File

@ -7,6 +7,7 @@ const k8s = require("@kubernetes/client-node");
const { GrpcError, grpc } = require("../utils/grpc");
const Handlebars = require("handlebars");
const { Mount } = require("../utils/mount");
const { ObjectiveFS } = require("../utils/objectivefs");
const { OneClient } = require("../utils/oneclient");
const { Filesystem } = require("../utils/filesystem");
const { ISCSI } = require("../utils/iscsi");
@ -181,6 +182,15 @@ class CsiBaseDriver {
});
}
getDefaultObjectiveFSInstance() {
return registry.get(
`${__REGISTRY_NS__}:default_objectivefs_instance`,
() => {
return new ObjectiveFS();
}
);
}
/**
*
* @returns CsiProxyClient
@ -456,6 +466,9 @@ class CsiBaseDriver {
/**
* technically zfs allows `:` and `.` in addition to `_` and `-`
* TODO: make this more specific to each driver
* in particular Nomad per-alloc feature uses names with <name>-[<index>] syntax so square brackets are present
* TODO: allow for replacing chars vs absolute failure?
*/
let invalid_chars;
invalid_chars = volume_id.match(/[^a-z0-9_\-]/gi);
@ -728,6 +741,7 @@ class CsiBaseDriver {
}
switch (node_attach_driver) {
case "objectivefs":
case "oneclient":
// move along
break;
@ -1246,6 +1260,65 @@ class CsiBaseDriver {
return {};
}
break;
case "objectivefs":
let objectivefs = driver.getDefaultObjectiveFSInstance();
let ofs_filesystem = volume_context.filesystem;
let env = {};
for (const key in volume_context) {
if (key.startsWith("env.")) {
env[key.substr("env.".length)] = volume_context[key];
}
}
for (const key in normalizedSecrets) {
if (key.startsWith("env.")) {
env[key.substr("env.".length)] = normalizedSecrets[key];
}
}
let ofs_object_store = env["OBJECTSTORE"];
if (!ofs_filesystem) {
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`missing ofs volume filesystem`
);
}
if (!ofs_object_store) {
throw new GrpcError(
grpc.status.FAILED_PRECONDITION,
`missing required ofs volume env.OBJECTSTORE`
);
}
device = `${ofs_object_store}${ofs_filesystem}`;
result = await mount.deviceIsMountedAtPath(
device,
staging_target_path
);
if (result) {
return {};
}
result = await objectivefs.mount(
env,
ofs_filesystem,
staging_target_path,
mount_flags
);
if (result) {
return {};
}
throw new GrpcError(
grpc.status.UNKNOWN,
`failed to mount objectivefs: ${device}`
);
break;
case "oneclient":
let oneclient = driver.getDefaultOneClientInstance();
@ -2932,6 +3005,7 @@ class CsiBaseDriver {
case "nfs":
case "smb":
case "lustre":
case "objectivefs":
case "oneclient":
case "hostpath":
case "iscsi":

View File

@ -121,6 +121,10 @@ class NodeManualDriver extends CsiBaseDriver {
driverResourceType = "filesystem";
fs_types = ["lustre"];
break;
case "objectivefs":
driverResourceType = "filesystem";
fs_types = ["objectivefs", "fuse.objectivefs"];
break;
case "oneclient":
driverResourceType = "filesystem";
fs_types = ["oneclient", "fuse.oneclient"];

320
src/utils/objectivefs.js Normal file
View File

@ -0,0 +1,320 @@
const cp = require("child_process");
const DEFAULT_TIMEOUT = process.env.MOUNT_DEFAULT_TIMEOUT || 30000;
const EXIT_CODE_64 = "administrator can not mount filesystems";
const EXIT_CODE_78 = "missing or invalid passphrase";
/**
* https://objectivefs.com/
*/
class ObjectiveFS {
constructor(options = {}) {
const objectivefs = this;
objectivefs.options = options;
options.paths = options.paths || {};
if (!options.paths.objectivefs) {
options.paths.objectivefs = "mount.objectivefs";
}
if (!options.paths.sudo) {
options.paths.sudo = "/usr/bin/sudo";
}
if (!options.paths.chroot) {
options.paths.chroot = "/usr/sbin/chroot";
}
if (!options.env) {
options.env = {};
}
if (!options.executor) {
options.executor = {
spawn: cp.spawn,
//spawn: cp.execFile,
};
}
}
/**
* mount.objectivefs [-o <opt>[,<opt>]..] <filesystem> <dir>
*
* @param {*} env
* @param {*} filesystem
* @param {*} target
* @param {*} options
*/
async mount(env, filesystem, target, options = []) {
if (!env) {
env = {};
}
const objectivefs = this;
let args = [];
args = args.concat(options);
args = args.concat([filesystem, target]);
let result;
try {
result = await objectivefs.exec(
objectivefs.options.paths.objectivefs,
args,
{ env, operation: "mount" }
);
return result;
} catch (err) {
throw err;
}
}
/**
* mount.objectivefs create <your filesystem name>
* mount.objectivefs create -f <bucket>/<fs>
*
* @param {*} env
* @param {*} filesystem
* @param {*} options
*/
async create(env, filesystem, options = []) {
if (!env) {
env = {};
}
const objectivefs = this;
let args = ["create"];
args = args.concat(options);
args = args.concat([filesystem]);
let result;
try {
result = await objectivefs.exec(
objectivefs.options.paths.objectivefs,
args,
{ env }
);
return result;
} catch (err) {
if (err.code == 1 && err.stderr.includes("filesystem already exists")) {
return;
}
throw err;
}
}
/**
* echo 'y' | mount.objectivefs destroy <bucket>/<fs>
*
* @param {*} env
* @param {*} filesystem
* @param {*} options
*/
async destroy(env, filesystem, options = []) {
if (!env) {
env = {};
}
const objectivefs = this;
let args = ["destroy"];
args = args.concat(options);
args = args.concat([filesystem]);
let result;
try {
result = await objectivefs.exec(
"/bin/bash",
[
"-c",
`echo y | ${objectivefs.options.paths.objectivefs} ${args.join(" ")}`,
],
{ env }
);
return result;
} catch (err) {
if (
err.code == 68 &&
err.stdout.includes("does not look like an ObjectiveFS filesystem")
) {
return;
}
throw err;
}
}
parseListOutput(data) {
const lines = data.split("\n");
let headers = [];
let entries = [];
lines.forEach((line, i) => {
if (line.length < 1) {
return;
}
const parts = line.split("\t");
if (i == 0) {
headers = parts.map((header) => {
return header.trim();
});
return;
}
let entry = {};
headers.forEach((name, index) => {
entry[name.trim()] = parts[index].trim();
});
entries.push(entry);
});
return entries;
}
/**
* mount.objectivefs list [-asvz] [<filesystem>[@<time>]]
*
* @param {*} env
* @param {*} filesystem
* @param {*} options
*/
async list(env, filesystem = null, options = []) {
if (!env) {
env = {};
}
const objectivefs = this;
let args = ["list"];
args = args.concat(options);
if (filesystem) {
args = args.concat([filesystem]);
}
let result;
try {
result = await objectivefs.exec(
objectivefs.options.paths.objectivefs,
args,
{ env }
);
return objectivefs.parseListOutput(result.stdout);
} catch (err) {
throw err;
}
}
/**
* mount.objectivefs snapshot <filesystem>
*
* NOTE: fs must be mount on node to function
*
* @param {*} env
* @param {*} filesystem
* @param {*} options
*/
async snapshot(env, filesystem = null, options = []) {
if (!env) {
env = {};
}
const objectivefs = this;
let args = ["list"];
args = args.concat(options);
if (filesystem) {
args = args.concat([filesystem]);
}
let result;
try {
// NOTE: Successfully created snapshot: minio://ofs/test@2024-02-13T07:56:38Z (2024-02-13T00:56:38)
result = await objectivefs.exec(
objectivefs.options.paths.objectivefs,
args,
{ env }
);
return result;
} catch (err) {
throw err;
}
}
exec(command, args, options = {}) {
if (!options.hasOwnProperty("timeout")) {
options.timeout = DEFAULT_TIMEOUT;
}
const objectivefs = this;
args = args || [];
if (objectivefs.options.sudo) {
args.unshift(command);
command = objectivefs.options.paths.sudo;
}
// OBJECTIVEFS_ENV
options.env = { ...{}, ...objectivefs.options.env, ...options.env };
//console.log(options);
// truncate admin key during mount operations
if (options.operation == "mount") {
delete options.operation;
// standard license is 24
// admin key is 8
if (
options.env.OBJECTIVEFS_LICENSE &&
options.env.OBJECTIVEFS_LICENSE.length > 24
) {
options.env.OBJECTIVEFS_LICENSE =
options.env.OBJECTIVEFS_LICENSE.substr(0, 24);
}
}
const cleansedLog = `${command} ${args.join(" ")}`;
console.log("executing objectivefs command: %s", cleansedLog);
return new Promise((resolve, reject) => {
let stdin;
if (options.stdin) {
stdin = options.stdin;
delete options.stdin;
}
const child = objectivefs.options.executor.spawn(command, args, options);
if (stdin) {
child.stdin.write(stdin);
}
let stdout = "";
let stderr = "";
child.stdout.on("data", function (data) {
stdout = stdout + data;
});
child.stderr.on("data", function (data) {
stderr = stderr + data;
});
child.on("close", function (code) {
if (code == 78 && !stderr) {
stderr += EXIT_CODE_78;
}
if (code == 64 && !stderr) {
stderr += EXIT_CODE_64;
}
const result = { code, stdout, stderr, timeout: false };
// timeout scenario
if (code === null) {
result.timeout = true;
reject(result);
}
if (code) {
reject(result);
} else {
resolve(result);
}
});
});
}
}
module.exports.ObjectiveFS = ObjectiveFS;