diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5bff4fa..528fb9d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -250,7 +250,7 @@ jobs: run: | export ARCH=$([ $(uname -m) = "x86_64" ] && echo "amd64" || echo "arm64") mkdir -p ~/.docker/cli-plugins/ - wget -qO ~/.docker/cli-plugins/docker-buildx https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.linux-${ARCH} + wget -qO ~/.docker/cli-plugins/docker-buildx https://github.com/docker/buildx/releases/download/v0.8.2/buildx-v0.8.2.linux-${ARCH} chmod a+x ~/.docker/cli-plugins/docker-buildx docker info docker buildx version diff --git a/bin/democratic-csi b/bin/democratic-csi index 2a2148e..df94d93 100755 --- a/bin/democratic-csi +++ b/bin/democratic-csi @@ -136,6 +136,14 @@ let operationLock = new Set(); async function requestHandlerProxy(call, callback, serviceMethodName) { const cleansedCall = JSON.parse(stringify(call)); + + delete cleansedCall.call; + delete cleansedCall.canceled; + for (const key in cleansedCall) { + if (key.startsWith("_")) { + delete cleansedCall[key]; + } + } for (const key in cleansedCall.request) { if (key.includes("secret")) { cleansedCall.request[key] = "redacted"; @@ -165,6 +173,17 @@ async function requestHandlerProxy(call, callback, serviceMethodName) { }); } + // for testing purposes + //await GeneralUtils.sleep(10000); + + // for CI/testing purposes + if (["NodePublishVolume", "NodeStageVolume"].includes(serviceMethodName)) { + await driver.setVolumeContextCache( + call.request.volume_id, + call.request.volume_context + ); + } + let response; let responseError; try { @@ -190,12 +209,21 @@ async function requestHandlerProxy(call, callback, serviceMethodName) { throw responseError; } + // for CI/testing purposes + if (serviceMethodName == "CreateVolume") { + await driver.setVolumeContextCache( + response.volume.volume_id, + response.volume.volume_context + ); + } + logger.info( "new response - driver: %s method: %s response: %j", driver.constructor.name, serviceMethodName, response ); + callback(null, response); } catch (e) { let message; @@ -205,7 +233,7 @@ async function requestHandlerProxy(call, callback, serviceMethodName) { message += ` ${e.stack}`; } } else { - message = JSON.stringify(e); + message = stringify(e); } logger.error( diff --git a/csi_proxy_proto/disk/v1/api.proto b/csi_proxy_proto/disk/v1/api.proto new file mode 100644 index 0000000..f73f412 --- /dev/null +++ b/csi_proxy_proto/disk/v1/api.proto @@ -0,0 +1,111 @@ +syntax = "proto3"; + +package v1; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/disk/v1"; + +service Disk { + // ListDiskLocations returns locations of all + // disk devices enumerated by the host. + rpc ListDiskLocations(ListDiskLocationsRequest) returns (ListDiskLocationsResponse) {} + + // PartitionDisk initializes and partitions a disk device with the GPT partition style + // (if the disk has not been partitioned already) and returns the resulting volume device ID. + rpc PartitionDisk(PartitionDiskRequest) returns (PartitionDiskResponse) {} + + // Rescan refreshes the host's storage cache. + rpc Rescan(RescanRequest) returns (RescanResponse) {} + + // ListDiskIDs returns a map of DiskID objects where the key is the disk number. + rpc ListDiskIDs(ListDiskIDsRequest) returns (ListDiskIDsResponse) {} + + // GetDiskStats returns the stats of a disk (currently it returns the disk size). + rpc GetDiskStats(GetDiskStatsRequest) returns (GetDiskStatsResponse) {} + + // SetDiskState sets the offline/online state of a disk. + rpc SetDiskState(SetDiskStateRequest) returns (SetDiskStateResponse) {} + + // GetDiskState gets the offline/online state of a disk. + rpc GetDiskState(GetDiskStateRequest) returns (GetDiskStateResponse) {} +} + +message ListDiskLocationsRequest { + // Intentionally empty. +} + +message DiskLocation { + string Adapter = 1; + string Bus = 2; + string Target = 3; + string LUNID = 4; +} + +message ListDiskLocationsResponse { + // Map of disk number and associated with each disk device. + map disk_locations = 1; +} + +message PartitionDiskRequest { + // Disk device number of the disk to partition. + uint32 disk_number = 1; +} + +message PartitionDiskResponse { + // Intentionally empty. +} + +message RescanRequest { + // Intentionally empty. +} + +message RescanResponse { + // Intentionally empty. +} + +message ListDiskIDsRequest { + // Intentionally empty. +} + +message DiskIDs { + // The disk page83 id. + string page83 = 1; + // The disk serial number. + string serial_number = 2; +} + +message ListDiskIDsResponse { + // Map of disk numbers and disk identifiers associated with each disk device. + map diskIDs = 1; // the case is intentional for protoc to generate the field as DiskIDs +} + +message GetDiskStatsRequest { + // Disk device number of the disk to get the stats from. + uint32 disk_number = 1; +} + +message GetDiskStatsResponse { + // Total size of the volume. + int64 total_bytes = 1; +} + +message SetDiskStateRequest { + // Disk device number of the disk. + uint32 disk_number = 1; + + // Online state to set for the disk. true for online, false for offline. + bool is_online = 2; +} + +message SetDiskStateResponse { + // Intentionally empty. +} + +message GetDiskStateRequest { + // Disk device number of the disk. + uint32 disk_number = 1; +} + +message GetDiskStateResponse { + // Online state of the disk. true for online, false for offline. + bool is_online = 1; +} diff --git a/csi_proxy_proto/disk/v1alpha1/api.proto b/csi_proxy_proto/disk/v1alpha1/api.proto new file mode 100644 index 0000000..03ef612 --- /dev/null +++ b/csi_proxy_proto/disk/v1alpha1/api.proto @@ -0,0 +1,62 @@ +syntax = "proto3"; + +package v1alpha1; + +service Disk { + // ListDiskLocations returns locations of all + // disk devices enumerated by the host + rpc ListDiskLocations(ListDiskLocationsRequest) returns (ListDiskLocationsResponse) {} + + // PartitionDisk initializes and partitions a disk device (if the disk has not + // been partitioned already) and returns the resulting volume device ID + rpc PartitionDisk(PartitionDiskRequest) returns (PartitionDiskResponse) {} + + // Rescan refreshes the host's storage cache + rpc Rescan(RescanRequest) returns (RescanResponse) {} + + // GetDiskNumberByName returns disk number based on the passing disk name information + rpc GetDiskNumberByName(GetDiskNumberByNameRequest) returns (GetDiskNumberByNameResponse) {} +} + +message ListDiskLocationsRequest { + // Intentionally empty +} + +message DiskLocation { + string Adapter = 1; + string Bus = 2; + string Target = 3; + string LUNID = 4; +} + +message ListDiskLocationsResponse { + // Map of disk device IDs and associated with each disk device + map disk_locations = 1; +} + +message PartitionDiskRequest { + // Disk device ID of the disk to partition + string diskID = 1; +} + +message PartitionDiskResponse { + // Intentionally empty +} + +message RescanRequest { + // Intentionally empty +} + +message RescanResponse { + // Intentionally empty +} + +message GetDiskNumberByNameRequest { + // Disk ID + string disk_name = 1; +} + +message GetDiskNumberByNameResponse { + // Disk number + string disk_number = 1; +} diff --git a/csi_proxy_proto/disk/v1beta1/api.proto b/csi_proxy_proto/disk/v1beta1/api.proto new file mode 100644 index 0000000..4673b2c --- /dev/null +++ b/csi_proxy_proto/disk/v1beta1/api.proto @@ -0,0 +1,81 @@ +syntax = "proto3"; + +package v1beta1; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/disk/v1beta1"; + +service Disk { + // ListDiskLocations returns locations of all + // disk devices enumerated by the host + rpc ListDiskLocations(ListDiskLocationsRequest) returns (ListDiskLocationsResponse) {} + + // PartitionDisk initializes and partitions a disk device (if the disk has not + // been partitioned already) and returns the resulting volume device ID + rpc PartitionDisk(PartitionDiskRequest) returns (PartitionDiskResponse) {} + + // Rescan refreshes the host's storage cache + rpc Rescan(RescanRequest) returns (RescanResponse) {} + + // ListDiskIDs returns a map of DiskID objects where the key is the disk number + rpc ListDiskIDs(ListDiskIDsRequest) returns (ListDiskIDsResponse) {} + + // DiskStats returns the stats for the disk + rpc DiskStats(DiskStatsRequest) returns (DiskStatsResponse) {} +} + +message ListDiskLocationsRequest { + // Intentionally empty +} + +message DiskLocation { + string Adapter = 1; + string Bus = 2; + string Target = 3; + string LUNID = 4; +} + +message ListDiskLocationsResponse { + // Map of disk device IDs and associated with each disk device + map disk_locations = 1; +} + +message PartitionDiskRequest { + // Disk device ID of the disk to partition + string diskID = 1; +} + +message PartitionDiskResponse { + // Intentionally empty +} + +message RescanRequest { + // Intentionally empty +} + +message RescanResponse { + // Intentionally empty +} + +message ListDiskIDsRequest { + // Intentionally empty +} + +message DiskIDs { + // Map of Disk ID types and Disk ID values + map identifiers = 1; +} + +message ListDiskIDsResponse { + // Map of disk device numbers and IDs associated with each disk device + map diskIDs = 1; +} + +message DiskStatsRequest { + // Disk device ID of the disk to get the size from + string diskID = 1; +} + +message DiskStatsResponse { + //Total size of the volume + int64 diskSize = 1; +} diff --git a/csi_proxy_proto/disk/v1beta2/api.proto b/csi_proxy_proto/disk/v1beta2/api.proto new file mode 100644 index 0000000..c9f6c8f --- /dev/null +++ b/csi_proxy_proto/disk/v1beta2/api.proto @@ -0,0 +1,109 @@ +syntax = "proto3"; + +package v1beta2; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/disk/v1beta2"; + +service Disk { + // ListDiskLocations returns locations of all + // disk devices enumerated by the host + rpc ListDiskLocations(ListDiskLocationsRequest) returns (ListDiskLocationsResponse) {} + + // PartitionDisk initializes and partitions a disk device (if the disk has not + // been partitioned already) and returns the resulting volume device ID + rpc PartitionDisk(PartitionDiskRequest) returns (PartitionDiskResponse) {} + + // Rescan refreshes the host's storage cache + rpc Rescan(RescanRequest) returns (RescanResponse) {} + + // ListDiskIDs returns a map of DiskID objects where the key is the disk number + rpc ListDiskIDs(ListDiskIDsRequest) returns (ListDiskIDsResponse) {} + + // DiskStats returns the stats for the disk + rpc DiskStats(DiskStatsRequest) returns (DiskStatsResponse) {} + + // SetAttachState sets the offline/online state of a disk + rpc SetAttachState(SetAttachStateRequest) returns (SetAttachStateResponse) {} + + // GetAttachState gets the offline/online state of a disk + rpc GetAttachState(GetAttachStateRequest) returns (GetAttachStateResponse) {} +} + +message ListDiskLocationsRequest { + // Intentionally empty +} + +message DiskLocation { + string Adapter = 1; + string Bus = 2; + string Target = 3; + string LUNID = 4; +} + +message ListDiskLocationsResponse { + // Map of disk device IDs and associated with each disk device + map disk_locations = 1; +} + +message PartitionDiskRequest { + // Disk device ID of the disk to partition + string diskID = 1; +} + +message PartitionDiskResponse { + // Intentionally empty +} + +message RescanRequest { + // Intentionally empty +} + +message RescanResponse { + // Intentionally empty +} + +message ListDiskIDsRequest { + // Intentionally empty +} + +message DiskIDs { + // Map of Disk ID types and Disk ID values + map identifiers = 1; +} + +message ListDiskIDsResponse { + // Map of disk device numbers and IDs associated with each disk device + map diskIDs = 1; +} + +message DiskStatsRequest { + // Disk device ID of the disk to get the size from + string diskID = 1; +} + +message DiskStatsResponse { + //Total size of the volume + int64 diskSize = 1; +} + +message SetAttachStateRequest { + // Disk device ID (number) of the disk which state will change + string diskID = 1; + + // Online state to set for the disk. true for online, false for offline + bool isOnline = 2; +} + +message SetAttachStateResponse { +} + +message GetAttachStateRequest { + // Disk device ID (number) of the disk + string diskID = 1; +} + +message GetAttachStateResponse { + // Online state of the disk. true for online, false for offline + bool isOnline = 1; +} + diff --git a/csi_proxy_proto/disk/v1beta3/api.proto b/csi_proxy_proto/disk/v1beta3/api.proto new file mode 100644 index 0000000..8f8283e --- /dev/null +++ b/csi_proxy_proto/disk/v1beta3/api.proto @@ -0,0 +1,111 @@ +syntax = "proto3"; + +package v1beta3; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/disk/v1beta3"; + +service Disk { + // ListDiskLocations returns locations of all + // disk devices enumerated by the host. + rpc ListDiskLocations(ListDiskLocationsRequest) returns (ListDiskLocationsResponse) {} + + // PartitionDisk initializes and partitions a disk device with the GPT partition style + // (if the disk has not been partitioned already) and returns the resulting volume device ID. + rpc PartitionDisk(PartitionDiskRequest) returns (PartitionDiskResponse) {} + + // Rescan refreshes the host's storage cache. + rpc Rescan(RescanRequest) returns (RescanResponse) {} + + // ListDiskIDs returns a map of DiskID objects where the key is the disk number. + rpc ListDiskIDs(ListDiskIDsRequest) returns (ListDiskIDsResponse) {} + + // GetDiskStats returns the stats of a disk (currently it returns the disk size). + rpc GetDiskStats(GetDiskStatsRequest) returns (GetDiskStatsResponse) {} + + // SetDiskState sets the offline/online state of a disk. + rpc SetDiskState(SetDiskStateRequest) returns (SetDiskStateResponse) {} + + // GetDiskState gets the offline/online state of a disk. + rpc GetDiskState(GetDiskStateRequest) returns (GetDiskStateResponse) {} +} + +message ListDiskLocationsRequest { + // Intentionally empty. +} + +message DiskLocation { + string Adapter = 1; + string Bus = 2; + string Target = 3; + string LUNID = 4; +} + +message ListDiskLocationsResponse { + // Map of disk number and associated with each disk device. + map disk_locations = 1; +} + +message PartitionDiskRequest { + // Disk device number of the disk to partition. + uint32 disk_number = 1; +} + +message PartitionDiskResponse { + // Intentionally empty. +} + +message RescanRequest { + // Intentionally empty. +} + +message RescanResponse { + // Intentionally empty. +} + +message ListDiskIDsRequest { + // Intentionally empty. +} + +message DiskIDs { + // The disk page83 id. + string page83 = 1; + // The disk serial number. + string serial_number = 2; +} + +message ListDiskIDsResponse { + // Map of disk numbers and disk identifiers associated with each disk device. + map diskIDs = 1; // the case is intentional for protoc to generate the field as DiskIDs +} + +message GetDiskStatsRequest { + // Disk device number of the disk to get the stats from. + uint32 disk_number = 1; +} + +message GetDiskStatsResponse { + // Total size of the volume. + int64 total_bytes = 1; +} + +message SetDiskStateRequest { + // Disk device number of the disk. + uint32 disk_number = 1; + + // Online state to set for the disk. true for online, false for offline. + bool is_online = 2; +} + +message SetDiskStateResponse { + // Intentionally empty. +} + +message GetDiskStateRequest { + // Disk device number of the disk. + uint32 disk_number = 1; +} + +message GetDiskStateResponse { + // Online state of the disk. true for online, false for offline. + bool is_online = 1; +} diff --git a/csi_proxy_proto/errors.proto b/csi_proxy_proto/errors.proto new file mode 100644 index 0000000..60c74cd --- /dev/null +++ b/csi_proxy_proto/errors.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package api; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api"; + +// CommandError details errors yielded by cmdlet calls. +message CmdletError { + // Name of the cmdlet that errored out. + string cmdlet_name = 1; + + // Error code that got returned. + uint32 code = 2; + + // Human-readable error message - can be empty. + string message = 3; +} diff --git a/csi_proxy_proto/filesystem/v1/api.proto b/csi_proxy_proto/filesystem/v1/api.proto new file mode 100644 index 0000000..151a1ff --- /dev/null +++ b/csi_proxy_proto/filesystem/v1/api.proto @@ -0,0 +1,136 @@ +syntax = "proto3"; + +package v1; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/filesystem/v1"; + +service Filesystem { + // PathExists checks if the requested path exists in the host filesystem. + rpc PathExists(PathExistsRequest) returns (PathExistsResponse) {} + + // Mkdir creates a directory at the requested path in the host filesystem. + rpc Mkdir(MkdirRequest) returns (MkdirResponse) {} + + // Rmdir removes the directory at the requested path in the host filesystem. + // This may be used for unlinking a symlink created through CreateSymlink. + rpc Rmdir(RmdirRequest) returns (RmdirResponse) {} + + // CreateSymlink creates a symbolic link called target_path that points to source_path + // in the host filesystem (target_path is the name of the symbolic link created, + // source_path is the existing path). + rpc CreateSymlink(CreateSymlinkRequest) returns (CreateSymlinkResponse) {} + + // IsSymlink checks if a given path is a symlink. + rpc IsSymlink(IsSymlinkRequest) returns (IsSymlinkResponse) {} +} + +message PathExistsRequest { + // The path whose existence we want to check in the host's filesystem + string path = 1; +} + +message PathExistsResponse { + // Indicates whether the path in PathExistsRequest exists in the host's filesystem + bool exists = 1; +} + +message MkdirRequest { + // The path to create in the host's filesystem. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // Non-existent parent directories in the path will be automatically created. + // Directories will be created with Read and Write privileges of the Windows + // User account under which csi-proxy is started (typically LocalSystem). + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // Depending on the context parameter of this function, the path prefix needs + // to match the paths specified either as kubelet-csi-plugins-path + // or as kubelet-pod-path parameters of csi-proxy. + // The path parameter cannot already exist in the host's filesystem. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // Maximum path length will be capped to 260 characters. + string path = 1; +} + +message MkdirResponse { + // Intentionally empty. +} + +message RmdirRequest { + // The path to remove in the host's filesystem. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // Depending on the context parameter of this function, the path prefix needs + // to match the paths specified either as kubelet-csi-plugins-path + // or as kubelet-pod-path parameters of csi-proxy. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // Path cannot be a file of type symlink. + // Maximum path length will be capped to 260 characters. + string path = 1; + + // Force remove all contents under path (if any). + bool force = 2; +} + +message RmdirResponse { + // Intentionally empty. +} + +message CreateSymlinkRequest { + // The path of the existing directory to be linked. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // The path prefix needs needs to match the paths specified as + // kubelet-csi-plugins-path parameter of csi-proxy. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // source_path cannot already exist in the host filesystem. + // Maximum path length will be capped to 260 characters. + string source_path = 1; + + // Target path is the location of the new directory entry to be created in the host's filesystem. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // The path prefix needs to match the paths specified as + // kubelet-pod-path parameter of csi-proxy. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // target_path needs to exist as a directory in the host that is empty. + // target_path cannot be a symbolic link. + // Maximum path length will be capped to 260 characters. + string target_path = 2; +} + +message CreateSymlinkResponse { + // Intentionally empty. +} + +message IsSymlinkRequest { + // The path whose existence as a symlink we want to check in the host's filesystem. + string path = 1; +} + +message IsSymlinkResponse { + // Indicates whether the path in IsSymlinkRequest is a symlink. + bool is_symlink = 1; +} diff --git a/csi_proxy_proto/filesystem/v1alpha1/api.proto b/csi_proxy_proto/filesystem/v1alpha1/api.proto new file mode 100644 index 0000000..8856da5 --- /dev/null +++ b/csi_proxy_proto/filesystem/v1alpha1/api.proto @@ -0,0 +1,168 @@ +syntax = "proto3"; + +package v1alpha1; + +service Filesystem { + // PathExists checks if the requested path exists in the host's filesystem + rpc PathExists(PathExistsRequest) returns (PathExistsResponse) {} + + // Mkdir creates a directory at the requested path in the host's filesystem + rpc Mkdir(MkdirRequest) returns (MkdirResponse) {} + + // Rmdir removes the directory at the requested path in the host's filesystem. + // This may be used for unlinking a symlink created through LinkPath + rpc Rmdir(RmdirRequest) returns (RmdirResponse) {} + + // LinkPath creates a local directory symbolic link between a source path + // and target path in the host's filesystem + rpc LinkPath(LinkPathRequest) returns (LinkPathResponse) {} + + //IsMountPoint checks if a given path is mount or not + rpc IsMountPoint(IsMountPointRequest) returns (IsMountPointResponse) {} +} + +// Context of the paths used for path prefix validation +enum PathContext { + // Indicates the kubelet-csi-plugins-path parameter of csi-proxy be used as + // the path context. This may be used while handling NodeStageVolume where + // a volume may need to be mounted at a plugin-specific path like: + // kubelet\plugins\kubernetes.io\csi\pv\\globalmount + PLUGIN = 0; + // Indicates the kubelet-pod-path parameter of csi-proxy be used as the path + // context. This may be used while handling NodePublishVolume where a staged + // volume may be need to be symlinked to a pod-specific path like: + // kubelet\pods\\volumes\kubernetes.io~csi\\mount + POD = 1; +} + +message PathExistsRequest { + // The path whose existence we want to check in the host's filesystem + string path = 1; + + // Context of the path parameter. + // This is used to validate prefix for absolute paths passed + PathContext context = 2; +} + +message PathExistsResponse { + // Error message if any. Empty string indicates success + string error = 1; + + // Indicates whether the path in PathExistsRequest exists in the host's filesystem + bool exists = 2; +} + +message MkdirRequest { + // The path to create in the host's filesystem. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // Non-existent parent directories in the path will be automatically created. + // Directories will be created with Read and Write privileges of the Windows + // User account under which csi-proxy is started (typically LocalSystem). + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // Depending on the context parameter of this function, the path prefix needs + // to match the paths specified either as kubelet-csi-plugins-path + // or as kubelet-pod-path parameters of csi-proxy. + // The path parameter cannot already exist in the host's filesystem. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // Maximum path length will be capped to 260 characters. + string path = 1; + + // Context of the path parameter. + // This is used to validate prefix for absolute paths passed + PathContext context = 2; +} + +message MkdirResponse { + // Error message if any. Empty string indicates success + string error = 1; +} + +message RmdirRequest { + // The path to remove in the host's filesystem. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // Depending on the context parameter of this function, the path prefix needs + // to match the paths specified either as kubelet-csi-plugins-path + // or as kubelet-pod-path parameters of csi-proxy. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // Path cannot be a file of type symlink. + // Maximum path length will be capped to 260 characters. + string path = 1; + + // Context of the path parameter. + // This is used to validate prefix for absolute paths passed + PathContext context = 2; + + // Force remove all contents under path (if any). + bool force = 3; +} + +message RmdirResponse { + // Error message if any. Empty string indicates success + string error = 1; +} + +message LinkPathRequest { + // The path where the symlink is created in the host's filesystem. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // The path prefix needs needs to match the paths specified as + // kubelet-csi-plugins-path parameter of csi-proxy. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // source_path cannot already exist in the host filesystem. + // Maximum path length will be capped to 260 characters. + string source_path = 1; + + // Target path in the host's filesystem used for the symlink creation. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // The path prefix needs to match the paths specified as + // kubelet-pod-path parameter of csi-proxy. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // target_path needs to exist as a directory in the host that is empty. + // target_path cannot be a symbolic link. + // Maximum path length will be capped to 260 characters. + string target_path = 2; +} + +message LinkPathResponse { + // Error message if any. Empty string indicates success + string error = 1; +} + +message IsMountPointRequest { + // The path whose existence we want to check in the host's filesystem + string path = 1; +} + +message IsMountPointResponse { + // Error message if any. Empty string indicates success + string error = 1; + + // Indicates whether the path in PathExistsRequest exists in the host's filesystem + bool is_mount_point = 2; +} diff --git a/csi_proxy_proto/filesystem/v1beta1/api.proto b/csi_proxy_proto/filesystem/v1beta1/api.proto new file mode 100644 index 0000000..3b4c0ab --- /dev/null +++ b/csi_proxy_proto/filesystem/v1beta1/api.proto @@ -0,0 +1,168 @@ +syntax = "proto3"; + +package v1beta1; + +service Filesystem { + // PathExists checks if the requested path exists in the host's filesystem + rpc PathExists(PathExistsRequest) returns (PathExistsResponse) {} + + // Mkdir creates a directory at the requested path in the host's filesystem + rpc Mkdir(MkdirRequest) returns (MkdirResponse) {} + + // Rmdir removes the directory at the requested path in the host's filesystem. + // This may be used for unlinking a symlink created through LinkPath + rpc Rmdir(RmdirRequest) returns (RmdirResponse) {} + + // LinkPath creates a local directory symbolic link between a source path + // and target path in the host's filesystem + rpc LinkPath(LinkPathRequest) returns (LinkPathResponse) {} + + //IsMountPoint checks if a given path is mount or not + rpc IsMountPoint(IsMountPointRequest) returns (IsMountPointResponse) {} +} + +// Context of the paths used for path prefix validation +enum PathContext { + // Indicates the kubelet-csi-plugins-path parameter of csi-proxy be used as + // the path context. This may be used while handling NodeStageVolume where + // a volume may need to be mounted at a plugin-specific path like: + // kubelet\plugins\kubernetes.io\csi\pv\\globalmount + PLUGIN = 0; + // Indicates the kubelet-pod-path parameter of csi-proxy be used as the path + // context. This may be used while handling NodePublishVolume where a staged + // volume may be need to be symlinked to a pod-specific path like: + // kubelet\pods\\volumes\kubernetes.io~csi\\mount + POD = 1; +} + +message PathExistsRequest { + // The path whose existence we want to check in the host's filesystem + string path = 1; + + // Context of the path parameter. + // This is used to validate prefix for absolute paths passed + PathContext context = 2; +} + +message PathExistsResponse { + // Error message if any. Empty string indicates success + string error = 1; + + // Indicates whether the path in PathExistsRequest exists in the host's filesystem + bool exists = 2; +} + +message MkdirRequest { + // The path to create in the host's filesystem. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // Non-existent parent directories in the path will be automatically created. + // Directories will be created with Read and Write privileges of the Windows + // User account under which csi-proxy is started (typically LocalSystem). + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // Depending on the context parameter of this function, the path prefix needs + // to match the paths specified either as kubelet-csi-plugins-path + // or as kubelet-pod-path parameters of csi-proxy. + // The path parameter cannot already exist in the host's filesystem. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // Maximum path length will be capped to 260 characters. + string path = 1; + + // Context of the path parameter. + // This is used to validate prefix for absolute paths passed + PathContext context = 2; +} + +message MkdirResponse { + // Error message if any. Empty string indicates success + string error = 1; +} + +message RmdirRequest { + // The path to remove in the host's filesystem. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // Depending on the context parameter of this function, the path prefix needs + // to match the paths specified either as kubelet-csi-plugins-path + // or as kubelet-pod-path parameters of csi-proxy. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // Path cannot be a file of type symlink. + // Maximum path length will be capped to 260 characters. + string path = 1; + + // Context of the path parameter. + // This is used to validate prefix for absolute paths passed + PathContext context = 2; + + // Force remove all contents under path (if any). + bool force = 3; +} + +message RmdirResponse { + // Error message if any. Empty string indicates success + string error = 1; +} + +message LinkPathRequest { + // The path where the symlink is created in the host's filesystem. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // The path prefix needs needs to match the paths specified as + // kubelet-csi-plugins-path parameter of csi-proxy. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // source_path cannot already exist in the host filesystem. + // Maximum path length will be capped to 260 characters. + string source_path = 1; + + // Target path in the host's filesystem used for the symlink creation. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // The path prefix needs to match the paths specified as + // kubelet-pod-path parameter of csi-proxy. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // target_path needs to exist as a directory in the host that is empty. + // target_path cannot be a symbolic link. + // Maximum path length will be capped to 260 characters. + string target_path = 2; +} + +message LinkPathResponse { + // Error message if any. Empty string indicates success + string error = 1; +} + +message IsMountPointRequest { + // The path whose existence we want to check in the host's filesystem + string path = 1; +} + +message IsMountPointResponse { + // Error message if any. Empty string indicates success + string error = 1; + + // Indicates whether the path in PathExistsRequest exists in the host's filesystem + bool is_mount_point = 2; +} diff --git a/csi_proxy_proto/filesystem/v1beta2/api.proto b/csi_proxy_proto/filesystem/v1beta2/api.proto new file mode 100644 index 0000000..fdaf391 --- /dev/null +++ b/csi_proxy_proto/filesystem/v1beta2/api.proto @@ -0,0 +1,136 @@ +syntax = "proto3"; + +package v1beta2; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/filesystem/v1beta2"; + +service Filesystem { + // PathExists checks if the requested path exists in the host filesystem. + rpc PathExists(PathExistsRequest) returns (PathExistsResponse) {} + + // Mkdir creates a directory at the requested path in the host filesystem. + rpc Mkdir(MkdirRequest) returns (MkdirResponse) {} + + // Rmdir removes the directory at the requested path in the host filesystem. + // This may be used for unlinking a symlink created through CreateSymlink. + rpc Rmdir(RmdirRequest) returns (RmdirResponse) {} + + // CreateSymlink creates a symbolic link called target_path that points to source_path + // in the host filesystem (target_path is the name of the symbolic link created, + // source_path is the existing path). + rpc CreateSymlink(CreateSymlinkRequest) returns (CreateSymlinkResponse) {} + + // IsSymlink checks if a given path is a symlink. + rpc IsSymlink(IsSymlinkRequest) returns (IsSymlinkResponse) {} +} + +message PathExistsRequest { + // The path whose existence we want to check in the host's filesystem + string path = 1; +} + +message PathExistsResponse { + // Indicates whether the path in PathExistsRequest exists in the host's filesystem + bool exists = 1; +} + +message MkdirRequest { + // The path to create in the host's filesystem. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // Non-existent parent directories in the path will be automatically created. + // Directories will be created with Read and Write privileges of the Windows + // User account under which csi-proxy is started (typically LocalSystem). + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // Depending on the context parameter of this function, the path prefix needs + // to match the paths specified either as kubelet-csi-plugins-path + // or as kubelet-pod-path parameters of csi-proxy. + // The path parameter cannot already exist in the host's filesystem. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // Maximum path length will be capped to 260 characters. + string path = 1; +} + +message MkdirResponse { + // Intentionally empty. +} + +message RmdirRequest { + // The path to remove in the host's filesystem. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // Depending on the context parameter of this function, the path prefix needs + // to match the paths specified either as kubelet-csi-plugins-path + // or as kubelet-pod-path parameters of csi-proxy. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // Path cannot be a file of type symlink. + // Maximum path length will be capped to 260 characters. + string path = 1; + + // Force remove all contents under path (if any). + bool force = 2; +} + +message RmdirResponse { + // Intentionally empty. +} + +message CreateSymlinkRequest { + // The path of the existing directory to be linked. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // The path prefix needs needs to match the paths specified as + // kubelet-csi-plugins-path parameter of csi-proxy. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // source_path cannot already exist in the host filesystem. + // Maximum path length will be capped to 260 characters. + string source_path = 1; + + // Target path is the location of the new directory entry to be created in the host's filesystem. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // The path prefix needs to match the paths specified as + // kubelet-pod-path parameter of csi-proxy. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // target_path needs to exist as a directory in the host that is empty. + // target_path cannot be a symbolic link. + // Maximum path length will be capped to 260 characters. + string target_path = 2; +} + +message CreateSymlinkResponse { + // Intentionally empty. +} + +message IsSymlinkRequest { + // The path whose existence as a symlink we want to check in the host's filesystem. + string path = 1; +} + +message IsSymlinkResponse { + // Indicates whether the path in IsSymlinkRequest is a symlink. + bool is_symlink = 1; +} diff --git a/csi_proxy_proto/filesystem/v2alpha1/api.proto b/csi_proxy_proto/filesystem/v2alpha1/api.proto new file mode 100644 index 0000000..b46d736 --- /dev/null +++ b/csi_proxy_proto/filesystem/v2alpha1/api.proto @@ -0,0 +1,163 @@ +syntax = "proto3"; + +package v2alpha1; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/filesystem/v2alpha1"; + +service Filesystem { + // PathExists checks if the requested path exists in the host filesystem. + rpc PathExists(PathExistsRequest) returns (PathExistsResponse) {} + + // Mkdir creates a directory at the requested path in the host filesystem. + rpc Mkdir(MkdirRequest) returns (MkdirResponse) {} + + // Rmdir removes the directory at the requested path in the host filesystem. + // This may be used for unlinking a symlink created through CreateSymlink. + rpc Rmdir(RmdirRequest) returns (RmdirResponse) {} + + // RmdirContents removes the contents of a directory in the host filesystem. + // Unlike Rmdir it won't delete the requested path, it'll only delete its contents. + rpc RmdirContents(RmdirContentsRequest) returns (RmdirContentsResponse) {} + + // CreateSymlink creates a symbolic link called target_path that points to source_path + // in the host filesystem (target_path is the name of the symbolic link created, + // source_path is the existing path). + rpc CreateSymlink(CreateSymlinkRequest) returns (CreateSymlinkResponse) {} + + // IsSymlink checks if a given path is a symlink. + rpc IsSymlink(IsSymlinkRequest) returns (IsSymlinkResponse) {} +} + +message PathExistsRequest { + // The path whose existence we want to check in the host's filesystem + string path = 1; +} + +message PathExistsResponse { + // Indicates whether the path in PathExistsRequest exists in the host's filesystem + bool exists = 1; +} + +message MkdirRequest { + // The path to create in the host's filesystem. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // Non-existent parent directories in the path will be automatically created. + // Directories will be created with Read and Write privileges of the Windows + // User account under which csi-proxy is started (typically LocalSystem). + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // Depending on the context parameter of this function, the path prefix needs + // to match the paths specified either as kubelet-csi-plugins-path + // or as kubelet-pod-path parameters of csi-proxy. + // The path parameter cannot already exist in the host's filesystem. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // Maximum path length will be capped to 260 characters. + string path = 1; +} + +message MkdirResponse { + // Intentionally empty. +} + +message RmdirRequest { + // The path to remove in the host's filesystem. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // Depending on the context parameter of this function, the path prefix needs + // to match the paths specified either as kubelet-csi-plugins-path + // or as kubelet-pod-path parameters of csi-proxy. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // Path cannot be a file of type symlink. + // Maximum path length will be capped to 260 characters. + string path = 1; + + // Force remove all contents under path (if any). + bool force = 2; +} + +message RmdirResponse { + // Intentionally empty. +} + +message RmdirContentsRequest { + // The path whose contents will be removed in the host's filesystem. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // Depending on the context parameter of this function, the path prefix needs + // to match the paths specified either as kubelet-csi-plugins-path + // or as kubelet-pod-path parameters of csi-proxy. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // Path cannot be a file of type symlink. + // Maximum path length will be capped to 260 characters. + string path = 1; +} + +message RmdirContentsResponse { + // Intentionally empty. +} + +message CreateSymlinkRequest { + // The path of the existing directory to be linked. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // The path prefix needs needs to match the paths specified as + // kubelet-csi-plugins-path parameter of csi-proxy. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // source_path cannot already exist in the host filesystem. + // Maximum path length will be capped to 260 characters. + string source_path = 1; + + // Target path is the location of the new directory entry to be created in the host's filesystem. + // All special characters allowed by Windows in path names will be allowed + // except for restrictions noted below. For details, please check: + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // + // Restrictions: + // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. + // The path prefix needs to match the paths specified as + // kubelet-pod-path parameter of csi-proxy. + // UNC paths of the form "\\server\share\path\file" are not allowed. + // All directory separators need to be backslash character: "\". + // Characters: .. / : | ? * in the path are not allowed. + // target_path needs to exist as a directory in the host that is empty. + // target_path cannot be a symbolic link. + // Maximum path length will be capped to 260 characters. + string target_path = 2; +} + +message CreateSymlinkResponse { + // Intentionally empty. +} + +message IsSymlinkRequest { + // The path whose existence as a symlink we want to check in the host's filesystem. + string path = 1; +} + +message IsSymlinkResponse { + // Indicates whether the path in IsSymlinkRequest is a symlink. + bool is_symlink = 1; +} diff --git a/csi_proxy_proto/iscsi/v1alpha1/api.proto b/csi_proxy_proto/iscsi/v1alpha1/api.proto new file mode 100644 index 0000000..b667f8d --- /dev/null +++ b/csi_proxy_proto/iscsi/v1alpha1/api.proto @@ -0,0 +1,153 @@ +syntax = "proto3"; + +package v1alpha1; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/iscsi/v1alpha1"; + +service Iscsi { + // AddTargetPortal registers an iSCSI target network address for later + // discovery. + // AddTargetPortal currently does not support selecting different NICs or + // a different iSCSI initiator (e.g a hardware initiator). This means that + // Windows will select the initiator NIC and instance on its own. + rpc AddTargetPortal(AddTargetPortalRequest) + returns (AddTargetPortalResponse) {} + + // DiscoverTargetPortal initiates discovery on an iSCSI target network address + // and returns discovered IQNs. + rpc DiscoverTargetPortal(DiscoverTargetPortalRequest) + returns (DiscoverTargetPortalResponse) {} + + // RemoveTargetPortal removes an iSCSI target network address registration. + rpc RemoveTargetPortal(RemoveTargetPortalRequest) + returns (RemoveTargetPortalResponse) {} + + // ListTargetPortal lists all currently registered iSCSI target network + // addresses. + rpc ListTargetPortals(ListTargetPortalsRequest) + returns (ListTargetPortalsResponse) {} + + // ConnectTarget connects to an iSCSI Target + rpc ConnectTarget(ConnectTargetRequest) returns (ConnectTargetResponse) {} + + // DisconnectTarget disconnects from an iSCSI Target + rpc DisconnectTarget(DisconnectTargetRequest) + returns (DisconnectTargetResponse) {} + + // GetTargetDisks returns the disk addresses that correspond to an iSCSI + // target + rpc GetTargetDisks(GetTargetDisksRequest) returns (GetTargetDisksResponse) {} +} + +// TargetPortal is an address and port pair for a specific iSCSI storage +// target. +message TargetPortal { + // iSCSI Target (server) address + string target_address = 1; + + // iSCSI Target port (default iSCSI port is 3260) + uint32 target_port = 2; +} + +message AddTargetPortalRequest { + // iSCSI Target Portal to register in the initiator + TargetPortal target_portal = 1; +} + +message AddTargetPortalResponse { + // Intentionally empty +} + +message DiscoverTargetPortalRequest { + // iSCSI Target Portal on which to initiate discovery + TargetPortal target_portal = 1; +} + +message DiscoverTargetPortalResponse { + // List of discovered IQN addresses + // follows IQN format: iqn.yyyy-mm.naming-authority:unique-name + repeated string iqns = 1; +} + +message RemoveTargetPortalRequest { + // iSCSI Target Portal + TargetPortal target_portal = 1; +} + +message RemoveTargetPortalResponse { + // Intentionally empty +} + +message ListTargetPortalsRequest { + // Intentionally empty +} + +message ListTargetPortalsResponse { + // A list of Target Portals currently registered in the initiator + repeated TargetPortal target_portals = 1; +} + +enum AuthenticationType { + // No authentication is used + NONE = 0; + + // One way CHAP authentication. The target authenticates the initiator. + ONE_WAY_CHAP = 1; + + // Mutual CHAP authentication. The target and initiator authenticate each + // other. + MUTUAL_CHAP = 2; +} + +message ConnectTargetRequest { + // Target portal to which the initiator will connect + TargetPortal target_portal = 1; + + // IQN of the iSCSI Target + string iqn = 2; + + // Connection authentication type, None by default + // + // One Way Chap uses the chap_username and chap_secret + // fields mentioned below to authenticate the initiator. + // + // Mutual Chap uses both the user/secret mentioned below + // and the Initiator Chap Secret to authenticate the target and initiator. + AuthenticationType auth_type = 3; + + // CHAP Username used to authenticate the initiator + string chap_username = 4; + + // CHAP password used to authenticate the initiator + string chap_secret = 5; +} + +message ConnectTargetResponse { + // Intentionally empty +} + +message GetTargetDisksRequest { + // Target portal whose disks will be queried + TargetPortal target_portal = 1; + + // IQN of the iSCSI Target + string iqn = 2; +} + +message GetTargetDisksResponse { + // List composed of disk ids (numbers) that are associated with the + // iSCSI target + repeated string diskIDs = 1; +} + +message DisconnectTargetRequest { + // Target portal from which initiator will disconnect + TargetPortal target_portal = 1; + + // IQN of the iSCSI Target + string iqn = 2; +} + +message DisconnectTargetResponse { + // Intentionally empty +} diff --git a/csi_proxy_proto/iscsi/v1alpha2/api.proto b/csi_proxy_proto/iscsi/v1alpha2/api.proto new file mode 100644 index 0000000..baa5bee --- /dev/null +++ b/csi_proxy_proto/iscsi/v1alpha2/api.proto @@ -0,0 +1,175 @@ +syntax = "proto3"; + +package v1alpha2; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/iscsi/v1alpha2"; + +service Iscsi { + // AddTargetPortal registers an iSCSI target network address for later + // discovery. + // AddTargetPortal currently does not support selecting different NICs or + // a different iSCSI initiator (e.g a hardware initiator). This means that + // Windows will select the initiator NIC and instance on its own. + rpc AddTargetPortal(AddTargetPortalRequest) + returns (AddTargetPortalResponse) {} + + // DiscoverTargetPortal initiates discovery on an iSCSI target network address + // and returns discovered IQNs. + rpc DiscoverTargetPortal(DiscoverTargetPortalRequest) + returns (DiscoverTargetPortalResponse) {} + + // RemoveTargetPortal removes an iSCSI target network address registration. + rpc RemoveTargetPortal(RemoveTargetPortalRequest) + returns (RemoveTargetPortalResponse) {} + + // ListTargetPortal lists all currently registered iSCSI target network + // addresses. + rpc ListTargetPortals(ListTargetPortalsRequest) + returns (ListTargetPortalsResponse) {} + + // ConnectTarget connects to an iSCSI Target + rpc ConnectTarget(ConnectTargetRequest) returns (ConnectTargetResponse) {} + + // DisconnectTarget disconnects from an iSCSI Target + rpc DisconnectTarget(DisconnectTargetRequest) + returns (DisconnectTargetResponse) {} + + // GetTargetDisks returns the disk addresses that correspond to an iSCSI + // target + rpc GetTargetDisks(GetTargetDisksRequest) returns (GetTargetDisksResponse) {} + + // SetMutualChapSecret sets the default CHAP secret that all initiators on + // this machine (node) use to authenticate the target on mutual CHAP + // authentication. + // NOTE: This method affects global node state and should only be used + // with consideration to other CSI drivers that run concurrently. + rpc SetMutualChapSecret(SetMutualChapSecretRequest) + returns (SetMutualChapSecretResponse) {} +} + +// TargetPortal is an address and port pair for a specific iSCSI storage +// target. +message TargetPortal { + // iSCSI Target (server) address + string target_address = 1; + + // iSCSI Target port (default iSCSI port is 3260) + uint32 target_port = 2; +} + +message AddTargetPortalRequest { + // iSCSI Target Portal to register in the initiator + TargetPortal target_portal = 1; +} + +message AddTargetPortalResponse { + // Intentionally empty +} + +message DiscoverTargetPortalRequest { + // iSCSI Target Portal on which to initiate discovery + TargetPortal target_portal = 1; +} + +message DiscoverTargetPortalResponse { + // List of discovered IQN addresses + // follows IQN format: iqn.yyyy-mm.naming-authority:unique-name + repeated string iqns = 1; +} + +message RemoveTargetPortalRequest { + // iSCSI Target Portal + TargetPortal target_portal = 1; +} + +message RemoveTargetPortalResponse { + // Intentionally empty +} + +message ListTargetPortalsRequest { + // Intentionally empty +} + +message ListTargetPortalsResponse { + // A list of Target Portals currently registered in the initiator + repeated TargetPortal target_portals = 1; +} + +// iSCSI logon authentication type +enum AuthenticationType { + // No authentication is used + NONE = 0; + + // One way CHAP authentication. The target authenticates the initiator. + ONE_WAY_CHAP = 1; + + // Mutual CHAP authentication. The target and initiator authenticate each + // other. + MUTUAL_CHAP = 2; +} + +message ConnectTargetRequest { + // Target portal to which the initiator will connect + TargetPortal target_portal = 1; + + // IQN of the iSCSI Target + string iqn = 2; + + // Connection authentication type, None by default + // + // One Way Chap uses the chap_username and chap_secret + // fields mentioned below to authenticate the initiator. + // + // Mutual Chap uses both the user/secret mentioned below + // and the Initiator Chap Secret (See `SetMutualChapSecret`) + // to authenticate the target and initiator. + AuthenticationType auth_type = 3; + + // CHAP Username used to authenticate the initiator + string chap_username = 4; + + // CHAP password used to authenticate the initiator + string chap_secret = 5; +} + +message ConnectTargetResponse { + // Intentionally empty +} + +message GetTargetDisksRequest { + // Target portal whose disks will be queried + TargetPortal target_portal = 1; + + // IQN of the iSCSI Target + string iqn = 2; +} + +message GetTargetDisksResponse { + // List composed of disk ids (numbers) that are associated with the + // iSCSI target + repeated string diskIDs = 1; +} + +message DisconnectTargetRequest { + // Target portal from which initiator will disconnect + TargetPortal target_portal = 1; + + // IQN of the iSCSI Target + string iqn = 2; +} + +message DisconnectTargetResponse { + // Intentionally empty +} + +message SetMutualChapSecretRequest { + // the default CHAP secret that all initiators on this machine (node) use to + // authenticate the target on mutual CHAP authentication. + // Must be at least 12 byte long for non-Ipsec connections, at least one + // byte long for Ipsec connections, and at most 16 bytes long. + string MutualChapSecret = 1; +} + +message SetMutualChapSecretResponse { + // Intentionally empty +} diff --git a/csi_proxy_proto/smb/v1/api.proto b/csi_proxy_proto/smb/v1/api.proto new file mode 100644 index 0000000..6f10635 --- /dev/null +++ b/csi_proxy_proto/smb/v1/api.proto @@ -0,0 +1,58 @@ +syntax = "proto3"; + +package v1; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/smb/v1"; + +service Smb { + // NewSmbGlobalMapping creates an SMB mapping on the SMB client to an SMB share. + rpc NewSmbGlobalMapping(NewSmbGlobalMappingRequest) returns (NewSmbGlobalMappingResponse) {} + + // RemoveSmbGlobalMapping removes the SMB mapping to an SMB share. + rpc RemoveSmbGlobalMapping(RemoveSmbGlobalMappingRequest) returns (RemoveSmbGlobalMappingResponse) {} +} + + +message NewSmbGlobalMappingRequest { + // A remote SMB share to mount + // All unicode characters allowed in SMB server name specifications are + // permitted except for restrictions below + // + // Restrictions: + // SMB remote path specified in the format: \\server-name\sharename, \\server.fqdn\sharename or \\a.b.c.d\sharename + // If not an IP address, share name has to be a valid DNS name. + // UNC specifications to local paths or prefix: \\?\ is not allowed. + // Characters: + [ ] " / : ; | < > , ? * = $ are not allowed. + string remote_path = 1; + + // Optional local path to mount the smb on + string local_path = 2; + + // Username credential associated with the share + string username = 3; + + // Password credential associated with the share + string password = 4; +} + +message NewSmbGlobalMappingResponse { + // Intentionally empty. +} + + +message RemoveSmbGlobalMappingRequest { + // A remote SMB share mapping to remove + // All unicode characters allowed in SMB server name specifications are + // permitted except for restrictions below + // + // Restrictions: + // SMB share specified in the format: \\server-name\sharename, \\server.fqdn\sharename or \\a.b.c.d\sharename + // If not an IP address, share name has to be a valid DNS name. + // UNC specifications to local paths or prefix: \\?\ is not allowed. + // Characters: + [ ] " / : ; | < > , ? * = $ are not allowed. + string remote_path = 1; +} + +message RemoveSmbGlobalMappingResponse { + // Intentionally empty. +} diff --git a/csi_proxy_proto/smb/v1alpha1/api.proto b/csi_proxy_proto/smb/v1alpha1/api.proto new file mode 100644 index 0000000..f4d96e9 --- /dev/null +++ b/csi_proxy_proto/smb/v1alpha1/api.proto @@ -0,0 +1,59 @@ +syntax = "proto3"; + +package v1alpha1; + +service Smb { + // NewSmbGlobalMapping creates an SMB mapping on the SMB client to an SMB share. + rpc NewSmbGlobalMapping(NewSmbGlobalMappingRequest) returns (NewSmbGlobalMappingResponse) {} + + // RemoveSmbGlobalMapping removes the SMB mapping to an SMB share. + rpc RemoveSmbGlobalMapping(RemoveSmbGlobalMappingRequest) returns (RemoveSmbGlobalMappingResponse) {} +} + + +message NewSmbGlobalMappingRequest { + // A remote SMB share to mount + // All unicode characters allowed in SMB server name specifications are + // permitted except for restrictions below + // + // Restrictions: + // SMB remote path specified in the format: \\server-name\sharename, \\server.fqdn\sharename or \\a.b.c.d\sharename + // If not an IP address, share name has to be a valid DNS name. + // UNC specifications to local paths or prefix: \\?\ is not allowed. + // Characters: + [ ] " / : ; | < > , ? * = $ are not allowed. + string remote_path = 1; + // Optional local path to mount the smb on + string local_path = 2; + + // Username credential associated with the share + string username = 3; + + // Password credential associated with the share + string password = 4; +} + +message NewSmbGlobalMappingResponse { + // Windows error code + // Success is represented as 0 + string error = 1; +} + + +message RemoveSmbGlobalMappingRequest { + // A remote SMB share mapping to remove + // All unicode characters allowed in SMB server name specifications are + // permitted except for restrictions below + // + // Restrictions: + // SMB share specified in the format: \\server-name\sharename, \\server.fqdn\sharename or \\a.b.c.d\sharename + // If not an IP address, share name has to be a valid DNS name. + // UNC specifications to local paths or prefix: \\?\ is not allowed. + // Characters: + [ ] " / : ; | < > , ? * = $ are not allowed. + string remote_path = 1; +} + +message RemoveSmbGlobalMappingResponse { + // Windows error code + // Success is represented as 0 + string error = 1; +} \ No newline at end of file diff --git a/csi_proxy_proto/smb/v1beta1/api.proto b/csi_proxy_proto/smb/v1beta1/api.proto new file mode 100644 index 0000000..8a2b515 --- /dev/null +++ b/csi_proxy_proto/smb/v1beta1/api.proto @@ -0,0 +1,59 @@ +syntax = "proto3"; + +package v1beta1; + +service Smb { + // NewSmbGlobalMapping creates an SMB mapping on the SMB client to an SMB share. + rpc NewSmbGlobalMapping(NewSmbGlobalMappingRequest) returns (NewSmbGlobalMappingResponse) {} + + // RemoveSmbGlobalMapping removes the SMB mapping to an SMB share. + rpc RemoveSmbGlobalMapping(RemoveSmbGlobalMappingRequest) returns (RemoveSmbGlobalMappingResponse) {} +} + + +message NewSmbGlobalMappingRequest { + // A remote SMB share to mount + // All unicode characters allowed in SMB server name specifications are + // permitted except for restrictions below + // + // Restrictions: + // SMB remote path specified in the format: \\server-name\sharename, \\server.fqdn\sharename or \\a.b.c.d\sharename + // If not an IP address, share name has to be a valid DNS name. + // UNC specifications to local paths or prefix: \\?\ is not allowed. + // Characters: + [ ] " / : ; | < > , ? * = $ are not allowed. + string remote_path = 1; + // Optional local path to mount the smb on + string local_path = 2; + + // Username credential associated with the share + string username = 3; + + // Password credential associated with the share + string password = 4; +} + +message NewSmbGlobalMappingResponse { + // Windows error code + // Success is represented as 0 + string error = 1; +} + + +message RemoveSmbGlobalMappingRequest { + // A remote SMB share mapping to remove + // All unicode characters allowed in SMB server name specifications are + // permitted except for restrictions below + // + // Restrictions: + // SMB share specified in the format: \\server-name\sharename, \\server.fqdn\sharename or \\a.b.c.d\sharename + // If not an IP address, share name has to be a valid DNS name. + // UNC specifications to local paths or prefix: \\?\ is not allowed. + // Characters: + [ ] " / : ; | < > , ? * = $ are not allowed. + string remote_path = 1; +} + +message RemoveSmbGlobalMappingResponse { + // Windows error code + // Success is represented as 0 + string error = 1; +} diff --git a/csi_proxy_proto/smb/v1beta2/api.proto b/csi_proxy_proto/smb/v1beta2/api.proto new file mode 100644 index 0000000..ff28b11 --- /dev/null +++ b/csi_proxy_proto/smb/v1beta2/api.proto @@ -0,0 +1,58 @@ +syntax = "proto3"; + +package v1beta2; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/smb/v1beta2"; + +service Smb { + // NewSmbGlobalMapping creates an SMB mapping on the SMB client to an SMB share. + rpc NewSmbGlobalMapping(NewSmbGlobalMappingRequest) returns (NewSmbGlobalMappingResponse) {} + + // RemoveSmbGlobalMapping removes the SMB mapping to an SMB share. + rpc RemoveSmbGlobalMapping(RemoveSmbGlobalMappingRequest) returns (RemoveSmbGlobalMappingResponse) {} +} + + +message NewSmbGlobalMappingRequest { + // A remote SMB share to mount + // All unicode characters allowed in SMB server name specifications are + // permitted except for restrictions below + // + // Restrictions: + // SMB remote path specified in the format: \\server-name\sharename, \\server.fqdn\sharename or \\a.b.c.d\sharename + // If not an IP address, share name has to be a valid DNS name. + // UNC specifications to local paths or prefix: \\?\ is not allowed. + // Characters: + [ ] " / : ; | < > , ? * = $ are not allowed. + string remote_path = 1; + + // Optional local path to mount the smb on + string local_path = 2; + + // Username credential associated with the share + string username = 3; + + // Password credential associated with the share + string password = 4; +} + +message NewSmbGlobalMappingResponse { + // Intentionally empty. +} + + +message RemoveSmbGlobalMappingRequest { + // A remote SMB share mapping to remove + // All unicode characters allowed in SMB server name specifications are + // permitted except for restrictions below + // + // Restrictions: + // SMB share specified in the format: \\server-name\sharename, \\server.fqdn\sharename or \\a.b.c.d\sharename + // If not an IP address, share name has to be a valid DNS name. + // UNC specifications to local paths or prefix: \\?\ is not allowed. + // Characters: + [ ] " / : ; | < > , ? * = $ are not allowed. + string remote_path = 1; +} + +message RemoveSmbGlobalMappingResponse { + // Intentionally empty. +} diff --git a/csi_proxy_proto/system/v1alpha1/api.proto b/csi_proxy_proto/system/v1alpha1/api.proto new file mode 100644 index 0000000..d04923e --- /dev/null +++ b/csi_proxy_proto/system/v1alpha1/api.proto @@ -0,0 +1,93 @@ +syntax = "proto3"; + +package v1alpha1; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/system/v1alpha1"; + +service System { + // GetBIOSSerialNumber returns the device's serial number + rpc GetBIOSSerialNumber(GetBIOSSerialNumberRequest) + returns (GetBIOSSerialNumberResponse) {} + + // StartService starts a Windows service + // NOTE: This method affects global node state and should only be used + // with consideration to other CSI drivers that run concurrently. + rpc StartService(StartServiceRequest) returns (StartServiceResponse) {} + + // StopService stops a Windows service + // NOTE: This method affects global node state and should only be used + // with consideration to other CSI drivers that run concurrently. + rpc StopService(StopServiceRequest) returns (StopServiceResponse) {} + + // GetService queries a Windows service state + rpc GetService(GetServiceRequest) returns (GetServiceResponse) {} +} + +message GetBIOSSerialNumberRequest { + // Intentionally empty +} + +message GetBIOSSerialNumberResponse { + // Serial number + string serial_number = 1; +} + +message StartServiceRequest { + // Service name (as listed in System\CCS\Services keys) + string name = 1; +} + +message StartServiceResponse { + // Intentionally empty +} + +message StopServiceRequest { + // Service name (as listed in System\CCS\Services keys) + string name = 1; + + // Forces stopping of services that has dependent services + bool force = 2; +} + +message StopServiceResponse { + // Intentionally empty +} + +// https://docs.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_status#members +enum ServiceStatus { + UNKNOWN = 0; + STOPPED = 1; + START_PENDING = 2; + STOP_PENDING = 3; + RUNNING = 4; + CONTINUE_PENDING = 5; + PAUSE_PENDING = 6; + PAUSED = 7; +} + +// https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-changeserviceconfiga +enum StartType { + BOOT = 0; + SYSTEM = 1; + AUTOMATIC = 2; + MANUAL = 3; + DISABLED = 4; +} + +message GetServiceRequest { + // Service name (as listed in System\CCS\Services keys) + string name = 1; +} + +message GetServiceResponse { + // Service display name + string display_name = 1; + + // Service start type. + // Used to control whether a service will start on boot, and if so on which + // boot phase. + StartType start_type = 2; + + // Service status, e.g. stopped, running, paused + ServiceStatus status = 3; +} diff --git a/csi_proxy_proto/volume/v1/api.proto b/csi_proxy_proto/volume/v1/api.proto new file mode 100644 index 0000000..a065381 --- /dev/null +++ b/csi_proxy_proto/volume/v1/api.proto @@ -0,0 +1,143 @@ +syntax = "proto3"; + +package v1; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/volume/v1"; + +service Volume { + // ListVolumesOnDisk returns the volume IDs (in \\.\Volume{GUID} format) for all volumes from a + // given disk number and partition number (optional) + rpc ListVolumesOnDisk(ListVolumesOnDiskRequest) returns (ListVolumesOnDiskResponse) {} + + // MountVolume mounts the volume at the requested global staging path. + rpc MountVolume(MountVolumeRequest) returns (MountVolumeResponse) {} + + // UnmountVolume flushes data cache to disk and removes the global staging path. + rpc UnmountVolume(UnmountVolumeRequest) returns (UnmountVolumeResponse) {} + + // IsVolumeFormatted checks if a volume is formatted. + rpc IsVolumeFormatted(IsVolumeFormattedRequest) returns (IsVolumeFormattedResponse) {} + + // FormatVolume formats a volume with NTFS. + rpc FormatVolume(FormatVolumeRequest) returns (FormatVolumeResponse) {} + + // ResizeVolume performs resizing of the partition and file system for a block based volume. + rpc ResizeVolume(ResizeVolumeRequest) returns (ResizeVolumeResponse) {} + + // GetVolumeStats gathers total bytes and used bytes for a volume. + rpc GetVolumeStats(GetVolumeStatsRequest) returns (GetVolumeStatsResponse) {} + + // GetDiskNumberFromVolumeID gets the disk number of the disk where the volume is located. + rpc GetDiskNumberFromVolumeID(GetDiskNumberFromVolumeIDRequest) returns (GetDiskNumberFromVolumeIDResponse ) {} + + // GetVolumeIDFromTargetPath gets the volume id for a given target path. + rpc GetVolumeIDFromTargetPath(GetVolumeIDFromTargetPathRequest) returns (GetVolumeIDFromTargetPathResponse) {} + + // WriteVolumeCache write volume cache to disk. + rpc WriteVolumeCache(WriteVolumeCacheRequest) returns (WriteVolumeCacheResponse) {} +} + +message ListVolumesOnDiskRequest { + // Disk device number of the disk to query for volumes. + uint32 disk_number = 1; + // The partition number (optional), by default it uses the first partition of the disk. + uint32 partition_number = 2; +} + +message ListVolumesOnDiskResponse { + // Volume device IDs of volumes on the specified disk. + repeated string volume_ids = 1; +} + +message MountVolumeRequest { + // Volume device ID of the volume to mount. + string volume_id = 1; + // Path in the host's file system where the volume needs to be mounted. + string target_path = 2; +} + +message MountVolumeResponse { + // Intentionally empty. +} + +message UnmountVolumeRequest { + // Volume device ID of the volume to dismount. + string volume_id = 1; + // Path where the volume has been mounted. + string target_path = 2; +} + +message UnmountVolumeResponse { + // Intentionally empty. +} + +message IsVolumeFormattedRequest { + // Volume device ID of the volume to check. + string volume_id = 1; +} + +message IsVolumeFormattedResponse { + // Is the volume formatted with NTFS. + bool formatted = 1; +} + +message FormatVolumeRequest { + // Volume device ID of the volume to format. + string volume_id = 1; +} + +message FormatVolumeResponse { + // Intentionally empty. +} + +message ResizeVolumeRequest { + // Volume device ID of the volume to resize. + string volume_id = 1; + // New size in bytes of the volume. + int64 size_bytes = 2; +} + +message ResizeVolumeResponse { + // Intentionally empty. +} + +message GetVolumeStatsRequest{ + // Volume device Id of the volume to get the stats for. + string volume_id = 1; +} + +message GetVolumeStatsResponse{ + // Total bytes + int64 total_bytes = 1; + // Used bytes + int64 used_bytes = 2; +} + +message GetDiskNumberFromVolumeIDRequest { + // Volume device ID of the volume to get the disk number for. + string volume_id = 1; +} + +message GetDiskNumberFromVolumeIDResponse { + // Corresponding disk number. + uint32 disk_number = 1; +} + +message GetVolumeIDFromTargetPathRequest { + // The target path. + string target_path = 1; +} + +message GetVolumeIDFromTargetPathResponse { + // The volume device ID. + string volume_id = 1; +} + +message WriteVolumeCacheRequest { + // Volume device ID of the volume to flush the cache. + string volume_id = 1; +} + +message WriteVolumeCacheResponse { + // Intentionally empty. +} diff --git a/csi_proxy_proto/volume/v1alpha1/api.proto b/csi_proxy_proto/volume/v1alpha1/api.proto new file mode 100644 index 0000000..5037859 --- /dev/null +++ b/csi_proxy_proto/volume/v1alpha1/api.proto @@ -0,0 +1,69 @@ +syntax = "proto3"; + +package v1alpha1; + +service Volume { + // ListVolumesOnDisk returns the volume IDs (in \\.\Volume{GUID} format) for + // all volumes on a Disk device + rpc ListVolumesOnDisk(ListVolumesOnDiskRequest) returns (ListVolumesOnDiskResponse) {} + // MountVolume mounts the volume at the requested global staging path + rpc MountVolume(MountVolumeRequest) returns (MountVolumeResponse) {} + // DismountVolume gracefully dismounts a volume + rpc DismountVolume(DismountVolumeRequest) returns (DismountVolumeResponse) {} + // IsVolumeFormatted checks if a volume is formatted with NTFS + rpc IsVolumeFormatted(IsVolumeFormattedRequest) returns (IsVolumeFormattedResponse) {} + // FormatVolume formats a volume with the provided file system + rpc FormatVolume(FormatVolumeRequest) returns (FormatVolumeResponse) {} + // ResizeVolume performs resizing of the partition and file system for a block based volume + rpc ResizeVolume(ResizeVolumeRequest) returns (ResizeVolumeResponse) {} +} +message ListVolumesOnDiskRequest { + // Disk device ID of the disk to query for volumes + string disk_id = 1; +} +message ListVolumesOnDiskResponse { + // Volume device IDs of volumes on the specified disk + repeated string volume_ids = 1; +} +message MountVolumeRequest { + // Volume device ID of the volume to mount + string volume_id = 1; + // Path in the host's file system where the volume needs to be mounted + string path = 2; +} +message MountVolumeResponse { + // Intentionally empty +} +message DismountVolumeRequest { + // Volume device ID of the volume to dismount + string volume_id = 1; + // Path where the volume has been mounted. + string path = 2; +} +message DismountVolumeResponse { + // Intentionally empty +} +message IsVolumeFormattedRequest { + // Volume device ID of the volume to check + string volume_id = 1; +} +message IsVolumeFormattedResponse { + // Is the volume formatted with NTFS + bool formatted = 1; +} +message FormatVolumeRequest { + // Volume device ID of the volume to format + string volume_id = 1; +} +message FormatVolumeResponse { + // Intentionally empty +} +message ResizeVolumeRequest { + // Volume device ID of the volume to dismount + string volume_id = 1; + // New size of the volume + int64 size = 2; +} +message ResizeVolumeResponse { + // Intentionally empty +} \ No newline at end of file diff --git a/csi_proxy_proto/volume/v1beta1/api.proto b/csi_proxy_proto/volume/v1beta1/api.proto new file mode 100644 index 0000000..07eb7b2 --- /dev/null +++ b/csi_proxy_proto/volume/v1beta1/api.proto @@ -0,0 +1,121 @@ +syntax = "proto3"; + +package v1beta1; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/volume/v1beta1"; + +service Volume { + // ListVolumesOnDisk returns the volume IDs (in \\.\Volume{GUID} format) for + // all volumes on a Disk device + rpc ListVolumesOnDisk(ListVolumesOnDiskRequest) returns (ListVolumesOnDiskResponse) {} + // MountVolume mounts the volume at the requested global staging path + rpc MountVolume(MountVolumeRequest) returns (MountVolumeResponse) {} + // DismountVolume gracefully dismounts a volume + rpc DismountVolume(DismountVolumeRequest) returns (DismountVolumeResponse) {} + // IsVolumeFormatted checks if a volume is formatted with NTFS + rpc IsVolumeFormatted(IsVolumeFormattedRequest) returns (IsVolumeFormattedResponse) {} + // FormatVolume formats a volume with the provided file system + rpc FormatVolume(FormatVolumeRequest) returns (FormatVolumeResponse) {} + // ResizeVolume performs resizing of the partition and file system for a block based volume + rpc ResizeVolume(ResizeVolumeRequest) returns (ResizeVolumeResponse) {} + // VolumeStats gathers DiskSize, VolumeSize and VolumeUsedSize for a volume + rpc VolumeStats(VolumeStatsRequest) returns (VolumeStatsResponse) {} + // GetVolumeDiskNumber gets the disk number of the disk where the volume is located + rpc GetVolumeDiskNumber(VolumeDiskNumberRequest) returns (VolumeDiskNumberResponse) {} + // GetVolumeIDFromMount gets the volume id for a given mount + rpc GetVolumeIDFromMount(VolumeIDFromMountRequest) returns (VolumeIDFromMountResponse) {} +} + +message ListVolumesOnDiskRequest { + // Disk device ID of the disk to query for volumes + string disk_id = 1; +} + +message ListVolumesOnDiskResponse { + // Volume device IDs of volumes on the specified disk + repeated string volume_ids = 1; +} + +message MountVolumeRequest { + // Volume device ID of the volume to mount + string volume_id = 1; + // Path in the host's file system where the volume needs to be mounted + string path = 2; +} + +message MountVolumeResponse { + // Intentionally empty +} + +message DismountVolumeRequest { + // Volume device ID of the volume to dismount + string volume_id = 1; + // Path where the volume has been mounted. + string path = 2; +} + +message DismountVolumeResponse { + // Intentionally empty +} + +message IsVolumeFormattedRequest { + // Volume device ID of the volume to check + string volume_id = 1; +} + +message IsVolumeFormattedResponse { + // Is the volume formatted with NTFS + bool formatted = 1; +} + +message FormatVolumeRequest { + // Volume device ID of the volume to format + string volume_id = 1; +} + +message FormatVolumeResponse { + // Intentionally empty +} + +message ResizeVolumeRequest { + // Volume device ID of the volume to dismount + string volume_id = 1; + // New size of the volume + int64 size = 2; +} + +message ResizeVolumeResponse { + // Intentionally empty +} + +message VolumeStatsRequest{ + // Volume device Id of the volume to get the stats for + string volume_id = 1; +} + +message VolumeStatsResponse{ + // Capacity of the volume + int64 volumeSize = 1; + // Used bytes + int64 volumeUsedSize = 2; +} + +message VolumeDiskNumberRequest{ + // Volume device Id of the volume to get the disk number for + string volume_id = 1; +} + +message VolumeDiskNumberResponse{ + // Corresponding disk number + int64 diskNumber = 1; +} + +message VolumeIDFromMountRequest { + // Mount + string mount = 1; +} + +message VolumeIDFromMountResponse { + // Mount + string volume_id = 1; +} diff --git a/csi_proxy_proto/volume/v1beta2/api.proto b/csi_proxy_proto/volume/v1beta2/api.proto new file mode 100644 index 0000000..c88e1f5 --- /dev/null +++ b/csi_proxy_proto/volume/v1beta2/api.proto @@ -0,0 +1,132 @@ +syntax = "proto3"; + +package v1beta2; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/volume/v1beta2"; + +service Volume { + // ListVolumesOnDisk returns the volume IDs (in \\.\Volume{GUID} format) for + // all volumes on a Disk device + rpc ListVolumesOnDisk(ListVolumesOnDiskRequest) returns (ListVolumesOnDiskResponse) {} + // MountVolume mounts the volume at the requested global staging path + rpc MountVolume(MountVolumeRequest) returns (MountVolumeResponse) {} + // DismountVolume gracefully dismounts a volume + rpc DismountVolume(DismountVolumeRequest) returns (DismountVolumeResponse) {} + // IsVolumeFormatted checks if a volume is formatted with NTFS + rpc IsVolumeFormatted(IsVolumeFormattedRequest) returns (IsVolumeFormattedResponse) {} + // FormatVolume formats a volume with the provided file system + rpc FormatVolume(FormatVolumeRequest) returns (FormatVolumeResponse) {} + // ResizeVolume performs resizing of the partition and file system for a block based volume + rpc ResizeVolume(ResizeVolumeRequest) returns (ResizeVolumeResponse) {} + // VolumeStats gathers DiskSize, VolumeSize and VolumeUsedSize for a volume + rpc VolumeStats(VolumeStatsRequest) returns (VolumeStatsResponse) {} + // GetVolumeDiskNumber gets the disk number of the disk where the volume is located + rpc GetVolumeDiskNumber(VolumeDiskNumberRequest) returns (VolumeDiskNumberResponse) {} + // GetVolumeIDFromMount gets the volume id for a given mount + rpc GetVolumeIDFromMount(VolumeIDFromMountRequest) returns (VolumeIDFromMountResponse) {} + // WriteVolumeCache write volume cache to disk + rpc WriteVolumeCache(WriteVolumeCacheRequest) returns (WriteVolumeCacheResponse) {} +} + +message ListVolumesOnDiskRequest { + // Disk device ID of the disk to query for volumes + string disk_id = 1; +} + +message ListVolumesOnDiskResponse { + // Volume device IDs of volumes on the specified disk + repeated string volume_ids = 1; +} + +message MountVolumeRequest { + // Volume device ID of the volume to mount + string volume_id = 1; + // Path in the host's file system where the volume needs to be mounted + string path = 2; +} + +message MountVolumeResponse { + // Intentionally empty +} + +message DismountVolumeRequest { + // Volume device ID of the volume to dismount + string volume_id = 1; + // Path where the volume has been mounted. + string path = 2; +} + +message DismountVolumeResponse { + // Intentionally empty +} + +message IsVolumeFormattedRequest { + // Volume device ID of the volume to check + string volume_id = 1; +} + +message IsVolumeFormattedResponse { + // Is the volume formatted with NTFS + bool formatted = 1; +} + +message FormatVolumeRequest { + // Volume device ID of the volume to format + string volume_id = 1; +} + +message FormatVolumeResponse { + // Intentionally empty +} + +message ResizeVolumeRequest { + // Volume device ID of the volume to dismount + string volume_id = 1; + // New size of the volume + int64 size = 2; +} + +message ResizeVolumeResponse { + // Intentionally empty +} + +message VolumeStatsRequest{ + // Volume device Id of the volume to get the stats for + string volume_id = 1; +} + +message VolumeStatsResponse{ + // Capacity of the volume + int64 volumeSize = 1; + // Used bytes + int64 volumeUsedSize = 2; +} + +message VolumeDiskNumberRequest{ + // Volume device Id of the volume to get the disk number for + string volume_id = 1; +} + +message VolumeDiskNumberResponse{ + // Corresponding disk number + int64 diskNumber = 1; +} + +message VolumeIDFromMountRequest { + // Mount + string mount = 1; +} + +message VolumeIDFromMountResponse { + // Mount + string volume_id = 1; +} + +message WriteVolumeCacheRequest { + // Volume device ID of the volume to flush the cache + string volume_id = 1; +} + +message WriteVolumeCacheResponse { + // Intentionally empty +} \ No newline at end of file diff --git a/csi_proxy_proto/volume/v1beta3/api.proto b/csi_proxy_proto/volume/v1beta3/api.proto new file mode 100644 index 0000000..2340b3c --- /dev/null +++ b/csi_proxy_proto/volume/v1beta3/api.proto @@ -0,0 +1,143 @@ +syntax = "proto3"; + +package v1beta3; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/volume/v1beta3"; + +service Volume { + // ListVolumesOnDisk returns the volume IDs (in \\.\Volume{GUID} format) for all volumes from a + // given disk number and partition number (optional) + rpc ListVolumesOnDisk(ListVolumesOnDiskRequest) returns (ListVolumesOnDiskResponse) {} + + // MountVolume mounts the volume at the requested global staging path. + rpc MountVolume(MountVolumeRequest) returns (MountVolumeResponse) {} + + // UnmountVolume flushes data cache to disk and removes the global staging path. + rpc UnmountVolume(UnmountVolumeRequest) returns (UnmountVolumeResponse) {} + + // IsVolumeFormatted checks if a volume is formatted. + rpc IsVolumeFormatted(IsVolumeFormattedRequest) returns (IsVolumeFormattedResponse) {} + + // FormatVolume formats a volume with NTFS. + rpc FormatVolume(FormatVolumeRequest) returns (FormatVolumeResponse) {} + + // ResizeVolume performs resizing of the partition and file system for a block based volume. + rpc ResizeVolume(ResizeVolumeRequest) returns (ResizeVolumeResponse) {} + + // GetVolumeStats gathers total bytes and used bytes for a volume. + rpc GetVolumeStats(GetVolumeStatsRequest) returns (GetVolumeStatsResponse) {} + + // GetDiskNumberFromVolumeID gets the disk number of the disk where the volume is located. + rpc GetDiskNumberFromVolumeID(GetDiskNumberFromVolumeIDRequest) returns (GetDiskNumberFromVolumeIDResponse ) {} + + // GetVolumeIDFromTargetPath gets the volume id for a given target path. + rpc GetVolumeIDFromTargetPath(GetVolumeIDFromTargetPathRequest) returns (GetVolumeIDFromTargetPathResponse) {} + + // WriteVolumeCache write volume cache to disk. + rpc WriteVolumeCache(WriteVolumeCacheRequest) returns (WriteVolumeCacheResponse) {} +} + +message ListVolumesOnDiskRequest { + // Disk device number of the disk to query for volumes. + uint32 disk_number = 1; + // The partition number (optional), by default it uses the first partition of the disk. + uint32 partition_number = 2; +} + +message ListVolumesOnDiskResponse { + // Volume device IDs of volumes on the specified disk. + repeated string volume_ids = 1; +} + +message MountVolumeRequest { + // Volume device ID of the volume to mount. + string volume_id = 1; + // Path in the host's file system where the volume needs to be mounted. + string target_path = 2; +} + +message MountVolumeResponse { + // Intentionally empty. +} + +message UnmountVolumeRequest { + // Volume device ID of the volume to dismount. + string volume_id = 1; + // Path where the volume has been mounted. + string target_path = 2; +} + +message UnmountVolumeResponse { + // Intentionally empty. +} + +message IsVolumeFormattedRequest { + // Volume device ID of the volume to check. + string volume_id = 1; +} + +message IsVolumeFormattedResponse { + // Is the volume formatted with NTFS. + bool formatted = 1; +} + +message FormatVolumeRequest { + // Volume device ID of the volume to format. + string volume_id = 1; +} + +message FormatVolumeResponse { + // Intentionally empty. +} + +message ResizeVolumeRequest { + // Volume device ID of the volume to resize. + string volume_id = 1; + // New size in bytes of the volume. + int64 size_bytes = 2; +} + +message ResizeVolumeResponse { + // Intentionally empty. +} + +message GetVolumeStatsRequest{ + // Volume device Id of the volume to get the stats for. + string volume_id = 1; +} + +message GetVolumeStatsResponse{ + // Total bytes + int64 total_bytes = 1; + // Used bytes + int64 used_bytes = 2; +} + +message GetDiskNumberFromVolumeIDRequest { + // Volume device ID of the volume to get the disk number for. + string volume_id = 1; +} + +message GetDiskNumberFromVolumeIDResponse { + // Corresponding disk number. + uint32 disk_number = 1; +} + +message GetVolumeIDFromTargetPathRequest { + // The target path. + string target_path = 1; +} + +message GetVolumeIDFromTargetPathResponse { + // The volume device ID. + string volume_id = 1; +} + +message WriteVolumeCacheRequest { + // Volume device ID of the volume to flush the cache. + string volume_id = 1; +} + +message WriteVolumeCacheResponse { + // Intentionally empty. +} diff --git a/csi_proxy_proto/volume/v2alpha1/api.proto b/csi_proxy_proto/volume/v2alpha1/api.proto new file mode 100644 index 0000000..611d4ab --- /dev/null +++ b/csi_proxy_proto/volume/v2alpha1/api.proto @@ -0,0 +1,158 @@ +syntax = "proto3"; + +package v2alpha1; + +option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/volume/v2alpha1"; + +service Volume { + // ListVolumesOnDisk returns the volume IDs (in \\.\Volume{GUID} format) for all volumes from a + // given disk number and partition number (optional) + rpc ListVolumesOnDisk(ListVolumesOnDiskRequest) returns (ListVolumesOnDiskResponse) {} + + // MountVolume mounts the volume at the requested global staging path. + rpc MountVolume(MountVolumeRequest) returns (MountVolumeResponse) {} + + // UnmountVolume flushes data cache to disk and removes the global staging path. + rpc UnmountVolume(UnmountVolumeRequest) returns (UnmountVolumeResponse) {} + + // IsVolumeFormatted checks if a volume is formatted. + rpc IsVolumeFormatted(IsVolumeFormattedRequest) returns (IsVolumeFormattedResponse) {} + + // FormatVolume formats a volume with NTFS. + rpc FormatVolume(FormatVolumeRequest) returns (FormatVolumeResponse) {} + + // ResizeVolume performs resizing of the partition and file system for a block based volume. + rpc ResizeVolume(ResizeVolumeRequest) returns (ResizeVolumeResponse) {} + + // GetVolumeStats gathers total bytes and used bytes for a volume. + rpc GetVolumeStats(GetVolumeStatsRequest) returns (GetVolumeStatsResponse) {} + + // GetDiskNumberFromVolumeID gets the disk number of the disk where the volume is located. + rpc GetDiskNumberFromVolumeID(GetDiskNumberFromVolumeIDRequest) returns (GetDiskNumberFromVolumeIDResponse ) {} + + // GetVolumeIDFromTargetPath gets the volume id for a given target path. + rpc GetVolumeIDFromTargetPath(GetVolumeIDFromTargetPathRequest) returns (GetVolumeIDFromTargetPathResponse) {} + + // GetClosestVolumeIDFromTargetPath gets the closest volume id for a given target path + // by following symlinks and moving up in the filesystem, if after moving up in the filesystem + // we get to a DriveLetter then the volume corresponding to this drive letter is returned instead. + rpc GetClosestVolumeIDFromTargetPath(GetClosestVolumeIDFromTargetPathRequest) returns (GetClosestVolumeIDFromTargetPathResponse) {} + + // WriteVolumeCache write volume cache to disk. + rpc WriteVolumeCache(WriteVolumeCacheRequest) returns (WriteVolumeCacheResponse) {} +} + +message ListVolumesOnDiskRequest { + // Disk device number of the disk to query for volumes. + uint32 disk_number = 1; + // The partition number (optional), by default it uses the first partition of the disk. + uint32 partition_number = 2; +} + +message ListVolumesOnDiskResponse { + // Volume device IDs of volumes on the specified disk. + repeated string volume_ids = 1; +} + +message MountVolumeRequest { + // Volume device ID of the volume to mount. + string volume_id = 1; + // Path in the host's file system where the volume needs to be mounted. + string target_path = 2; +} + +message MountVolumeResponse { + // Intentionally empty. +} + +message UnmountVolumeRequest { + // Volume device ID of the volume to dismount. + string volume_id = 1; + // Path where the volume has been mounted. + string target_path = 2; +} + +message UnmountVolumeResponse { + // Intentionally empty. +} + +message IsVolumeFormattedRequest { + // Volume device ID of the volume to check. + string volume_id = 1; +} + +message IsVolumeFormattedResponse { + // Is the volume formatted with NTFS. + bool formatted = 1; +} + +message FormatVolumeRequest { + // Volume device ID of the volume to format. + string volume_id = 1; +} + +message FormatVolumeResponse { + // Intentionally empty. +} + +message ResizeVolumeRequest { + // Volume device ID of the volume to resize. + string volume_id = 1; + // New size in bytes of the volume. + int64 size_bytes = 2; +} + +message ResizeVolumeResponse { + // Intentionally empty. +} + +message GetVolumeStatsRequest{ + // Volume device Id of the volume to get the stats for. + string volume_id = 1; +} + +message GetVolumeStatsResponse{ + // Total bytes + int64 total_bytes = 1; + // Used bytes + int64 used_bytes = 2; +} + +message GetDiskNumberFromVolumeIDRequest { + // Volume device ID of the volume to get the disk number for. + string volume_id = 1; +} + +message GetDiskNumberFromVolumeIDResponse { + // Corresponding disk number. + uint32 disk_number = 1; +} + +message GetVolumeIDFromTargetPathRequest { + // The target path. + string target_path = 1; +} + +message GetVolumeIDFromTargetPathResponse { + // The volume device ID. + string volume_id = 1; +} + +message GetClosestVolumeIDFromTargetPathRequest { + // The target path. + string target_path = 1; +} + +message GetClosestVolumeIDFromTargetPathResponse { + // The volume device ID. + string volume_id = 1; +} + +message WriteVolumeCacheRequest { + // Volume device ID of the volume to flush the cache. + string volume_id = 1; +} + +message WriteVolumeCacheResponse { + // Intentionally empty. +} diff --git a/examples/freenas-api-nfs.yaml b/examples/freenas-api-nfs.yaml index 68084cb..97b8a53 100644 --- a/examples/freenas-api-nfs.yaml +++ b/examples/freenas-api-nfs.yaml @@ -43,6 +43,8 @@ zfs: datasetPermissionsMode: "0777" datasetPermissionsUser: 0 datasetPermissionsGroup: 0 + + # not supported yet #datasetPermissionsAcls: #- "-m everyone@:full_set:allow" #- "-m u:kube:full_set:allow" diff --git a/examples/freenas-api-smb.yaml b/examples/freenas-api-smb.yaml index 7b45f4c..a8e0a84 100644 --- a/examples/freenas-api-smb.yaml +++ b/examples/freenas-api-smb.yaml @@ -34,9 +34,10 @@ zfs: # "org.freenas:test": "{{ parameters.foo }}" # "org.freenas:test2": "some value" - datasetProperties: - aclmode: restricted - casesensitivity: mixed + # these are managed automatically via the volume creation process when flagged as an smb volume + #datasetProperties: + # aclmode: restricted + # casesensitivity: mixed datasetParentName: tank/k8s/a/vols # do NOT make datasetParentName and detachedSnapshotsDatasetParentName overlap @@ -47,8 +48,10 @@ zfs: datasetPermissionsMode: "0777" datasetPermissionsUser: 0 datasetPermissionsGroup: 0 - datasetPermissionsAcls: - - "-m everyone@:full_set:allow" + + # not supported yet in api + #datasetPermissionsAcls: + #- "-m everyone@:full_set:allow" #- "-m u:kube:full_set:allow" smb: diff --git a/examples/freenas-smb.yaml b/examples/freenas-smb.yaml index 6d08b7e..8a2ed4d 100644 --- a/examples/freenas-smb.yaml +++ b/examples/freenas-smb.yaml @@ -46,7 +46,9 @@ zfs: datasetProperties: aclmode: restricted - casesensitivity: mixed + aclinherit: passthrough + acltype: nfsv4 + casesensitivity: insensitive datasetParentName: tank/k8s/a/vols # do NOT make datasetParentName and detachedSnapshotsDatasetParentName overlap @@ -54,12 +56,41 @@ zfs: detachedSnapshotsDatasetParentName: tank/k8s/a/snaps datasetEnableQuotas: true datasetEnableReservation: false - datasetPermissionsMode: "0777" - datasetPermissionsUser: nobody - datasetPermissionsGroup: nobody + datasetPermissionsMode: "0770" + + # as appropriate create a dedicated user for smb connections + # and set this + datasetPermissionsUser: 65534 + datasetPermissionsGroup: 65534 + + # CORE + #datasetPermissionsAclsBinary: setfacl + + # SCALE + #datasetPermissionsAclsBinary: nfs4xdr_setfacl + + # if using a user other than guest/nobody comment the 'everyone@' acl + # and uncomment the appropriate block below datasetPermissionsAcls: - - "-m everyone@:full_set:allow" - #- "-m u:kube:full_set:allow" + - "-m everyone@:full_set:fd:allow" + + # CORE + # in CORE you cannot have multiple entries for the same principle + # or said differently, they are declarative so using -m will replace + # whatever the current value is for the principle rather than adding a + # entry in the acl list + #- "-m g:builtin_users:full_set:fd:allow" + #- "-m group@:modify_set:fd:allow" + #- "-m owner@:full_set:fd:allow" + + # SCALE + # https://www.truenas.com/community/threads/get-setfacl-on-scale-with-nfsv4-acls.95231/ + # -s replaces everything + # so we put this in specific order to mimic the defaults of SCALE when using the api + #- -s group:builtin_users:full_set:fd:allow + #- -a group:builtin_users:modify_set:fd:allow + #- -a group@:modify_set:fd:allow + #- -a owner@:full_set:fd:allow smb: shareHost: server address @@ -77,7 +108,7 @@ smb: shareAllowedHosts: [] shareDeniedHosts: [] #shareDefaultPermissions: true - shareGuestOk: true + shareGuestOk: false #shareGuestOnly: true #shareShowHiddenFiles: true shareRecycleBin: true diff --git a/examples/node-common.yaml b/examples/node-common.yaml index 0fb61e7..1f0f3df 100644 --- a/examples/node-common.yaml +++ b/examples/node-common.yaml @@ -2,6 +2,9 @@ node: mount: + # predominantly used to facilitate testing + # mount_flags should generally be defined in storage classes, etc + mount_flags: "" # should fsck be executed before mounting the fs checkFilesystem: xfs: diff --git a/examples/private.yaml b/examples/private.yaml new file mode 100644 index 0000000..8d2d969 --- /dev/null +++ b/examples/private.yaml @@ -0,0 +1,18 @@ +# +# these SHOULD NOT be used +# they are here for documentation purposes only and are likely to: +# - be removed +# - break things +# + +_private: + csi: + volume: + derivedContext: + # driver left blank is used to auto select + driver: memory # strictly to facilitate testing + #driver: kubernetes + idHash: + strategy: crc16 + #strategy: crc32 + #strategy: md5 diff --git a/package-lock.json b/package-lock.json index d0ba2ca..cb81a02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "democratic-csi", - "version": "1.6.3", + "version": "1.7.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "democratic-csi", - "version": "1.6.3", + "version": "1.7.0", "license": "MIT", "dependencies": { "@grpc/grpc-js": "^1.5.7", @@ -70,9 +70,9 @@ } }, "node_modules/@grpc/grpc-js": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.2.tgz", - "integrity": "sha512-9+89Ne1K8F9u86T+l1yIV2DS+dWHYVK61SsDZN4MFTFehOOaJ4rHxa1cW8Lwdn2/6tOx7N3+SY/vfcjztOHopA==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.5.tgz", + "integrity": "sha512-h0KSwgLiF5rmSAU6qnzK1aoD1MNqOw9HJK96N8VW3dR5FHMpq+0JNdLQFP//NcaIWVB7I7vkHl4JmU9hUw82Aw==", "dependencies": { "@grpc/proto-loader": "^0.6.4", "@types/node": ">=12.12.47" @@ -315,9 +315,9 @@ } }, "node_modules/@types/node": { - "version": "17.0.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", - "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==" + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.24.tgz", + "integrity": "sha512-aveCYRQbgTH9Pssp1voEP7HiuWlD2jW2BO56w+bVrJn04i61yh6mRfoKO6hEYQD9vF+W8Chkwc6j1M36uPkx4g==" }, "node_modules/@types/request": { "version": "2.48.8", @@ -356,9 +356,9 @@ } }, "node_modules/@types/tough-cookie": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", - "integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==" }, "node_modules/@types/underscore": { "version": "1.11.4", @@ -465,9 +465,9 @@ } }, "node_modules/async": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" }, "node_modules/async-mutex": { "version": "0.3.2", @@ -947,9 +947,9 @@ } }, "node_modules/eslint": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.12.0.tgz", - "integrity": "sha512-it1oBL9alZg1S8UycLm5YDMAkIhtH6FtAzuZs6YvoGVldWjbS08BkAdb/ymP9LlAyq8koANu32U7Ib/w+UNh8Q==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.13.0.tgz", + "integrity": "sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==", "dev": true, "dependencies": { "@eslint/eslintrc": "^1.2.1", @@ -1163,9 +1163,9 @@ "dev": true }, "node_modules/fecha": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.1.tgz", - "integrity": "sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==" + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" }, "node_modules/file-entry-cache": { "version": "6.0.1", @@ -1757,9 +1757,9 @@ } }, "node_modules/lru-cache": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.0.tgz", - "integrity": "sha512-AmXqneQZL3KZMIgBpaPTeI6pfwh+xQ2vutMsyqOu1TBdEXFZgpG/80wuJ531w2ZN7TI0/oc8CPxzh/DKQudZqg==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.1.tgz", + "integrity": "sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg==", "engines": { "node": ">=12" } @@ -2127,18 +2127,18 @@ } }, "node_modules/prompt": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.2.2.tgz", - "integrity": "sha512-XNXhNv3PUHJDcDkISpCwSJxtw9Bor4FZnlMUDW64N/KCPdxhfVlpD5+YUXI/Z8a9QWmOhs9KSiVtR8nzPS0BYA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.3.0.tgz", + "integrity": "sha512-ZkaRWtaLBZl7KKAKndKYUL8WqNT+cQHKRZnT4RYYms48jQkFw3rrBL+/N5K/KtdEveHkxs982MX2BkDKub2ZMg==", "dependencies": { "@colors/colors": "1.5.0", - "async": "~0.9.0", + "async": "3.2.3", "read": "1.0.x", "revalidator": "0.1.x", "winston": "2.x" }, "engines": { - "node": ">= 0.6.6" + "node": ">= 6.0.0" } }, "node_modules/prompt/node_modules/winston": { @@ -2440,17 +2440,28 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { - "version": "7.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz", - "integrity": "sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dependencies": { - "lru-cache": "^7.4.0" + "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" }, "engines": { - "node": "^10.0.0 || ^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, "node_modules/shebang-command": { @@ -2761,9 +2772,9 @@ } }, "node_modules/uglify-js": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.3.tgz", - "integrity": "sha512-6iCVm2omGJbsu3JWac+p6kUiOpg3wFO2f8lIXjfEb8RrmLjzog1wTPMmwKB7swfzzqxj9YM+sGUM++u1qN4qJg==", + "version": "3.15.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.4.tgz", + "integrity": "sha512-vMOPGDuvXecPs34V74qDKk4iJ/SN4vL3Ow/23ixafENYvtrNvtbcgUeugTcUGRGsOF/5fU8/NYSL5Hyb3l1OJA==", "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" @@ -2864,11 +2875,6 @@ "node": ">= 6.4.0" } }, - "node_modules/winston/node_modules/async": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" - }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -2938,9 +2944,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yargs": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.4.0.tgz", - "integrity": "sha512-WJudfrk81yWFSOkZYpAZx4Nt7V4xp7S/uJkX0CnxovMCt1wCE8LNftPpNuF9X/u9gN5nsD7ycYtRcDf2pL3UiA==", + "version": "17.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.4.1.tgz", + "integrity": "sha512-WSZD9jgobAg3ZKuCQZSa3g9QOJeCCqLoLAykiWgmXnDo9EPnn4RPf5qVTtzgOx66o6/oqhcA5tHtJXpG8pMt3g==", "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -2997,9 +3003,9 @@ } }, "@grpc/grpc-js": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.2.tgz", - "integrity": "sha512-9+89Ne1K8F9u86T+l1yIV2DS+dWHYVK61SsDZN4MFTFehOOaJ4rHxa1cW8Lwdn2/6tOx7N3+SY/vfcjztOHopA==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.5.tgz", + "integrity": "sha512-h0KSwgLiF5rmSAU6qnzK1aoD1MNqOw9HJK96N8VW3dR5FHMpq+0JNdLQFP//NcaIWVB7I7vkHl4JmU9hUw82Aw==", "requires": { "@grpc/proto-loader": "^0.6.4", "@types/node": ">=12.12.47" @@ -3216,9 +3222,9 @@ } }, "@types/node": { - "version": "17.0.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", - "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==" + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.24.tgz", + "integrity": "sha512-aveCYRQbgTH9Pssp1voEP7HiuWlD2jW2BO56w+bVrJn04i61yh6mRfoKO6hEYQD9vF+W8Chkwc6j1M36uPkx4g==" }, "@types/request": { "version": "2.48.8", @@ -3257,9 +3263,9 @@ } }, "@types/tough-cookie": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", - "integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==" }, "@types/underscore": { "version": "1.11.4", @@ -3339,9 +3345,9 @@ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "async": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" }, "async-mutex": { "version": "0.3.2", @@ -3720,9 +3726,9 @@ "dev": true }, "eslint": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.12.0.tgz", - "integrity": "sha512-it1oBL9alZg1S8UycLm5YDMAkIhtH6FtAzuZs6YvoGVldWjbS08BkAdb/ymP9LlAyq8koANu32U7Ib/w+UNh8Q==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.13.0.tgz", + "integrity": "sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==", "dev": true, "requires": { "@eslint/eslintrc": "^1.2.1", @@ -3884,9 +3890,9 @@ "dev": true }, "fecha": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.1.tgz", - "integrity": "sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==" + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" }, "file-entry-cache": { "version": "6.0.1", @@ -4329,9 +4335,9 @@ "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" }, "lru-cache": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.0.tgz", - "integrity": "sha512-AmXqneQZL3KZMIgBpaPTeI6pfwh+xQ2vutMsyqOu1TBdEXFZgpG/80wuJ531w2ZN7TI0/oc8CPxzh/DKQudZqg==" + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.1.tgz", + "integrity": "sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg==" }, "make-error": { "version": "1.3.6", @@ -4610,12 +4616,12 @@ "dev": true }, "prompt": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.2.2.tgz", - "integrity": "sha512-XNXhNv3PUHJDcDkISpCwSJxtw9Bor4FZnlMUDW64N/KCPdxhfVlpD5+YUXI/Z8a9QWmOhs9KSiVtR8nzPS0BYA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.3.0.tgz", + "integrity": "sha512-ZkaRWtaLBZl7KKAKndKYUL8WqNT+cQHKRZnT4RYYms48jQkFw3rrBL+/N5K/KtdEveHkxs982MX2BkDKub2ZMg==", "requires": { "@colors/colors": "1.5.0", - "async": "~0.9.0", + "async": "3.2.3", "read": "1.0.x", "revalidator": "0.1.x", "winston": "2.x" @@ -4842,11 +4848,21 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver": { - "version": "7.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz", - "integrity": "sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "requires": { - "lru-cache": "^7.4.0" + "lru-cache": "^6.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + } } }, "shebang-command": { @@ -5073,9 +5089,9 @@ "dev": true }, "uglify-js": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.3.tgz", - "integrity": "sha512-6iCVm2omGJbsu3JWac+p6kUiOpg3wFO2f8lIXjfEb8RrmLjzog1wTPMmwKB7swfzzqxj9YM+sGUM++u1qN4qJg==", + "version": "3.15.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.4.tgz", + "integrity": "sha512-vMOPGDuvXecPs34V74qDKk4iJ/SN4vL3Ow/23ixafENYvtrNvtbcgUeugTcUGRGsOF/5fU8/NYSL5Hyb3l1OJA==", "optional": true }, "underscore": { @@ -5140,13 +5156,6 @@ "stack-trace": "0.0.x", "triple-beam": "^1.3.0", "winston-transport": "^4.5.0" - }, - "dependencies": { - "async": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" - } } }, "winston-transport": { @@ -5202,9 +5211,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yargs": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.4.0.tgz", - "integrity": "sha512-WJudfrk81yWFSOkZYpAZx4Nt7V4xp7S/uJkX0CnxovMCt1wCE8LNftPpNuF9X/u9gN5nsD7ycYtRcDf2pL3UiA==", + "version": "17.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.4.1.tgz", + "integrity": "sha512-WSZD9jgobAg3ZKuCQZSa3g9QOJeCCqLoLAykiWgmXnDo9EPnn4RPf5qVTtzgOx66o6/oqhcA5tHtJXpG8pMt3g==", "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", diff --git a/package.json b/package.json index e8e807a..8459eeb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "democratic-csi", - "version": "1.6.3", + "version": "1.7.0", "description": "kubernetes csi driver framework", "main": "bin/democratic-csi", "scripts": { diff --git a/src/driver/controller-synology/index.js b/src/driver/controller-synology/index.js index 6db629f..abe71a0 100644 --- a/src/driver/controller-synology/index.js +++ b/src/driver/controller-synology/index.js @@ -3,7 +3,7 @@ const { GrpcError, grpc } = require("../../utils/grpc"); const registry = require("../../utils/registry"); const SynologyHttpClient = require("./http").SynologyHttpClient; const semver = require("semver"); -const sleep = require("../../utils/general").sleep; +const GeneralUtils = require("../../utils/general"); const __REGISTRY_NS__ = "ControllerSynologyDriver"; @@ -171,7 +171,9 @@ class ControllerSynologyDriver extends CsiBaseDriver { if ( capability.mount.fs_type && - !["nfs", "cifs"].includes(capability.mount.fs_type) + !GeneralUtils.default_supported_file_filesystems().includes( + capability.mount.fs_type + ) ) { message = `invalid fs_type ${capability.mount.fs_type}`; return false; @@ -198,7 +200,7 @@ class ControllerSynologyDriver extends CsiBaseDriver { if (capability.access_type == "mount") { if ( capability.mount.fs_type && - !["btrfs", "ext3", "ext4", "ext4dev", "xfs"].includes( + !GeneralUtils.default_supported_block_filesystems().includes( capability.mount.fs_type ) ) { @@ -609,12 +611,12 @@ class ControllerSynologyDriver extends CsiBaseDriver { let waitTimeBetweenChecks = settleSeconds * 1000; - await sleep(waitTimeBetweenChecks); + await GeneralUtils.sleep(waitTimeBetweenChecks); lun_uuid = await httpClient.GetLunUUIDByName(iscsiName); while (currentCheck <= settleMaxRetries && lun_uuid) { currentCheck++; - await sleep(waitTimeBetweenChecks); + await GeneralUtils.sleep(waitTimeBetweenChecks); lun_uuid = await httpClient.GetLunUUIDByName(iscsiName); } diff --git a/src/driver/controller-zfs-local/index.js b/src/driver/controller-zfs-local/index.js index 04f1a17..3c1911b 100644 --- a/src/driver/controller-zfs-local/index.js +++ b/src/driver/controller-zfs-local/index.js @@ -1,6 +1,7 @@ const _ = require("lodash"); const { ControllerZfsBaseDriver } = require("../controller-zfs"); const { GrpcError, grpc } = require("../../utils/grpc"); +const GeneralUtils = require("../../utils/general"); const LocalCliExecClient = require("./exec").LocalCliClient; const registry = require("../../utils/registry"); const { Zetabyte } = require("../../utils/zfs"); @@ -95,7 +96,7 @@ class ControllerZfsLocalDriver extends ControllerZfsBaseDriver { case "filesystem": return ["zfs"]; case "volume": - return ["btrfs", "ext3", "ext4", "ext4dev", "xfs"]; + return GeneralUtils.default_supported_block_filesystems(); } } diff --git a/src/driver/controller-zfs/index.js b/src/driver/controller-zfs/index.js index d354e43..94f7ea7 100644 --- a/src/driver/controller-zfs/index.js +++ b/src/driver/controller-zfs/index.js @@ -1,7 +1,7 @@ const _ = require("lodash"); const { CsiBaseDriver } = require("../index"); const { GrpcError, grpc } = require("../../utils/grpc"); -const sleep = require("../../utils/general").sleep; +const GeneralUtils = require("../../utils/general"); const getLargestNumber = require("../../utils/general").getLargestNumber; const Handlebars = require("handlebars"); @@ -201,9 +201,9 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { const driverZfsResourceType = this.getDriverZfsResourceType(); switch (driverZfsResourceType) { case "filesystem": - return ["nfs", "cifs"]; + return GeneralUtils.default_supported_file_filesystems(); case "volume": - return ["btrfs", "ext3", "ext4", "ext4dev", "xfs"]; + return GeneralUtils.default_supported_block_filesystems(); } } @@ -620,6 +620,7 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { let snapshotParentDatasetName = this.getDetachedSnapshotParentDatasetName(); let zvolBlocksize = this.options.zfs.zvolBlocksize || "16K"; let name = call.request.name; + let volume_id = await driver.getVolumeIdFromName(name); let volume_content_source = call.request.volume_content_source; if (!datasetParentName) { @@ -710,7 +711,7 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { * NOTE: avoid the urge to templatize this given the name length limits for zvols * ie: namespace-name may quite easily exceed 58 chars */ - const datasetName = datasetParentName + "/" + name; + const datasetName = datasetParentName + "/" + volume_id; // ensure volumes with the same name being requested a 2nd time but with a different size fails try { @@ -862,7 +863,7 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { volume_content_source_snapshot_id + "@" + VOLUME_SOURCE_CLONE_SNAPSHOT_PREFIX + - name; + volume_id; } driver.ctx.logger.debug("full snapshot name: %s", fullSnapshotName); @@ -909,6 +910,12 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { }); } else { try { + // remove readonly/undesired props + let cloneProperties = volumeProperties; + delete cloneProperties["aclmode"]; + delete cloneProperties["aclinherit"]; + delete cloneProperties["acltype"]; + delete cloneProperties["casesensitivity"]; response = await zb.zfs.clone(fullSnapshotName, datasetName, { properties: volumeProperties, }); @@ -971,7 +978,7 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { volume_content_source_volume_id + "@" + VOLUME_SOURCE_CLONE_SNAPSHOT_PREFIX + - name; + volume_id; driver.ctx.logger.debug("full snapshot name: %s", fullSnapshotName); @@ -1024,9 +1031,15 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { } else { // create clone // zfs origin property contains parent info, ie: pool0/k8s/test/PVC-111@clone-test + // remove readonly/undesired props + let cloneProperties = volumeProperties; + delete cloneProperties["aclmode"]; + delete cloneProperties["aclinherit"]; + delete cloneProperties["acltype"]; + delete cloneProperties["casesensitivity"]; try { response = await zb.zfs.clone(fullSnapshotName, datasetName, { - properties: volumeProperties, + properties: cloneProperties, }); } catch (err) { if (err.toString().includes("dataset does not exist")) { @@ -1128,8 +1141,13 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { // TODO: this is unsfafe approach, make it better // probably could see if ^-.*\s and split and then shell escape if (this.options.zfs.datasetPermissionsAcls) { + let aclBinary = _.get( + driver.options, + "zfs.datasetPermissionsAclsBinary", + "setfacl" + ); for (const acl of this.options.zfs.datasetPermissionsAcls) { - command = execClient.buildCommand("setfacl", [ + command = execClient.buildCommand(aclBinary, [ acl, properties.mountpoint.value, ]); @@ -1147,7 +1165,6 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { } } } - break; case "volume": // set properties @@ -1191,7 +1208,7 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { const res = { volume: { - volume_id: name, + volume_id, //capacity_bytes: capacity_bytes, // kubernetes currently pukes if capacity is returned as 0 capacity_bytes: this.options.zfs.datasetEnableQuotas || @@ -1315,7 +1332,7 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { if (current_try > max_tries) { throw err; } else { - await sleep(sleep_time); + await GeneralUtils.sleep(sleep_time); } } else { throw err; @@ -2190,7 +2207,7 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { }); // let things settle down - //await sleep(3000); + //await GneralUtils.sleep(3000); } else { try { await zb.zfs.snapshot(fullSnapshotName, { @@ -2198,7 +2215,7 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { }); // let things settle down - //await sleep(3000); + //await GeneralUtils.sleep(3000); } catch (err) { if (err.toString().includes("dataset does not exist")) { throw new GrpcError( diff --git a/src/driver/freenas/api.js b/src/driver/freenas/api.js index f436ff9..8af3853 100644 --- a/src/driver/freenas/api.js +++ b/src/driver/freenas/api.js @@ -4,9 +4,8 @@ const { CsiBaseDriver } = require("../index"); const HttpClient = require("./http").Client; const TrueNASApiClient = require("./http/api").Api; const { Zetabyte } = require("../../utils/zfs"); -const getLargestNumber = require("../../utils/general").getLargestNumber; const registry = require("../../utils/registry"); -const sleep = require("../../utils/general").sleep; +const GeneralUtils = require("../../utils/general"); const Handlebars = require("handlebars"); const uuidv4 = require("uuid").v4; @@ -1565,7 +1564,7 @@ class FreeNASApiDriver extends CsiBaseDriver { targetId, retries ); - await sleep(retryWait); + await GeneralUtils.sleep(retryWait); response = await httpClient.delete(endpoint); } @@ -1958,7 +1957,7 @@ class FreeNASApiDriver extends CsiBaseDriver { if (capability.access_type == "mount") { if ( capability.mount.fs_type && - !["btrfs", "ext3", "ext4", "ext4dev", "xfs"].includes( + !GeneralUtils.default_supported_block_filesystems().includes( capability.mount.fs_type ) ) { @@ -2066,6 +2065,7 @@ class FreeNASApiDriver extends CsiBaseDriver { let snapshotParentDatasetName = this.getDetachedSnapshotParentDatasetName(); let zvolBlocksize = this.options.zfs.zvolBlocksize || "16K"; let name = call.request.name; + let volume_id = await driver.getVolumeIdFromName(name); let volume_content_source = call.request.volume_content_source; let minimum_volume_size = await driver.getMinimumVolumeSize(); let default_required_bytes = 1073741824; @@ -2171,7 +2171,7 @@ class FreeNASApiDriver extends CsiBaseDriver { * NOTE: avoid the urge to templatize this given the name length limits for zvols * ie: namespace-name may quite easily exceed 58 chars */ - const datasetName = datasetParentName + "/" + name; + const datasetName = datasetParentName + "/" + volume_id; // ensure volumes with the same name being requested a 2nd time but with a different size fails try { @@ -2326,7 +2326,7 @@ class FreeNASApiDriver extends CsiBaseDriver { volume_content_source_snapshot_id + "@" + VOLUME_SOURCE_CLONE_SNAPSHOT_PREFIX + - name; + volume_id; } driver.ctx.logger.debug("full snapshot name: %s", fullSnapshotName); @@ -2378,7 +2378,7 @@ class FreeNASApiDriver extends CsiBaseDriver { ) { job = await httpApiClient.CoreGetJobs({ id: job_id }); job = job[0]; - await sleep(3000); + await GeneralUtils.sleep(3000); } job.error = job.error || ""; @@ -2488,7 +2488,7 @@ class FreeNASApiDriver extends CsiBaseDriver { volume_content_source_volume_id + "@" + VOLUME_SOURCE_CLONE_SNAPSHOT_PREFIX + - name; + volume_id; driver.ctx.logger.debug("full snapshot name: %s", fullSnapshotName); @@ -2538,7 +2538,7 @@ class FreeNASApiDriver extends CsiBaseDriver { ) { job = await httpApiClient.CoreGetJobs({ id: job_id }); job = job[0]; - await sleep(3000); + await GeneralUtils.sleep(3000); } job.error = job.error || ""; @@ -2626,6 +2626,9 @@ class FreeNASApiDriver extends CsiBaseDriver { volsize: driverZfsResourceType == "volume" ? capacity_bytes : undefined, sparse: driverZfsResourceType == "volume" ? sparse : undefined, create_ancestors: true, + share_type: driver.getDriverShareType().includes("smb") + ? "SMB" + : "GENERIC", user_properties: httpApiClient.getPropertiesKeyValueArray( httpApiClient.getUserProperties(volumeProperties) ), @@ -2721,7 +2724,18 @@ class FreeNASApiDriver extends CsiBaseDriver { } if (setPerms) { - await httpApiClient.FilesystemSetperm(perms); + response = await httpApiClient.FilesystemSetperm(perms); + await httpApiClient.CoreWaitForJob(response, 30); + // SetPerm does not alter ownership with extended ACLs + // run this in addition just for good measure + if (perms.uid || perms.gid) { + response = await httpApiClient.FilesystemChown({ + path: perms.path, + uid: perms.uid, + gid: perms.gid, + }); + await httpApiClient.CoreWaitForJob(response, 30); + } } // set acls @@ -2777,7 +2791,7 @@ class FreeNASApiDriver extends CsiBaseDriver { const res = { volume: { - volume_id: name, + volume_id, //capacity_bytes: capacity_bytes, // kubernetes currently pukes if capacity is returned as 0 capacity_bytes: this.options.zfs.datasetEnableQuotas || @@ -3649,7 +3663,10 @@ class FreeNASApiDriver extends CsiBaseDriver { // so we must be cognizant and use the highest possible value here // note that whatever value is returned here can/will essentially impact the refquota // value of a derived volume - size_bytes = getLargestNumber(row.referenced, row.logicalreferenced); + size_bytes = GeneralUtils.getLargestNumber( + row.referenced, + row.logicalreferenced + ); } else { // get the size of the parent volume size_bytes = row.volsize; @@ -3930,7 +3947,7 @@ class FreeNASApiDriver extends CsiBaseDriver { while (!job || !["SUCCESS", "ABORTED", "FAILED"].includes(job.state)) { job = await httpApiClient.CoreGetJobs({ id: job_id }); job = job[0]; - await sleep(3000); + await GeneralUtils.sleep(3000); } job.error = job.error || ""; @@ -4045,7 +4062,7 @@ class FreeNASApiDriver extends CsiBaseDriver { // so we must be cognizant and use the highest possible value here // note that whatever value is returned here can/will essentially impact the refquota // value of a derived volume - size_bytes = getLargestNumber( + size_bytes = GeneralUtils.getLargestNumber( properties.referenced.rawvalue, properties.logicalreferenced.rawvalue // TODO: perhaps include minimum volume size here? diff --git a/src/driver/freenas/http/api.js b/src/driver/freenas/http/api.js index ef4b52a..9878d18 100644 --- a/src/driver/freenas/http/api.js +++ b/src/driver/freenas/http/api.js @@ -681,7 +681,13 @@ class Api { throw new Error(JSON.stringify(response.body)); } - async CoreWaitForJob(job_id, timeout = 0) { + /** + * + * @param {*} job_id + * @param {*} timeout in seconds + * @returns + */ + async CoreWaitForJob(job_id, timeout = 0, check_interval = 3000) { if (!job_id) { throw new Error("invalid job_id"); } @@ -692,16 +698,17 @@ class Api { let job; // wait for job to finish - while (!job || !["SUCCESS", "ABORTED", "FAILED"].includes(job.state)) { + do { + if (job) { + await sleep(check_interval); + } job = await this.CoreGetJobs({ id: job_id }); job = job[0]; - await sleep(3000); - currentTime = Date.now() / 1000; if (timeout > 0 && currentTime > startTime + timeout) { throw new Error("timeout waiting for job to complete"); } - } + } while (!["SUCCESS", "ABORTED", "FAILED"].includes(job.state)); return job; } @@ -754,7 +761,38 @@ class Api { response = await httpClient.post(endpoint, data); if (response.statusCode == 200) { - return; + return response.body; + } + + throw new Error(JSON.stringify(response.body)); + } + + /** + * + * @param {*} data + */ + async FilesystemChown(data) { + /* + { + "path": "string", + "uid": 0, + "gid": 0, + "options": { + "recursive": false, + "traverse": false + } + } + */ + + const httpClient = await this.getHttpClient(false); + let response; + let endpoint; + + endpoint = `/filesystem/chown`; + response = await httpClient.post(endpoint, data); + + if (response.statusCode == 200) { + return response.body; } throw new Error(JSON.stringify(response.body)); diff --git a/src/driver/freenas/http/index.js b/src/driver/freenas/http/index.js index d516610..9b0a02b 100644 --- a/src/driver/freenas/http/index.js +++ b/src/driver/freenas/http/index.js @@ -122,6 +122,9 @@ class Client { _.set(options, prop, "redacted"); } + delete options.httpAgent; + delete options.httpsAgent; + this.logger.debug("FREENAS HTTP REQUEST: " + stringify(options)); this.logger.debug("FREENAS HTTP ERROR: " + error); this.logger.debug("FREENAS HTTP STATUS: " + response.statusCode); diff --git a/src/driver/freenas/ssh.js b/src/driver/freenas/ssh.js index 12c3821..0ce6d1a 100644 --- a/src/driver/freenas/ssh.js +++ b/src/driver/freenas/ssh.js @@ -4,6 +4,7 @@ const { GrpcError, grpc } = require("../../utils/grpc"); const registry = require("../../utils/registry"); const SshClient = require("../../utils/ssh").SshClient; const HttpClient = require("./http").Client; +const TrueNASApiClient = require("./http/api").Api; const { Zetabyte, ZfsSshProcessManager } = require("../../utils/zfs"); const { sleep, stringify } = require("../../utils/general"); @@ -112,6 +113,13 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver { ); } + async getTrueNASHttpApiClient() { + return registry.getAsync(`${__REGISTRY_NS__}:api_client`, async () => { + const httpClient = await this.getHttpClient(); + return new TrueNASApiClient(httpClient, this.ctx.cache); + }); + } + getDriverShareType() { switch (this.options.driver) { case "freenas-nfs": @@ -1716,6 +1724,7 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver { async setFilesystemMode(path, mode) { const httpClient = await this.getHttpClient(); const apiVersion = httpClient.getApiVersion(); + const httpApiClient = await this.getTrueNASHttpApiClient(); switch (apiVersion) { case 1: @@ -1747,6 +1756,7 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver { response = await httpClient.post(endpoint, perms); if (response.statusCode == 200) { + await httpApiClient.CoreWaitForJob(response.body, 30); return; } @@ -1764,6 +1774,7 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver { async setFilesystemOwnership(path, user = false, group = false) { const httpClient = await this.getHttpClient(); const apiVersion = httpClient.getApiVersion(); + const httpApiClient = await this.getTrueNASHttpApiClient(); if (user === false || typeof user == "undefined" || user === null) { user = ""; @@ -1832,6 +1843,7 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver { response = await httpClient.post(endpoint, perms); if (response.statusCode == 200) { + await httpApiClient.CoreWaitForJob(response.body, 30); return; } diff --git a/src/driver/index.js b/src/driver/index.js index 735effc..1b9d30b 100644 --- a/src/driver/index.js +++ b/src/driver/index.js @@ -2,6 +2,8 @@ const _ = require("lodash"); const cp = require("child_process"); const os = require("os"); const fs = require("fs"); +const CsiProxyClient = require("../utils/csi_proxy_client").CsiProxyClient; +const k8s = require("@kubernetes/client-node"); const { GrpcError, grpc } = require("../utils/grpc"); const { Mount } = require("../utils/mount"); const { OneClient } = require("../utils/oneclient"); @@ -9,11 +11,14 @@ const { Filesystem } = require("../utils/filesystem"); const { ISCSI } = require("../utils/iscsi"); const registry = require("../utils/registry"); const semver = require("semver"); -const sleep = require("../utils/general").sleep; +const GeneralUtils = require("../utils/general"); const { Zetabyte } = require("../utils/zfs"); const __REGISTRY_NS__ = "CsiBaseDriver"; +const NODE_OS_DRIVER_CSI_PROXY = "csi-proxy"; +const NODE_OS_DRIVER_POSIX = "posix"; + /** * common code shared between all drivers * this is **NOT** meant to work as a proxy @@ -161,6 +166,202 @@ class CsiBaseDriver { }); } + /** + * + * @returns CsiProxyClient + */ + getDefaultCsiProxyClientInstance() { + return registry.get(`${__REGISTRY_NS__}:default_csi_proxy_instance`, () => { + const options = {}; + options.services = _.get(this.options, "node.csiProxy.services", {}); + return new CsiProxyClient(options); + }); + } + + getDefaultKubernetsConfigInstance() { + return registry.get( + `${__REGISTRY_NS__}:default_kubernetes_config_instance`, + () => { + const kc = new k8s.KubeConfig(); + kc.loadFromDefault(); + return kc; + } + ); + } + + getCsiProxyEnabled() { + const defaultValue = process.platform == "win32"; + return _.get(this.options, "node.csiProxy.enabled", defaultValue); + } + + getNodeIsWindows() { + return process.platform == "win32"; + } + + __getNodeOsDriver() { + if (this.getNodeIsWindows() || this.getCsiProxyEnabled()) { + return NODE_OS_DRIVER_CSI_PROXY; + } + + return NODE_OS_DRIVER_POSIX; + } + + getMountFlagValue(mount_flags = [], flag = "") { + for (let i = mount_flags.length - 1; i >= 0; i--) { + const mount_flag = mount_flags[i]; + if (mount_flag.startsWith(`${flag}=`)) { + return mount_flag.split("=", 2)[1] || ""; + } + } + } + + async getDerivedVolumeContextDriver() { + const driver = this; + let d = _.get(driver.options, "_private.csi.volume.volumeContext.driver"); + if ( + !d && + (process.env.KUBERNETES_SERVICE_HOST || + process.env.KUBERNETES_SERVICE_PORT) + ) { + // test for k8s + d = "kubernetes"; + } + + if (!d) { + // test for Nomad + } + + if (!d && process.env.CSI_SANITY == 1) { + d = "memory"; + } + + return d; + } + + /** + * Used predominantly with windows due to limitations with the csi-proxy + * + * @param {*} call + * @returns + */ + async getDerivedVolumeContext(call) { + const driver = this; + const volume_id = call.request.volume_id; + const d = await driver.getDerivedVolumeContextDriver(); + driver.ctx.logger.debug(`looking up volume_context using driver: ${d}`); + let volume_context; + switch (d) { + case "memory": + driver.volume_context_cache = driver.volume_context_cache || {}; + volume_context = driver.volume_context_cache[volume_id]; + break; + case "kubernetes": + const kc = driver.getDefaultKubernetsConfigInstance(); + const k8sApi = kc.makeApiClient(k8s.CoreV1Api); + + async function findPVByDriverHandle(driver, volumeHandle) { + if (!driver || !volumeHandle) { + return; + } + + let pv; + let pvs; + let kcontinue; + do { + pvs = await k8sApi.listPersistentVolume( + undefined, + undefined, + kcontinue, + undefined, + undefined, + undefined // limit + ); + pv = pvs.body.items.find((item) => { + return ( + item.spec.csi.driver == driver && + item.spec.csi.volumeHandle == volumeHandle + ); + }); + kcontinue = pvs.body.metadata._continue; + } while (!pv && pvs.body.metadata._continue); + + return pv; + } + + const pv = await findPVByDriverHandle( + driver.ctx.args.csiName, + volume_id + ); + if (pv) { + volume_context = pv.spec.csi.volumeAttributes; + } + break; + default: + throw new Error(`unknow derived volume context driver: ${d}`); + } + + //if (!volume_context) { + // throw new Error(`failed to retrieve volume_context for ${volume_id}`); + //} + + driver.ctx.logger.debug( + "retrived derived volume_context %j", + volume_context + ); + return volume_context; + } + + /** + * Should only be used for testing purposes, generally these details should + * come from a CO or some other stateful storage mechanism + * + * @param {*} volume_id + * @param {*} volume_context + */ + async setVolumeContextCache(volume_id, volume_context) { + const driver = this; + if (process.env.CSI_SANITY == 1) { + if (!driver.volume_context_cache) { + driver.volume_context_cache = {}; + } + if (!driver.volume_context_cache[volume_id]) { + driver.ctx.logger.debug( + "setting volume_context_cache %s %j", + volume_id, + volume_context + ); + driver.volume_context_cache[volume_id] = volume_context; + } + } + } + + /** + * Translates a `name` to a `volume_id`. Generally the purpose is to shorten + * the value of `volume_id` to play nicely with scenarios that do not support + * long names (ie: smb share, etc) + * + * @param {*} name + * @returns + */ + async getVolumeIdFromName(name) { + const driver = this; + const strategy = _.get( + driver.options, + "_private.csi.volume.idHash.strategy", + "" + ); + switch (strategy.toLowerCase()) { + case "md5": + return GeneralUtils.md5(name); + case "crc32": + return GeneralUtils.crc32(name); + case "crc16": + return GeneralUtils.crc16(name); + default: + return name; + } + } + async GetPluginInfo(call) { return { name: this.ctx.args.csiName, @@ -367,7 +568,7 @@ class CsiBaseDriver { } const access_type = capability.access_type || "mount"; const volume_context = call.request.volume_context; - let fs_type; + let fs_type = _.get(capability, "mount.fs_type"); let mount_flags; let volume_mount_group; const node_attach_driver = volume_context.node_attach_driver; @@ -390,11 +591,18 @@ class CsiBaseDriver { */ if (access_type == "mount") { - fs_type = capability.mount.fs_type; mount_flags = capability.mount.mount_flags || []; + + // yaml mount_flags + if (_.get(driver.options, "node.mount.mount_flags")) { + mount_flags.push( + ..._.get(driver.options, "node.mount.mount_flags").split(",") + ); + } + // add secrets mount_flags if (normalizedSecrets.mount_flags) { - mount_flags.push(normalizedSecrets.mount_flags); + mount_flags.push(...normalizedSecrets.mount_flags.split(",")); } switch (node_attach_driver) { @@ -438,465 +646,830 @@ class CsiBaseDriver { } } - // csi spec stipulates that staging_target_path is a directory even for block mounts - result = await filesystem.pathExists(staging_target_path); - if (!result) { - await filesystem.mkdir(staging_target_path, ["-p", "-m", "0750"]); - } - - switch (node_attach_driver) { - case "nfs": - case "lustre": - device = `${volume_context.server}:${volume_context.share}`; - break; - case "smb": - device = `//${volume_context.server}/${volume_context.share}`; - - // if not present add guest - let has_username = mount_flags.some((element) => { - element = element.trim().toLowerCase(); - return element.startsWith("username="); - }); - - // prevents driver from hanging on stdin waiting for a password to be entered at the cli - if (!has_username) { - let has_guest = mount_flags.some((element) => { - element = element.trim().toLowerCase(); - return element === "guest"; - }); - - if (!has_guest) { - mount_flags.push("guest"); - } - } - break; - case "iscsi": - let portals = []; - if (volume_context.portal) { - portals.push(volume_context.portal.trim()); - } - - if (volume_context.portals) { - volume_context.portals.split(",").forEach((portal) => { - portals.push(portal.trim()); - }); - } - - // ensure full portal value - portals = portals.map((value) => { - if (!value.includes(":")) { - value += ":3260"; - } - - return value.trim(); - }); - - // ensure unique entries only - portals = [...new Set(portals)]; - - // stores actual device paths after iscsi login - let iscsiDevices = []; - - // stores configuration of targets/iqn/luns to connect to - let iscsiConnections = []; - for (let portal of portals) { - iscsiConnections.push({ - portal, - iqn: volume_context.iqn, - lun: volume_context.lun, - }); - } - - /** - * TODO: allow sending in iscsiConnection in a raw/manual format - * TODO: allow option to determine if send_targets should be invoked - * TODO: allow option to control whether nodedb entry should be created by driver - * TODO: allow option to control whether nodedb entry should be deleted by driver - */ - - for (let iscsiConnection of iscsiConnections) { - // create DB entry - // https://library.netapp.com/ecmdocs/ECMP1654943/html/GUID-8EC685B4-8CB6-40D8-A8D5-031A3899BCDC.html - // put these options in place to force targets managed by csi to be explicitly attached (in the case of unclearn shutdown etc) - let nodeDB = { - "node.startup": "manual", - //"node.session.scan": "manual", - }; - const nodeDBKeyPrefix = "node-db."; - for (const key in normalizedSecrets) { - if (key.startsWith(nodeDBKeyPrefix)) { - nodeDB[key.substr(nodeDBKeyPrefix.length)] = - normalizedSecrets[key]; - } - } - - // create 'DB' entry - await iscsi.iscsiadm.createNodeDBEntry( - iscsiConnection.iqn, - iscsiConnection.portal, - nodeDB - ); - // login - await iscsi.iscsiadm.login( - iscsiConnection.iqn, - iscsiConnection.portal - ); - - // get associated session - let session = await iscsi.iscsiadm.getSession( - iscsiConnection.iqn, - iscsiConnection.portal - ); - - // rescan in scenarios when login previously occurred but volumes never appeared - await iscsi.iscsiadm.rescanSession(session); - - // find device name - device = `/dev/disk/by-path/ip-${iscsiConnection.portal}-iscsi-${iscsiConnection.iqn}-lun-${iscsiConnection.lun}`; - let deviceByPath = device; - - // can take some time for device to show up, loop for some period - result = await filesystem.pathExists(device); - let timer_start = Math.round(new Date().getTime() / 1000); - let timer_max = 30; - let deviceCreated = result; - while (!result) { - await sleep(2000); - result = await filesystem.pathExists(device); - - if (result) { - deviceCreated = true; - break; - } - - let current_time = Math.round(new Date().getTime() / 1000); - if (!result && current_time - timer_start > timer_max) { - driver.ctx.logger.warn( - `hit timeout waiting for device node to appear: ${device}` - ); - break; - } - } - - if (deviceCreated) { - device = await filesystem.realpath(device); - iscsiDevices.push(device); - - driver.ctx.logger.info( - `successfully logged into portal ${iscsiConnection.portal} and created device ${deviceByPath} with realpath ${device}` - ); - } - } - - // let things settle - // this will help in dm scenarios - await sleep(2000); - - // filter duplicates - iscsiDevices = iscsiDevices.filter((value, index, self) => { - return self.indexOf(value) === index; - }); - - // only throw an error if we were not able to attach to *any* devices - if (iscsiDevices.length < 1) { - throw new GrpcError( - grpc.status.UNKNOWN, - `unable to attach any iscsi devices` - ); - } - - if (iscsiDevices.length != iscsiConnections.length) { - driver.ctx.logger.warn( - `failed to attach all iscsi devices/targets/portals` - ); - - // TODO: allow a parameter to control this behavior in some form - if (false) { - throw new GrpcError( - grpc.status.UNKNOWN, - `unable to attach all iscsi devices` - ); - } - } - - // compare all device-mapper slaves with the newly created devices - // if any of the new devices are device-mapper slaves treat this as a - // multipath scenario - let allDeviceMapperSlaves = - await filesystem.getAllDeviceMapperSlaveDevices(); - let commonDevices = allDeviceMapperSlaves.filter((value) => - iscsiDevices.includes(value) - ); - - const useMultipath = - iscsiConnections.length > 1 || commonDevices.length > 0; - - // discover multipath device to use - if (useMultipath) { - device = await filesystem.getDeviceMapperDeviceFromSlaves( - iscsiDevices, - false - ); - - if (!device) { - throw new GrpcError( - grpc.status.UNKNOWN, - `failed to discover multipath device` - ); - } - } - break; - case "hostpath": - result = await mount.pathIsMounted(staging_target_path); - // if not mounted, mount + switch (driver.__getNodeOsDriver()) { + case NODE_OS_DRIVER_POSIX: + // csi spec stipulates that staging_target_path is a directory even for block mounts + result = await filesystem.pathExists(staging_target_path); if (!result) { - await mount.bindMount(volume_context.path, staging_target_path); - return {}; - } else { - return {}; + await filesystem.mkdir(staging_target_path, ["-p", "-m", "0750"]); } - break; - case "oneclient": - let oneclient = driver.getDefaultOneClientInstance(); - device = "oneclient"; - result = await mount.deviceIsMountedAtPath(device, staging_target_path); - if (result) { - return {}; - } + // get the `device` set + switch (node_attach_driver) { + case "nfs": + case "lustre": + device = `${volume_context.server}:${volume_context.share}`; + break; + case "smb": + device = `//${volume_context.server}/${volume_context.share}`; - if (volume_context.space_names) { - volume_context.space_names.split(",").forEach((space) => { - mount_flags.push("--space", space); - }); - } + // if not present add guest + let has_username = mount_flags.some((element) => { + element = element.trim().toLowerCase(); + return element.startsWith("username="); + }); - if (volume_context.space_ids) { - volume_context.space_ids.split(",").forEach((space) => { - mount_flags.push("--space-id", space); - }); - } - - if (normalizedSecrets.token) { - mount_flags.push("-t", normalizedSecrets.token); - } else { - 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) - const zb = driver.getDefaultZetabyteInstance(); - result = await zb.zfs.get(`${volume_context.zfs_asset_name}`, [ - "type", - "mountpoint", - ]); - result = result[`${volume_context.zfs_asset_name}`]; - switch (result.type.value) { - case "filesystem": - if (result.mountpoint.value != "legacy") { - // zfs set mountpoint=legacy - // zfs inherit mountpoint - await zb.zfs.set(`${volume_context.zfs_asset_name}`, { - mountpoint: "legacy", + // prevents driver from hanging on stdin waiting for a password to be entered at the cli + if (!has_username) { + let has_guest = mount_flags.some((element) => { + element = element.trim().toLowerCase(); + return element === "guest"; }); - } - device = `${volume_context.zfs_asset_name}`; - if (!fs_type) { - fs_type = "zfs"; + + if (!has_guest) { + mount_flags.push("guest"); + } } break; - case "volume": - device = `/dev/zvol/${volume_context.zfs_asset_name}`; + case "iscsi": + let portals = []; + if (volume_context.portal) { + portals.push(volume_context.portal.trim()); + } + + if (volume_context.portals) { + volume_context.portals.split(",").forEach((portal) => { + portals.push(portal.trim()); + }); + } + + // ensure full portal value + portals = portals.map((value) => { + if (!value.includes(":")) { + value += ":3260"; + } + + return value.trim(); + }); + + // ensure unique entries only + portals = [...new Set(portals)]; + + // stores actual device paths after iscsi login + let iscsiDevices = []; + + // stores configuration of targets/iqn/luns to connect to + let iscsiConnections = []; + for (let portal of portals) { + iscsiConnections.push({ + portal, + iqn: volume_context.iqn, + lun: volume_context.lun, + }); + } + + /** + * TODO: allow sending in iscsiConnection in a raw/manual format + * TODO: allow option to determine if send_targets should be invoked + * TODO: allow option to control whether nodedb entry should be created by driver + * TODO: allow option to control whether nodedb entry should be deleted by driver + */ + + for (let iscsiConnection of iscsiConnections) { + // create DB entry + // https://library.netapp.com/ecmdocs/ECMP1654943/html/GUID-8EC685B4-8CB6-40D8-A8D5-031A3899BCDC.html + // put these options in place to force targets managed by csi to be explicitly attached (in the case of unclearn shutdown etc) + let nodeDB = { + "node.startup": "manual", + //"node.session.scan": "manual", + }; + const nodeDBKeyPrefix = "node-db."; + for (const key in normalizedSecrets) { + if (key.startsWith(nodeDBKeyPrefix)) { + nodeDB[key.substr(nodeDBKeyPrefix.length)] = + normalizedSecrets[key]; + } + } + + // create 'DB' entry + await iscsi.iscsiadm.createNodeDBEntry( + iscsiConnection.iqn, + iscsiConnection.portal, + nodeDB + ); + // login + await iscsi.iscsiadm.login( + iscsiConnection.iqn, + iscsiConnection.portal + ); + + // get associated session + let session = await iscsi.iscsiadm.getSession( + iscsiConnection.iqn, + iscsiConnection.portal + ); + + // rescan in scenarios when login previously occurred but volumes never appeared + await iscsi.iscsiadm.rescanSession(session); + + // find device name + device = `/dev/disk/by-path/ip-${iscsiConnection.portal}-iscsi-${iscsiConnection.iqn}-lun-${iscsiConnection.lun}`; + let deviceByPath = device; + + // can take some time for device to show up, loop for some period + result = await filesystem.pathExists(device); + let timer_start = Math.round(new Date().getTime() / 1000); + let timer_max = 30; + let deviceCreated = result; + while (!result) { + await GeneralUtils.sleep(2000); + result = await filesystem.pathExists(device); + + if (result) { + deviceCreated = true; + break; + } + + let current_time = Math.round(new Date().getTime() / 1000); + if (!result && current_time - timer_start > timer_max) { + driver.ctx.logger.warn( + `hit timeout waiting for device node to appear: ${device}` + ); + break; + } + } + + if (deviceCreated) { + device = await filesystem.realpath(device); + iscsiDevices.push(device); + + driver.ctx.logger.info( + `successfully logged into portal ${iscsiConnection.portal} and created device ${deviceByPath} with realpath ${device}` + ); + } + } + + // let things settle + // this will help in dm scenarios + await GeneralUtils.sleep(2000); + + // filter duplicates + iscsiDevices = iscsiDevices.filter((value, index, self) => { + return self.indexOf(value) === index; + }); + + // only throw an error if we were not able to attach to *any* devices + if (iscsiDevices.length < 1) { + throw new GrpcError( + grpc.status.UNKNOWN, + `unable to attach any iscsi devices` + ); + } + + if (iscsiDevices.length != iscsiConnections.length) { + driver.ctx.logger.warn( + `failed to attach all iscsi devices/targets/portals` + ); + + // TODO: allow a parameter to control this behavior in some form + if (false) { + throw new GrpcError( + grpc.status.UNKNOWN, + `unable to attach all iscsi devices` + ); + } + } + + // compare all device-mapper slaves with the newly created devices + // if any of the new devices are device-mapper slaves treat this as a + // multipath scenario + let allDeviceMapperSlaves = + await filesystem.getAllDeviceMapperSlaveDevices(); + let commonDevices = allDeviceMapperSlaves.filter((value) => + iscsiDevices.includes(value) + ); + + const useMultipath = + iscsiConnections.length > 1 || commonDevices.length > 0; + + // discover multipath device to use + if (useMultipath) { + device = await filesystem.getDeviceMapperDeviceFromSlaves( + iscsiDevices, + false + ); + + if (!device) { + throw new GrpcError( + grpc.status.UNKNOWN, + `failed to discover multipath device` + ); + } + } + + break; + case "hostpath": + result = await mount.pathIsMounted(staging_target_path); + // if not mounted, mount + if (!result) { + await mount.bindMount(volume_context.path, staging_target_path); + return {}; + } else { + return {}; + } + + break; + case "oneclient": + let oneclient = driver.getDefaultOneClientInstance(); + 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 (normalizedSecrets.token) { + mount_flags.push("-t", normalizedSecrets.token); + } else { + 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) + const zb = driver.getDefaultZetabyteInstance(); + result = await zb.zfs.get(`${volume_context.zfs_asset_name}`, [ + "type", + "mountpoint", + ]); + result = result[`${volume_context.zfs_asset_name}`]; + switch (result.type.value) { + case "filesystem": + if (result.mountpoint.value != "legacy") { + // zfs set mountpoint=legacy + // zfs inherit mountpoint + await zb.zfs.set(`${volume_context.zfs_asset_name}`, { + mountpoint: "legacy", + }); + } + device = `${volume_context.zfs_asset_name}`; + if (!fs_type) { + fs_type = "zfs"; + } + break; + case "volume": + device = `/dev/zvol/${volume_context.zfs_asset_name}`; + break; + default: + throw new GrpcError( + grpc.status.UNKNOWN, + `unknown zfs asset type: ${result.type.value}` + ); + } break; default: throw new GrpcError( - grpc.status.UNKNOWN, - `unknown zfs asset type: ${result.type.value}` + grpc.status.INVALID_ARGUMENT, + `unknown/unsupported node_attach_driver: ${node_attach_driver}` ); } - break; - default: - throw new GrpcError( - grpc.status.INVALID_ARGUMENT, - `unknown/unsupported node_attach_driver: ${node_attach_driver}` - ); - } - switch (access_type) { - case "mount": - let is_block = false; - switch (node_attach_driver) { - case "iscsi": - is_block = true; - break; - case "zfs-local": - is_block = device.startsWith("/dev/zvol/"); - break; - } - - if (is_block) { - // block specific logic - if (!fs_type) { - fs_type = "ext4"; - } - - if (await filesystem.isBlockDevice(device)) { - // format - result = await filesystem.deviceIsFormatted(device); - if (!result) { - let formatOptions = _.get( - driver.options.node.format, - [fs_type, "customOptions"], - [] - ); - if (!Array.isArray(formatOptions)) { - formatOptions = []; - } - await filesystem.formatDevice(device, fs_type, formatOptions); + // deal with `device` now that we have one + switch (access_type) { + case "mount": + let is_block = false; + switch (node_attach_driver) { + case "iscsi": + is_block = true; + break; + case "zfs-local": + is_block = device.startsWith("/dev/zvol/"); + break; } - let fs_info = await filesystem.getDeviceFilesystemInfo(device); - fs_type = fs_info.type; + // format device + if (is_block) { + // block specific logic + if (!fs_type) { + fs_type = "ext4"; + } - // fsck + if (await filesystem.isBlockDevice(device)) { + // format + result = await filesystem.deviceIsFormatted(device); + if (!result) { + let formatOptions = _.get( + driver.options.node.format, + [fs_type, "customOptions"], + [] + ); + if (!Array.isArray(formatOptions)) { + formatOptions = []; + } + await filesystem.formatDevice(device, fs_type, formatOptions); + } + + let fs_info = await filesystem.getDeviceFilesystemInfo(device); + fs_type = fs_info.type; + + // fsck + result = await mount.deviceIsMountedAtPath( + device, + staging_target_path + ); + if (!result) { + // https://github.com/democratic-csi/democratic-csi/issues/52#issuecomment-768463401 + let checkFilesystem = + driver.options.node.mount.checkFilesystem[fs_type] || {}; + if (checkFilesystem.enabled) { + await filesystem.checkFilesystem( + device, + fs_type, + checkFilesystem.customOptions || [], + checkFilesystem.customFilesystemOptions || [] + ); + } + } + } + } + + // set default fs_type if still unset + if (!fs_type) { + switch (node_attach_driver) { + case "nfs": + fs_type = "nfs"; + break; + case "lustre": + fs_type = "lustre"; + break; + case "smb": + fs_type = "cifs"; + break; + case "iscsi": + fs_type = "ext4"; + break; + default: + break; + } + } + + // mount `device` result = await mount.deviceIsMountedAtPath( device, staging_target_path ); if (!result) { - // https://github.com/democratic-csi/democratic-csi/issues/52#issuecomment-768463401 - let checkFilesystem = - driver.options.node.mount.checkFilesystem[fs_type] || {}; - if (checkFilesystem.enabled) { - await filesystem.checkFilesystem( - device, - fs_type, - checkFilesystem.customOptions || [], - checkFilesystem.customFilesystemOptions || [] - ); + await mount.mount( + device, + staging_target_path, + ["-t", fs_type].concat(["-o", mount_flags.join(",")]) + ); + } + + // expand fs if necessary + if (await filesystem.isBlockDevice(device)) { + // go ahead and expand fs (this covers cloned setups where expand is not explicitly invoked) + switch (fs_type) { + case "ext4": + case "ext3": + case "ext4dev": + //await filesystem.checkFilesystem(device, fs_info.type); + try { + await filesystem.expandFilesystem(device, fs_type); + } catch (err) { + // mount is clean and rw, but it will not expand until clean umount has been done + // failed to execute filesystem command: resize2fs /dev/sda, response: {"code":1,"stdout":"Couldn't find valid filesystem superblock.\n","stderr":"resize2fs 1.44.5 (15-Dec-2018)\nresize2fs: Superblock checksum does not match superblock while trying to open /dev/sda\n"} + // /dev/sda on /var/lib/kubelet/plugins/kubernetes.io/csi/pv/pvc-4a80757e-5e87-475d-826f-44fcc4719348/globalmount type ext4 (rw,relatime,stripe=256) + if ( + err.code == 1 && + err.stdout.includes("find valid filesystem superblock") && + err.stderr.includes("checksum does not match superblock") + ) { + driver.ctx.logger.warn( + `successful mount, unsuccessful fs resize: attempting abnormal umount/mount/resize2fs to clear things up ${staging_target_path} (${device})` + ); + + // try an unmount/mount/fsck cycle again just to clean things up + await mount.umount(staging_target_path, []); + await mount.mount( + device, + staging_target_path, + ["-t", fs_type].concat(["-o", mount_flags.join(",")]) + ); + await filesystem.expandFilesystem(device, fs_type); + } else { + throw err; + } + } + break; + case "btrfs": + case "xfs": + //await filesystem.checkFilesystem(device, fs_info.type); + await filesystem.expandFilesystem( + staging_target_path, + fs_type + ); + break; + default: + // unsupported filesystem + throw new GrpcError( + grpc.status.FAILED_PRECONDITION, + `unsupported/unknown filesystem ${fs_type}` + ); } } - } - } - result = await mount.deviceIsMountedAtPath(device, staging_target_path); - if (!result) { - if (!fs_type) { - switch (node_attach_driver) { - case "nfs": - fs_type = "nfs"; - break; - case "lustre": - fs_type = "lustre"; - break; - case "smb": - fs_type = "cifs"; - break; - case "iscsi": - fs_type = "ext4"; - break; - default: - break; + break; + case "block": + //result = await mount.deviceIsMountedAtPath(device, block_path); + result = await mount.deviceIsMountedAtPath("dev", block_path); + if (!result) { + result = await filesystem.pathExists(staging_target_path); + if (!result) { + await filesystem.mkdir(staging_target_path, [ + "-p", + "-m", + "0750", + ]); + } + + result = await filesystem.pathExists(block_path); + if (!result) { + await filesystem.touch(block_path); + } + + await mount.bindMount(device, block_path, [ + "-o", + bind_mount_flags.join(","), + ]); } - } - await mount.mount( - device, - staging_target_path, - ["-t", fs_type].concat(["-o", mount_flags.join(",")]) + break; + default: + throw new GrpcError( + grpc.status.INVALID_ARGUMENT, + `unknown/unsupported access_type: ${access_type}` + ); + } + break; + case NODE_OS_DRIVER_CSI_PROXY: + // sanity check node_attach_driver + if (!["smb", "iscsi"].includes(node_attach_driver)) { + throw new GrpcError( + grpc.status.UNIMPLEMENTED, + `csi-proxy does not work with node_attach_driver: ${node_attach_driver}` ); } - if (await filesystem.isBlockDevice(device)) { - // go ahead and expand fs (this covers cloned setups where expand is not explicitly invoked) - switch (fs_type) { - case "ext4": - case "ext3": - case "ext4dev": - //await filesystem.checkFilesystem(device, fs_info.type); - try { - await filesystem.expandFilesystem(device, fs_type); - } catch (err) { - // mount is clean and rw, but it will not expand until clean umount has been done - // failed to execute filesystem command: resize2fs /dev/sda, response: {"code":1,"stdout":"Couldn't find valid filesystem superblock.\n","stderr":"resize2fs 1.44.5 (15-Dec-2018)\nresize2fs: Superblock checksum does not match superblock while trying to open /dev/sda\n"} - // /dev/sda on /var/lib/kubelet/plugins/kubernetes.io/csi/pv/pvc-4a80757e-5e87-475d-826f-44fcc4719348/globalmount type ext4 (rw,relatime,stripe=256) - if ( - err.code == 1 && - err.stdout.includes("find valid filesystem superblock") && - err.stderr.includes("checksum does not match superblock") - ) { - driver.ctx.logger.warn( - `successful mount, unsuccessful fs resize: attempting abnormal umount/mount/resize2fs to clear things up ${staging_target_path} (${device})` - ); + // sanity check fs_type + if (fs_type && !["ntfs", "cifs"].includes(fs_type)) { + throw new GrpcError( + grpc.status.UNIMPLEMENTED, + `csi-proxy does not work with fs_type: ${fs_type}` + ); + } - // try an unmount/mount/fsck cycle again just to clean things up - await mount.umount(staging_target_path, []); - await mount.mount( - device, - staging_target_path, - ["-t", fs_type].concat(["-o", mount_flags.join(",")]) - ); - await filesystem.expandFilesystem(device, fs_type); - } else { - throw err; + // load up the client instance + const csiProxyClient = driver.getDefaultCsiProxyClientInstance(); + + switch (node_attach_driver) { + case "smb": + /** + * smb mount creates a link at this location and if the dir already exists + * it explodes + * + * if path exists but is NOT symlink delete it + */ + result = await csiProxyClient.FilesystemPathExists( + staging_target_path + ); + if (result) { + result = await csiProxyClient.FilesystemIsSymlink( + staging_target_path + ); + if (!result) { + await csiProxyClient.executeRPC("filesystem", "Rmdir", { + path: staging_target_path, + }); + } + } + + device = `//${volume_context.server}/${volume_context.share}`; + const username = driver.getMountFlagValue(mount_flags, "username"); + const password = driver.getMountFlagValue(mount_flags, "password"); + + if (!username || !password) { + throw new Error("username and password required"); + } + + try { + await csiProxyClient.executeRPC("smb", "NewSmbGlobalMapping", { + // convert path separator for windows style path + remote_path: + filesystem.covertUnixSeparatorToWindowsSeparator(device), + local_path: staging_target_path, + username: `${volume_context.server}\\${username}`, + password, + }); + } catch (e) { + let details = _.get(e, "details", ""); + if (!details.includes("ResourceExists")) { + throw e; + } else { + // path should be a symlink if already present + result = await csiProxyClient.executeRPC( + "filesystem", + "IsSymlink", + { path: staging_target_path } + ); + if (!_.get(result, "is_symlink", false)) { + throw e; } } - break; - case "btrfs": - case "xfs": - //await filesystem.checkFilesystem(device, fs_info.type); - await filesystem.expandFilesystem(staging_target_path, fs_type); - break; - default: - // unsupported filesystem - throw new GrpcError( - grpc.status.FAILED_PRECONDITION, - `unsupported/unknown filesystem ${fs_type}` - ); - } + } + + break; + case "iscsi": + switch (access_type) { + case "mount": + let portals = []; + if (volume_context.portal) { + portals.push(volume_context.portal.trim()); + } + + if (volume_context.portals) { + volume_context.portals.split(",").forEach((portal) => { + portals.push(portal.trim()); + }); + } + + // ensure full portal value + portals = portals.map((value) => { + if (!value.includes(":")) { + value += ":3260"; + } + + return value.trim(); + }); + + // ensure unique entries only + portals = [...new Set(portals)]; + + // stores actual device paths after iscsi login + let iscsiDevices = []; + + // stores configuration of targets/iqn/luns to connect to + let iscsiConnections = []; + for (let portal of portals) { + iscsiConnections.push({ + portal, + iqn: volume_context.iqn, + lun: volume_context.lun, + }); + } + + // no multipath support yet + // https://github.com/kubernetes-csi/csi-proxy/pull/99 + for (let iscsiConnection of iscsiConnections) { + // add target portal + let parts = iscsiConnection.portal.split(":"); + let target_address = parts[0]; + let target_port = parts[1] || "3260"; + let target_portal = { + target_address, + target_port, + }; + // this is idempotent + await csiProxyClient.executeRPC("iscsi", "AddTargetPortal", { + target_portal, + }); + + // login + try { + let auth_type = "NONE"; + let chap_username = ""; + let chap_secret = ""; + if ( + normalizedSecrets[ + "node-db.node.session.auth.authmethod" + ] == "CHAP" + ) { + // set auth_type + if ( + normalizedSecrets[ + "node-db.node.session.auth.username" + ] && + normalizedSecrets[ + "node-db.node.session.auth.password" + ] && + normalizedSecrets[ + "node-db.node.session.auth.username_in" + ] && + normalizedSecrets[ + "node-db.node.session.auth.password_in" + ] + ) { + auth_type = "MUTUAL_CHAP"; + } else if ( + normalizedSecrets[ + "node-db.node.session.auth.username" + ] && + normalizedSecrets["node-db.node.session.auth.password"] + ) { + auth_type = "ONE_WAY_CHAP"; + } + + // set credentials + if ( + normalizedSecrets[ + "node-db.node.session.auth.username" + ] && + normalizedSecrets["node-db.node.session.auth.password"] + ) { + chap_username = + normalizedSecrets[ + "node-db.node.session.auth.username" + ]; + + chap_secret = + normalizedSecrets[ + "node-db.node.session.auth.password" + ]; + } + } + await csiProxyClient.executeRPC("iscsi", "ConnectTarget", { + target_portal, + iqn: iscsiConnection.iqn, + /** + * NONE + * ONE_WAY_CHAP + * MUTUAL_CHAP + */ + auth_type, + chap_username, + chap_secret, + }); + } catch (e) { + let details = _.get(e, "details", ""); + if ( + !details.includes( + "The target has already been logged in via an iSCSI session" + ) + ) { + throw e; + } + } + + // discover? + //await csiProxyClient.executeRPC("iscsi", "DiscoverTargetPortal", { + // target_portal, + //}); + + // rescan + await csiProxyClient.executeRPC("disk", "Rescan"); + + // get device + result = await csiProxyClient.executeRPC( + "iscsi", + "GetTargetDisks", + { + target_portal, + iqn: iscsiConnection.iqn, + } + ); + + // TODO: this is a gross assumption since we currently only allow 1 lun per target + // iterate this response and find disk + //result = await csiProxyClient.executeRPC("disk", "ListDiskLocations"); + let diskIds = _.get(result, "diskIDs", []); + if (diskIds.length != 1) { + throw new Error( + `${diskIds.length} disks on the target, no way to know which is the relevant disk` + ); + } + let disk_number = diskIds[0]; + + result = await csiProxyClient.executeRPC( + "volume", + "ListVolumesOnDisk", + { disk_number } + ); + + let node_volume_id; + node_volume_id = + await csiProxyClient.getVolumeIdFromDiskNumber(disk_number); + + if (!node_volume_id) { + // this is technically idempotent call so should not hurt anything if already initialized + await csiProxyClient.executeRPC("disk", "PartitionDisk", { + disk_number, + }); + node_volume_id = + await csiProxyClient.getVolumeIdFromDiskNumber( + disk_number + ); + } + + if (!node_volume_id) { + throw new Error( + "failed to create/discover volume for disk" + ); + } + result = await csiProxyClient.executeRPC( + "volume", + "IsVolumeFormatted", + { volume_id: node_volume_id } + ); + + // format device + if (!result.formatted) { + await csiProxyClient.executeRPC("volume", "FormatVolume", { + volume_id: node_volume_id, + }); + } + + // ensure staging path present + result = await csiProxyClient.FilesystemPathExists( + staging_target_path + ); + if (!result) { + await csiProxyClient.executeRPC("filesystem", "Mkdir", { + path: staging_target_path, + }); + } + + // mount up! + try { + result = await csiProxyClient.executeRPC( + "volume", + "MountVolume", + { + volume_id: node_volume_id, + target_path: staging_target_path, + } + ); + } catch (e) { + // assume for now that if something is mounted in the location it the desired volume + let details = _.get(e, "details", ""); + if ( + !details.includes( + "The requested access path is already in use" + ) + ) { + throw e; + } + } + + // let things settle + // this will help in dm scenarios + await GeneralUtils.sleep(2000); + + // windows does not support multipath currently + // break if we make it this far + break; + } + + break; + case "block": + default: + throw new GrpcError( + grpc.status.UNIMPLEMENTED, + `access_type ${access_type} unsupported` + ); + } + break; + default: + throw new GrpcError( + grpc.status.INVALID_ARGUMENT, + `unknown/unsupported node_attach_driver: ${node_attach_driver}` + ); + break; } - break; - case "block": - //result = await mount.deviceIsMountedAtPath(device, block_path); - result = await mount.deviceIsMountedAtPath("dev", block_path); - if (!result) { - result = await filesystem.pathExists(staging_target_path); - if (!result) { - await filesystem.mkdir(staging_target_path, ["-p", "-m", "0750"]); - } - - result = await filesystem.pathExists(block_path); - if (!result) { - await filesystem.touch(block_path); - } - - await mount.bindMount(device, block_path, [ - "-o", - bind_mount_flags.join(","), - ]); - } break; default: throw new GrpcError( - grpc.status.INVALID_ARGUMENT, - `unknown/unsupported access_type: ${access_type}` + grpc.status.UNIMPLEMENTED, + `unkown NODE OS DRIVER: ${driver.__getNodeOsDriver()}` ); } @@ -943,189 +1516,320 @@ class CsiBaseDriver { // TODO: use the x-* mount options to detect if we should delete target - try { - result = await mount.pathIsMounted(block_path); - } catch (err) { - /** - * on stalled fs such as nfs, even findmnt will return immediately for the base mount point - * so in the case of timeout here (base mount point and then a file/folder beneath it) we almost certainly are not a block device - * AND the fs is probably stalled - */ - if (err.timeout) { - driver.ctx.logger.warn( - `detected stale mount, attempting to force unmount: ${normalized_staging_path}` - ); - await mount.umount( - normalized_staging_path, - umount_args.concat(umount_force_extra_args) - ); - result = false; // assume we are *NOT* a block device at this point - } else { - throw err; - } - } + switch (driver.__getNodeOsDriver()) { + case NODE_OS_DRIVER_POSIX: + try { + result = await mount.pathIsMounted(block_path); + } catch (err) { + /** + * on stalled fs such as nfs, even findmnt will return immediately for the base mount point + * so in the case of timeout here (base mount point and then a file/folder beneath it) we almost certainly are not a block device + * AND the fs is probably stalled + */ + if (err.timeout) { + driver.ctx.logger.warn( + `detected stale mount, attempting to force unmount: ${normalized_staging_path}` + ); + await mount.umount( + normalized_staging_path, + umount_args.concat(umount_force_extra_args) + ); + result = false; // assume we are *NOT* a block device at this point + } else { + throw err; + } + } - if (result) { - is_block = true; - access_type = "block"; - block_device_info = await filesystem.getBlockDevice(block_path); - normalized_staging_path = block_path; - } else { - result = await mount.pathIsMounted(staging_target_path); - if (result) { - let device = await mount.getMountPointDevice(staging_target_path); - result = await filesystem.isBlockDevice(device); if (result) { is_block = true; - block_device_info = await filesystem.getBlockDevice(device); - } - } - } - - result = await mount.pathIsMounted(normalized_staging_path); - if (result) { - try { - result = await mount.umount(normalized_staging_path, umount_args); - } catch (err) { - if (err.timeout) { - driver.ctx.logger.warn( - `hit timeout waiting to unmount path: ${normalized_staging_path}` - ); - result = await mount.getMountDetails(normalized_staging_path); - switch (result.fstype) { - case "nfs": - case "nfs4": - driver.ctx.logger.warn( - `detected stale nfs filesystem, attempting to force unmount: ${normalized_staging_path}` - ); - result = await mount.umount( - normalized_staging_path, - umount_args.concat(umount_force_extra_args) - ); - break; - default: - throw err; - break; - } + access_type = "block"; + block_device_info = await filesystem.getBlockDevice(block_path); + normalized_staging_path = block_path; } else { - throw err; - } - } - } - - if (is_block) { - let realBlockDeviceInfos = []; - // detect if is a multipath device - is_device_mapper = await filesystem.isDeviceMapperDevice( - block_device_info.path - ); - - if (is_device_mapper) { - let realBlockDevices = await filesystem.getDeviceMapperDeviceSlaves( - block_device_info.path - ); - for (const realBlockDevice of realBlockDevices) { - realBlockDeviceInfos.push( - await filesystem.getBlockDevice(realBlockDevice) - ); - } - } else { - realBlockDeviceInfos = [block_device_info]; - } - - // TODO: this could be made async to detach all simultaneously - for (const block_device_info_i of realBlockDeviceInfos) { - if (block_device_info_i.tran == "iscsi") { - // figure out which iscsi session this belongs to and logout - // scan /dev/disk/by-path/ip-*? - // device = `/dev/disk/by-path/ip-${volume_context.portal}-iscsi-${volume_context.iqn}-lun-${volume_context.lun}`; - // parse output from `iscsiadm -m session -P 3` - let sessions = await iscsi.iscsiadm.getSessionsDetails(); - for (let i = 0; i < sessions.length; i++) { - let session = sessions[i]; - let is_attached_to_session = false; - - if ( - session.attached_scsi_devices && - session.attached_scsi_devices.host && - session.attached_scsi_devices.host.devices - ) { - is_attached_to_session = - session.attached_scsi_devices.host.devices.some((device) => { - if (device.attached_scsi_disk == block_device_info_i.name) { - return true; - } - return false; - }); + result = await mount.pathIsMounted(staging_target_path); + if (result) { + let device = await mount.getMountPointDevice(staging_target_path); + result = await filesystem.isBlockDevice(device); + if (result) { + is_block = true; + block_device_info = await filesystem.getBlockDevice(device); } + } + } - if (is_attached_to_session) { - let timer_start; - let timer_max; - - timer_start = Math.round(new Date().getTime() / 1000); - timer_max = 30; - let loggedOut = false; - while (!loggedOut) { - try { - await iscsi.iscsiadm.logout(session.target, [ - session.persistent_portal, - ]); - loggedOut = true; - } catch (err) { - await sleep(2000); - let current_time = Math.round(new Date().getTime() / 1000); - if (current_time - timer_start > timer_max) { - // not throwing error for now as future invocations would not enter code path anyhow - loggedOut = true; - //throw new GrpcError( - // grpc.status.UNKNOWN, - // `hit timeout trying to logout of iscsi target: ${session.persistent_portal}` - //); - } - } - } - - timer_start = Math.round(new Date().getTime() / 1000); - timer_max = 30; - let deletedEntry = false; - while (!deletedEntry) { - try { - await iscsi.iscsiadm.deleteNodeDBEntry( - session.target, - session.persistent_portal + result = await mount.pathIsMounted(normalized_staging_path); + if (result) { + try { + result = await mount.umount(normalized_staging_path, umount_args); + } catch (err) { + if (err.timeout) { + driver.ctx.logger.warn( + `hit timeout waiting to unmount path: ${normalized_staging_path}` + ); + result = await mount.getMountDetails(normalized_staging_path); + switch (result.fstype) { + case "nfs": + case "nfs4": + driver.ctx.logger.warn( + `detected stale nfs filesystem, attempting to force unmount: ${normalized_staging_path}` ); - deletedEntry = true; - } catch (err) { - await sleep(2000); - let current_time = Math.round(new Date().getTime() / 1000); - if (current_time - timer_start > timer_max) { - // not throwing error for now as future invocations would not enter code path anyhow - deletedEntry = true; - //throw new GrpcError( - // grpc.status.UNKNOWN, - // `hit timeout trying to delete iscsi node DB entry: ${session.target}, ${session.persistent_portal}` - //); + result = await mount.umount( + normalized_staging_path, + umount_args.concat(umount_force_extra_args) + ); + break; + default: + throw err; + } + } else { + throw err; + } + } + } + + if (is_block) { + let realBlockDeviceInfos = []; + // detect if is a multipath device + is_device_mapper = await filesystem.isDeviceMapperDevice( + block_device_info.path + ); + + if (is_device_mapper) { + let realBlockDevices = await filesystem.getDeviceMapperDeviceSlaves( + block_device_info.path + ); + for (const realBlockDevice of realBlockDevices) { + realBlockDeviceInfos.push( + await filesystem.getBlockDevice(realBlockDevice) + ); + } + } else { + realBlockDeviceInfos = [block_device_info]; + } + + // TODO: this could be made async to detach all simultaneously + for (const block_device_info_i of realBlockDeviceInfos) { + if (block_device_info_i.tran == "iscsi") { + // figure out which iscsi session this belongs to and logout + // scan /dev/disk/by-path/ip-*? + // device = `/dev/disk/by-path/ip-${volume_context.portal}-iscsi-${volume_context.iqn}-lun-${volume_context.lun}`; + // parse output from `iscsiadm -m session -P 3` + let sessions = await iscsi.iscsiadm.getSessionsDetails(); + for (let i = 0; i < sessions.length; i++) { + let session = sessions[i]; + let is_attached_to_session = false; + + if ( + session.attached_scsi_devices && + session.attached_scsi_devices.host && + session.attached_scsi_devices.host.devices + ) { + is_attached_to_session = + session.attached_scsi_devices.host.devices.some( + (device) => { + if ( + device.attached_scsi_disk == block_device_info_i.name + ) { + return true; + } + return false; + } + ); + } + + if (is_attached_to_session) { + let timer_start; + let timer_max; + + timer_start = Math.round(new Date().getTime() / 1000); + timer_max = 30; + let loggedOut = false; + while (!loggedOut) { + try { + await iscsi.iscsiadm.logout(session.target, [ + session.persistent_portal, + ]); + loggedOut = true; + } catch (err) { + await GeneralUtils.sleep(2000); + let current_time = Math.round( + new Date().getTime() / 1000 + ); + if (current_time - timer_start > timer_max) { + // not throwing error for now as future invocations would not enter code path anyhow + loggedOut = true; + //throw new GrpcError( + // grpc.status.UNKNOWN, + // `hit timeout trying to logout of iscsi target: ${session.persistent_portal}` + //); + } + } + } + + timer_start = Math.round(new Date().getTime() / 1000); + timer_max = 30; + let deletedEntry = false; + while (!deletedEntry) { + try { + await iscsi.iscsiadm.deleteNodeDBEntry( + session.target, + session.persistent_portal + ); + deletedEntry = true; + } catch (err) { + await GeneralUtils.sleep(2000); + let current_time = Math.round( + new Date().getTime() / 1000 + ); + if (current_time - timer_start > timer_max) { + // not throwing error for now as future invocations would not enter code path anyhow + deletedEntry = true; + //throw new GrpcError( + // grpc.status.UNKNOWN, + // `hit timeout trying to delete iscsi node DB entry: ${session.target}, ${session.persistent_portal}` + //); + } + } } } } } } } - } - } - if (access_type == "block") { - // remove touched file - result = await filesystem.pathExists(block_path); - if (result) { - result = await filesystem.rm(block_path); - } - } + if (access_type == "block") { + // remove touched file + result = await filesystem.pathExists(block_path); + if (result) { + result = await filesystem.rm(block_path); + } + } - result = await filesystem.pathExists(staging_target_path); - if (result) { - result = await filesystem.rmdir(staging_target_path); + result = await filesystem.pathExists(staging_target_path); + if (result) { + result = await filesystem.rmdir(staging_target_path); + } + break; + case NODE_OS_DRIVER_CSI_PROXY: + // load up the client instance + const csiProxyClient = driver.getDefaultCsiProxyClientInstance(); + // for testing purposes + const volume_context = await driver.getDerivedVolumeContext(call); + if (!volume_context) { + throw new GrpcError( + grpc.status.NOT_FOUND, + `unable to retrieve volume_context for volume: ${volume_id}` + ); + } + + const node_attach_driver = volume_context.node_attach_driver; + + async function removePath(p) { + // remove staging path + try { + await csiProxyClient.executeRPC("filesystem", "Rmdir", { + path: p, + // remove all contents under the directory + //force: false, + }); + } catch (e) { + let details = _.get(e, "details", ""); + if ( + !details.includes("The system cannot find the file specified") + ) { + throw e; + } + } + } + + switch (node_attach_driver) { + case "smb": + try { + await csiProxyClient.executeRPC("smb", "RemoveSmbGlobalMapping", { + remote_path: `\\\\${volume_context.server}\\${volume_context.share}`, + }); + } catch (e) { + let details = _.get(e, "details", ""); + if (!details.includes("No MSFT_SmbGlobalMapping objects found")) { + throw e; + } + } + + break; + case "iscsi": + let target_portal = { + target_address: volume_context.portal.split(":")[0], + target_port: volume_context.portal.split(":")[1] || 3260, + }; + + let iqn = volume_context.iqn; + let node_volume_id; + + // ok to be null/undefined + node_volume_id = await csiProxyClient.getVolumeIdFromIscsiTarget( + target_portal, + iqn + ); + + if (node_volume_id) { + // write volume cache + await csiProxyClient.executeRPC("volume", "WriteVolumeCache", { + volume_id: node_volume_id, + }); + + // umount first + try { + await csiProxyClient.executeRPC("volume", "UnmountVolume", { + volume_id: node_volume_id, + target_path: staging_target_path, + }); + } catch (e) { + let details = _.get(e, "details", ""); + if (!details.includes("The access path is not valid")) { + throw e; + } + } + } + + try { + await csiProxyClient.executeRPC("iscsi", "DisconnectTarget", { + target_portal, + iqn, + }); + } catch (e) { + let details = _.get(e, "details", ""); + if (!details.includes("ObjectNotFound")) { + throw e; + } + } + + try { + await csiProxyClient.executeRPC("iscsi", "RemoveTargetPortal", { + target_portal, + }); + } catch (e) { + let details = _.get(e, "details", ""); + if (!details.includes("ObjectNotFound")) { + throw e; + } + } + + break; + default: + throw new GrpcError( + grpc.status.INVALID_ARGUMENT, + `unknown/unsupported node_attach_driver: ${node_attach_driver}` + ); + } + + // remove staging path + await removePath(normalized_staging_path); + break; + default: + throw new GrpcError( + grpc.status.UNIMPLEMENTED, + `unkown NODE OS DRIVER: ${driver.__getNodeOsDriver()}` + ); } return {}; @@ -1181,109 +1885,195 @@ class CsiBaseDriver { if (readonly) bind_mount_flags.push("ro"); // , "x-democratic-csi.ro" - switch (node_attach_driver) { - case "nfs": - case "smb": - case "lustre": - case "oneclient": - case "hostpath": - case "iscsi": - case "zfs-local": - // ensure appropriate directories/files - switch (access_type) { - case "mount": - // ensure directory exists - result = await filesystem.pathExists(target_path); - if (!result) { - await filesystem.mkdir(target_path, ["-p", "-m", "0750"]); + switch (driver.__getNodeOsDriver()) { + case NODE_OS_DRIVER_POSIX: + switch (node_attach_driver) { + case "nfs": + case "smb": + case "lustre": + case "oneclient": + case "hostpath": + case "iscsi": + case "zfs-local": + // ensure appropriate directories/files + switch (access_type) { + case "mount": + // ensure directory exists + result = await filesystem.pathExists(target_path); + if (!result) { + await filesystem.mkdir(target_path, ["-p", "-m", "0750"]); + } + + break; + case "block": + // ensure target_path directory exists as target path should be a file + let target_dir = await filesystem.dirname(target_path); + result = await filesystem.pathExists(target_dir); + if (!result) { + await filesystem.mkdir(target_dir, ["-p", "-m", "0750"]); + } + + // ensure target file exists + result = await filesystem.pathExists(target_path); + if (!result) { + await filesystem.touch(target_path); + } + break; + default: + throw new GrpcError( + grpc.status.INVALID_ARGUMENT, + `unsupported/unknown access_type ${access_type}` + ); } - break; - case "block": - // ensure target_path directory exists as target path should be a file - let target_dir = await filesystem.dirname(target_path); - result = await filesystem.pathExists(target_dir); - if (!result) { - await filesystem.mkdir(target_dir, ["-p", "-m", "0750"]); + // ensure bind mount + if (staging_target_path) { + let normalized_staging_device; + let normalized_staging_path; + + if (access_type == "block") { + normalized_staging_path = staging_target_path + "/block_device"; + } else { + normalized_staging_path = staging_target_path; + } + + // sanity check to ensure the staged path is actually mounted + result = await mount.pathIsMounted(normalized_staging_path); + if (!result) { + throw new GrpcError( + grpc.status.FAILED_PRECONDITION, + `staging path is not mounted: ${normalized_staging_path}` + ); + } + + result = await mount.pathIsMounted(target_path); + // if not mounted, mount + if (!result) { + await mount.bindMount(normalized_staging_path, target_path, [ + "-o", + bind_mount_flags.join(","), + ]); + } else { + // if is mounted, ensure proper source + if (access_type == "block") { + normalized_staging_device = "dev"; // special syntax for single file bind mounts + } else { + normalized_staging_device = await mount.getMountPointDevice( + staging_target_path + ); + } + result = await mount.deviceIsMountedAtPath( + normalized_staging_device, + target_path + ); + if (!result) { + throw new GrpcError( + grpc.status.FAILED_PRECONDITION, + `it appears something else is already mounted at ${target_path}` + ); + } + } + + return {}; } - // ensure target file exists - result = await filesystem.pathExists(target_path); - if (!result) { - await filesystem.touch(target_path); - } - break; + // unsupported filesystem + throw new GrpcError( + grpc.status.FAILED_PRECONDITION, + `only staged configurations are valid` + ); default: throw new GrpcError( grpc.status.INVALID_ARGUMENT, - `unsupported/unknown access_type ${access_type}` + `unknown/unsupported node_attach_driver: ${node_attach_driver}` ); } + break; + case NODE_OS_DRIVER_CSI_PROXY: + switch (node_attach_driver) { + //case "nfs": + case "smb": + //case "lustre": + //case "oneclient": + //case "hostpath": + case "iscsi": + //case "zfs-local": + // ensure appropriate directories/files + switch (access_type) { + case "mount": + break; + case "block": + default: + throw new GrpcError( + grpc.status.INVALID_ARGUMENT, + `unsupported/unknown access_type ${access_type}` + ); + } - // ensure bind mount - if (staging_target_path) { - let normalized_staging_device; - let normalized_staging_path; + // ensure bind mount + if (staging_target_path) { + const csiProxyClient = driver.getDefaultCsiProxyClientInstance(); - if (access_type == "block") { - normalized_staging_path = staging_target_path + "/block_device"; - } else { - normalized_staging_path = staging_target_path; - } + let normalized_staging_path; - // sanity check to ensure the staged path is actually mounted - result = await mount.pathIsMounted(normalized_staging_path); - if (!result) { + if (access_type == "block") { + normalized_staging_path = staging_target_path + "/block_device"; + } else { + normalized_staging_path = staging_target_path; + } + + // source path + result = await csiProxyClient.FilesystemPathExists( + normalized_staging_path + ); + if (!result) { + throw new GrpcError( + grpc.status.FAILED_PRECONDITION, + `staging path is not mounted: ${normalized_staging_path}` + ); + } + + // target path + result = await csiProxyClient.FilesystemPathExists(target_path); + // already published + if (result) { + result = await csiProxyClient.FilesystemIsSymlink(target_path); + if (!result) { + throw new GrpcError( + grpc.status.FAILED_PRECONDITION, + `target path exists but is not a symlink as it should be: ${target_path}` + ); + } + return {}; + } + + // create symlink + await csiProxyClient.executeRPC("filesystem", "CreateSymlink", { + source_path: normalized_staging_path, + target_path, + }); + + return {}; + } + + // unsupported filesystem throw new GrpcError( grpc.status.FAILED_PRECONDITION, - `staging path is not mounted: ${normalized_staging_path}` + `only staged configurations are valid` ); - } - - result = await mount.pathIsMounted(target_path); - // if not mounted, mount - if (!result) { - await mount.bindMount(normalized_staging_path, target_path, [ - "-o", - bind_mount_flags.join(","), - ]); - } else { - // if is mounted, ensure proper source - if (access_type == "block") { - normalized_staging_device = "dev"; // special syntax for single file bind mounts - } else { - normalized_staging_device = await mount.getMountPointDevice( - staging_target_path - ); - } - result = await mount.deviceIsMountedAtPath( - normalized_staging_device, - target_path + default: + throw new GrpcError( + grpc.status.INVALID_ARGUMENT, + `unknown/unsupported node_attach_driver: ${node_attach_driver}` ); - if (!result) { - throw new GrpcError( - grpc.status.FAILED_PRECONDITION, - `it appears something else is already mounted at ${target_path}` - ); - } - } - - return {}; } - - // unsupported filesystem - throw new GrpcError( - grpc.status.FAILED_PRECONDITION, - `only staged configurations are valid` - ); + break; default: throw new GrpcError( - grpc.status.INVALID_ARGUMENT, - `unknown/unsupported node_attach_driver: ${node_attach_driver}` + grpc.status.UNIMPLEMENTED, + `unkown NODE OS DRIVER: ${driver.__getNodeOsDriver()}` ); } - - return {}; } async NodeUnpublishVolume(call) { @@ -1303,64 +2093,97 @@ class CsiBaseDriver { const umount_args = []; const umount_force_extra_args = ["--force", "--lazy"]; - try { - result = await mount.pathIsMounted(target_path); - } catch (err) { - // running findmnt on non-existant paths return immediately - // the only time this should timeout is on a stale fs - // so if timeout is hit we should be near certain it is indeed mounted - if (err.timeout) { - driver.ctx.logger.warn( - `detected stale mount, attempting to force unmount: ${target_path}` - ); - await mount.umount( - target_path, - umount_args.concat(umount_force_extra_args) - ); - result = false; // assume we have fully unmounted - } else { - throw err; - } - } - - if (result) { - try { - result = await mount.umount(target_path, umount_args); - } catch (err) { - if (err.timeout) { - driver.ctx.logger.warn( - `hit timeout waiting to unmount path: ${target_path}` - ); - // bind mounts do show the 'real' fs details - result = await mount.getMountDetails(target_path); - switch (result.fstype) { - case "nfs": - case "nfs4": - driver.ctx.logger.warn( - `detected stale nfs filesystem, attempting to force unmount: ${target_path}` - ); - result = await mount.umount( - target_path, - umount_args.concat(umount_force_extra_args) - ); - break; - default: - throw err; - break; + switch (driver.__getNodeOsDriver()) { + case NODE_OS_DRIVER_POSIX: + try { + result = await mount.pathIsMounted(target_path); + } catch (err) { + // running findmnt on non-existant paths return immediately + // the only time this should timeout is on a stale fs + // so if timeout is hit we should be near certain it is indeed mounted + if (err.timeout) { + driver.ctx.logger.warn( + `detected stale mount, attempting to force unmount: ${target_path}` + ); + await mount.umount( + target_path, + umount_args.concat(umount_force_extra_args) + ); + result = false; // assume we have fully unmounted + } else { + throw err; } - } else { - throw err; } - } - } - result = await filesystem.pathExists(target_path); - if (result) { - if (fs.lstatSync(target_path).isDirectory()) { - result = await filesystem.rmdir(target_path); - } else { - result = await filesystem.rm([target_path]); - } + if (result) { + try { + result = await mount.umount(target_path, umount_args); + } catch (err) { + if (err.timeout) { + driver.ctx.logger.warn( + `hit timeout waiting to unmount path: ${target_path}` + ); + // bind mounts do show the 'real' fs details + result = await mount.getMountDetails(target_path); + switch (result.fstype) { + case "nfs": + case "nfs4": + driver.ctx.logger.warn( + `detected stale nfs filesystem, attempting to force unmount: ${target_path}` + ); + result = await mount.umount( + target_path, + umount_args.concat(umount_force_extra_args) + ); + break; + default: + throw err; + } + } else { + throw err; + } + } + } + + result = await filesystem.pathExists(target_path); + if (result) { + if (fs.lstatSync(target_path).isDirectory()) { + result = await filesystem.rmdir(target_path); + } else { + result = await filesystem.rm([target_path]); + } + } + + break; + case NODE_OS_DRIVER_CSI_PROXY: + const csiProxyClient = driver.getDefaultCsiProxyClientInstance(); + + result = await csiProxyClient.FilesystemPathExists(target_path); + if (!result) { + return {}; + } + + result = await csiProxyClient.executeRPC("filesystem", "IsSymlink", { + path: target_path, + }); + + if (!result.is_symlink) { + throw new GrpcError( + grpc.status.FAILED_PRECONDITION, + `target path is not a symlink ${target_path}` + ); + } + + await csiProxyClient.executeRPC("filesystem", "Rmdir", { + path: target_path, + }); + + break; + default: + throw new GrpcError( + grpc.status.UNIMPLEMENTED, + `unkown NODE OS DRIVER: ${driver.__getNodeOsDriver()}` + ); } return {}; @@ -1397,60 +2220,124 @@ class CsiBaseDriver { res.volume_condition = { abnormal, message }; } - if ( - (await mount.isBindMountedBlockDevice(volume_path)) || - (await mount.isBindMountedBlockDevice(block_path)) - ) { - device_path = block_path; - access_type = "block"; - } else { - device_path = volume_path; - access_type = "mount"; - } - - switch (access_type) { - case "mount": - if (!(await mount.pathIsMounted(device_path))) { - throw new GrpcError( - grpc.status.NOT_FOUND, - `nothing mounted at path: ${device_path}` - ); + switch (driver.__getNodeOsDriver()) { + case NODE_OS_DRIVER_POSIX: + if ( + (await mount.isBindMountedBlockDevice(volume_path)) || + (await mount.isBindMountedBlockDevice(block_path)) + ) { + device_path = block_path; + access_type = "block"; + } else { + device_path = volume_path; + access_type = "mount"; + } + + switch (access_type) { + case "mount": + if (!(await mount.pathIsMounted(device_path))) { + throw new GrpcError( + grpc.status.NOT_FOUND, + `nothing mounted at path: ${device_path}` + ); + } + result = await mount.getMountDetails(device_path, [ + "avail", + "size", + "used", + ]); + + res.usage = [ + { + available: result.avail, + total: result.size, + used: result.used, + unit: "BYTES", + }, + ]; + break; + case "block": + if (!(await filesystem.pathExists(device_path))) { + throw new GrpcError( + grpc.status.NOT_FOUND, + `nothing mounted at path: ${device_path}` + ); + } + result = await filesystem.getBlockDevice(device_path); + + res.usage = [ + { + total: result.size, + unit: "BYTES", + }, + ]; + break; + default: + throw new GrpcError( + grpc.status.INVALID_ARGUMENT, + `unsupported/unknown access_type ${access_type}` + ); } - result = await mount.getMountDetails(device_path, [ - "avail", - "size", - "used", - ]); - res.usage = [ - { - available: result.avail, - total: result.size, - used: result.used, - unit: "BYTES", - }, - ]; break; - case "block": - if (!(await filesystem.pathExists(device_path))) { + case NODE_OS_DRIVER_CSI_PROXY: + const csiProxyClient = driver.getDefaultCsiProxyClientInstance(); + const volume_context = await driver.getDerivedVolumeContext(call); + if (!volume_context) { throw new GrpcError( grpc.status.NOT_FOUND, - `nothing mounted at path: ${device_path}` + `unable to retrieve volume_context for volume: ${volume_id}` ); } - result = await filesystem.getBlockDevice(device_path); - res.usage = [ - { - total: result.size, - unit: "BYTES", - }, - ]; + const node_attach_driver = volume_context.node_attach_driver; + + // ensure path is mounted + result = await csiProxyClient.FilesystemPathExists(volume_path); + if (!result) { + throw new GrpcError( + grpc.status.NOT_FOUND, + `volume_path ${volume_path} is not currently mounted` + ); + } + + switch (node_attach_driver) { + case "smb": + res.usage = [{ total: 0, unit: "BYTES" }]; + break; + case "iscsi": + let node_volume_id = + await csiProxyClient.getVolumeIdFromIscsiTarget( + volume_context.portal, + volume_context.iqn + ); + result = await csiProxyClient.executeRPC( + "volume", + "GetVolumeStats", + { + volume_id: node_volume_id, + } + ); + res.usage = [ + { + available: result.total_bytes - result.used_bytes, + total: result.total_bytes, + used: result.used_bytes, + unit: "BYTES", + }, + ]; + break; + default: + throw new GrpcError( + grpc.status.INVALID_ARGUMENT, + `unknown/unsupported node_attach_driver: ${node_attach_driver}` + ); + } break; default: throw new GrpcError( - grpc.status.INVALID_ARGUMENT, - `unsupported/unknown access_type ${access_type}` + grpc.status.UNIMPLEMENTED, + `unkown NODE OS DRIVER: ${driver.__getNodeOsDriver()}` ); } @@ -1477,6 +2364,7 @@ class CsiBaseDriver { let is_formatted; let fs_type; let is_device_mapper = false; + let result; const volume_id = call.request.volume_id; if (!volume_id) { @@ -1490,91 +2378,206 @@ class CsiBaseDriver { const capacity_range = call.request.capacity_range; const volume_capability = call.request.volume_capability; - if ( - (await mount.isBindMountedBlockDevice(volume_path)) || - (await mount.isBindMountedBlockDevice(block_path)) - ) { - access_type = "block"; - device_path = block_path; - } else { - access_type = "mount"; - device_path = volume_path; - } + switch (driver.__getNodeOsDriver()) { + case NODE_OS_DRIVER_POSIX: + if ( + (await mount.isBindMountedBlockDevice(volume_path)) || + (await mount.isBindMountedBlockDevice(block_path)) + ) { + access_type = "block"; + device_path = block_path; + } else { + access_type = "mount"; + device_path = volume_path; + } - try { - device = await mount.getMountPointDevice(device_path); - is_formatted = await filesystem.deviceIsFormatted(device); - is_block = await filesystem.isBlockDevice(device); - } catch (err) { - if (err.code == 1) { - throw new GrpcError( - grpc.status.NOT_FOUND, - `volume_path ${volume_path} is not currently mounted` - ); - } - } - - if (is_block) { - let rescan_devices = []; - // detect if is a multipath device - is_device_mapper = await filesystem.isDeviceMapperDevice(device); - if (is_device_mapper) { - // NOTE: want to make sure we scan the dm device *after* all the underlying slaves - rescan_devices = await filesystem.getDeviceMapperDeviceSlaves(device); - } - - rescan_devices.push(device); - - for (let sdevice of rescan_devices) { - // TODO: technically rescan is only relevant/available for remote drives - // such as iscsi etc, should probably limit this call as appropriate - // for now crudely checking the scenario inside the method itself - await filesystem.rescanDevice(sdevice); - } - - // let things settle - // it appears the dm devices can take a second to figure things out - if (is_device_mapper || true) { - await sleep(2000); - } - - if (is_formatted && access_type == "mount") { - fs_info = await filesystem.getDeviceFilesystemInfo(device); - fs_type = fs_info.type; - if (fs_type) { - switch (fs_type) { - case "ext4": - case "ext3": - case "ext4dev": - //await filesystem.checkFilesystem(device, fs_info.type); - await filesystem.expandFilesystem(device, fs_type); - break; - case "btrfs": - case "xfs": - let mount_info = await mount.getMountDetails(device_path); - if (["btrfs", "xfs"].includes(mount_info.fstype)) { - //await filesystem.checkFilesystem(device, fs_info.type); - await filesystem.expandFilesystem(device_path, fs_type); - } - break; - default: - // unsupported filesystem - throw new GrpcError( - grpc.status.FAILED_PRECONDITION, - `unsupported/unknown filesystem ${fs_type}` - ); + try { + device = await mount.getMountPointDevice(device_path); + is_formatted = await filesystem.deviceIsFormatted(device); + is_block = await filesystem.isBlockDevice(device); + } catch (err) { + if (err.code == 1) { + throw new GrpcError( + grpc.status.NOT_FOUND, + `volume_path ${volume_path} is not currently mounted` + ); } } - } else { - //block device unformatted - return {}; - } - } else { - // not block device - return {}; + + if (is_block) { + let rescan_devices = []; + // detect if is a multipath device + is_device_mapper = await filesystem.isDeviceMapperDevice(device); + if (is_device_mapper) { + // NOTE: want to make sure we scan the dm device *after* all the underlying slaves + rescan_devices = await filesystem.getDeviceMapperDeviceSlaves( + device + ); + } + + rescan_devices.push(device); + + for (let sdevice of rescan_devices) { + // TODO: technically rescan is only relevant/available for remote drives + // such as iscsi etc, should probably limit this call as appropriate + // for now crudely checking the scenario inside the method itself + await filesystem.rescanDevice(sdevice); + } + + // let things settle + // it appears the dm devices can take a second to figure things out + if (is_device_mapper || true) { + await GeneralUtils.sleep(2000); + } + + if (is_formatted && access_type == "mount") { + fs_info = await filesystem.getDeviceFilesystemInfo(device); + fs_type = fs_info.type; + if (fs_type) { + switch (fs_type) { + case "ext4": + case "ext3": + case "ext4dev": + //await filesystem.checkFilesystem(device, fs_info.type); + await filesystem.expandFilesystem(device, fs_type); + break; + case "btrfs": + case "xfs": + let mount_info = await mount.getMountDetails(device_path); + if (["btrfs", "xfs"].includes(mount_info.fstype)) { + //await filesystem.checkFilesystem(device, fs_info.type); + await filesystem.expandFilesystem(device_path, fs_type); + } + break; + default: + // unsupported filesystem + throw new GrpcError( + grpc.status.FAILED_PRECONDITION, + `unsupported/unknown filesystem ${fs_type}` + ); + } + } + } else { + //block device unformatted + return {}; + } + } else { + // not block device + return {}; + } + + break; + case NODE_OS_DRIVER_CSI_PROXY: + const csiProxyClient = driver.getDefaultCsiProxyClientInstance(); + const volume_context = await driver.getDerivedVolumeContext(call); + if (!volume_context) { + throw new GrpcError( + grpc.status.NOT_FOUND, + `unable to retrieve volume_context for volume: ${volume_id}` + ); + } + + const node_attach_driver = volume_context.node_attach_driver; + + // ensure path is mounted + result = await csiProxyClient.FilesystemPathExists(volume_path); + if (!result) { + throw new GrpcError( + grpc.status.NOT_FOUND, + `volume_path ${volume_path} is not currently mounted` + ); + } + + switch (node_attach_driver) { + case "iscsi": + const node_volume_id = + await csiProxyClient.getVolumeIdFromIscsiTarget( + volume_context.portal, + volume_context.iqn + ); + const disk_number = + await csiProxyClient.getDiskNumberFromIscsiTarget( + volume_context.portal, + volume_context.iqn + ); + + if (node_volume_id) { + const required_bytes = _.get( + call.request, + "capacity_range.required_bytes" + ); + if (required_bytes) { + await csiProxyClient.executeRPC("disk", "Rescan"); + try { + await csiProxyClient.executeRPC("volume", "ResizeVolume", { + volume_id: node_volume_id, + resize_bytes: required_bytes, + }); + } catch (e) { + let details = _.get(e, "details", ""); + // seems to be a false positive + if ( + !details.includes( + "The size of the extent is less than the minimum of 1MB" + ) + ) { + throw e; + } + + await csiProxyClient.executeRPC("disk", "GetDiskStats", { + disk_number, + }); + + result = await csiProxyClient.executeRPC( + "volume", + "GetVolumeStats", + { + volume_id: node_volume_id, + } + ); + + let diff = Math.abs(result.total_bytes - required_bytes); + let percentage_diff = parseInt((diff / required_bytes) * 100); + /** + * 15MB is used by the 1ast partition on the initialized disk + * + * 100MB + * TODO: possibly change this to a percentage instead of absolute numbers + */ + let max_delta = 104857600; + driver.ctx.logger.debug( + "resize diff %s (%s%%)", + diff, + percentage_diff + ); + if (diff > max_delta) { + throw new GrpcError( + grpc.status.OUT_OF_RANGE, + `expanded size ${result.total_bytes} is too far off (${diff}) from requested size (${required_bytes})` + ); + } + } + } + } else { + throw new GrpcError(grpc.status.NOT_FOUND, `cannot find volume`); + } + break; + default: + throw new GrpcError( + grpc.status.INVALID_ARGUMENT, + `unknown/unsupported node_attach_driver: ${node_attach_driver}` + ); + } + break; + default: + throw new GrpcError( + grpc.status.UNIMPLEMENTED, + `unkown NODE OS DRIVER: ${driver.__getNodeOsDriver()}` + ); } return {}; } } + module.exports.CsiBaseDriver = CsiBaseDriver; diff --git a/src/utils/csi_proxy_client.js b/src/utils/csi_proxy_client.js new file mode 100644 index 0000000..fb656a1 --- /dev/null +++ b/src/utils/csi_proxy_client.js @@ -0,0 +1,257 @@ +const _ = require("lodash"); +const grpc = require("./grpc").grpc; +const protoLoader = require("@grpc/proto-loader"); + +const PROTO_BASE_PATH = __dirname + "/../../csi_proxy_proto"; + +/** + * leave connection null as by default the named pipe is derrived + */ +const DEFAULT_SERVICES = { + filesystem: { version: "v1", connection: null }, + disk: { version: "v1", connection: null }, + volume: { version: "v1", connection: null }, + smb: { version: "v1", connection: null }, + system: { version: "v1alpha1", connection: null }, + iscsi: { version: "v1alpha2", connection: null }, +}; + +function capitalize(s) { + return s && s[0].toUpperCase() + s.slice(1); +} + +class CsiProxyClient { + constructor(options = {}) { + this.clients = {}; + + // initialize all clients + const services = Object.assign( + {}, + DEFAULT_SERVICES, + options.services || {} + ); + + const pipePrefix = options.pipe_prefix || "csi-proxy"; + + for (const serviceName in services) { + const service = services[serviceName]; + const serviceVersion = + service.version || DEFAULT_SERVICES[serviceName].version; + const serviceConnection = + service.connection || + `\\\\.\\\\pipe\\\\${pipePrefix}-${serviceName}-${serviceVersion}`; + + const PROTO_PATH = `/${PROTO_BASE_PATH}/${serviceName}/${serviceVersion}/api.proto`; + const packageDefinition = protoLoader.loadSync(PROTO_PATH, { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + includeDirs: [__dirname + "/../csi_proxy_proto"], + }); + const protoDescriptor = grpc.loadPackageDefinition(packageDefinition); + const serviceInstance = new protoDescriptor[serviceVersion][ + capitalize(serviceName) + ](serviceConnection, grpc.credentials.createInsecure()); + this.clients[serviceName] = serviceInstance; + } + } + + async executeRPC(serviceName, methodName, options = {}) { + function rescursivePathFixer(obj) { + for (const k in obj) { + if (typeof obj[k] == "object" && obj[k] !== null) { + rescursivePathFixer(obj[k]); + } else { + if (k.includes("path")) { + obj[k] = obj[k].replaceAll("/", "\\"); + } + } + } + } + + rescursivePathFixer(options); + + const cleansedOptions = JSON.parse(JSON.stringify(options)); + // This function handles arrays and objects + function recursiveCleanse(obj) { + for (const k in obj) { + if (typeof obj[k] == "object" && obj[k] !== null) { + recursiveCleanse(obj[k]); + } else { + if ( + k.includes("secret") || + k.includes("username") || + k.includes("password") + ) { + obj[k] = "redacted"; + } + } + } + } + recursiveCleanse(cleansedOptions); + + console.log( + "csi-proxy request %s/%s - data: %j", + capitalize(serviceName), + methodName, + cleansedOptions + ); + + return new Promise((resolve, reject) => { + const functionRef = this.clients[serviceName.toLowerCase()][methodName]; + if (!functionRef) { + reject( + new Error( + `missing method ${methodName} on service ${capitalize(serviceName)}` + ) + ); + return; + } + + this.clients[serviceName.toLowerCase()][methodName]( + options, + (error, data) => { + console.log( + "csi-proxy response %s/%s - error: %j, data: %j", + capitalize(serviceName), + methodName, + error, + data + ); + + if (error) { + reject(error); + } + + resolve(data); + } + ); + }); + } + + /** + * Returns a disk_number if the target has 0 or 1 disks + * + * @param {*} target_portal + * @param {*} iqn + * @returns + */ + async getDiskNumberFromIscsiTarget(target_portal, iqn) { + let result; + + if (typeof target_portal != "object") { + target_portal = { + target_address: target_portal.split(":")[0], + target_port: target_portal.split(":")[1] || 3260, + }; + } + + // get device + try { + result = await this.executeRPC("iscsi", "GetTargetDisks", { + target_portal, + iqn, + }); + } catch (e) { + let details = _.get(e, "details", ""); + if (!details.includes("ObjectNotFound")) { + throw e; + } + } + + let diskIds = _.get(result, "diskIDs", []); + if (diskIds.length > 1) { + throw new Error( + `${diskIds.length} disks on the target, no way to know which is the relevant disk` + ); + } + + return diskIds[0]; + } + + /** + * Returns a volume_id if the disk has 0 or 1 volumes + * + * @param {*} disk_number + * @returns + */ + async getVolumeIdFromDiskNumber(disk_number) { + let result; + + if (disk_number == 0 || disk_number > 0) { + result = await this.executeRPC("volume", "ListVolumesOnDisk", { + disk_number, + }); + + let volume_ids = _.get(result, "volume_ids", []); + /** + * the 1st partition is a sort of system partion and is "" + * usually around 15MB in size + */ + volume_ids = volume_ids.filter((item) => { + return Boolean(item); + }); + + if (volume_ids.length > 1) { + throw new Error( + `${volume_ids.length} volumes on the disk, no way to know which is the relevant volume` + ); + } + + // ok of null/undefined + return volume_ids[0]; + } + } + + /** + * Return a volume_id if the target and disk both have 0 or 1 entries + * + * @param {*} target_portal + * @param {*} iqn + * @returns + */ + async getVolumeIdFromIscsiTarget(target_portal, iqn) { + const disk_number = await this.getDiskNumberFromIscsiTarget(...arguments); + return await this.getVolumeIdFromDiskNumber(disk_number); + } + + async FilesystemPathExists(path) { + let result; + try { + result = await this.executeRPC("filesystem", "PathExists", { + path, + }); + + return result.exists; + } catch (e) { + let details = _.get(e, "details", ""); + if (details.includes("not an absolute Windows path")) { + return false; + } else { + throw e; + } + } + } + + async FilesystemIsSymlink(path) { + let result; + try { + result = await this.executeRPC("filesystem", "IsSymlink", { + path, + }); + + return result.is_symlink; + } catch (e) { + let details = _.get(e, "details", ""); + if (details.includes("not an absolute Windows path")) { + return false; + } else { + throw e; + } + } + } +} + +module.exports.CsiProxyClient = CsiProxyClient; diff --git a/src/utils/filesystem.js b/src/utils/filesystem.js index f2c897c..deb62ea 100644 --- a/src/utils/filesystem.js +++ b/src/utils/filesystem.js @@ -1,5 +1,6 @@ const cp = require("child_process"); const fs = require("fs"); +const path = require("path"); const DEFAULT_TIMEOUT = process.env.FILESYSTEM_DEFAULT_TIMEOUT || 30000; @@ -25,6 +26,10 @@ class Filesystem { } } + covertUnixSeparatorToWindowsSeparator(p) { + return p.replaceAll(path.posix.sep, path.win32.sep); + } + /** * Attempt to discover if device is a block device * @@ -615,12 +620,12 @@ class Filesystem { command = filesystem.options.paths.sudo; } console.log("executing filesystem command: %s %s", command, args.join(" ")); - + return new Promise((resolve, reject) => { const child = filesystem.options.executor.spawn(command, args, options); let stdout = ""; let stderr = ""; - + child.stdout.on("data", function (data) { stdout = stdout + data; }); diff --git a/src/utils/general.js b/src/utils/general.js index accc106..ed25b3c 100644 --- a/src/utils/general.js +++ b/src/utils/general.js @@ -1,4 +1,5 @@ const axios = require("axios"); +const crypto = require("crypto"); function sleep(ms) { return new Promise((resolve) => { @@ -6,6 +7,64 @@ function sleep(ms) { }); } +function md5(val) { + return crypto.createHash("md5").update(val).digest("hex"); +} + +function crc32(val) { + for (var a, o = [], c = 0; c < 256; c++) { + a = c; + for (var f = 0; f < 8; f++) a = 1 & a ? 3988292384 ^ (a >>> 1) : a >>> 1; + o[c] = a; + } + for (var n = -1, t = 0; t < val.length; t++) + n = (n >>> 8) ^ o[255 & (n ^ val.charCodeAt(t))]; + return (-1 ^ n) >>> 0; +} + +const crctab16 = new Uint16Array([ + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, + 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108, + 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb, + 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399, + 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, + 0xfae7, 0xc87c, 0xd9f5, 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, + 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, + 0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, + 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285, + 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, + 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 0x6306, 0x728f, 0x4014, + 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5, + 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, + 0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, + 0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, + 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, + 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, + 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, + 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50, + 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710, + 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, + 0x6e6e, 0x5cf5, 0x4d7c, 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, + 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, + 0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, + 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e, + 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, + 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 0xf78f, 0xe606, 0xd49d, + 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, + 0x3de3, 0x2c6a, 0x1ef1, 0x0f78, +]); + +// calculate the 16-bit CRC of data with predetermined length. +function crc16(data) { + var res = 0x0ffff; + + for (let b of data) { + res = ((res >> 8) & 0x0ff) ^ crctab16[(res ^ b) & 0xff]; + } + + return ~res & 0x0ffff; +} + function lockKeysFromRequest(call, serviceMethodName) { switch (serviceMethodName) { // controller @@ -55,9 +114,9 @@ function getLargestNumber() { /** * transition function to replicate `request` style requests using axios - * - * @param {*} options - * @param {*} callback + * + * @param {*} options + * @param {*} callback */ function axios_request(options, callback = function () {}) { function prep_response(res) { @@ -110,8 +169,23 @@ function stringify(value) { return JSON.stringify(value, getCircularReplacer()); } +function default_supported_block_filesystems() { + return ["btrfs", "ext3", "ext4", "ext4dev", "xfs", "ntfs"]; +} + +function default_supported_file_filesystems() { + return ["nfs", "cifs"]; +} + module.exports.sleep = sleep; +module.exports.md5 = md5; +module.exports.crc32 = crc32; +module.exports.crc16 = crc16; module.exports.lockKeysFromRequest = lockKeysFromRequest; module.exports.getLargestNumber = getLargestNumber; module.exports.stringify = stringify; module.exports.axios_request = axios_request; +module.exports.default_supported_block_filesystems = + default_supported_block_filesystems; +module.exports.default_supported_file_filesystems = + default_supported_file_filesystems;