diff --git a/Dockerfile b/Dockerfile index faf576f..12dd1a5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ FROM alpine:latest LABEL maintainers="Synology Authors" \ description="Synology CSI Plugin" -RUN apk add --no-cache e2fsprogs e2fsprogs-extra xfsprogs xfsprogs-extra blkid util-linux iproute2 bash btrfs-progs ca-certificates cifs-utils +RUN apk add --no-cache e2fsprogs e2fsprogs-extra xfsprogs xfsprogs-extra blkid util-linux iproute2 bash btrfs-progs ca-certificates cifs-utils nfs-utils # Create symbolic link for chroot.sh WORKDIR / diff --git a/Makefile b/Makefile index 3bc209e..dfb52cf 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,9 @@ REGISTRY_NAME=synology IMAGE_NAME=synology-csi -IMAGE_VERSION=v1.1.3 +IMAGE_VERSION=v1.2.0 IMAGE_TAG=$(REGISTRY_NAME)/$(IMAGE_NAME):$(IMAGE_VERSION) +CONTAINER_BIN=$(shell which podman || which docker) # For now, only build linux/amd64 platform ifeq ($(GOARCH),) @@ -22,10 +23,10 @@ synology-csi-driver: $(BUILD_ENV) go build -v -ldflags $(BUILD_FLAGS) -o ./bin/synology-csi-driver ./ docker-build: - docker build -f Dockerfile -t $(IMAGE_TAG) . + $(CONTAINER_BIN) build -f Dockerfile -t $(IMAGE_TAG) . docker-build-multiarch: - docker buildx build -t $(IMAGE_TAG) --platform linux/amd64,linux/arm/v7,linux/arm64 . --push + $(CONTAINER_BIN) buildx build -t $(IMAGE_TAG) --platform linux/amd64,linux/arm/v7,linux/arm64 . --push synocli: @mkdir -p bin diff --git a/README.md b/README.md index 57470ec..8f70230 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The official [Container Storage Interface](https://github.com/container-storage- Driver Name: csi.san.synology.com | Driver Version | Image | Supported K8s Version | | -------------------------------------------------------------------------------- | --------------------------------------------------------------------- | --------------------- | -| [v1.1.3](https://github.com/SynologyOpenSource/synology-csi/tree/release-v1.1.3) | [synology-csi:v1.1.3](https://hub.docker.com/r/synology/synology-csi) | 1.20+ | +| [v1.2.0](https://github.com/SynologyOpenSource/synology-csi/tree/release-v1.2.0) | [synology-csi:v1.2.0](https://hub.docker.com/r/synology/synology-csi) | 1.20+ | @@ -158,17 +158,36 @@ Create and apply StorageClasses with the properties you want. allowVolumeExpansion: true ``` + **NFS Protocol** + ``` + apiVersion: storage.k8s.io/v1 + kind: StorageClass + metadata: + name: synostorage-nfs + provisioner: csi.san.synology.com + parameters: + protocol: "nfs" + dsm: "192.168.1.1" + location: '/volume1' + mountPermissions: '0755' + mountOptions: + - nfsvers=4.1 + reclaimPolicy: Delete + allowVolumeExpansion: true + ``` + 2. Configure the StorageClass properties by assigning the parameters in the table. You can also leave blank if you don’t have a preference: | Name | Type | Description | Default | Supported protocols | | ------------------------------------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | ------------------- | - | *dsm* | string | The IPv4 address of your DSM, which must be included in the `client-info.yml` for the CSI driver to log in to DSM | - | iSCSI, SMB | - | *location* | string | The location (/volume1, /volume2, ...) on DSM where the LUN for *PersistentVolume* will be created | - | iSCSI, SMB | + | *dsm* | string | The IPv4 address of your DSM, which must be included in the `client-info.yml` for the CSI driver to log in to DSM | - | iSCSI, SMB, NFS | + | *location* | string | The location (/volume1, /volume2, ...) on DSM where the LUN for *PersistentVolume* will be created | - | iSCSI, SMB, NFS | | *fsType* | string | The formatting file system of the *PersistentVolumes* when you mount them on the pods. This parameter only works with iSCSI. For SMB, the fsType is always ‘cifs‘. | 'ext4' | iSCSI | - | *protocol* | string | The storage backend protocol. Enter ‘iscsi’ to create LUNs or ‘smb‘ to create shared folders on DSM. | 'iscsi' | iSCSI, SMB | - | *formatOptions* | string | Additional options/arguments passed to `mkfs.*` command. See a linux manual that corresponds with your FS of choice. | - | iSCSI | + | *protocol* | string | The storage backend protocol. Enter ‘iscsi’ to create LUNs, or ‘smb‘ or 'nfs' to create shared folders on DSM. | 'iscsi' | iSCSI, SMB, NFS | + | *formatOptions* | string | Additional options/arguments passed to `mkfs.*` command. See a linux manual that corresponds with your FS of choice. | - | iSCSI | | *csi.storage.k8s.io/node-stage-secret-name* | string | The name of node-stage-secret. Required if DSM shared folder is accessed via SMB. | - | SMB | | *csi.storage.k8s.io/node-stage-secret-namespace* | string | The namespace of node-stage-secret. Required if DSM shared folder is accessed via SMB. | - | SMB | + | *mountPermissions* | string | Mounted folder permissions. If set as non-zero, driver will perform `chmod` after mount | '0750' | NFS | **Notice** @@ -205,7 +224,7 @@ Create and apply VolumeSnapshotClasses with the properties you want. | Name | Type | Description | Default | Supported protocols | | ------------- | ------ | -------------------------------------------- | ------- | ------------------- | | *description* | string | The description of the snapshot on DSM | "" | iSCSI | - | *is_locked* | string | Whether you want to lock the snapshot on DSM | 'false' | iSCSI, SMB | + | *is_locked* | string | Whether you want to lock the snapshot on DSM | 'false' | iSCSI, SMB, NFS | 3. Apply the YAML files to the Kubernetes cluster. diff --git a/deploy/example/storageclass-nfs.yaml b/deploy/example/storageclass-nfs.yaml new file mode 100644 index 0000000..e26ae47 --- /dev/null +++ b/deploy/example/storageclass-nfs.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: synology-nfs-storage +provisioner: csi.san.synology.com +parameters: + protocol: "nfs" # required for nfs protocol + mountPermissions: '0777' + # dsm: "1.1.1.1" + # location: '/volume1' +mountOptions: + - nfsvers=4 #3,4,4.1 +reclaimPolicy: Delete +allowVolumeExpansion: true diff --git a/deploy/helm/Chart.yaml b/deploy/helm/Chart.yaml index 02d40fd..92ea88a 100644 --- a/deploy/helm/Chart.yaml +++ b/deploy/helm/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -appVersion: v1.1.3 +appVersion: v1.2.0 name: synology-csi description: A Helm chart for the Synology CSI Driver keywords: diff --git a/deploy/kubernetes/v1.19/controller.yml b/deploy/kubernetes/v1.19/controller.yml index e0af4ad..d6a2eef 100644 --- a/deploy/kubernetes/v1.19/controller.yml +++ b/deploy/kubernetes/v1.19/controller.yml @@ -144,7 +144,7 @@ spec: capabilities: add: ["SYS_ADMIN"] allowPrivilegeEscalation: true - image: synology/synology-csi:v1.1.3 + image: synology/synology-csi:v1.2.0 args: - --nodeid=NotUsed - --endpoint=$(CSI_ENDPOINT) diff --git a/deploy/kubernetes/v1.19/node.yml b/deploy/kubernetes/v1.19/node.yml index 670fc30..499e003 100644 --- a/deploy/kubernetes/v1.19/node.yml +++ b/deploy/kubernetes/v1.19/node.yml @@ -86,7 +86,7 @@ spec: securityContext: privileged: true imagePullPolicy: IfNotPresent - image: synology/synology-csi:v1.1.3 + image: synology/synology-csi:v1.2.0 args: - --nodeid=$(KUBE_NODE_NAME) - --endpoint=$(CSI_ENDPOINT) diff --git a/deploy/kubernetes/v1.19/snapshotter/snapshotter.yaml b/deploy/kubernetes/v1.19/snapshotter/snapshotter.yaml index f219d86..a66ef1a 100644 --- a/deploy/kubernetes/v1.19/snapshotter/snapshotter.yaml +++ b/deploy/kubernetes/v1.19/snapshotter/snapshotter.yaml @@ -81,7 +81,7 @@ spec: capabilities: add: ["SYS_ADMIN"] allowPrivilegeEscalation: true - image: synology/synology-csi:v1.1.3 + image: synology/synology-csi:v1.2.0 args: - --nodeid=NotUsed - --endpoint=$(CSI_ENDPOINT) diff --git a/deploy/kubernetes/v1.20/controller.yml b/deploy/kubernetes/v1.20/controller.yml index 6d212e5..fb3bebd 100644 --- a/deploy/kubernetes/v1.20/controller.yml +++ b/deploy/kubernetes/v1.20/controller.yml @@ -144,7 +144,7 @@ spec: capabilities: add: ["SYS_ADMIN"] allowPrivilegeEscalation: true - image: synology/synology-csi:v1.1.3 + image: synology/synology-csi:v1.2.0 args: - --nodeid=NotUsed - --endpoint=$(CSI_ENDPOINT) diff --git a/deploy/kubernetes/v1.20/node.yml b/deploy/kubernetes/v1.20/node.yml index 670fc30..499e003 100644 --- a/deploy/kubernetes/v1.20/node.yml +++ b/deploy/kubernetes/v1.20/node.yml @@ -86,7 +86,7 @@ spec: securityContext: privileged: true imagePullPolicy: IfNotPresent - image: synology/synology-csi:v1.1.3 + image: synology/synology-csi:v1.2.0 args: - --nodeid=$(KUBE_NODE_NAME) - --endpoint=$(CSI_ENDPOINT) diff --git a/deploy/kubernetes/v1.20/snapshotter/snapshotter.yaml b/deploy/kubernetes/v1.20/snapshotter/snapshotter.yaml index e5d1feb..1e10c6b 100644 --- a/deploy/kubernetes/v1.20/snapshotter/snapshotter.yaml +++ b/deploy/kubernetes/v1.20/snapshotter/snapshotter.yaml @@ -81,7 +81,7 @@ spec: capabilities: add: ["SYS_ADMIN"] allowPrivilegeEscalation: true - image: synology/synology-csi:v1.1.3 + image: synology/synology-csi:v1.2.0 args: - --nodeid=NotUsed - --endpoint=$(CSI_ENDPOINT) diff --git a/go.mod b/go.mod index 1ce6f81..fd679fc 100644 --- a/go.mod +++ b/go.mod @@ -14,25 +14,43 @@ require ( google.golang.org/grpc v1.56.3 google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v2 v2.4.0 + k8s.io/apimachinery v0.19.0 + k8s.io/client-go v0.19.0 k8s.io/mount-utils v0.26.4 k8s.io/utils v0.0.0-20221107191617-1a15be271d1d ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/go-logr/logr v1.2.3 // indirect + github.com/gogo/protobuf v1.3.1 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/googleapis/gnostic v0.4.1 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/json-iterator/go v1.1.10 // indirect github.com/moby/sys/mountinfo v0.6.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect github.com/nxadm/tail v1.4.5 // indirect github.com/onsi/ginkgo v1.14.2 // indirect github.com/onsi/gomega v1.10.4 // indirect github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/net v0.17.0 // indirect + golang.org/x/oauth2 v0.7.0 // indirect + golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect + golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + k8s.io/api v0.19.0 // indirect k8s.io/klog/v2 v2.80.1 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.0.1 // indirect + sigs.k8s.io/yaml v1.2.0 // indirect ) diff --git a/go.sum b/go.sum index d8a167f..ce07202 100644 --- a/go.sum +++ b/go.sum @@ -105,6 +105,7 @@ github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dp github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -140,6 +141,7 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -152,6 +154,7 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -186,6 +189,7 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -226,8 +230,10 @@ github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vyg github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -328,6 +334,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -377,6 +385,8 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -417,6 +427,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -426,6 +438,7 @@ golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -466,6 +479,8 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -513,6 +528,7 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8X gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= @@ -536,8 +552,11 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +k8s.io/api v0.19.0 h1:XyrFIJqTYZJ2DU7FBE/bSPz7b1HvbVBuBf07oeo6eTc= k8s.io/api v0.19.0/go.mod h1:I1K45XlvTrDjmj5LoM5LuP/KYrhWbjUKT/SoPG0qTjw= +k8s.io/apimachinery v0.19.0 h1:gjKnAda/HZp5k4xQYjL0K/Yb66IvNqjthCb03QlKpaQ= k8s.io/apimachinery v0.19.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/client-go v0.19.0 h1:1+0E0zfWFIWeyRhQYWzimJOyAk2UT7TiARaLNwJCf7k= k8s.io/client-go v0.19.0/go.mod h1:H9E/VT95blcFQnlyShFgnFT9ZnJOAceiUHM3MlRC+mU= k8s.io/component-base v0.19.0/go.mod h1:dKsY8BxkA+9dZIAh2aWJLL/UdASFDNtGYTCItL4LM7Y= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= @@ -553,6 +572,8 @@ k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/ k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/pkg/driver/controllerserver.go b/pkg/driver/controllerserver.go index a60ffd5..5d2918f 100644 --- a/pkg/driver/controllerserver.go +++ b/pkg/driver/controllerserver.go @@ -69,6 +69,18 @@ func (cs *controllerServer) isVolumeAccessModeSupport(mode csi.VolumeCapability_ return false } +func parseNfsVesrion(ops []string) string { + for _, op := range ops { + if strings.HasPrefix(op, "nfsvers") { + kvpair := strings.Split(op, "=") + if len(kvpair) == 2 { + return kvpair[1] + } + } + } + return "" +} + func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { sizeInByte, err := getSizeByCapacityRange(req.GetCapacityRange()) volName, volCap := req.GetName(), req.GetVolumeCapabilities() @@ -89,7 +101,7 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol if volCap == nil { return nil, status.Errorf(codes.InvalidArgument, "No volume capabilities are provided") } - + var mountOptions []string for _, cap := range volCap { accessMode := cap.GetAccessMode().GetMode() @@ -102,6 +114,10 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol } else if accessMode == csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER { multiSession = true } + + if mount := cap.GetMount(); mount != nil { + mountOptions = mount.GetMountFlags() + } } if volContentSrc != nil { @@ -129,8 +145,15 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol } // not needed during CreateVolume method - // used only in NodeStageVolume though VolumeContext + // used only in NodeStageVolume through VolumeContext formatOptions := params["formatOptions"] + mountPermissions := params["mountPermissions"] + // check mountPermissions valid + if mountPermissions != "" { + if _, err := strconv.ParseUint(mountPermissions, 8, 32); err != nil { + return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("invalid mountPermissions %s in storage class", mountPermissions)) + } + } lunDescription := "" if _, ok := params["csi.storage.k8s.io/pvc/name"]; ok { @@ -141,6 +164,11 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol lunDescription = pvcNamespace + "/" + pvcName } + nfsVer := parseNfsVesrion(mountOptions) + if nfsVer != "" && !isNfsVersionAllowed(nfsVer) { + return nil, status.Errorf(codes.InvalidArgument, "Unsupported nfsvers: %s", nfsVer) + } + spec := &models.CreateK8sVolumeSpec{ DsmIp: params["dsm"], K8sVolumeName: volName, @@ -156,6 +184,7 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol SourceSnapshotId: srcSnapshotId, SourceVolumeId: srcVolumeId, Protocol: protocol, + NfsVersion: nfsVer, } // idempotency @@ -172,7 +201,8 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol } if (k8sVolume.Protocol == utils.ProtocolIscsi && k8sVolume.SizeInBytes != sizeInByte) || - (k8sVolume.Protocol == utils.ProtocolSmb && utils.BytesToMB(k8sVolume.SizeInBytes) != utils.BytesToMBCeil(sizeInByte)) { + (k8sVolume.Protocol == utils.ProtocolSmb && utils.BytesToMB(k8sVolume.SizeInBytes) != utils.BytesToMBCeil(sizeInByte)) || + (k8sVolume.Protocol == utils.ProtocolNfs && utils.BytesToMB(k8sVolume.SizeInBytes) != utils.BytesToMBCeil(sizeInByte)) { return nil, status.Errorf(codes.AlreadyExists, "Already existing volume name with different capacity") } @@ -182,10 +212,12 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol CapacityBytes: k8sVolume.SizeInBytes, ContentSource: volContentSrc, VolumeContext: map[string]string{ - "dsm": k8sVolume.DsmIp, - "protocol": k8sVolume.Protocol, - "source": k8sVolume.Source, - "formatOptions": formatOptions, + "dsm": k8sVolume.DsmIp, + "protocol": k8sVolume.Protocol, + "source": k8sVolume.Source, + "formatOptions": formatOptions, + "mountPermissions": mountPermissions, + "baseDir": k8sVolume.BaseDir, }, }, }, nil diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go index 2b25754..93f59d2 100644 --- a/pkg/driver/driver.go +++ b/pkg/driver/driver.go @@ -25,12 +25,13 @@ import ( const ( DriverName = "csi.san.synology.com" // CSI dirver name - DriverVersion = "1.1.3" + DriverVersion = "1.2.0" ) var ( MultipathEnabled = true - supportedProtocolList = []string{utils.ProtocolIscsi, utils.ProtocolSmb} + supportedProtocolList = []string{utils.ProtocolIscsi, utils.ProtocolSmb, utils.ProtocolNfs} + allowedNfsVersionList = []string{"3", "4", "4.0", "4.1"} ) type IDriver interface { @@ -141,3 +142,7 @@ func (d *Driver) getVolumeCapabilityAccessModes() []*csi.VolumeCapability_Access func isProtocolSupport(protocol string) bool { return utils.SliceContains(supportedProtocolList, protocol) } + +func isNfsVersionAllowed(ver string) bool { + return utils.SliceContains(allowedNfsVersionList, ver) +} diff --git a/pkg/driver/nfs_utils.go b/pkg/driver/nfs_utils.go new file mode 100644 index 0000000..9cacab8 --- /dev/null +++ b/pkg/driver/nfs_utils.go @@ -0,0 +1,40 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package driver + +import ( + "os" + log "github.com/sirupsen/logrus" +) + +// chmodIfPermissionMismatch only perform chmod when permission mismatches +func chmodIfPermissionMismatch(targetPath string, mode os.FileMode) error { + info, err := os.Lstat(targetPath) + if err != nil { + return err + } + perm := info.Mode() & os.ModePerm + if perm != mode { + log.Infof("chmod targetPath(%s, mode:0%o) with permissions(0%o)", targetPath, info.Mode(), mode) + if err := os.Chmod(targetPath, mode); err != nil { + return err + } + } else { + log.Infof("skip chmod on targetPath(%s) since mode is already 0%o)", targetPath, info.Mode()) + } + return nil +} \ No newline at end of file diff --git a/pkg/driver/nodeserver.go b/pkg/driver/nodeserver.go index 52cb304..4e374e6 100644 --- a/pkg/driver/nodeserver.go +++ b/pkg/driver/nodeserver.go @@ -21,6 +21,7 @@ import ( "fmt" "os" "path/filepath" + "strconv" "strings" "time" @@ -30,6 +31,8 @@ import ( "golang.org/x/sys/unix" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clientset "k8s.io/client-go/kubernetes" "k8s.io/mount-utils" "github.com/SynologyOpenSource/synology-csi/pkg/dsm/webapi" @@ -43,6 +46,7 @@ type nodeServer struct { Mounter *mount.SafeFormatAndMount dsmService interfaces.IDsmService Initiator *initiatorDriver + Client clientset.Interface tools tools } @@ -112,6 +116,21 @@ func getVolumeMountPath(iscsiDevPaths []string) string { return path } +func createTargetMountPathNFS(mounter mount.Interface, mountPath string, mountPermissionsUint uint64) (bool, error) { + notMount, err := mounter.IsLikelyNotMountPoint(mountPath) + if err != nil { + if os.IsNotExist(err) { + if err := os.MkdirAll(mountPath, os.FileMode(mountPermissionsUint)); err != nil { + return notMount, err + } + notMount = true + } else { + return false, err + } + } + return notMount, nil +} + func createTargetMountPath(mounter mount.Interface, mountPath string, isBlock bool) (bool, error) { notMount, err := mount.IsNotMountPoint(mounter, mountPath) if err != nil { @@ -263,6 +282,70 @@ func (ns *nodeServer) mountSensitiveWithRetry(sourcePath string, targetPath stri return nil } +func getNodeAddress(ctx context.Context, client clientset.Interface) ([]string, error) { + ips := []string{} + nodes, err := client.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) + if err != nil { + log.Errorf("Failed to list nodes, err: %v", err) + return nil, err + } + + for _, node := range nodes.Items { + for _, address := range node.Status.Addresses { + if address.Type == "InternalIP" { + ips = append(ips, address.Address) + } + } + } + + if len(ips) == 0 { + return nil, fmt.Errorf("Empty results") + } + return ips, nil +} + +func (ns *nodeServer) setNFSVolumePrivilege(sourcePath string, hostnames []string, authType utils.AuthType) error { + // NFSTODO: fix the parsing rule + s := strings.Split(strings.TrimPrefix(sourcePath, "//"), "/") + if len(s) != 2 { + return fmt.Errorf("Failed to parse dsmIp and shareName from source path") + } + dsmIp, shareName := s[0], s[1] + + dsm, err := ns.dsmService.GetDsm(dsmIp) + if err != nil { + return fmt.Errorf("Failed to get DSM[%s]", dsmIp) + } + + priv := webapi.SharePrivilege{ + ShareName: shareName, + } + + for _, hostname := range hostnames { + priv.Rule = append(priv.Rule, webapi.PrivilegeRule{ + Async: true, + Client: hostname, + Crossmnt: true, + Insecure: true, + Privilege: string(authType), + RootSquash: "root", + SecurityFlavor: webapi.SecurityFlavor{ + Kerbros: false, + KerbrosIntegrity: false, + KerbrosPrivacy: false, + Sys: true, + }, + }) + } + + err = dsm.ShareNfsPrivilegeSave(priv) + if err != nil { + log.Printf("Failed to save share NFS privilege. Priv:%v. %v", priv, err) + return err + } + return nil +} + func (ns *nodeServer) setSMBVolumePermission(sourcePath string, userName string, authType utils.AuthType) error { s := strings.Split(strings.TrimPrefix(sourcePath, "//"), "/") if len(s) != 2 { @@ -392,6 +475,18 @@ func (ns *nodeServer) nodeStageSMBVolume(ctx context.Context, spec *models.NodeS return &csi.NodeStageVolumeResponse{}, nil } +func (ns *nodeServer) nodeStageNFSVolume(ctx context.Context, spec *models.NodeStageVolumeSpec) (*csi.NodeStageVolumeResponse, error) { + nodeIps, err := getNodeAddress(ctx, ns.Client) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("Failed to get node IPs for NFS privilege setting, err: %v", err)) + } + + if err := ns.setNFSVolumePrivilege(spec.Source, nodeIps, utils.AuthTypeReadWrite); err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("Failed to set NFS privilege rule, source: %s, err: %v", spec.Source, err)) + } + return &csi.NodeStageVolumeResponse{}, nil +} + func (ns *nodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { volumeId, stagingTargetPath, volumeCapability := req.GetVolumeId(), req.GetStagingTargetPath(), req.GetVolumeCapability() @@ -417,6 +512,8 @@ func (ns *nodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol switch req.VolumeContext["protocol"] { case utils.ProtocolSmb: return ns.nodeStageSMBVolume(ctx, spec, req.GetSecrets()) + case utils.ProtocolNfs: + return ns.nodeStageNFSVolume(ctx, spec) default: return ns.nodeStageISCSIVolume(ctx, spec) } @@ -462,7 +559,71 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis isBlock := req.GetVolumeCapability().GetBlock() != nil // raw block, only for iscsi protocol fsType := req.GetVolumeCapability().GetMount().GetFsType() + options := []string{} + if req.GetReadonly() { + options = append(options, "ro") + } + + // nfs + if req.VolumeContext["protocol"] == utils.ProtocolNfs { + options = append(options, req.GetVolumeCapability().GetMount().GetMountFlags()...) + + var server, baseDir string //NFSTODO: subDir + var mountPermissionsUint uint64 = 0750 // default + for k, v := range req.GetVolumeContext() { + switch k { + case "dsm": + server = v + case "baseDir": + baseDir = v + case "mountPermissions": + if v != "" { + var err error + mountPermissionsUint, err = strconv.ParseUint(v, 8, 32) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("invalid mountPermissions %s", v)) + } + } + } + } + + if server == "" || baseDir == "" { + return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Invalid inputs: server(dsm) and baseDir are required.")) + } + source := fmt.Sprintf("%s:%s", server, baseDir) + notMount, err := createTargetMountPathNFS(ns.Mounter.Interface, targetPath, mountPermissionsUint) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + if !notMount { + log.Infof("NodePublishVolume: %s is already mounted", targetPath) + return &csi.NodePublishVolumeResponse{}, nil + } + + log.Debugf("NodePublishVolume: volumeId(%v) source(%s) targetPath(%s) mountflags(%v)", volumeId, source, targetPath, options) + err = ns.Mounter.Mount(source, targetPath, "nfs", options) + if err != nil { + if os.IsPermission(err) { + return nil, status.Error(codes.PermissionDenied, err.Error()) + } + if strings.Contains(err.Error(), "invalid argument") { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + return nil, status.Error(codes.Internal, err.Error()) + } + + if mountPermissionsUint > 0 { + if err := chmodIfPermissionMismatch(targetPath, os.FileMode(mountPermissionsUint)); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + } + + log.Debugf("NFS volume(%s) mount %s on %s succeeded", volumeId, source, targetPath) + return &csi.NodePublishVolumeResponse{}, nil + } + + // iscsi & smb notMount, err := createTargetMountPath(ns.Mounter.Interface, targetPath, isBlock) if err != nil { return nil, status.Error(codes.Internal, err.Error()) @@ -471,10 +632,7 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis return &csi.NodePublishVolumeResponse{}, nil } - options := []string{"bind"} - if req.GetReadonly() { - options = append(options, "ro") - } + options = append(options, "bind") switch req.VolumeContext["protocol"] { case utils.ProtocolSmb: @@ -575,7 +733,7 @@ func (ns *nodeServer) NodeGetVolumeStats(ctx context.Context, req *csi.NodeGetVo fmt.Sprintf("Volume[%s] does not exist on the %s", volumeId, volumePath)) } - if k8sVolume.Protocol == utils.ProtocolSmb { + if k8sVolume.Protocol == utils.ProtocolSmb || k8sVolume.Protocol == utils.ProtocolNfs { return &csi.NodeGetVolumeStatsResponse{ Usage: []*csi.VolumeUsage{ &csi.VolumeUsage{ @@ -636,7 +794,7 @@ func (ns *nodeServer) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandV return nil, status.Error(codes.NotFound, fmt.Sprintf("Volume[%s] is not found", volumeId)) } - if k8sVolume.Protocol == utils.ProtocolSmb { + if k8sVolume.Protocol == utils.ProtocolSmb || k8sVolume.Protocol == utils.ProtocolNfs { return &csi.NodeExpandVolumeResponse{ CapacityBytes: sizeInByte}, nil } diff --git a/pkg/driver/utils.go b/pkg/driver/utils.go index 1630140..90ee99c 100644 --- a/pkg/driver/utils.go +++ b/pkg/driver/utils.go @@ -27,6 +27,8 @@ import ( "github.com/kubernetes-csi/csi-lib-utils/protosanitizer" log "github.com/sirupsen/logrus" "google.golang.org/grpc" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "k8s.io/mount-utils" "k8s.io/utils/exec" ) @@ -48,6 +50,19 @@ func NewControllerServer(d *Driver) *controllerServer { } } +func getK8sClient() clientset.Interface { + config, err := rest.InClusterConfig() + if err != nil { + log.Fatalf("Failed to read in-cluster config: %v", err) + } + + client, err := clientset.NewForConfig(config) + if err != nil { + log.Fatalf("Failed to create Kubernetes client: %v", err) + } + return client +} + func NewNodeServer(d *Driver) *nodeServer { return &nodeServer{ Driver: d, @@ -61,7 +76,8 @@ func NewNodeServer(d *Driver) *nodeServer { chapPassword: "", tools: d.tools, }, - tools: d.tools, + Client: getK8sClient(), + tools: d.tools, } } diff --git a/pkg/dsm/service/dsm.go b/pkg/dsm/service/dsm.go index 122a545..e3833ce 100644 --- a/pkg/dsm/service/dsm.go +++ b/pkg/dsm/service/dsm.go @@ -100,7 +100,7 @@ func (service *DsmService) ListDsmVolumes(ip string) ([]webapi.VolInfo, error) { return allVolInfos, nil } -func (service *DsmService) getFirstAvailableVolume(dsm *webapi.DSM, sizeInBytes int64) (webapi.VolInfo, error) { +func (service *DsmService) getFirstAvailableVolume(dsm *webapi.DSM, sizeInBytes int64, protocol string) (webapi.VolInfo, error) { volInfos, err := dsm.VolumeList() if err != nil { return webapi.VolInfo{}, err @@ -121,6 +121,10 @@ func (service *DsmService) getFirstAvailableVolume(dsm *webapi.DSM, sizeInBytes if volInfo.Container == "external" && volInfo.Location == "sata" { continue } + + if volInfo.FsType == models.FsTypeExt4 && (protocol == utils.ProtocolSmb || protocol == utils.ProtocolNfs) { + continue + } return volInfo, nil } return webapi.VolInfo{}, fmt.Errorf("Cannot find any available volume") @@ -201,7 +205,7 @@ func (service *DsmService) createMappingTarget(dsm *webapi.DSM, spec *models.Cre func (service *DsmService) createVolumeByDsm(dsm *webapi.DSM, spec *models.CreateK8sVolumeSpec) (*models.K8sVolumeRespSpec, error) { // 1. Find a available location if spec.Location == "" { - vol, err := service.getFirstAvailableVolume(dsm, spec.Size) + vol, err := service.getFirstAvailableVolume(dsm, spec.Size, spec.Protocol) if err != nil { return nil, status.Errorf(codes.Internal, fmt.Sprintf("Failed to get available location, err: %v", err)) @@ -372,16 +376,25 @@ func (service *DsmService) createVolumeByVolume(dsm *webapi.DSM, spec *models.Cr return DsmLunToK8sVolume(dsm.Ip, lunInfo, targetInfo), nil } -func DsmShareToK8sVolume(dsmIp string, info webapi.ShareInfo) *models.K8sVolumeRespSpec { +func DsmShareToK8sVolume(dsmIp string, info webapi.ShareInfo, protocol string) *models.K8sVolumeRespSpec { + var source, baseDir string + if protocol == utils.ProtocolSmb { + source = "//" + dsmIp + "/" + info.Name + } else if protocol == utils.ProtocolNfs { + source = "//" + dsmIp + "/" + info.Name + baseDir = info.VolPath + "/" + info.Name + } + return &models.K8sVolumeRespSpec{ DsmIp: dsmIp, VolumeId: info.Uuid, SizeInBytes: utils.MBToBytes(info.QuotaValueInMB), Location: info.VolPath, Name: info.Name, - Source: "//" + dsmIp + "/" + info.Name, - Protocol: utils.ProtocolSmb, + Source: source, + Protocol: protocol, Share: info, + BaseDir: baseDir, } } @@ -399,6 +412,45 @@ func DsmLunToK8sVolume(dsmIp string, info webapi.LunInfo, targetInfo webapi.Targ } } +func isNfsVersionSupport(dsm *webapi.DSM, nfsVersion string) bool { + major := 0 + minor := 0 + + info, err := dsm.NfsGet() + if err != nil { + return false + } + + if nfsVersion == "" { + major = info.SupportMajorVer + minor = info.SupportMinorVer + } else if nfsVersion == "3" { + major = 3 + } else if nfsVersion == "4" || nfsVersion == "4.0" || nfsVersion == "4.1" { + major = 4 + if nfsVersion == "4.1" { + minor = 1 + } + } else { + log.Infof("Input nfsVersion = %s, not supported!", nfsVersion) + return false + } + + if major > info.SupportMajorVer || (major == info.SupportMajorVer && minor > info.SupportMinorVer) { + log.Infof("Dsm NFS version not supported") + return false + } + + // enable the highest NFS version the DSM supports + if err := dsm.NfsSet(true, (info.SupportMajorVer == 4), info.SupportMinorVer); err != nil { + log.Errorf("[%s] Failed to enable nfs: %v\n", dsm.Ip, err) + return false + } + + return true +} + + func (service *DsmService) CreateVolume(spec *models.CreateK8sVolumeSpec) (*models.K8sVolumeRespSpec, error) { if spec.SourceVolumeId != "" { /* Create volume by exists volume (Clone) */ @@ -414,8 +466,8 @@ func (service *DsmService) CreateVolume(spec *models.CreateK8sVolumeSpec) (*mode if spec.Protocol == utils.ProtocolIscsi { return service.createVolumeByVolume(dsm, spec, k8sVolume.Lun) - } else if spec.Protocol == utils.ProtocolSmb { - return service.createSMBVolumeByVolume(dsm, spec, k8sVolume.Share) + } else if spec.Protocol == utils.ProtocolSmb || spec.Protocol == utils.ProtocolNfs { + return service.createSMBorNFSVolumeByVolume(dsm, spec, k8sVolume.Share) } return nil, status.Error(codes.InvalidArgument, "Unknown protocol") } @@ -438,7 +490,10 @@ func (service *DsmService) CreateVolume(spec *models.CreateK8sVolumeSpec) (*mode snapshot.RootPath, spec.Location) return nil, status.Errorf(codes.InvalidArgument, msg) } - if spec.Protocol != snapshot.Protocol { + + log.Debugf("The source PVC protocol [%s] and the destination PVC protocol [%s]", snapshot.Protocol, spec.Protocol) + if (spec.Protocol == utils.ProtocolIscsi || snapshot.Protocol == utils.ProtocolIscsi) && + spec.Protocol != snapshot.Protocol { msg := fmt.Sprintf("The source PVC and destination PVCs shouldn't have different protocols. Source is %s, but new PVC is %s", snapshot.Protocol, spec.Protocol) return nil, status.Errorf(codes.InvalidArgument, msg) @@ -451,8 +506,8 @@ func (service *DsmService) CreateVolume(spec *models.CreateK8sVolumeSpec) (*mode if spec.Protocol == utils.ProtocolIscsi { return service.createVolumeBySnapshot(dsm, spec, snapshot) - } else if spec.Protocol == utils.ProtocolSmb { - return service.createSMBVolumeBySnapshot(dsm, spec, snapshot) + } else if spec.Protocol == utils.ProtocolSmb || spec.Protocol == utils.ProtocolNfs { + return service.createSMBorNFSVolumeBySnapshot(dsm, spec, snapshot) } return nil, status.Error(codes.InvalidArgument, "Unknown protocol") } @@ -468,7 +523,12 @@ func (service *DsmService) CreateVolume(spec *models.CreateK8sVolumeSpec) (*mode if spec.Protocol == utils.ProtocolIscsi { k8sVolume, err = service.createVolumeByDsm(dsm, spec) } else if spec.Protocol == utils.ProtocolSmb { - k8sVolume, err = service.createSMBVolumeByDsm(dsm, spec) + k8sVolume, err = service.createSMBorNFSVolumeByDsm(dsm, spec) + } else if spec.Protocol == utils.ProtocolNfs { + if !isNfsVersionSupport(dsm, spec.NfsVersion) { + continue + } + k8sVolume, err = service.createSMBorNFSVolumeByDsm(dsm, spec) } if err != nil { @@ -494,7 +554,7 @@ func (service *DsmService) DeleteVolume(volId string) error { return status.Errorf(codes.Internal, fmt.Sprintf("Failed to get DSM[%s]", k8sVolume.DsmIp)) } - if k8sVolume.Protocol == utils.ProtocolSmb { + if k8sVolume.Protocol == utils.ProtocolSmb || k8sVolume.Protocol == utils.ProtocolNfs { if err := dsm.ShareDelete(k8sVolume.Share.Name); err != nil { log.Errorf("[%s] Failed to delete Share(%s): %v", dsm.Ip, k8sVolume.Share.Name, err) return err @@ -561,7 +621,7 @@ func (service *DsmService) listISCSIVolumes(dsmIp string) (infos []*models.K8sVo func (service *DsmService) ListVolumes() (infos []*models.K8sVolumeRespSpec) { infos = append(infos, service.listISCSIVolumes("")...) - infos = append(infos, service.listSMBVolumes("")...) + infos = append(infos, service.listSMBorNFSVolumes("")...) return infos } @@ -616,7 +676,7 @@ func (service *DsmService) ExpandVolume(volId string, newSize int64) (*models.K8 return nil, status.Errorf(codes.Internal, fmt.Sprintf("Failed to get DSM[%s]", k8sVolume.DsmIp)) } - if k8sVolume.Protocol == utils.ProtocolSmb { + if k8sVolume.Protocol == utils.ProtocolSmb || k8sVolume.Protocol == utils.ProtocolNfs { newSizeInMB := utils.BytesToMBCeil(newSize) // round up to MB if err := dsm.SetShareQuota(k8sVolume.Share, newSizeInMB); err != nil { log.Errorf("[%s] Failed to set quota [%d (MB)] to Share [%s]: %v", @@ -674,7 +734,7 @@ func (service *DsmService) CreateSnapshot(spec *models.CreateK8sVolumeSnapshotSp } return nil, status.Errorf(codes.NotFound, fmt.Sprintf("Failed to get iscsi snapshot (%s). Not found", snapshotUuid)) - } else if k8sVolume.Protocol == utils.ProtocolSmb { + } else if k8sVolume.Protocol == utils.ProtocolSmb || k8sVolume.Protocol == utils.ProtocolNfs { snapshotSpec := webapi.ShareSnapshotCreateSpec{ ShareName: k8sVolume.Share.Name, Desc: models.ShareSnapshotDescPrefix + spec.SnapshotName, // limitations: don't change the desc by DSM @@ -686,13 +746,14 @@ func (service *DsmService) CreateSnapshot(spec *models.CreateK8sVolumeSnapshotSp return nil, status.Errorf(codes.Internal, fmt.Sprintf("Failed to ShareSnapshotCreate(%s), err: %v", srcVolId, err)) } - snapshots := service.listSMBSnapshotsByDsm(dsm) + snapshots := service.listSMBorNFSSnapshotsByDsm(dsm) for _, snapshot := range snapshots { if snapshot.Time == snapshotTime && snapshot.ParentUuid == srcVolId { return snapshot, nil } } - return nil, status.Errorf(codes.NotFound, fmt.Sprintf("Failed to get smb snapshot (%s, %s). Not found", snapshotTime, srcVolId)) + return nil, status.Errorf(codes.NotFound, fmt.Sprintf("Failed to get %s snapshot (%s, %s). Not found", + k8sVolume.Protocol, snapshotTime, srcVolId)) } return nil, status.Error(codes.InvalidArgument, "Unsupported volume protocol") @@ -718,9 +779,9 @@ func (service *DsmService) DeleteSnapshot(snapshotUuid string) error { return err } - if snapshot.Protocol == utils.ProtocolSmb { + if snapshot.Protocol == utils.ProtocolSmb || snapshot.Protocol == utils.ProtocolNfs { if err := dsm.ShareSnapshotDelete(snapshot.Time, snapshot.ParentName); err != nil { - if snapshot := service.getSMBSnapshot(snapshotUuid); snapshot == nil { // idempotency + if snapshot := service.getSMBorNFSSnapshot(snapshotUuid); snapshot == nil { // idempotency return nil } @@ -763,7 +824,7 @@ func (service *DsmService) ListAllSnapshots() []*models.K8sSnapshotRespSpec { for _, dsm := range service.dsms { allInfos = append(allInfos, service.listISCSISnapshotsByDsm(dsm)...) - allInfos = append(allInfos, service.listSMBSnapshotsByDsm(dsm)...) + allInfos = append(allInfos, service.listSMBorNFSSnapshotsByDsm(dsm)...) } return allInfos @@ -799,14 +860,14 @@ func (service *DsmService) ListSnapshots(volId string) []*models.K8sSnapshotResp return nil } for _, info := range infos { - allInfos = append(allInfos, DsmShareSnapshotToK8sSnapshot(dsm.Ip, info, k8sVolume.Share)) + allInfos = append(allInfos, DsmShareSnapshotToK8sSnapshot(dsm.Ip, info, k8sVolume.Share, k8sVolume.Protocol)) } } return allInfos } -func DsmShareSnapshotToK8sSnapshot(dsmIp string, info webapi.ShareSnapshotInfo, shareInfo webapi.ShareInfo) *models.K8sSnapshotRespSpec { +func DsmShareSnapshotToK8sSnapshot(dsmIp string, info webapi.ShareSnapshotInfo, shareInfo webapi.ShareInfo, protocol string) *models.K8sSnapshotRespSpec { return &models.K8sSnapshotRespSpec{ DsmIp: dsmIp, Name: strings.ReplaceAll(info.Desc, models.ShareSnapshotDescPrefix, ""), // snapshot-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX @@ -818,7 +879,7 @@ func DsmShareSnapshotToK8sSnapshot(dsmIp string, info webapi.ShareSnapshotInfo, CreateTime: GMTToUnixSecond(info.Time), Time: info.Time, RootPath: shareInfo.VolPath, - Protocol: utils.ProtocolSmb, + Protocol: protocol, } } diff --git a/pkg/dsm/service/share_volume.go b/pkg/dsm/service/share_volume.go index 4035d7c..6dd5d11 100644 --- a/pkg/dsm/service/share_volume.go +++ b/pkg/dsm/service/share_volume.go @@ -27,7 +27,7 @@ func GMTToUnixSecond(timeStr string) (int64) { return t.Unix() } -func (service *DsmService) createSMBVolumeBySnapshot(dsm *webapi.DSM, spec *models.CreateK8sVolumeSpec, srcSnapshot *models.K8sSnapshotRespSpec) (*models.K8sVolumeRespSpec, error) { +func (service *DsmService) createSMBorNFSVolumeBySnapshot(dsm *webapi.DSM, spec *models.CreateK8sVolumeSpec, srcSnapshot *models.K8sSnapshotRespSpec) (*models.K8sVolumeRespSpec, error) { srcShareInfo, err := dsm.ShareGet(srcSnapshot.ParentName) if err != nil { return nil, status.Errorf(codes.Internal, fmt.Sprintf("Failed to get share: %s, err: %v", srcSnapshot.ParentName, err)) @@ -75,12 +75,12 @@ func (service *DsmService) createSMBVolumeBySnapshot(dsm *webapi.DSM, spec *mode status.Errorf(codes.OutOfRange, "Requested share quotaMB [%d] is not equal to snapshot restore quotaMB [%d]", newSizeInMB, shareInfo.QuotaValueInMB) } - log.Debugf("[%s] createSMBVolumeBySnapshot Successfully. VolumeId: %s", dsm.Ip, shareInfo.Uuid); + log.Debugf("[%s] createSMBorNFSVolumeBySnapshot Successfully. VolumeId: %s", dsm.Ip, shareInfo.Uuid); - return DsmShareToK8sVolume(dsm.Ip, shareInfo), nil + return DsmShareToK8sVolume(dsm.Ip, shareInfo, spec.Protocol), nil } -func (service *DsmService) createSMBVolumeByVolume(dsm *webapi.DSM, spec *models.CreateK8sVolumeSpec, srcShareInfo webapi.ShareInfo) (*models.K8sVolumeRespSpec, error) { +func (service *DsmService) createSMBorNFSVolumeByVolume(dsm *webapi.DSM, spec *models.CreateK8sVolumeSpec, srcShareInfo webapi.ShareInfo) (*models.K8sVolumeRespSpec, error) { newSizeInMB := utils.BytesToMBCeil(spec.Size) if spec.Size != 0 && newSizeInMB != srcShareInfo.QuotaValueInMB { return nil, @@ -122,17 +122,17 @@ func (service *DsmService) createSMBVolumeByVolume(dsm *webapi.DSM, spec *models shareInfo.QuotaValueInMB = newSizeInMB } - log.Debugf("[%s] createSMBVolumeByVolume Successfully. VolumeId: %s", dsm.Ip, shareInfo.Uuid); + log.Debugf("[%s] createSMBorNFSVolumeByVolume Successfully. VolumeId: %s", dsm.Ip, shareInfo.Uuid); - return DsmShareToK8sVolume(dsm.Ip, shareInfo), nil + return DsmShareToK8sVolume(dsm.Ip, shareInfo, spec.Protocol), nil } -func (service *DsmService) createSMBVolumeByDsm(dsm *webapi.DSM, spec *models.CreateK8sVolumeSpec) (*models.K8sVolumeRespSpec, error) { +func (service *DsmService) createSMBorNFSVolumeByDsm(dsm *webapi.DSM, spec *models.CreateK8sVolumeSpec) (*models.K8sVolumeRespSpec, error) { // TODO: Check if share name is allowable // 1. Find a available location if spec.Location == "" { - vol, err := service.getFirstAvailableVolume(dsm, spec.Size) + vol, err := service.getFirstAvailableVolume(dsm, spec.Size, spec.Protocol) if err != nil { return nil, status.Errorf(codes.Internal, fmt.Sprintf("Failed to get available location, err: %v", err)) } @@ -140,11 +140,15 @@ func (service *DsmService) createSMBVolumeByDsm(dsm *webapi.DSM, spec *models.Cr } // 2. Check if location exists - _, err := dsm.VolumeGet(spec.Location) + dsmVolInfo, err := dsm.VolumeGet(spec.Location) if err != nil { return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("Unable to find location %s", spec.Location)) } + if dsmVolInfo.FsType == models.FsTypeExt4 { + return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("Location: %s with ext4 fstype was not supported for creating smb/nfs protocol's K8s volume", spec.Location)) + } + // 3. Create Share sizeInMB := utils.BytesToMBCeil(spec.Size) shareSpec := webapi.ShareCreateSpec{ @@ -173,12 +177,12 @@ func (service *DsmService) createSMBVolumeByDsm(dsm *webapi.DSM, spec *models.Cr status.Errorf(codes.Internal, fmt.Sprintf("Failed to get existed Share with name: %s, err: %v", spec.ShareName, err)) } - log.Debugf("[%s] createSMBVolumeByDsm Successfully. VolumeId: %s", dsm.Ip, shareInfo.Uuid) + log.Debugf("[%s] createSMBorNFSVolumeByDsm Successfully. VolumeId: %s", dsm.Ip, shareInfo.Uuid) - return DsmShareToK8sVolume(dsm.Ip, shareInfo), nil + return DsmShareToK8sVolume(dsm.Ip, shareInfo, spec.Protocol), nil } -func (service *DsmService) listSMBVolumes(dsmIp string) (infos []*models.K8sVolumeRespSpec) { +func (service *DsmService) listSMBorNFSVolumes(dsmIp string) (infos []*models.K8sVolumeRespSpec) { for _, dsm := range service.dsms { if dsmIp != "" && dsmIp != dsm.Ip { continue @@ -198,15 +202,25 @@ func (service *DsmService) listSMBVolumes(dsmIp string) (infos []*models.K8sVolu if !strings.HasPrefix(share.Name, models.SharePrefix) { continue } - infos = append(infos, DsmShareToK8sVolume(dsm.Ip, share)) + // if share has set nfs rule, deal it as NFS + sharePrivilege, err := dsm.ShareNfsPrivilegeLoad(share.Name) + if err != nil { + log.Errorf("[%s] Failed to load share nfs privilege: %v", dsm.Ip, err) + continue + } + if len(sharePrivilege.Rule) > 0 { + infos = append(infos, DsmShareToK8sVolume(dsm.Ip, share, utils.ProtocolNfs)) + } else { + infos = append(infos, DsmShareToK8sVolume(dsm.Ip, share, utils.ProtocolSmb)) + } } } return infos } -func (service *DsmService) listSMBSnapshotsByDsm(dsm *webapi.DSM) (infos []*models.K8sSnapshotRespSpec) { - volumes := service.listSMBVolumes(dsm.Ip) +func (service *DsmService) listSMBorNFSSnapshotsByDsm(dsm *webapi.DSM) (infos []*models.K8sSnapshotRespSpec) { + volumes := service.listSMBorNFSVolumes(dsm.Ip) for _, volume := range volumes { shareInfo := volume.Share shareSnaps, err := dsm.ShareSnapshotList(shareInfo.Name) @@ -215,15 +229,15 @@ func (service *DsmService) listSMBSnapshotsByDsm(dsm *webapi.DSM) (infos []*mode continue } for _, info := range shareSnaps { - infos = append(infos, DsmShareSnapshotToK8sSnapshot(dsm.Ip, info, shareInfo)) + infos = append(infos, DsmShareSnapshotToK8sSnapshot(dsm.Ip, info, shareInfo, volume.Protocol)) } } return infos } -func (service *DsmService) getSMBSnapshot(snapshotUuid string) *models.K8sSnapshotRespSpec { +func (service *DsmService) getSMBorNFSSnapshot(snapshotUuid string) *models.K8sSnapshotRespSpec { for _, dsm := range service.dsms { - snapshots := service.listSMBSnapshotsByDsm(dsm) + snapshots := service.listSMBorNFSSnapshotsByDsm(dsm) for _, snap := range snapshots { if snap.Uuid == snapshotUuid { return snap diff --git a/pkg/dsm/webapi/share.go b/pkg/dsm/webapi/share.go index b1b73b5..a4d05cb 100644 --- a/pkg/dsm/webapi/share.go +++ b/pkg/dsm/webapi/share.go @@ -76,6 +76,18 @@ type SharePermission struct { IsAdmin bool `json:"is_admin,omitempty"` // field for list } +type NfsInfo struct { + EnableNfs bool `json:"enable_nfs"` + EnableNfsV4 bool `json:"enable_nfs_v4"` + NfsV4Domain string `json:"nfs_v4_domain"` + ReadSize int `json:"read_size"` + SupportEncryptShare int `json:"support_encrypt_share"` + SupportMajorVer int `json:"support_major_ver"` + SupportMinorVer int `json:"support_minor_ver"` + UnixPriEnable bool `json:"unix_pri_enable"` + WriteSize int `json:"write_size"` +} + func shareErrCodeMapping(errCode int, oriErr error) error { switch errCode { case 402: // No such share @@ -331,6 +343,10 @@ func (dsm *DSM) SharePermissionSet(spec SharePermissionSetSpec) error { } params.Add("permissions", string(js)) + if logger.WebapiDebug { + log.Debugln(params) + } + resp, err := dsm.sendRequest("", &struct{}{}, params, "webapi/entry.cgi") return shareErrCodeMapping(resp.ErrorCode, err) @@ -361,3 +377,96 @@ func (dsm *DSM) SharePermissionList(shareName string, userGroupType string) ([]S return infos.Permissions, nil } +// ----------------------- FileServ NFS SharePrivilege APIs ----------------------- +type SecurityFlavor struct { + Kerbros bool `json:"kerberos"` + KerbrosIntegrity bool `json:"kerberos_integrity"` + KerbrosPrivacy bool `json:"kerberos_privacy"` + Sys bool `json:"sys"` +} + +type PrivilegeRule struct { + Async bool `json:"async"` + Client string `json:"client"` + Crossmnt bool `json:"crossmnt"` + Insecure bool `json:"insecure"` + Privilege string `json:"privilege"` + RootSquash string `json:"root_squash"` + SecurityFlavor SecurityFlavor `json:"security_flavor"` +} + +type SharePrivilege struct { + ShareName string `json:"share_name"` + Rule []PrivilegeRule `json:"rule"` +} + +func (dsm *DSM) ShareNfsPrivilegeSave(privilege SharePrivilege) error { + params := url.Values{} + params.Add("api", "SYNO.Core.FileServ.NFS.SharePrivilege") + params.Add("method", "save") + params.Add("share_name", strconv.Quote(privilege.ShareName)) + params.Add("version", "1") + + js, err := json.Marshal(privilege.Rule) + if err != nil { + return err + } + params.Add("rule", string(js)) + + _, err = dsm.sendRequest("", &struct{}{}, params, "webapi/entry.cgi") + if err != nil { + return err + } + + return nil +} + +func (dsm *DSM) ShareNfsPrivilegeLoad(shareName string) (SharePrivilege, error) { + params := url.Values{} + params.Add("api", "SYNO.Core.FileServ.NFS.SharePrivilege") + params.Add("method", "load") + params.Add("share_name", strconv.Quote(shareName)) + params.Add("version", "1") + + info := SharePrivilege{} + _, err := dsm.sendRequest("", &info, params, "webapi/entry.cgi") + if err != nil { + return SharePrivilege{}, err + } + + return info, nil +} + +func (dsm *DSM) NfsGet() (NfsInfo, error) { + params := url.Values{} + params.Add("api", "SYNO.Core.FileServ.NFS") + params.Add("method", "get") + params.Add("version", "2") + + info := NfsInfo{} + _, err := dsm.sendRequest("", &info, params, "webapi/entry.cgi") + if err != nil { + return NfsInfo{}, err + } + + return info, nil +} + +func (dsm *DSM) NfsSet(enableV3 bool, enableV4 bool, enabledMinorVer int) error { + params := url.Values{} + params.Add("api", "SYNO.Core.FileServ.NFS") + params.Add("method", "set") + params.Add("version", "2") + + params.Add("enable_nfs", strconv.FormatBool(enableV3)) + params.Add("enable_nfs_v4", strconv.FormatBool(enableV4)) + params.Add("enabled_minor_ver", strconv.Itoa(enabledMinorVer)) + + _, err := dsm.sendRequest("", &struct{}{}, params, "webapi/entry.cgi") + if err != nil { + return err + } + + return nil +} + diff --git a/pkg/models/dsm_req_spec.go b/pkg/models/dsm_req_spec.go index 3ef0571..354978b 100644 --- a/pkg/models/dsm_req_spec.go +++ b/pkg/models/dsm_req_spec.go @@ -23,6 +23,7 @@ type CreateK8sVolumeSpec struct { SourceSnapshotId string SourceVolumeId string Protocol string + NfsVersion string } type K8sVolumeRespSpec struct { @@ -36,6 +37,7 @@ type K8sVolumeRespSpec struct { Target webapi.TargetInfo Share webapi.ShareInfo Protocol string + BaseDir string } type K8sSnapshotRespSpec struct { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 167b518..f53505b 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -16,6 +16,7 @@ const ( ProtocolSmb = "smb" ProtocolIscsi = "iscsi" + ProtocolNfs = "nfs" ProtocolDefault = ProtocolIscsi AuthTypeReadWrite AuthType = "rw" diff --git a/scripts/deploy.sh b/scripts/deploy.sh index bcbed8d..613db35 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -1,38 +1,12 @@ #!/bin/bash plugin_name="csi.san.synology.com" -min_support_minor=19 -max_support_minor=20 -deploy_k8s_version="v1".$min_support_minor SCRIPT_PATH="$(realpath "$0")" SOURCE_PATH="$(realpath "$(dirname "${SCRIPT_PATH}")"/../)" config_file="${SOURCE_PATH}/config/client-info.yml" plugin_dir="/var/lib/kubelet/plugins/$plugin_name" -parse_version(){ - ver=$(kubectl version --short | grep Server | awk '{print $3}') - major=$(echo "${ver##*v}" | cut -d'.' -f1) - minor=$(echo "${ver##*v}" | cut -d'.' -f2) - - if [[ "$major" != 1 ]]; then - echo "Version not supported: $ver" - exit 1 - fi - - case "$minor" in - 19|20) - deploy_k8s_version="v1".$minor - ;; - *) - if [[ $minor -lt $min_support_minor ]]; then - deploy_k8s_version="v1".$min_support_minor - else - deploy_k8s_version="v1".$max_support_minor - fi - ;; - esac - echo "Deploy Version: $deploy_k8s_version" -} +source "$SOURCE_PATH"/scripts/functions.sh # 1. Build csi_build(){ @@ -44,6 +18,7 @@ csi_build(){ csi_install(){ echo "==== Creates namespace and secrets, then installs synology-csi ====" parse_version + echo "Deploy Version: $deploy_k8s_version" kubectl create ns synology-csi kubectl create secret -n synology-csi generic client-info-secret --from-file="$config_file" @@ -106,4 +81,4 @@ case "$1" in print_usage exit 1 ;; -esac \ No newline at end of file +esac diff --git a/scripts/functions.sh b/scripts/functions.sh new file mode 100644 index 0000000..12d3a3e --- /dev/null +++ b/scripts/functions.sh @@ -0,0 +1,29 @@ +#!/bin/bash +min_support_minor=19 +max_support_minor=20 +deploy_k8s_version="v1".$min_support_minor + +parse_version(){ + ver=$(kubectl version --output=json | awk -F'"' '/"serverVersion":/ {flag=1} flag && /"gitVersion":/ {print $(NF-1); flag=0}') + major=$(echo "${ver##*v}" | cut -d'.' -f1) + minor=$(echo "${ver##*v}" | cut -d'.' -f2) + + if [[ "$major" != 1 ]]; then + echo "Version not supported: $ver" + exit 1 + fi + + case "$minor" in + 19|20) + deploy_k8s_version="v1".$minor + ;; + *) + if [[ $minor -lt $min_support_minor ]]; then + deploy_k8s_version="v1".$min_support_minor + else + deploy_k8s_version="v1".$max_support_minor + fi + ;; + esac + echo "Current Server Version: $ver" +} \ No newline at end of file diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh index 1d8d2d4..bc988af 100755 --- a/scripts/uninstall.sh +++ b/scripts/uninstall.sh @@ -2,11 +2,17 @@ echo "Uninstalling synology-csi pods ..." plugin_name="csi.san.synology.com" -deploy_k8s_version="v1.19" +min_support_minor=19 +max_support_minor=20 +deploy_k8s_version="v1".$min_support_minor SCRIPT_PATH="$(realpath "$0")" SOURCE_PATH="$(realpath "$(dirname "$SCRIPT_PATH")"/../)" +source "$SOURCE_PATH"/scripts/functions.sh + +parse_version +echo "Uninstall Version: $deploy_k8s_version" kubectl delete -f "$SOURCE_PATH"/deploy/kubernetes/$deploy_k8s_version/snapshotter --ignore-not-found kubectl delete -f "$SOURCE_PATH"/deploy/kubernetes/$deploy_k8s_version --ignore-not-found -echo "End of synology-csi uninstallation." \ No newline at end of file +echo "End of synology-csi uninstallation."