From 38199e90b786148e49b38364488395e5758d0d90 Mon Sep 17 00:00:00 2001 From: Travis Glenn Hansen Date: Thu, 3 Feb 2022 01:51:54 -0700 Subject: [PATCH] oneclient support Signed-off-by: Travis Glenn Hansen --- Dockerfile | 2 + docker/oneclient | 3 + src/driver/index.js | 56 +++++++++++- src/driver/node-manual/index.js | 4 + src/utils/oneclient.js | 147 ++++++++++++++++++++++++++++++++ 5 files changed, 208 insertions(+), 4 deletions(-) create mode 100755 docker/oneclient create mode 100644 src/utils/oneclient.js diff --git a/Dockerfile b/Dockerfile index 9f7f97f..ff56999 100644 --- a/Dockerfile +++ b/Dockerfile @@ -101,6 +101,8 @@ ADD docker/zfs /usr/local/bin/zfs RUN chmod +x /usr/local/bin/zfs ADD docker/zpool /usr/local/bin/zpool RUN chmod +x /usr/local/bin/zpool +ADD docker/oneclient /usr/local/bin/oneclient +RUN chmod +x /usr/local/bin/oneclient # Run as a non-root user RUN useradd --create-home csi \ diff --git a/docker/oneclient b/docker/oneclient new file mode 100755 index 0000000..7b5ef21 --- /dev/null +++ b/docker/oneclient @@ -0,0 +1,3 @@ +#!/bin/bash + +chroot /host /usr/bin/env -i PATH="/sbin:/bin:/usr/bin:/usr/sbin" oneclient "${@:1}" diff --git a/src/driver/index.js b/src/driver/index.js index 0f02a56..a9b4467 100644 --- a/src/driver/index.js +++ b/src/driver/index.js @@ -4,6 +4,7 @@ const os = require("os"); const fs = require("fs"); const { GrpcError, grpc } = require("../utils/grpc"); const { Mount } = require("../utils/mount"); +const { OneClient } = require("../utils/oneclient"); const { Filesystem } = require("../utils/filesystem"); const { ISCSI } = require("../utils/iscsi"); const semver = require("semver"); @@ -327,11 +328,19 @@ class CsiBaseDriver { if (normalizedSecrets.mount_flags) { mount_flags.push(normalizedSecrets.mount_flags); } - mount_flags.push("defaults"); - // https://github.com/karelzak/util-linux/issues/1429 - //mount_flags.push("x-democratic-csi.managed"); - //mount_flags.push("x-democratic-csi.staged"); + switch (node_attach_driver) { + case "oneclient": + // move along + break; + default: + mount_flags.push("defaults"); + + // https://github.com/karelzak/util-linux/issues/1429 + //mount_flags.push("x-democratic-csi.managed"); + //mount_flags.push("x-democratic-csi.staged"); + break; + } if ( semver.satisfies(driver.ctx.csiVersion, ">=1.5.0") && @@ -566,6 +575,45 @@ class CsiBaseDriver { ); } } + break; + case "oneclient": + let oneclient = new OneClient(); + device = "oneclient"; + result = await mount.deviceIsMountedAtPath(device, staging_target_path); + if (result) { + return {}; + } + + if (volume_context.space_names) { + volume_context.space_names.split(",").forEach((space) => { + mount_flags.push("--space", space); + }); + } + + if (volume_context.space_ids) { + volume_context.space_ids.split(",").forEach((space) => { + mount_flags.push("--space-id", space); + }); + } + + if (volume_context.token) { + mount_flags.push("-t", volume_context.token); + } + + result = await oneclient.mount( + staging_target_path, + ["-H", volume_context.server].concat(mount_flags) + ); + + if (result) { + return {}; + } + + throw new GrpcError( + grpc.status.UNKNOWN, + `failed to mount oneclient: ${volume_context.server}` + ); + break; case "zfs-local": // TODO: make this a geneic zb instance (to ensure works with node-manual driver) diff --git a/src/driver/node-manual/index.js b/src/driver/node-manual/index.js index 73d00ec..bbe4640 100644 --- a/src/driver/node-manual/index.js +++ b/src/driver/node-manual/index.js @@ -121,6 +121,10 @@ class NodeManualDriver extends CsiBaseDriver { driverResourceType = "filesystem"; fs_types = ["lustre"]; break; + case "oneclient": + driverResourceType = "filesystem"; + fs_types = ["oneclient", "fuse.oneclient"]; + break; case "iscsi": driverResourceType = "volume"; fs_types = ["ext3", "ext4", "ext4dev", "xfs"]; diff --git a/src/utils/oneclient.js b/src/utils/oneclient.js new file mode 100644 index 0000000..c23416b --- /dev/null +++ b/src/utils/oneclient.js @@ -0,0 +1,147 @@ +const cp = require("child_process"); + +DEFAULT_TIMEOUT = process.env.MOUNT_DEFAULT_TIMEOUT || 30000; + +/** + * - https://github.com/onedata/oneclient + */ +class OneClient { + constructor(options = {}) { + const oneclient = this; + oneclient.options = options; + + options.paths = options.paths || {}; + if (!options.paths.oneclient) { + options.paths.oneclient = "oneclient"; + } + + if (!options.paths.sudo) { + options.paths.sudo = "/usr/bin/sudo"; + } + + if (!options.paths.chroot) { + options.paths.chroot = "/usr/sbin/chroot"; + } + + if (!options.timeout) { + options.timeout = 10 * 60 * 1000; + } + + if (!options.executor) { + options.executor = { + spawn: cp.spawn, + }; + } + } + + /** + * oneclient [options] + * + * @param {*} target + * @param {*} options + */ + async mount(target, options = []) { + const oneclient = this; + let args = []; + args = args.concat(options); + args = args.concat([target]); + + let result; + try { + result = await oneclient.exec(oneclient.options.paths.oneclient, args); + return result; + } catch (err) { + throw err; + } + } + + /** + * oneclient -u + * + * @param {*} target + */ + async umount(target) { + const oneclient = this; + let args = ["-u"]; + args.push(target); + + try { + await oneclient.exec(oneclient.options.paths.oneclient, args); + } catch (err) { + throw err; + } + return true; + } + + exec(command, args, options = {}) { + if (!options.hasOwnProperty("timeout")) { + options.timeout = DEFAULT_TIMEOUT; + } + + const oneclient = this; + args = args || []; + + let timeout; + let stdout = ""; + let stderr = ""; + + if (oneclient.options.sudo) { + args.unshift(command); + command = oneclient.options.paths.sudo; + } + + // replace -t with -t redacted + const regex = /(?<=\-t) (?:[^\s]+)/gi; + const cleansedLog = `${command} ${args.join(" ")}`.replace( + regex, + " redacted" + ); + + console.log("executing oneclient command: %s", cleansedLog); + const child = oneclient.options.executor.spawn(command, args, options); + + /** + * timeout option natively supported since v16 + * TODO: properly handle this based on nodejs version + */ + let didTimeout = false; + if (options && options.timeout) { + timeout = setTimeout(() => { + didTimeout = true; + child.kill(options.killSignal || "SIGTERM"); + }, options.timeout); + } + + return new Promise((resolve, reject) => { + child.stdout.on("data", function (data) { + stdout = stdout + data; + }); + + child.stderr.on("data", function (data) { + stderr = stderr + data; + }); + + child.on("close", function (code) { + const result = { code, stdout, stderr, timeout: false }; + + if (timeout) { + clearTimeout(timeout); + } + + // timeout scenario + if (code === null) { + result.timeout = true; + reject(result); + } + + if (code) { + reject(result); + } else { + resolve(result); + } + }); + }); + } +} + +module.exports.OneClient = OneClient;