From cce9f729928436fca59f090b6f3af48067632fb9 Mon Sep 17 00:00:00 2001 From: Tomy GUICHARD Date: Thu, 12 Oct 2023 17:03:05 +0200 Subject: [PATCH] Switch to new Scaleway block API --- .golangci.yml | 18 + Dockerfile | 3 +- Makefile | 6 +- README.md | 175 ++- cmd/scaleway-csi/main.go | 2 +- deploy/kubernetes/scaleway-csi-v0.1.0.yaml | 899 ------------- deploy/kubernetes/scaleway-csi-v0.1.1.yaml | 899 ------------- deploy/kubernetes/scaleway-csi-v0.1.2.yaml | 899 ------------- deploy/kubernetes/scaleway-csi-v0.1.3.yaml | 946 ------------- deploy/kubernetes/scaleway-csi-v0.1.4.yaml | 946 ------------- deploy/kubernetes/scaleway-csi-v0.1.5.yaml | 944 ------------- deploy/kubernetes/scaleway-csi-v0.1.7.yaml | 947 ------------- deploy/kubernetes/scaleway-csi-v0.1.8.yaml | 1072 --------------- deploy/kubernetes/scaleway-csi-v0.2.0.yaml | 1072 --------------- deploy/kubernetes/scaleway-secret.yaml | 16 - driver/controller.go | 867 ------------ driver/errors.go | 28 - driver/helpers.go | 284 ---- driver/helpers_test.go | 651 --------- driver/sanity_test.go | 515 -------- examples/kubernetes/README.md | 166 ++- examples/kubernetes/block-volume/pvc.yaml | 2 +- examples/kubernetes/importing/pv.yaml | 2 +- examples/kubernetes/importing/pvc.yaml | 2 +- examples/kubernetes/pvc-deployment/pvc.yaml | 2 +- examples/kubernetes/snapshots/pvc.yaml | 2 +- .../snapshots/restored-snapshot.yaml | 2 +- go.mod | 39 +- go.sum | 262 +--- pkg/driver/controller.go | 550 ++++++++ {driver => pkg/driver}/diskutils.go | 245 +--- pkg/driver/diskutils_fake.go | 192 +++ {driver => pkg/driver}/driver.go | 129 +- pkg/driver/helpers.go | 494 +++++++ pkg/driver/helpers_test.go | 1177 +++++++++++++++++ {driver => pkg/driver}/identity.go | 2 +- {driver => pkg/driver}/luks_utils.go | 33 +- {driver => pkg/driver}/node.go | 290 ++-- pkg/driver/sanity_test.go | 64 + {driver => pkg/driver}/version.go | 11 - pkg/scaleway/block.go | 293 ++++ pkg/scaleway/errors.go | 45 + pkg/scaleway/fake.go | 415 ++++++ pkg/scaleway/helpers.go | 119 ++ pkg/scaleway/instance.go | 118 ++ pkg/scaleway/interface.go | 29 + pkg/scaleway/metadata.go | 107 ++ pkg/scaleway/scaleway.go | 71 + scaleway/scaleway.go | 163 --- scaleway/utils.go | 18 - test/e2e/README.md | 59 + test/e2e/e2e.sh | 18 + test/e2e/test-driver.yaml | 25 + test/sanity/README.md | 55 + test/sanity/sanity_suite_test.go | 91 ++ test/sanity/sanity_test.go | 325 +++++ 56 files changed, 4877 insertions(+), 11929 deletions(-) create mode 100644 .golangci.yml delete mode 100644 deploy/kubernetes/scaleway-csi-v0.1.0.yaml delete mode 100644 deploy/kubernetes/scaleway-csi-v0.1.1.yaml delete mode 100644 deploy/kubernetes/scaleway-csi-v0.1.2.yaml delete mode 100644 deploy/kubernetes/scaleway-csi-v0.1.3.yaml delete mode 100644 deploy/kubernetes/scaleway-csi-v0.1.4.yaml delete mode 100644 deploy/kubernetes/scaleway-csi-v0.1.5.yaml delete mode 100644 deploy/kubernetes/scaleway-csi-v0.1.7.yaml delete mode 100644 deploy/kubernetes/scaleway-csi-v0.1.8.yaml delete mode 100644 deploy/kubernetes/scaleway-csi-v0.2.0.yaml delete mode 100644 deploy/kubernetes/scaleway-secret.yaml delete mode 100644 driver/controller.go delete mode 100644 driver/errors.go delete mode 100644 driver/helpers.go delete mode 100644 driver/helpers_test.go delete mode 100644 driver/sanity_test.go create mode 100644 pkg/driver/controller.go rename {driver => pkg/driver}/diskutils.go (51%) create mode 100644 pkg/driver/diskutils_fake.go rename {driver => pkg/driver}/driver.go (52%) create mode 100644 pkg/driver/helpers.go create mode 100644 pkg/driver/helpers_test.go rename {driver => pkg/driver}/identity.go (99%) rename {driver => pkg/driver}/luks_utils.go (80%) rename {driver => pkg/driver}/node.go (65%) create mode 100644 pkg/driver/sanity_test.go rename {driver => pkg/driver}/version.go (78%) create mode 100644 pkg/scaleway/block.go create mode 100644 pkg/scaleway/errors.go create mode 100644 pkg/scaleway/fake.go create mode 100644 pkg/scaleway/helpers.go create mode 100644 pkg/scaleway/instance.go create mode 100644 pkg/scaleway/interface.go create mode 100644 pkg/scaleway/metadata.go create mode 100644 pkg/scaleway/scaleway.go delete mode 100644 scaleway/scaleway.go delete mode 100644 scaleway/utils.go create mode 100644 test/e2e/README.md create mode 100755 test/e2e/e2e.sh create mode 100644 test/e2e/test-driver.yaml create mode 100644 test/sanity/README.md create mode 100644 test/sanity/sanity_suite_test.go create mode 100644 test/sanity/sanity_test.go diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..a5a3a79 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,18 @@ +linters: + enable: + - dupl + - errname + - ginkgolinter + - goconst + - godox + - gosec + - nilerr + - prealloc + - reassign + - tparallel + - wrapcheck + +linters-settings: + wrapcheck: + ignorePackageGlobs: + - google.golang.org/grpc/status diff --git a/Dockerfile b/Dockerfile index 0f0a6ba..9aec446 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,8 +9,7 @@ COPY go.sum go.sum RUN go mod download COPY cmd/ cmd/ -COPY scaleway/ scaleway/ -COPY driver/ driver/ +COPY pkg/ pkg/ ARG TAG ARG COMMIT_SHA diff --git a/Makefile b/Makefile index dc2bcc6..e418286 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,11 @@ clean: .PHONY: test test: - go test -timeout=1m -v -race -short ./... + go test -timeout=1m -v -race -short `go list ./... | grep -v /test` + +.PHONY: test-sanity +test-sanity: + go test -count=1 -v -timeout 10m github.com/scaleway/scaleway-csi/test/sanity .PHONY: fmt fmt: diff --git a/README.md b/README.md index d1d112f..27cc5b1 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,82 @@ # Scaleway Block Volume CSI driver -The [Scaleway Block Volume](https://www.scaleway.com/en/block-storage/) Container Storage Interface (CSI) driver is an implementation of the [CSI interface](https://github.com/container-storage-interface/spec/blob/master/spec.md) to provide a way to manage Scaleway Block Volumes through a container orchestration system, like Kubernetes. +> **Important** +> ⚠️ **Please read this first before doing anything else!** ⚠️ +> +> You are currently on a branch which corresponds to the `v0.3` release of the Scaleway CSI. +> +> This release is **NOT** compatible with the `v0.2` and `v0.1` releases as it introduces +> a major **breaking change**: it now uses the **new Scaleway Block Storage (SBS) API** (which +> is still in private beta) instead of the Instance API to manage block volumes and +> snapshots. +> +> - If you are currently using the `v0.2.x` or `v0.1.x` versions of the Scaleway CSI: +> please do not use this release until we provide a tool to migrate from `v0.2` to the `v0.3` release. +> - If you are looking to install the CSI for the first time using the `v0.3` release: +> make sure you have access to the new Scaleway Block Storage API. You can request +> access [here](https://www.scaleway.com/en/betas/#block-storage-low-latency). +> +> Not sure what to do? +> +> ➡️ Use the [release-0.2](https://github.com/scaleway/scaleway-csi/tree/release-0.2) +> branch for now. +> +> ➡️ Reach us on the *#k8s* channel on the [Scaleway Slack community](https://www.scaleway.com/en/docs/tutorials/scaleway-slack-community/). + +The [Scaleway Block Volume](https://www.scaleway.com/en/block-storage/) +Container Storage Interface (CSI) driver is an implementation of the +[CSI interface](https://github.com/container-storage-interface/spec/blob/master/spec.md) +to provide a way to manage Scaleway Block Volumes through a container orchestration system, like Kubernetes. + +## CSI Specification Compatibility Matrix + +| Scaleway CSI Driver \ CSI Version | v1.2.0 | v1.6.0 | v1.8.0 | +| --------------------------------- | ------ | ------ | ------ | +| master branch | ✅ | ✅ | ✅ | +| v0.1.x | ✅ | ❌ | ❌ | +| v0.2.x | ✅ | ✅ | ❌ | +| v0.3.x | ✅ | ✅ | ✅ | + +## Scaleway Storage API Compatibility Matrix + +Scaleway currently offers two APIs to manage Block Volumes and Snapshots: + +- [Instance API](https://www.scaleway.com/en/developers/api/instance/) +- [Scaleway Block Storage API](https://www.scaleway.com/en/developers/api/block/) + +A Block Volume or Snapshot currently managed by the *Instance API* **cannot** be managed by the +*Scaleway Block Storage API* and vice versa. + +In the future, all Block Volumes and Snapshots will be managed by the *Scaleway Block Storage API*. +In order to use newer versions of the CSI (v0.3+), it is needed to migrate Block Volumes and Snapshots +from the *Instance API* to the *Scaleway Block Storage API* (currently not possible). + +A tool will be made available later to facilitate upgrades from older CSI releases to v0.3+. + +| Scaleway CSI Driver \ Scaleway Storage API compatibility | Instance API | Scaleway Block Storage API | +| -------------------------------------------------------- | ------------ | -------------------------- | +| master branch | ❌ | ✅ | +| v0.1.x | ✅ | ❌ | +| v0.2.x | ✅ | ❌ | +| v0.3.x | ❌ | ✅ | + +## Features + +Here is a list of features implemented by the Scaleway CSI driver. + +### Block device resizing + +The Scaleway CSI driver implements the resize feature ([example for Kubernetes](https://kubernetes.io/blog/2018/07/12/resizing-persistent-volumes-using-kubernetes/)). +It allows an online resize (without the need to detach the block device). +However resizing can only be done upwards, decreasing a volume's size is not supported. + +### Raw Block Volume + +[Raw Block Volumes](https://kubernetes.io/blog/2019/03/07/raw-block-volume-support-to-beta/) +allows the block volume to be exposed directly to the container as a block device, +instead of a mounted filesystem. To enable it, the `volumeMode` needs to be set to `Block`. +For instance, here is a PVC in raw block volume mode: -**WARNING**: ⚠️ This project is under active development and should be considered alpha. - -### CSI Specification Compability Matrix - -| Scaleway CSI Driver \ CSI Version | v1.2.0 | v1.6.0 | -|-----------------------------------|--------|--------| -| master branch | yes | yes | -| v0.1.x | yes | no | -| v0.2.x | yes | yes | - -### Features - -Here is a list of functionality implemented by the Scaleway CSI driver. - -#### Block device resizing - -The Scaleway CSI driver implements the resize feature ([example for Kubernetes](https://kubernetes.io/blog/2018/07/12/resizing-persistent-volumes-using-kubernetes/)). It allows an online resize (without the need to detach the block device). However resizing can only be done upwards, decreasing a volume's size is not supported. - -#### Raw Block Volume - -[Raw Block Volumes](https://kubernetes.io/blog/2019/03/07/raw-block-volume-support-to-beta/) allows the block volume to be exposed directly to the container as a block device, instead of a mounted filesystem. To enable it, the `volumeMode` needs to be set to `Block`. For instance, here is a PVC in raw block volume mode: ```yaml apiVersion: v1 kind: PersistentVolumeClaim @@ -33,30 +87,54 @@ spec: [...] ``` -#### At-Rest Encryption +### At-Rest Encryption + +Support for volume encryption with Cryptsetup/LUKS. [See more details in examples](./examples/kubernetes#encrypting-volumes) + +### Volume Snapshots -Support for volume encryption with Cryptsetup/LUKS. [See more details in examples](https://github.com/scaleway/scaleway-csi/tree/master/examples/kubernetes#encrypting-volumes) +[Volume Snapshots](https://kubernetes.io/docs/concepts/storage/volume-snapshots/) +allows the user to create a snapshot of a specific block volume. -#### Volume Snapshots +### Volume Statistics -[Volume Snapshots](https://kubernetes.io/docs/concepts/storage/volume-snapshots/) allows the user to create a snapshot of a specific block volume. +The Scaleway CSI driver implements the [`NodeGetVolumeStats`](https://github.com/container-storage-interface/spec/blob/master/spec.md#nodegetvolumestats) +CSI method. It is used to gather statistics about the used block volumes. In Kubernetes, +`kubelet` exposes these metrics. -#### Volume Statistics +### Block Storage Low Latency -The Scaleway CSI driver implements the [`NodeGetVolumeStats`](https://github.com/container-storage-interface/spec/blob/master/spec.md#nodegetvolumestats) CSI method. It is used to gather statistics about the used block volumes. In Kubernetes, `kubelet` exposes these metrics. +The Scaleway CSI driver is built upon the Block Storage Low Latency Scaleway product. +It currently provides volumes with up to 15,000 IOPS. By default, created volumes +have 5000 IOPS. To create volumes with higher IOPS, you can set the `iops` parameter +to the requested number of IOPS in your `StorageClass`. For example: + +```yaml +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: my-15k-iops-storage-class +provisioner: csi.scaleway.com +reclaimPolicy: Delete +parameters: + iops: "15000" +``` ## Kubernetes -This section is Kubernetes specific. Note that Scaleway CSI driver may work for older Kubernetes versions than those announced. -The CSI driver allows to use [Persistent Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) in Kubernetes. +This section is Kubernetes specific. Note that Scaleway CSI driver may work for +older Kubernetes versions than those announced. The CSI driver allows to use +[Persistent Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) +in Kubernetes. -### Kubernetes Version Compability Matrix +### Kubernetes Version Compatibility Matrix | Scaleway CSI Driver \ Kubernetes Version | Min K8s Version | Max K8s Version | -|------------------------------------------|-----------------|-----------------| +| ---------------------------------------- | --------------- | --------------- | | master branch | v1.20 | - | | v0.1.x | v1.17 | - | | v0.2.x | v1.20 | - | +| v0.3.x | v1.20 | - | ### Examples @@ -64,17 +142,14 @@ Some examples are available [here](./examples/kubernetes). ### Installation -These steps will cover how to install the Scaleway CSI driver in your Kubernetes cluster, using Helm. - -> **Warning** -> Please note that the manifest files provided in `deploy/kubernetes` are deprecated and -> no longer maintained. +These steps will cover how to install the Scaleway CSI driver in your Kubernetes +cluster, using Helm. #### Requirements -* A Kubernetes cluster running on Scaleway instances (v1.20+) -* Scaleway Project or Organization ID, Access and Secret key -* Helm v3 +- A Kubernetes cluster running on Scaleway instances (v1.20+) +- Scaleway Project or Organization ID, Access and Secret key +- Helm v3 #### Deployment @@ -95,7 +170,8 @@ These steps will cover how to install the Scaleway CSI driver in your Kubernetes --set controller.scaleway.env.SCW_SECRET_KEY=11111111-1111-1111-1111-111111111111 ``` - Review the [configuration values](https://github.com/scaleway/helm-charts/blob/master/charts/scaleway-csi/values.yaml) for the Helm chart. + Review the [configuration values](https://github.com/scaleway/helm-charts/blob/master/charts/scaleway-csi/values.yaml) + for the Helm chart. 3. You can now verify that the driver is running: @@ -115,11 +191,14 @@ These steps will cover how to install the Scaleway CSI driver in your Kubernetes ### Build You can build the Scaleway CSI driver executable using the following commands: + ```bash make build ``` -You can build a local docker image named scaleway-csi for your current architecture using the following command: +You can build a local docker image named scaleway-csi for your current architecture +using the following command: + ```bash make docker-build ``` @@ -127,10 +206,16 @@ make docker-build ### Test In order to run the tests: + ```bash make test ``` +In addition to unit tests, we provide tools to run the following tests: + +- [Kubernetes external storage e2e tests](./test/e2e/) +- [CSI sanity tests](./test/sanity/) + ### Contribute If you are looking for a way to contribute please read the [contributing guide](./CONTRIBUTING.md) @@ -141,8 +226,10 @@ Participation in the Kubernetes community is governed by the [CNCF Code of Condu ## Reach us -We love feedback. Feel free to reach us on [Scaleway Slack community](https://slack.scaleway.com), we are waiting for you on #k8s. +We love feedback. Feel free to reach us on [Scaleway Slack community](https://slack.scaleway.com), +we are waiting for you on #k8s. You can also join the official Kubernetes slack on #scaleway-k8s channel -You can also [raise an issue](https://github.com/scaleway/scaleway-csi/issues/new) if you think you've found a bug. +You can also [raise an issue](https://github.com/scaleway/scaleway-csi/issues/new) +if you think you've found a bug. diff --git a/cmd/scaleway-csi/main.go b/cmd/scaleway-csi/main.go index f2b3546..c1a8a60 100644 --- a/cmd/scaleway-csi/main.go +++ b/cmd/scaleway-csi/main.go @@ -5,7 +5,7 @@ import ( "fmt" "os" - "github.com/scaleway/scaleway-csi/driver" + "github.com/scaleway/scaleway-csi/pkg/driver" "k8s.io/klog/v2" ) diff --git a/deploy/kubernetes/scaleway-csi-v0.1.0.yaml b/deploy/kubernetes/scaleway-csi-v0.1.0.yaml deleted file mode 100644 index 0227ca8..0000000 --- a/deploy/kubernetes/scaleway-csi-v0.1.0.yaml +++ /dev/null @@ -1,899 +0,0 @@ -apiVersion: storage.k8s.io/v1beta1 -kind: CSIDriver -metadata: - name: csi.scaleway.com -spec: - attachRequired: true - podInfoOnMount: true ---- -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: scw-bssd - namespace: kube-system -provisioner: csi.scaleway.com -reclaimPolicy: Delete ---- -kind: DaemonSet -apiVersion: apps/v1 -metadata: - name: scaleway-csi-node - namespace: kube-system - labels: -spec: - selector: - matchLabels: - app: scaleway-csi-node - template: - metadata: - labels: - app: scaleway-csi-node - role: csi - spec: - serviceAccount: scaleway-csi-node - nodeSelector: - kubernetes.io/os: linux - priorityClassName: system-node-critical - hostNetwork: true - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.1.0 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--v=4" - - "--mode=node" - env: - - name: CSI_ENDPOINT - value: unix:///csi/csi.sock - securityContext: - privileged: true - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - volumeMounts: - - name: plugin-dir - mountPath: /csi - - name: kubelet-dir - mountPath: /var/lib/kubelet - mountPropagation: "Bidirectional" - - name: device-dir - mountPath: /dev - - name: csi-node-driver-registrar - image: quay.io/k8scsi/csi-node-driver-registrar:v1.2.0 - args: - - "--v=2" - - "--csi-address=$(CSI_ADDRESS)" - - "--kubelet-registration-path=$(KUBELET_REGISTRATION_PATH)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - - name: KUBELET_REGISTRATION_PATH - value: /var/lib/kubelet/plugins/csi.scaleway.com/csi.sock - - name: KUBE_NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - volumeMounts: - - name: plugin-dir - mountPath: /csi/ - - name: registration-dir - mountPath: /registration/ - - name: liveness-probe - image: quay.io/k8scsi/livenessprobe:v2.0.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: plugin-dir - mountPath: /csi - volumes: - - name: registration-dir - hostPath: - path: /var/lib/kubelet/plugins_registry/ - type: DirectoryOrCreate - - name: plugin-dir - hostPath: - path: /var/lib/kubelet/plugins/csi.scaleway.com - type: DirectoryOrCreate - - name: kubelet-dir - hostPath: - path: /var/lib/kubelet - type: Directory - - name: device-dir - hostPath: - path: /dev ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: scaleway-csi-node - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar - namespace: kube-system -rules: - - apiGroups: [""] - resources: ["events"] - verbs: ["get", "list", "watch", "create", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar -subjects: - - kind: ServiceAccount - name: scaleway-csi-node - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-node-driver-registrar - apiGroup: rbac.authorization.k8s.io ---- -## volumesnapshotclasses CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.2.5 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/260" - creationTimestamp: null - name: volumesnapshotclasses.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .driver - name: Driver - type: string - - JSONPath: .deletionPolicy - description: Determines whether a VolumeSnapshotContent created through the VolumeSnapshotClass - should be deleted when its bound VolumeSnapshot is deleted. - name: DeletionPolicy - type: string - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotClass - listKind: VolumeSnapshotClassList - plural: volumesnapshotclasses - singular: volumesnapshotclass - preserveUnknownFields: false - scope: Cluster - subresources: {} - validation: - openAPIV3Schema: - description: VolumeSnapshotClass specifies parameters that a underlying storage - system uses when creating a volume snapshot. A specific VolumeSnapshotClass - is used by specifying its name in a VolumeSnapshot object. VolumeSnapshotClasses - are non-namespaced - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - deletionPolicy: - description: deletionPolicy determines whether a VolumeSnapshotContent created - through the VolumeSnapshotClass should be deleted when its bound VolumeSnapshot - is deleted. Supported values are "Retain" and "Delete". "Retain" means - that the VolumeSnapshotContent and its physical snapshot on underlying - storage system are kept. "Delete" means that the VolumeSnapshotContent - and its physical snapshot on underlying storage system are deleted. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the storage driver that handles this - VolumeSnapshotClass. Required. - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - parameters: - additionalProperties: - type: string - description: parameters is a key-value map with storage driver specific - parameters for creating snapshots. These values are opaque to Kubernetes. - type: object - required: - - deletionPolicy - - driver - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshotcontents CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.2.5 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/260" - creationTimestamp: null - name: volumesnapshotcontents.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .status.readyToUse - description: Indicates if a snapshot is ready to be used to restore a volume. - name: ReadyToUse - type: boolean - - JSONPath: .status.restoreSize - description: Represents the complete size of the snapshot in bytes - name: RestoreSize - type: integer - - JSONPath: .spec.deletionPolicy - description: Determines whether this VolumeSnapshotContent and its physical snapshot - on the underlying storage system should be deleted when its bound VolumeSnapshot - is deleted. - name: DeletionPolicy - type: string - - JSONPath: .spec.driver - description: Name of the CSI driver used to create the physical snapshot on the - underlying storage system. - name: Driver - type: string - - JSONPath: .spec.volumeSnapshotClassName - description: Name of the VolumeSnapshotClass to which this snapshot belongs. - name: VolumeSnapshotClass - type: string - - JSONPath: .spec.volumeSnapshotRef.name - description: Name of the VolumeSnapshot object to which this VolumeSnapshotContent - object is bound. - name: VolumeSnapshot - type: string - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotContent - listKind: VolumeSnapshotContentList - plural: volumesnapshotcontents - singular: volumesnapshotcontent - preserveUnknownFields: false - scope: Cluster - subresources: - status: {} - validation: - openAPIV3Schema: - description: VolumeSnapshotContent represents the actual "on-disk" snapshot - object in the underlying storage system - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: spec defines properties of a VolumeSnapshotContent created - by the underlying storage system. Required. - properties: - deletionPolicy: - description: deletionPolicy determines whether this VolumeSnapshotContent - and its physical snapshot on the underlying storage system should - be deleted when its bound VolumeSnapshot is deleted. Supported values - are "Retain" and "Delete". "Retain" means that the VolumeSnapshotContent - and its physical snapshot on underlying storage system are kept. "Delete" - means that the VolumeSnapshotContent and its physical snapshot on - underlying storage system are deleted. In dynamic snapshot creation - case, this field will be filled in with the "DeletionPolicy" field - defined in the VolumeSnapshotClass the VolumeSnapshot refers to. For - pre-existing snapshots, users MUST specify this field when creating - the VolumeSnapshotContent object. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the CSI driver used to create the - physical snapshot on the underlying storage system. This MUST be the - same as the name returned by the CSI GetPluginName() call for that - driver. Required. - type: string - source: - description: source specifies from where a snapshot will be created. - This field is immutable after creation. Required. - properties: - snapshotHandle: - description: snapshotHandle specifies the CSI "snapshot_id" of a - pre-existing snapshot on the underlying storage system. This field - is immutable. - type: string - volumeHandle: - description: volumeHandle specifies the CSI "volume_id" of the volume - from which a snapshot should be dynamically taken from. This field - is immutable. - type: string - type: object - volumeSnapshotClassName: - description: name of the VolumeSnapshotClass to which this snapshot - belongs. - type: string - volumeSnapshotRef: - description: volumeSnapshotRef specifies the VolumeSnapshot object to - which this VolumeSnapshotContent object is bound. VolumeSnapshot.Spec.VolumeSnapshotContentName - field must reference to this VolumeSnapshotContent's name for the - bidirectional binding to be valid. For a pre-existing VolumeSnapshotContent - object, name and namespace of the VolumeSnapshot object MUST be provided - for binding to happen. This field is immutable after creation. Required. - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object instead of an - entire object, this string should contain a valid JSON/Go field - access statement, such as desiredState.manifest.containers[2]. - For example, if the object reference is to a container within - a pod, this would take on a value like: "spec.containers{name}" - (where "name" refers to the name of the container that triggered - the event) or if no container name is specified "spec.containers[2]" - (container with index 2 in this pod). This syntax is chosen only - to have some well-defined way of referencing a part of an object. - TODO: this design is not final and this field is subject to change - in the future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this reference is - made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - required: - - deletionPolicy - - driver - - source - - volumeSnapshotRef - type: object - status: - description: status represents the current information of a snapshot. - properties: - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot - is taken by the underlying storage system. In dynamic snapshot creation - case, this field will be filled in with the "creation_time" value - returned from CSI "CreateSnapshotRequest" gRPC call. For a pre-existing - snapshot, this field will be filled with the "creation_time" value - returned from the CSI "ListSnapshots" gRPC call if the driver supports - it. If not specified, it indicates the creation time is unknown. The - format of this field is a Unix nanoseconds time encoded as an int64. - On Unix, the command `date +%s%N` returns the current time in nanoseconds - since 1970-01-01 00:00:00 UTC. - format: int64 - type: integer - error: - description: error is the latest observed error during snapshot creation, - if any. - properties: - message: - description: 'message is a string detailing the encountered error - during snapshot creation if specified. NOTE: message may be logged, - and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used - to restore a volume. In dynamic snapshot creation case, this field - will be filled in with the "ready_to_use" value returned from CSI - "CreateSnapshotRequest" gRPC call. For a pre-existing snapshot, this - field will be filled with the "ready_to_use" value returned from the - CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, - this field will be set to "True". If not specified, it means the readiness - of a snapshot is unknown. - type: boolean - restoreSize: - description: restoreSize represents the complete size of the snapshot - in bytes. In dynamic snapshot creation case, this field will be filled - in with the "size_bytes" value returned from CSI "CreateSnapshotRequest" - gRPC call. For a pre-existing snapshot, this field will be filled - with the "size_bytes" value returned from the CSI "ListSnapshots" - gRPC call if the driver supports it. When restoring a volume from - this snapshot, the size of the volume MUST NOT be smaller than the - restoreSize if it is specified, otherwise the restoration will fail. - If not specified, it indicates that the size is unknown. - format: int64 - minimum: 0 - type: integer - snapshotHandle: - description: snapshotHandle is the CSI "snapshot_id" of a snapshot on - the underlying storage system. If not specified, it indicates that - dynamic snapshot creation has either failed or it is still in progress. - type: string - type: object - required: - - spec - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshots CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.2.5 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/260" - creationTimestamp: null - name: volumesnapshots.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .status.readyToUse - description: Indicates if a snapshot is ready to be used to restore a volume. - name: ReadyToUse - type: boolean - - JSONPath: .spec.source.persistentVolumeClaimName - description: Name of the source PVC from where a dynamically taken snapshot will - be created. - name: SourcePVC - type: string - - JSONPath: .spec.source.volumeSnapshotContentName - description: Name of the VolumeSnapshotContent which represents a pre-provisioned - snapshot. - name: SourceSnapshotContent - type: string - - JSONPath: .status.restoreSize - description: Represents the complete size of the snapshot. - name: RestoreSize - type: string - - JSONPath: .spec.volumeSnapshotClassName - description: The name of the VolumeSnapshotClass requested by the VolumeSnapshot. - name: SnapshotClass - type: string - - JSONPath: .status.boundVolumeSnapshotContentName - description: The name of the VolumeSnapshotContent to which this VolumeSnapshot - is bound. - name: SnapshotContent - type: string - - JSONPath: .status.creationTime - description: Timestamp when the point-in-time snapshot is taken by the underlying - storage system. - name: CreationTime - type: date - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshot - listKind: VolumeSnapshotList - plural: volumesnapshots - singular: volumesnapshot - preserveUnknownFields: false - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - description: VolumeSnapshot is a user's request for either creating a point-in-time - snapshot of a persistent volume, or binding to a pre-existing snapshot. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: 'spec defines the desired characteristics of a snapshot requested - by a user. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshots#volumesnapshots - Required.' - properties: - source: - description: source specifies where a snapshot will be created from. - This field is immutable after creation. Required. - properties: - persistentVolumeClaimName: - description: persistentVolumeClaimName specifies the name of the - PersistentVolumeClaim object in the same namespace as the VolumeSnapshot - object where the snapshot should be dynamically taken from. This - field is immutable. - type: string - volumeSnapshotContentName: - description: volumeSnapshotContentName specifies the name of a pre-existing - VolumeSnapshotContent object. This field is immutable. - type: string - type: object - volumeSnapshotClassName: - description: 'volumeSnapshotClassName is the name of the VolumeSnapshotClass - requested by the VolumeSnapshot. If not specified, the default snapshot - class will be used if one exists. If not specified, and there is no - default snapshot class, dynamic snapshot creation will fail. Empty - string is not allowed for this field. TODO(xiangqian): a webhook validation - on empty string. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshot-classes' - type: string - required: - - source - type: object - status: - description: 'status represents the current information of a snapshot. NOTE: - status can be modified by sources other than system controllers, and must - not be depended upon for accuracy. Controllers should only use information - from the VolumeSnapshotContent object after verifying that the binding - is accurate and complete.' - properties: - boundVolumeSnapshotContentName: - description: 'boundVolumeSnapshotContentName represents the name of - the VolumeSnapshotContent object to which the VolumeSnapshot object - is bound. If not specified, it indicates that the VolumeSnapshot object - has not been successfully bound to a VolumeSnapshotContent object - yet. NOTE: Specified boundVolumeSnapshotContentName alone does not - mean binding is valid. Controllers MUST always verify bidirectional - binding between VolumeSnapshot and VolumeSnapshotContent to - avoid possible security issues.' - type: string - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot - is taken by the underlying storage system. In dynamic snapshot creation - case, this field will be filled in with the "creation_time" value - returned from CSI "CreateSnapshotRequest" gRPC call. For a pre-existing - snapshot, this field will be filled with the "creation_time" value - returned from the CSI "ListSnapshots" gRPC call if the driver supports - it. If not specified, it indicates that the creation time of the snapshot - is unknown. - format: date-time - type: string - error: - description: error is the last observed error during snapshot creation, - if any. This field could be helpful to upper level controllers(i.e., - application controller) to decide whether they should continue on - waiting for the snapshot to be created based on the type of error - reported. - properties: - message: - description: 'message is a string detailing the encountered error - during snapshot creation if specified. NOTE: message may be logged, - and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used - to restore a volume. In dynamic snapshot creation case, this field - will be filled in with the "ready_to_use" value returned from CSI - "CreateSnapshotRequest" gRPC call. For a pre-existing snapshot, this - field will be filled with the "ready_to_use" value returned from the - CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, - this field will be set to "True". If not specified, it means the readiness - of a snapshot is unknown. - type: boolean - restoreSize: - anyOf: - - type: integer - - type: string - description: restoreSize represents the complete size of the snapshot - in bytes. In dynamic snapshot creation case, this field will be filled - in with the "size_bytes" value returned from CSI "CreateSnapshotRequest" - gRPC call. For a pre-existing snapshot, this field will be filled - with the "size_bytes" value returned from the CSI "ListSnapshots" - gRPC call if the driver supports it. When restoring a volume from - this snapshot, the size of the volume MUST NOT be smaller than the - restoreSize if it is specified, otherwise the restoration will fail. - If not specified, it indicates that the size is unknown. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - required: - - spec - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: snapshot.storage.k8s.io/v1beta1 -kind: VolumeSnapshotClass -metadata: - name: scw-snapshot -driver: csi.scaleway.com -deletionPolicy: Delete ---- -kind: Deployment -apiVersion: apps/v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system -spec: - selector: - matchLabels: - app: scaleway-csi-controller - replicas: 1 - template: - metadata: - labels: - app: scaleway-csi-controller - spec: - priorityClassName: system-cluster-critical - serviceAccount: scaleway-csi-controller - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.1.0 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--mode=controller" - env: - - name: CSI_ENDPOINT - value: unix:///var/lib/csi/sockets/pluginproxy/csi.sock - envFrom: - - secretRef: - name: scaleway-secret - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - - name: csi-provisioner - image: quay.io/k8scsi/csi-provisioner:v1.5.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--enable-leader-election" - - "--leader-election-type=leases" - - "--feature-gates=Topology=true" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-attacher - image: quay.io/k8scsi/csi-attacher:v2.1.1 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-snapshotter - image: quay.io/k8scsi/csi-snapshotter:v2.0.1 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: snapshot-controller - image: quay.io/k8scsi/snapshot-controller:v2.0.1 - args: - - "--v=5" - - "--leader-election" - - name: liveness-probe - image: quay.io/k8scsi/livenessprobe:v2.0.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /csi - volumes: - - name: socket-dir - emptyDir: {} ---- -kind: ServiceAccount -apiVersion: v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-provisioner -rules: - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "create", "delete"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-controller -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-provisioner - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "update", "patch"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["volumeattachments"] - verbs: ["get", "list", "watch", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-attacher - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["create", "get", "list", "watch", "update", "delete"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots/status"] - verbs: ["update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents/status"] - verbs: ["update"] - - apiGroups: ["apiextensions.k8s.io"] - resources: ["customresourcedefinitions"] - verbs: ["create", "list", "watch", "delete", "get", "update"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-snapshotter - apiGroup: rbac.authorization.k8s.io diff --git a/deploy/kubernetes/scaleway-csi-v0.1.1.yaml b/deploy/kubernetes/scaleway-csi-v0.1.1.yaml deleted file mode 100644 index 5909493..0000000 --- a/deploy/kubernetes/scaleway-csi-v0.1.1.yaml +++ /dev/null @@ -1,899 +0,0 @@ -apiVersion: storage.k8s.io/v1beta1 -kind: CSIDriver -metadata: - name: csi.scaleway.com -spec: - attachRequired: true - podInfoOnMount: true ---- -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: scw-bssd - namespace: kube-system -provisioner: csi.scaleway.com -reclaimPolicy: Delete ---- -kind: DaemonSet -apiVersion: apps/v1 -metadata: - name: scaleway-csi-node - namespace: kube-system - labels: -spec: - selector: - matchLabels: - app: scaleway-csi-node - template: - metadata: - labels: - app: scaleway-csi-node - role: csi - spec: - serviceAccount: scaleway-csi-node - nodeSelector: - kubernetes.io/os: linux - priorityClassName: system-node-critical - hostNetwork: true - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.1.1 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--v=4" - - "--mode=node" - env: - - name: CSI_ENDPOINT - value: unix:///csi/csi.sock - securityContext: - privileged: true - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - volumeMounts: - - name: plugin-dir - mountPath: /csi - - name: kubelet-dir - mountPath: /var/lib/kubelet - mountPropagation: "Bidirectional" - - name: device-dir - mountPath: /dev - - name: csi-node-driver-registrar - image: quay.io/k8scsi/csi-node-driver-registrar:v1.2.0 - args: - - "--v=2" - - "--csi-address=$(CSI_ADDRESS)" - - "--kubelet-registration-path=$(KUBELET_REGISTRATION_PATH)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - - name: KUBELET_REGISTRATION_PATH - value: /var/lib/kubelet/plugins/csi.scaleway.com/csi.sock - - name: KUBE_NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - volumeMounts: - - name: plugin-dir - mountPath: /csi/ - - name: registration-dir - mountPath: /registration/ - - name: liveness-probe - image: quay.io/k8scsi/livenessprobe:v2.0.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: plugin-dir - mountPath: /csi - volumes: - - name: registration-dir - hostPath: - path: /var/lib/kubelet/plugins_registry/ - type: DirectoryOrCreate - - name: plugin-dir - hostPath: - path: /var/lib/kubelet/plugins/csi.scaleway.com - type: DirectoryOrCreate - - name: kubelet-dir - hostPath: - path: /var/lib/kubelet - type: Directory - - name: device-dir - hostPath: - path: /dev ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: scaleway-csi-node - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar - namespace: kube-system -rules: - - apiGroups: [""] - resources: ["events"] - verbs: ["get", "list", "watch", "create", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar -subjects: - - kind: ServiceAccount - name: scaleway-csi-node - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-node-driver-registrar - apiGroup: rbac.authorization.k8s.io ---- -## volumesnapshotclasses CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.2.5 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/260" - creationTimestamp: null - name: volumesnapshotclasses.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .driver - name: Driver - type: string - - JSONPath: .deletionPolicy - description: Determines whether a VolumeSnapshotContent created through the VolumeSnapshotClass - should be deleted when its bound VolumeSnapshot is deleted. - name: DeletionPolicy - type: string - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotClass - listKind: VolumeSnapshotClassList - plural: volumesnapshotclasses - singular: volumesnapshotclass - preserveUnknownFields: false - scope: Cluster - subresources: {} - validation: - openAPIV3Schema: - description: VolumeSnapshotClass specifies parameters that a underlying storage - system uses when creating a volume snapshot. A specific VolumeSnapshotClass - is used by specifying its name in a VolumeSnapshot object. VolumeSnapshotClasses - are non-namespaced - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - deletionPolicy: - description: deletionPolicy determines whether a VolumeSnapshotContent created - through the VolumeSnapshotClass should be deleted when its bound VolumeSnapshot - is deleted. Supported values are "Retain" and "Delete". "Retain" means - that the VolumeSnapshotContent and its physical snapshot on underlying - storage system are kept. "Delete" means that the VolumeSnapshotContent - and its physical snapshot on underlying storage system are deleted. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the storage driver that handles this - VolumeSnapshotClass. Required. - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - parameters: - additionalProperties: - type: string - description: parameters is a key-value map with storage driver specific - parameters for creating snapshots. These values are opaque to Kubernetes. - type: object - required: - - deletionPolicy - - driver - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshotcontents CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.2.5 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/260" - creationTimestamp: null - name: volumesnapshotcontents.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .status.readyToUse - description: Indicates if a snapshot is ready to be used to restore a volume. - name: ReadyToUse - type: boolean - - JSONPath: .status.restoreSize - description: Represents the complete size of the snapshot in bytes - name: RestoreSize - type: integer - - JSONPath: .spec.deletionPolicy - description: Determines whether this VolumeSnapshotContent and its physical snapshot - on the underlying storage system should be deleted when its bound VolumeSnapshot - is deleted. - name: DeletionPolicy - type: string - - JSONPath: .spec.driver - description: Name of the CSI driver used to create the physical snapshot on the - underlying storage system. - name: Driver - type: string - - JSONPath: .spec.volumeSnapshotClassName - description: Name of the VolumeSnapshotClass to which this snapshot belongs. - name: VolumeSnapshotClass - type: string - - JSONPath: .spec.volumeSnapshotRef.name - description: Name of the VolumeSnapshot object to which this VolumeSnapshotContent - object is bound. - name: VolumeSnapshot - type: string - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotContent - listKind: VolumeSnapshotContentList - plural: volumesnapshotcontents - singular: volumesnapshotcontent - preserveUnknownFields: false - scope: Cluster - subresources: - status: {} - validation: - openAPIV3Schema: - description: VolumeSnapshotContent represents the actual "on-disk" snapshot - object in the underlying storage system - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: spec defines properties of a VolumeSnapshotContent created - by the underlying storage system. Required. - properties: - deletionPolicy: - description: deletionPolicy determines whether this VolumeSnapshotContent - and its physical snapshot on the underlying storage system should - be deleted when its bound VolumeSnapshot is deleted. Supported values - are "Retain" and "Delete". "Retain" means that the VolumeSnapshotContent - and its physical snapshot on underlying storage system are kept. "Delete" - means that the VolumeSnapshotContent and its physical snapshot on - underlying storage system are deleted. In dynamic snapshot creation - case, this field will be filled in with the "DeletionPolicy" field - defined in the VolumeSnapshotClass the VolumeSnapshot refers to. For - pre-existing snapshots, users MUST specify this field when creating - the VolumeSnapshotContent object. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the CSI driver used to create the - physical snapshot on the underlying storage system. This MUST be the - same as the name returned by the CSI GetPluginName() call for that - driver. Required. - type: string - source: - description: source specifies from where a snapshot will be created. - This field is immutable after creation. Required. - properties: - snapshotHandle: - description: snapshotHandle specifies the CSI "snapshot_id" of a - pre-existing snapshot on the underlying storage system. This field - is immutable. - type: string - volumeHandle: - description: volumeHandle specifies the CSI "volume_id" of the volume - from which a snapshot should be dynamically taken from. This field - is immutable. - type: string - type: object - volumeSnapshotClassName: - description: name of the VolumeSnapshotClass to which this snapshot - belongs. - type: string - volumeSnapshotRef: - description: volumeSnapshotRef specifies the VolumeSnapshot object to - which this VolumeSnapshotContent object is bound. VolumeSnapshot.Spec.VolumeSnapshotContentName - field must reference to this VolumeSnapshotContent's name for the - bidirectional binding to be valid. For a pre-existing VolumeSnapshotContent - object, name and namespace of the VolumeSnapshot object MUST be provided - for binding to happen. This field is immutable after creation. Required. - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object instead of an - entire object, this string should contain a valid JSON/Go field - access statement, such as desiredState.manifest.containers[2]. - For example, if the object reference is to a container within - a pod, this would take on a value like: "spec.containers{name}" - (where "name" refers to the name of the container that triggered - the event) or if no container name is specified "spec.containers[2]" - (container with index 2 in this pod). This syntax is chosen only - to have some well-defined way of referencing a part of an object. - TODO: this design is not final and this field is subject to change - in the future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this reference is - made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - required: - - deletionPolicy - - driver - - source - - volumeSnapshotRef - type: object - status: - description: status represents the current information of a snapshot. - properties: - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot - is taken by the underlying storage system. In dynamic snapshot creation - case, this field will be filled in with the "creation_time" value - returned from CSI "CreateSnapshotRequest" gRPC call. For a pre-existing - snapshot, this field will be filled with the "creation_time" value - returned from the CSI "ListSnapshots" gRPC call if the driver supports - it. If not specified, it indicates the creation time is unknown. The - format of this field is a Unix nanoseconds time encoded as an int64. - On Unix, the command `date +%s%N` returns the current time in nanoseconds - since 1970-01-01 00:00:00 UTC. - format: int64 - type: integer - error: - description: error is the latest observed error during snapshot creation, - if any. - properties: - message: - description: 'message is a string detailing the encountered error - during snapshot creation if specified. NOTE: message may be logged, - and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used - to restore a volume. In dynamic snapshot creation case, this field - will be filled in with the "ready_to_use" value returned from CSI - "CreateSnapshotRequest" gRPC call. For a pre-existing snapshot, this - field will be filled with the "ready_to_use" value returned from the - CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, - this field will be set to "True". If not specified, it means the readiness - of a snapshot is unknown. - type: boolean - restoreSize: - description: restoreSize represents the complete size of the snapshot - in bytes. In dynamic snapshot creation case, this field will be filled - in with the "size_bytes" value returned from CSI "CreateSnapshotRequest" - gRPC call. For a pre-existing snapshot, this field will be filled - with the "size_bytes" value returned from the CSI "ListSnapshots" - gRPC call if the driver supports it. When restoring a volume from - this snapshot, the size of the volume MUST NOT be smaller than the - restoreSize if it is specified, otherwise the restoration will fail. - If not specified, it indicates that the size is unknown. - format: int64 - minimum: 0 - type: integer - snapshotHandle: - description: snapshotHandle is the CSI "snapshot_id" of a snapshot on - the underlying storage system. If not specified, it indicates that - dynamic snapshot creation has either failed or it is still in progress. - type: string - type: object - required: - - spec - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshots CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.2.5 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/260" - creationTimestamp: null - name: volumesnapshots.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .status.readyToUse - description: Indicates if a snapshot is ready to be used to restore a volume. - name: ReadyToUse - type: boolean - - JSONPath: .spec.source.persistentVolumeClaimName - description: Name of the source PVC from where a dynamically taken snapshot will - be created. - name: SourcePVC - type: string - - JSONPath: .spec.source.volumeSnapshotContentName - description: Name of the VolumeSnapshotContent which represents a pre-provisioned - snapshot. - name: SourceSnapshotContent - type: string - - JSONPath: .status.restoreSize - description: Represents the complete size of the snapshot. - name: RestoreSize - type: string - - JSONPath: .spec.volumeSnapshotClassName - description: The name of the VolumeSnapshotClass requested by the VolumeSnapshot. - name: SnapshotClass - type: string - - JSONPath: .status.boundVolumeSnapshotContentName - description: The name of the VolumeSnapshotContent to which this VolumeSnapshot - is bound. - name: SnapshotContent - type: string - - JSONPath: .status.creationTime - description: Timestamp when the point-in-time snapshot is taken by the underlying - storage system. - name: CreationTime - type: date - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshot - listKind: VolumeSnapshotList - plural: volumesnapshots - singular: volumesnapshot - preserveUnknownFields: false - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - description: VolumeSnapshot is a user's request for either creating a point-in-time - snapshot of a persistent volume, or binding to a pre-existing snapshot. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: 'spec defines the desired characteristics of a snapshot requested - by a user. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshots#volumesnapshots - Required.' - properties: - source: - description: source specifies where a snapshot will be created from. - This field is immutable after creation. Required. - properties: - persistentVolumeClaimName: - description: persistentVolumeClaimName specifies the name of the - PersistentVolumeClaim object in the same namespace as the VolumeSnapshot - object where the snapshot should be dynamically taken from. This - field is immutable. - type: string - volumeSnapshotContentName: - description: volumeSnapshotContentName specifies the name of a pre-existing - VolumeSnapshotContent object. This field is immutable. - type: string - type: object - volumeSnapshotClassName: - description: 'volumeSnapshotClassName is the name of the VolumeSnapshotClass - requested by the VolumeSnapshot. If not specified, the default snapshot - class will be used if one exists. If not specified, and there is no - default snapshot class, dynamic snapshot creation will fail. Empty - string is not allowed for this field. TODO(xiangqian): a webhook validation - on empty string. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshot-classes' - type: string - required: - - source - type: object - status: - description: 'status represents the current information of a snapshot. NOTE: - status can be modified by sources other than system controllers, and must - not be depended upon for accuracy. Controllers should only use information - from the VolumeSnapshotContent object after verifying that the binding - is accurate and complete.' - properties: - boundVolumeSnapshotContentName: - description: 'boundVolumeSnapshotContentName represents the name of - the VolumeSnapshotContent object to which the VolumeSnapshot object - is bound. If not specified, it indicates that the VolumeSnapshot object - has not been successfully bound to a VolumeSnapshotContent object - yet. NOTE: Specified boundVolumeSnapshotContentName alone does not - mean binding is valid. Controllers MUST always verify bidirectional - binding between VolumeSnapshot and VolumeSnapshotContent to - avoid possible security issues.' - type: string - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot - is taken by the underlying storage system. In dynamic snapshot creation - case, this field will be filled in with the "creation_time" value - returned from CSI "CreateSnapshotRequest" gRPC call. For a pre-existing - snapshot, this field will be filled with the "creation_time" value - returned from the CSI "ListSnapshots" gRPC call if the driver supports - it. If not specified, it indicates that the creation time of the snapshot - is unknown. - format: date-time - type: string - error: - description: error is the last observed error during snapshot creation, - if any. This field could be helpful to upper level controllers(i.e., - application controller) to decide whether they should continue on - waiting for the snapshot to be created based on the type of error - reported. - properties: - message: - description: 'message is a string detailing the encountered error - during snapshot creation if specified. NOTE: message may be logged, - and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used - to restore a volume. In dynamic snapshot creation case, this field - will be filled in with the "ready_to_use" value returned from CSI - "CreateSnapshotRequest" gRPC call. For a pre-existing snapshot, this - field will be filled with the "ready_to_use" value returned from the - CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, - this field will be set to "True". If not specified, it means the readiness - of a snapshot is unknown. - type: boolean - restoreSize: - anyOf: - - type: integer - - type: string - description: restoreSize represents the complete size of the snapshot - in bytes. In dynamic snapshot creation case, this field will be filled - in with the "size_bytes" value returned from CSI "CreateSnapshotRequest" - gRPC call. For a pre-existing snapshot, this field will be filled - with the "size_bytes" value returned from the CSI "ListSnapshots" - gRPC call if the driver supports it. When restoring a volume from - this snapshot, the size of the volume MUST NOT be smaller than the - restoreSize if it is specified, otherwise the restoration will fail. - If not specified, it indicates that the size is unknown. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - required: - - spec - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: snapshot.storage.k8s.io/v1beta1 -kind: VolumeSnapshotClass -metadata: - name: scw-snapshot -driver: csi.scaleway.com -deletionPolicy: Delete ---- -kind: Deployment -apiVersion: apps/v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system -spec: - selector: - matchLabels: - app: scaleway-csi-controller - replicas: 1 - template: - metadata: - labels: - app: scaleway-csi-controller - spec: - priorityClassName: system-cluster-critical - serviceAccount: scaleway-csi-controller - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.1.1 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--mode=controller" - env: - - name: CSI_ENDPOINT - value: unix:///var/lib/csi/sockets/pluginproxy/csi.sock - envFrom: - - secretRef: - name: scaleway-secret - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - - name: csi-provisioner - image: quay.io/k8scsi/csi-provisioner:v1.5.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--enable-leader-election" - - "--leader-election-type=leases" - - "--feature-gates=Topology=true" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-attacher - image: quay.io/k8scsi/csi-attacher:v2.1.1 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-snapshotter - image: quay.io/k8scsi/csi-snapshotter:v2.0.1 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: snapshot-controller - image: quay.io/k8scsi/snapshot-controller:v2.0.1 - args: - - "--v=5" - - "--leader-election" - - name: liveness-probe - image: quay.io/k8scsi/livenessprobe:v2.0.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /csi - volumes: - - name: socket-dir - emptyDir: {} ---- -kind: ServiceAccount -apiVersion: v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-provisioner -rules: - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "create", "delete"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-controller -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-provisioner - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "update", "patch"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["volumeattachments"] - verbs: ["get", "list", "watch", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-attacher - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["create", "get", "list", "watch", "update", "delete"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots/status"] - verbs: ["update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents/status"] - verbs: ["update"] - - apiGroups: ["apiextensions.k8s.io"] - resources: ["customresourcedefinitions"] - verbs: ["create", "list", "watch", "delete", "get", "update"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-snapshotter - apiGroup: rbac.authorization.k8s.io diff --git a/deploy/kubernetes/scaleway-csi-v0.1.2.yaml b/deploy/kubernetes/scaleway-csi-v0.1.2.yaml deleted file mode 100644 index b567bc4..0000000 --- a/deploy/kubernetes/scaleway-csi-v0.1.2.yaml +++ /dev/null @@ -1,899 +0,0 @@ -apiVersion: storage.k8s.io/v1beta1 -kind: CSIDriver -metadata: - name: csi.scaleway.com -spec: - attachRequired: true - podInfoOnMount: true ---- -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: scw-bssd - namespace: kube-system -provisioner: csi.scaleway.com -reclaimPolicy: Delete ---- -kind: DaemonSet -apiVersion: apps/v1 -metadata: - name: scaleway-csi-node - namespace: kube-system - labels: -spec: - selector: - matchLabels: - app: scaleway-csi-node - template: - metadata: - labels: - app: scaleway-csi-node - role: csi - spec: - serviceAccount: scaleway-csi-node - nodeSelector: - kubernetes.io/os: linux - priorityClassName: system-node-critical - hostNetwork: true - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.1.2 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--v=4" - - "--mode=node" - env: - - name: CSI_ENDPOINT - value: unix:///csi/csi.sock - securityContext: - privileged: true - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - volumeMounts: - - name: plugin-dir - mountPath: /csi - - name: kubelet-dir - mountPath: /var/lib/kubelet - mountPropagation: "Bidirectional" - - name: device-dir - mountPath: /dev - - name: csi-node-driver-registrar - image: quay.io/k8scsi/csi-node-driver-registrar:v1.3.0 - args: - - "--v=2" - - "--csi-address=$(CSI_ADDRESS)" - - "--kubelet-registration-path=$(KUBELET_REGISTRATION_PATH)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - - name: KUBELET_REGISTRATION_PATH - value: /var/lib/kubelet/plugins/csi.scaleway.com/csi.sock - - name: KUBE_NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - volumeMounts: - - name: plugin-dir - mountPath: /csi/ - - name: registration-dir - mountPath: /registration/ - - name: liveness-probe - image: quay.io/k8scsi/livenessprobe:v2.0.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: plugin-dir - mountPath: /csi - volumes: - - name: registration-dir - hostPath: - path: /var/lib/kubelet/plugins_registry/ - type: DirectoryOrCreate - - name: plugin-dir - hostPath: - path: /var/lib/kubelet/plugins/csi.scaleway.com - type: DirectoryOrCreate - - name: kubelet-dir - hostPath: - path: /var/lib/kubelet - type: Directory - - name: device-dir - hostPath: - path: /dev ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: scaleway-csi-node - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar - namespace: kube-system -rules: - - apiGroups: [""] - resources: ["events"] - verbs: ["get", "list", "watch", "create", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar -subjects: - - kind: ServiceAccount - name: scaleway-csi-node - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-node-driver-registrar - apiGroup: rbac.authorization.k8s.io ---- -## volumesnapshotclasses CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.2.5 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/260" - creationTimestamp: null - name: volumesnapshotclasses.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .driver - name: Driver - type: string - - JSONPath: .deletionPolicy - description: Determines whether a VolumeSnapshotContent created through the VolumeSnapshotClass - should be deleted when its bound VolumeSnapshot is deleted. - name: DeletionPolicy - type: string - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotClass - listKind: VolumeSnapshotClassList - plural: volumesnapshotclasses - singular: volumesnapshotclass - preserveUnknownFields: false - scope: Cluster - subresources: {} - validation: - openAPIV3Schema: - description: VolumeSnapshotClass specifies parameters that a underlying storage - system uses when creating a volume snapshot. A specific VolumeSnapshotClass - is used by specifying its name in a VolumeSnapshot object. VolumeSnapshotClasses - are non-namespaced - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - deletionPolicy: - description: deletionPolicy determines whether a VolumeSnapshotContent created - through the VolumeSnapshotClass should be deleted when its bound VolumeSnapshot - is deleted. Supported values are "Retain" and "Delete". "Retain" means - that the VolumeSnapshotContent and its physical snapshot on underlying - storage system are kept. "Delete" means that the VolumeSnapshotContent - and its physical snapshot on underlying storage system are deleted. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the storage driver that handles this - VolumeSnapshotClass. Required. - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - parameters: - additionalProperties: - type: string - description: parameters is a key-value map with storage driver specific - parameters for creating snapshots. These values are opaque to Kubernetes. - type: object - required: - - deletionPolicy - - driver - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshotcontents CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.2.5 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/260" - creationTimestamp: null - name: volumesnapshotcontents.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .status.readyToUse - description: Indicates if a snapshot is ready to be used to restore a volume. - name: ReadyToUse - type: boolean - - JSONPath: .status.restoreSize - description: Represents the complete size of the snapshot in bytes - name: RestoreSize - type: integer - - JSONPath: .spec.deletionPolicy - description: Determines whether this VolumeSnapshotContent and its physical snapshot - on the underlying storage system should be deleted when its bound VolumeSnapshot - is deleted. - name: DeletionPolicy - type: string - - JSONPath: .spec.driver - description: Name of the CSI driver used to create the physical snapshot on the - underlying storage system. - name: Driver - type: string - - JSONPath: .spec.volumeSnapshotClassName - description: Name of the VolumeSnapshotClass to which this snapshot belongs. - name: VolumeSnapshotClass - type: string - - JSONPath: .spec.volumeSnapshotRef.name - description: Name of the VolumeSnapshot object to which this VolumeSnapshotContent - object is bound. - name: VolumeSnapshot - type: string - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotContent - listKind: VolumeSnapshotContentList - plural: volumesnapshotcontents - singular: volumesnapshotcontent - preserveUnknownFields: false - scope: Cluster - subresources: - status: {} - validation: - openAPIV3Schema: - description: VolumeSnapshotContent represents the actual "on-disk" snapshot - object in the underlying storage system - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: spec defines properties of a VolumeSnapshotContent created - by the underlying storage system. Required. - properties: - deletionPolicy: - description: deletionPolicy determines whether this VolumeSnapshotContent - and its physical snapshot on the underlying storage system should - be deleted when its bound VolumeSnapshot is deleted. Supported values - are "Retain" and "Delete". "Retain" means that the VolumeSnapshotContent - and its physical snapshot on underlying storage system are kept. "Delete" - means that the VolumeSnapshotContent and its physical snapshot on - underlying storage system are deleted. In dynamic snapshot creation - case, this field will be filled in with the "DeletionPolicy" field - defined in the VolumeSnapshotClass the VolumeSnapshot refers to. For - pre-existing snapshots, users MUST specify this field when creating - the VolumeSnapshotContent object. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the CSI driver used to create the - physical snapshot on the underlying storage system. This MUST be the - same as the name returned by the CSI GetPluginName() call for that - driver. Required. - type: string - source: - description: source specifies from where a snapshot will be created. - This field is immutable after creation. Required. - properties: - snapshotHandle: - description: snapshotHandle specifies the CSI "snapshot_id" of a - pre-existing snapshot on the underlying storage system. This field - is immutable. - type: string - volumeHandle: - description: volumeHandle specifies the CSI "volume_id" of the volume - from which a snapshot should be dynamically taken from. This field - is immutable. - type: string - type: object - volumeSnapshotClassName: - description: name of the VolumeSnapshotClass to which this snapshot - belongs. - type: string - volumeSnapshotRef: - description: volumeSnapshotRef specifies the VolumeSnapshot object to - which this VolumeSnapshotContent object is bound. VolumeSnapshot.Spec.VolumeSnapshotContentName - field must reference to this VolumeSnapshotContent's name for the - bidirectional binding to be valid. For a pre-existing VolumeSnapshotContent - object, name and namespace of the VolumeSnapshot object MUST be provided - for binding to happen. This field is immutable after creation. Required. - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object instead of an - entire object, this string should contain a valid JSON/Go field - access statement, such as desiredState.manifest.containers[2]. - For example, if the object reference is to a container within - a pod, this would take on a value like: "spec.containers{name}" - (where "name" refers to the name of the container that triggered - the event) or if no container name is specified "spec.containers[2]" - (container with index 2 in this pod). This syntax is chosen only - to have some well-defined way of referencing a part of an object. - TODO: this design is not final and this field is subject to change - in the future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this reference is - made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - required: - - deletionPolicy - - driver - - source - - volumeSnapshotRef - type: object - status: - description: status represents the current information of a snapshot. - properties: - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot - is taken by the underlying storage system. In dynamic snapshot creation - case, this field will be filled in with the "creation_time" value - returned from CSI "CreateSnapshotRequest" gRPC call. For a pre-existing - snapshot, this field will be filled with the "creation_time" value - returned from the CSI "ListSnapshots" gRPC call if the driver supports - it. If not specified, it indicates the creation time is unknown. The - format of this field is a Unix nanoseconds time encoded as an int64. - On Unix, the command `date +%s%N` returns the current time in nanoseconds - since 1970-01-01 00:00:00 UTC. - format: int64 - type: integer - error: - description: error is the latest observed error during snapshot creation, - if any. - properties: - message: - description: 'message is a string detailing the encountered error - during snapshot creation if specified. NOTE: message may be logged, - and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used - to restore a volume. In dynamic snapshot creation case, this field - will be filled in with the "ready_to_use" value returned from CSI - "CreateSnapshotRequest" gRPC call. For a pre-existing snapshot, this - field will be filled with the "ready_to_use" value returned from the - CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, - this field will be set to "True". If not specified, it means the readiness - of a snapshot is unknown. - type: boolean - restoreSize: - description: restoreSize represents the complete size of the snapshot - in bytes. In dynamic snapshot creation case, this field will be filled - in with the "size_bytes" value returned from CSI "CreateSnapshotRequest" - gRPC call. For a pre-existing snapshot, this field will be filled - with the "size_bytes" value returned from the CSI "ListSnapshots" - gRPC call if the driver supports it. When restoring a volume from - this snapshot, the size of the volume MUST NOT be smaller than the - restoreSize if it is specified, otherwise the restoration will fail. - If not specified, it indicates that the size is unknown. - format: int64 - minimum: 0 - type: integer - snapshotHandle: - description: snapshotHandle is the CSI "snapshot_id" of a snapshot on - the underlying storage system. If not specified, it indicates that - dynamic snapshot creation has either failed or it is still in progress. - type: string - type: object - required: - - spec - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshots CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.2.5 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/260" - creationTimestamp: null - name: volumesnapshots.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .status.readyToUse - description: Indicates if a snapshot is ready to be used to restore a volume. - name: ReadyToUse - type: boolean - - JSONPath: .spec.source.persistentVolumeClaimName - description: Name of the source PVC from where a dynamically taken snapshot will - be created. - name: SourcePVC - type: string - - JSONPath: .spec.source.volumeSnapshotContentName - description: Name of the VolumeSnapshotContent which represents a pre-provisioned - snapshot. - name: SourceSnapshotContent - type: string - - JSONPath: .status.restoreSize - description: Represents the complete size of the snapshot. - name: RestoreSize - type: string - - JSONPath: .spec.volumeSnapshotClassName - description: The name of the VolumeSnapshotClass requested by the VolumeSnapshot. - name: SnapshotClass - type: string - - JSONPath: .status.boundVolumeSnapshotContentName - description: The name of the VolumeSnapshotContent to which this VolumeSnapshot - is bound. - name: SnapshotContent - type: string - - JSONPath: .status.creationTime - description: Timestamp when the point-in-time snapshot is taken by the underlying - storage system. - name: CreationTime - type: date - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshot - listKind: VolumeSnapshotList - plural: volumesnapshots - singular: volumesnapshot - preserveUnknownFields: false - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - description: VolumeSnapshot is a user's request for either creating a point-in-time - snapshot of a persistent volume, or binding to a pre-existing snapshot. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: 'spec defines the desired characteristics of a snapshot requested - by a user. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshots#volumesnapshots - Required.' - properties: - source: - description: source specifies where a snapshot will be created from. - This field is immutable after creation. Required. - properties: - persistentVolumeClaimName: - description: persistentVolumeClaimName specifies the name of the - PersistentVolumeClaim object in the same namespace as the VolumeSnapshot - object where the snapshot should be dynamically taken from. This - field is immutable. - type: string - volumeSnapshotContentName: - description: volumeSnapshotContentName specifies the name of a pre-existing - VolumeSnapshotContent object. This field is immutable. - type: string - type: object - volumeSnapshotClassName: - description: 'volumeSnapshotClassName is the name of the VolumeSnapshotClass - requested by the VolumeSnapshot. If not specified, the default snapshot - class will be used if one exists. If not specified, and there is no - default snapshot class, dynamic snapshot creation will fail. Empty - string is not allowed for this field. TODO(xiangqian): a webhook validation - on empty string. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshot-classes' - type: string - required: - - source - type: object - status: - description: 'status represents the current information of a snapshot. NOTE: - status can be modified by sources other than system controllers, and must - not be depended upon for accuracy. Controllers should only use information - from the VolumeSnapshotContent object after verifying that the binding - is accurate and complete.' - properties: - boundVolumeSnapshotContentName: - description: 'boundVolumeSnapshotContentName represents the name of - the VolumeSnapshotContent object to which the VolumeSnapshot object - is bound. If not specified, it indicates that the VolumeSnapshot object - has not been successfully bound to a VolumeSnapshotContent object - yet. NOTE: Specified boundVolumeSnapshotContentName alone does not - mean binding is valid. Controllers MUST always verify bidirectional - binding between VolumeSnapshot and VolumeSnapshotContent to - avoid possible security issues.' - type: string - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot - is taken by the underlying storage system. In dynamic snapshot creation - case, this field will be filled in with the "creation_time" value - returned from CSI "CreateSnapshotRequest" gRPC call. For a pre-existing - snapshot, this field will be filled with the "creation_time" value - returned from the CSI "ListSnapshots" gRPC call if the driver supports - it. If not specified, it indicates that the creation time of the snapshot - is unknown. - format: date-time - type: string - error: - description: error is the last observed error during snapshot creation, - if any. This field could be helpful to upper level controllers(i.e., - application controller) to decide whether they should continue on - waiting for the snapshot to be created based on the type of error - reported. - properties: - message: - description: 'message is a string detailing the encountered error - during snapshot creation if specified. NOTE: message may be logged, - and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used - to restore a volume. In dynamic snapshot creation case, this field - will be filled in with the "ready_to_use" value returned from CSI - "CreateSnapshotRequest" gRPC call. For a pre-existing snapshot, this - field will be filled with the "ready_to_use" value returned from the - CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, - this field will be set to "True". If not specified, it means the readiness - of a snapshot is unknown. - type: boolean - restoreSize: - anyOf: - - type: integer - - type: string - description: restoreSize represents the complete size of the snapshot - in bytes. In dynamic snapshot creation case, this field will be filled - in with the "size_bytes" value returned from CSI "CreateSnapshotRequest" - gRPC call. For a pre-existing snapshot, this field will be filled - with the "size_bytes" value returned from the CSI "ListSnapshots" - gRPC call if the driver supports it. When restoring a volume from - this snapshot, the size of the volume MUST NOT be smaller than the - restoreSize if it is specified, otherwise the restoration will fail. - If not specified, it indicates that the size is unknown. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - required: - - spec - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: snapshot.storage.k8s.io/v1beta1 -kind: VolumeSnapshotClass -metadata: - name: scw-snapshot -driver: csi.scaleway.com -deletionPolicy: Delete ---- -kind: Deployment -apiVersion: apps/v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system -spec: - selector: - matchLabels: - app: scaleway-csi-controller - replicas: 1 - template: - metadata: - labels: - app: scaleway-csi-controller - spec: - priorityClassName: system-cluster-critical - serviceAccount: scaleway-csi-controller - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.1.2 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--mode=controller" - env: - - name: CSI_ENDPOINT - value: unix:///var/lib/csi/sockets/pluginproxy/csi.sock - envFrom: - - secretRef: - name: scaleway-secret - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - - name: csi-provisioner - image: quay.io/k8scsi/csi-provisioner:v1.6.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--enable-leader-election" - - "--leader-election-type=leases" - - "--feature-gates=Topology=true" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-attacher - image: quay.io/k8scsi/csi-attacher:v2.2.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-snapshotter - image: quay.io/k8scsi/csi-snapshotter:v2.1.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: snapshot-controller - image: quay.io/k8scsi/snapshot-controller:v2.1.0 - args: - - "--v=5" - - "--leader-election" - - name: liveness-probe - image: quay.io/k8scsi/livenessprobe:v2.0.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /csi - volumes: - - name: socket-dir - emptyDir: {} ---- -kind: ServiceAccount -apiVersion: v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-provisioner -rules: - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "create", "delete"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-controller -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-provisioner - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "update", "patch"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["volumeattachments"] - verbs: ["get", "list", "watch", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-attacher - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["create", "get", "list", "watch", "update", "delete"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots/status"] - verbs: ["update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents/status"] - verbs: ["update"] - - apiGroups: ["apiextensions.k8s.io"] - resources: ["customresourcedefinitions"] - verbs: ["create", "list", "watch", "delete", "get", "update"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-snapshotter - apiGroup: rbac.authorization.k8s.io diff --git a/deploy/kubernetes/scaleway-csi-v0.1.3.yaml b/deploy/kubernetes/scaleway-csi-v0.1.3.yaml deleted file mode 100644 index 7b40985..0000000 --- a/deploy/kubernetes/scaleway-csi-v0.1.3.yaml +++ /dev/null @@ -1,946 +0,0 @@ -apiVersion: storage.k8s.io/v1beta1 -kind: CSIDriver -metadata: - name: csi.scaleway.com -spec: - attachRequired: true - podInfoOnMount: true ---- -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: scw-bssd - namespace: kube-system -provisioner: csi.scaleway.com -reclaimPolicy: Delete -allowVolumeExpansion: true ---- -kind: DaemonSet -apiVersion: apps/v1 -metadata: - name: scaleway-csi-node - namespace: kube-system - labels: -spec: - selector: - matchLabels: - app: scaleway-csi-node - template: - metadata: - labels: - app: scaleway-csi-node - role: csi - spec: - serviceAccount: scaleway-csi-node - nodeSelector: - kubernetes.io/os: linux - priorityClassName: system-node-critical - hostNetwork: true - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.1.3 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--v=4" - - "--mode=node" - env: - - name: CSI_ENDPOINT - value: unix:///csi/csi.sock - securityContext: - privileged: true - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - volumeMounts: - - name: plugin-dir - mountPath: /csi - - name: kubelet-dir - mountPath: /var/lib/kubelet - mountPropagation: "Bidirectional" - - name: device-dir - mountPath: /dev - - name: csi-node-driver-registrar - image: quay.io/k8scsi/csi-node-driver-registrar:v1.3.0 - args: - - "--v=2" - - "--csi-address=$(CSI_ADDRESS)" - - "--kubelet-registration-path=$(KUBELET_REGISTRATION_PATH)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - - name: KUBELET_REGISTRATION_PATH - value: /var/lib/kubelet/plugins/csi.scaleway.com/csi.sock - - name: KUBE_NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - volumeMounts: - - name: plugin-dir - mountPath: /csi/ - - name: registration-dir - mountPath: /registration/ - - name: liveness-probe - image: quay.io/k8scsi/livenessprobe:v2.0.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: plugin-dir - mountPath: /csi - volumes: - - name: registration-dir - hostPath: - path: /var/lib/kubelet/plugins_registry/ - type: DirectoryOrCreate - - name: plugin-dir - hostPath: - path: /var/lib/kubelet/plugins/csi.scaleway.com - type: DirectoryOrCreate - - name: kubelet-dir - hostPath: - path: /var/lib/kubelet - type: Directory - - name: device-dir - hostPath: - path: /dev ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: scaleway-csi-node - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar - namespace: kube-system -rules: - - apiGroups: [""] - resources: ["events"] - verbs: ["get", "list", "watch", "create", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar -subjects: - - kind: ServiceAccount - name: scaleway-csi-node - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-node-driver-registrar - apiGroup: rbac.authorization.k8s.io ---- -## volumesnapshotclasses CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.2.5 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/260" - creationTimestamp: null - name: volumesnapshotclasses.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .driver - name: Driver - type: string - - JSONPath: .deletionPolicy - description: Determines whether a VolumeSnapshotContent created through the VolumeSnapshotClass - should be deleted when its bound VolumeSnapshot is deleted. - name: DeletionPolicy - type: string - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotClass - listKind: VolumeSnapshotClassList - plural: volumesnapshotclasses - singular: volumesnapshotclass - preserveUnknownFields: false - scope: Cluster - subresources: {} - validation: - openAPIV3Schema: - description: VolumeSnapshotClass specifies parameters that a underlying storage - system uses when creating a volume snapshot. A specific VolumeSnapshotClass - is used by specifying its name in a VolumeSnapshot object. VolumeSnapshotClasses - are non-namespaced - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - deletionPolicy: - description: deletionPolicy determines whether a VolumeSnapshotContent created - through the VolumeSnapshotClass should be deleted when its bound VolumeSnapshot - is deleted. Supported values are "Retain" and "Delete". "Retain" means - that the VolumeSnapshotContent and its physical snapshot on underlying - storage system are kept. "Delete" means that the VolumeSnapshotContent - and its physical snapshot on underlying storage system are deleted. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the storage driver that handles this - VolumeSnapshotClass. Required. - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - parameters: - additionalProperties: - type: string - description: parameters is a key-value map with storage driver specific - parameters for creating snapshots. These values are opaque to Kubernetes. - type: object - required: - - deletionPolicy - - driver - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshotcontents CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.2.5 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/260" - creationTimestamp: null - name: volumesnapshotcontents.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .status.readyToUse - description: Indicates if a snapshot is ready to be used to restore a volume. - name: ReadyToUse - type: boolean - - JSONPath: .status.restoreSize - description: Represents the complete size of the snapshot in bytes - name: RestoreSize - type: integer - - JSONPath: .spec.deletionPolicy - description: Determines whether this VolumeSnapshotContent and its physical snapshot - on the underlying storage system should be deleted when its bound VolumeSnapshot - is deleted. - name: DeletionPolicy - type: string - - JSONPath: .spec.driver - description: Name of the CSI driver used to create the physical snapshot on the - underlying storage system. - name: Driver - type: string - - JSONPath: .spec.volumeSnapshotClassName - description: Name of the VolumeSnapshotClass to which this snapshot belongs. - name: VolumeSnapshotClass - type: string - - JSONPath: .spec.volumeSnapshotRef.name - description: Name of the VolumeSnapshot object to which this VolumeSnapshotContent - object is bound. - name: VolumeSnapshot - type: string - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotContent - listKind: VolumeSnapshotContentList - plural: volumesnapshotcontents - singular: volumesnapshotcontent - preserveUnknownFields: false - scope: Cluster - subresources: - status: {} - validation: - openAPIV3Schema: - description: VolumeSnapshotContent represents the actual "on-disk" snapshot - object in the underlying storage system - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: spec defines properties of a VolumeSnapshotContent created - by the underlying storage system. Required. - properties: - deletionPolicy: - description: deletionPolicy determines whether this VolumeSnapshotContent - and its physical snapshot on the underlying storage system should - be deleted when its bound VolumeSnapshot is deleted. Supported values - are "Retain" and "Delete". "Retain" means that the VolumeSnapshotContent - and its physical snapshot on underlying storage system are kept. "Delete" - means that the VolumeSnapshotContent and its physical snapshot on - underlying storage system are deleted. In dynamic snapshot creation - case, this field will be filled in with the "DeletionPolicy" field - defined in the VolumeSnapshotClass the VolumeSnapshot refers to. For - pre-existing snapshots, users MUST specify this field when creating - the VolumeSnapshotContent object. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the CSI driver used to create the - physical snapshot on the underlying storage system. This MUST be the - same as the name returned by the CSI GetPluginName() call for that - driver. Required. - type: string - source: - description: source specifies from where a snapshot will be created. - This field is immutable after creation. Required. - properties: - snapshotHandle: - description: snapshotHandle specifies the CSI "snapshot_id" of a - pre-existing snapshot on the underlying storage system. This field - is immutable. - type: string - volumeHandle: - description: volumeHandle specifies the CSI "volume_id" of the volume - from which a snapshot should be dynamically taken from. This field - is immutable. - type: string - type: object - volumeSnapshotClassName: - description: name of the VolumeSnapshotClass to which this snapshot - belongs. - type: string - volumeSnapshotRef: - description: volumeSnapshotRef specifies the VolumeSnapshot object to - which this VolumeSnapshotContent object is bound. VolumeSnapshot.Spec.VolumeSnapshotContentName - field must reference to this VolumeSnapshotContent's name for the - bidirectional binding to be valid. For a pre-existing VolumeSnapshotContent - object, name and namespace of the VolumeSnapshot object MUST be provided - for binding to happen. This field is immutable after creation. Required. - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object instead of an - entire object, this string should contain a valid JSON/Go field - access statement, such as desiredState.manifest.containers[2]. - For example, if the object reference is to a container within - a pod, this would take on a value like: "spec.containers{name}" - (where "name" refers to the name of the container that triggered - the event) or if no container name is specified "spec.containers[2]" - (container with index 2 in this pod). This syntax is chosen only - to have some well-defined way of referencing a part of an object. - TODO: this design is not final and this field is subject to change - in the future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this reference is - made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - required: - - deletionPolicy - - driver - - source - - volumeSnapshotRef - type: object - status: - description: status represents the current information of a snapshot. - properties: - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot - is taken by the underlying storage system. In dynamic snapshot creation - case, this field will be filled in with the "creation_time" value - returned from CSI "CreateSnapshotRequest" gRPC call. For a pre-existing - snapshot, this field will be filled with the "creation_time" value - returned from the CSI "ListSnapshots" gRPC call if the driver supports - it. If not specified, it indicates the creation time is unknown. The - format of this field is a Unix nanoseconds time encoded as an int64. - On Unix, the command `date +%s%N` returns the current time in nanoseconds - since 1970-01-01 00:00:00 UTC. - format: int64 - type: integer - error: - description: error is the latest observed error during snapshot creation, - if any. - properties: - message: - description: 'message is a string detailing the encountered error - during snapshot creation if specified. NOTE: message may be logged, - and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used - to restore a volume. In dynamic snapshot creation case, this field - will be filled in with the "ready_to_use" value returned from CSI - "CreateSnapshotRequest" gRPC call. For a pre-existing snapshot, this - field will be filled with the "ready_to_use" value returned from the - CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, - this field will be set to "True". If not specified, it means the readiness - of a snapshot is unknown. - type: boolean - restoreSize: - description: restoreSize represents the complete size of the snapshot - in bytes. In dynamic snapshot creation case, this field will be filled - in with the "size_bytes" value returned from CSI "CreateSnapshotRequest" - gRPC call. For a pre-existing snapshot, this field will be filled - with the "size_bytes" value returned from the CSI "ListSnapshots" - gRPC call if the driver supports it. When restoring a volume from - this snapshot, the size of the volume MUST NOT be smaller than the - restoreSize if it is specified, otherwise the restoration will fail. - If not specified, it indicates that the size is unknown. - format: int64 - minimum: 0 - type: integer - snapshotHandle: - description: snapshotHandle is the CSI "snapshot_id" of a snapshot on - the underlying storage system. If not specified, it indicates that - dynamic snapshot creation has either failed or it is still in progress. - type: string - type: object - required: - - spec - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshots CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.2.5 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/260" - creationTimestamp: null - name: volumesnapshots.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .status.readyToUse - description: Indicates if a snapshot is ready to be used to restore a volume. - name: ReadyToUse - type: boolean - - JSONPath: .spec.source.persistentVolumeClaimName - description: Name of the source PVC from where a dynamically taken snapshot will - be created. - name: SourcePVC - type: string - - JSONPath: .spec.source.volumeSnapshotContentName - description: Name of the VolumeSnapshotContent which represents a pre-provisioned - snapshot. - name: SourceSnapshotContent - type: string - - JSONPath: .status.restoreSize - description: Represents the complete size of the snapshot. - name: RestoreSize - type: string - - JSONPath: .spec.volumeSnapshotClassName - description: The name of the VolumeSnapshotClass requested by the VolumeSnapshot. - name: SnapshotClass - type: string - - JSONPath: .status.boundVolumeSnapshotContentName - description: The name of the VolumeSnapshotContent to which this VolumeSnapshot - is bound. - name: SnapshotContent - type: string - - JSONPath: .status.creationTime - description: Timestamp when the point-in-time snapshot is taken by the underlying - storage system. - name: CreationTime - type: date - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshot - listKind: VolumeSnapshotList - plural: volumesnapshots - singular: volumesnapshot - preserveUnknownFields: false - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - description: VolumeSnapshot is a user's request for either creating a point-in-time - snapshot of a persistent volume, or binding to a pre-existing snapshot. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: 'spec defines the desired characteristics of a snapshot requested - by a user. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshots#volumesnapshots - Required.' - properties: - source: - description: source specifies where a snapshot will be created from. - This field is immutable after creation. Required. - properties: - persistentVolumeClaimName: - description: persistentVolumeClaimName specifies the name of the - PersistentVolumeClaim object in the same namespace as the VolumeSnapshot - object where the snapshot should be dynamically taken from. This - field is immutable. - type: string - volumeSnapshotContentName: - description: volumeSnapshotContentName specifies the name of a pre-existing - VolumeSnapshotContent object. This field is immutable. - type: string - type: object - volumeSnapshotClassName: - description: 'volumeSnapshotClassName is the name of the VolumeSnapshotClass - requested by the VolumeSnapshot. If not specified, the default snapshot - class will be used if one exists. If not specified, and there is no - default snapshot class, dynamic snapshot creation will fail. Empty - string is not allowed for this field. TODO(xiangqian): a webhook validation - on empty string. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshot-classes' - type: string - required: - - source - type: object - status: - description: 'status represents the current information of a snapshot. NOTE: - status can be modified by sources other than system controllers, and must - not be depended upon for accuracy. Controllers should only use information - from the VolumeSnapshotContent object after verifying that the binding - is accurate and complete.' - properties: - boundVolumeSnapshotContentName: - description: 'boundVolumeSnapshotContentName represents the name of - the VolumeSnapshotContent object to which the VolumeSnapshot object - is bound. If not specified, it indicates that the VolumeSnapshot object - has not been successfully bound to a VolumeSnapshotContent object - yet. NOTE: Specified boundVolumeSnapshotContentName alone does not - mean binding is valid. Controllers MUST always verify bidirectional - binding between VolumeSnapshot and VolumeSnapshotContent to - avoid possible security issues.' - type: string - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot - is taken by the underlying storage system. In dynamic snapshot creation - case, this field will be filled in with the "creation_time" value - returned from CSI "CreateSnapshotRequest" gRPC call. For a pre-existing - snapshot, this field will be filled with the "creation_time" value - returned from the CSI "ListSnapshots" gRPC call if the driver supports - it. If not specified, it indicates that the creation time of the snapshot - is unknown. - format: date-time - type: string - error: - description: error is the last observed error during snapshot creation, - if any. This field could be helpful to upper level controllers(i.e., - application controller) to decide whether they should continue on - waiting for the snapshot to be created based on the type of error - reported. - properties: - message: - description: 'message is a string detailing the encountered error - during snapshot creation if specified. NOTE: message may be logged, - and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used - to restore a volume. In dynamic snapshot creation case, this field - will be filled in with the "ready_to_use" value returned from CSI - "CreateSnapshotRequest" gRPC call. For a pre-existing snapshot, this - field will be filled with the "ready_to_use" value returned from the - CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, - this field will be set to "True". If not specified, it means the readiness - of a snapshot is unknown. - type: boolean - restoreSize: - anyOf: - - type: integer - - type: string - description: restoreSize represents the complete size of the snapshot - in bytes. In dynamic snapshot creation case, this field will be filled - in with the "size_bytes" value returned from CSI "CreateSnapshotRequest" - gRPC call. For a pre-existing snapshot, this field will be filled - with the "size_bytes" value returned from the CSI "ListSnapshots" - gRPC call if the driver supports it. When restoring a volume from - this snapshot, the size of the volume MUST NOT be smaller than the - restoreSize if it is specified, otherwise the restoration will fail. - If not specified, it indicates that the size is unknown. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - required: - - spec - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: snapshot.storage.k8s.io/v1beta1 -kind: VolumeSnapshotClass -metadata: - name: scw-snapshot -driver: csi.scaleway.com -deletionPolicy: Delete ---- -kind: Deployment -apiVersion: apps/v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system -spec: - selector: - matchLabels: - app: scaleway-csi-controller - replicas: 1 - template: - metadata: - labels: - app: scaleway-csi-controller - spec: - priorityClassName: system-cluster-critical - serviceAccount: scaleway-csi-controller - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.1.3 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--mode=controller" - env: - - name: CSI_ENDPOINT - value: unix:///var/lib/csi/sockets/pluginproxy/csi.sock - envFrom: - - secretRef: - name: scaleway-secret - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - - name: csi-provisioner - image: quay.io/k8scsi/csi-provisioner:v1.6.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--enable-leader-election" - - "--leader-election-type=leases" - - "--feature-gates=Topology=true" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-attacher - image: quay.io/k8scsi/csi-attacher:v2.2.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-snapshotter - image: quay.io/k8scsi/csi-snapshotter:v2.1.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: snapshot-controller - image: quay.io/k8scsi/snapshot-controller:v2.1.0 - args: - - "--v=5" - - "--leader-election" - - name: csi-resizer - image: quay.io/k8scsi/csi-resizer:canary - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/mock.socket - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: liveness-probe - image: quay.io/k8scsi/livenessprobe:v2.0.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /csi - volumes: - - name: socket-dir - emptyDir: {} ---- -kind: ServiceAccount -apiVersion: v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-provisioner -rules: - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "create", "delete"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-controller -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-provisioner - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "update", "patch"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["volumeattachments"] - verbs: ["get", "list", "watch", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-attacher - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["create", "get", "list", "watch", "update", "delete"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots/status"] - verbs: ["update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents/status"] - verbs: ["update"] - - apiGroups: ["apiextensions.k8s.io"] - resources: ["customresourcedefinitions"] - verbs: ["create", "list", "watch", "delete", "get", "update"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-snapshotter - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: external-resizer -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "patch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["pods"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims/status"] - verbs: ["patch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: csi-resizer-role -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: external-resizer - apiGroup: rbac.authorization.k8s.io diff --git a/deploy/kubernetes/scaleway-csi-v0.1.4.yaml b/deploy/kubernetes/scaleway-csi-v0.1.4.yaml deleted file mode 100644 index dcafa1e..0000000 --- a/deploy/kubernetes/scaleway-csi-v0.1.4.yaml +++ /dev/null @@ -1,946 +0,0 @@ -apiVersion: storage.k8s.io/v1beta1 -kind: CSIDriver -metadata: - name: csi.scaleway.com -spec: - attachRequired: true - podInfoOnMount: true ---- -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: scw-bssd - namespace: kube-system -provisioner: csi.scaleway.com -reclaimPolicy: Delete -allowVolumeExpansion: true ---- -kind: DaemonSet -apiVersion: apps/v1 -metadata: - name: scaleway-csi-node - namespace: kube-system - labels: -spec: - selector: - matchLabels: - app: scaleway-csi-node - template: - metadata: - labels: - app: scaleway-csi-node - role: csi - spec: - serviceAccount: scaleway-csi-node - nodeSelector: - kubernetes.io/os: linux - priorityClassName: system-node-critical - hostNetwork: true - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.1.4 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--v=4" - - "--mode=node" - env: - - name: CSI_ENDPOINT - value: unix:///csi/csi.sock - securityContext: - privileged: true - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - volumeMounts: - - name: plugin-dir - mountPath: /csi - - name: kubelet-dir - mountPath: /var/lib/kubelet - mountPropagation: "Bidirectional" - - name: device-dir - mountPath: /dev - - name: csi-node-driver-registrar - image: quay.io/k8scsi/csi-node-driver-registrar:v1.3.0 - args: - - "--v=2" - - "--csi-address=$(CSI_ADDRESS)" - - "--kubelet-registration-path=$(KUBELET_REGISTRATION_PATH)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - - name: KUBELET_REGISTRATION_PATH - value: /var/lib/kubelet/plugins/csi.scaleway.com/csi.sock - - name: KUBE_NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - volumeMounts: - - name: plugin-dir - mountPath: /csi/ - - name: registration-dir - mountPath: /registration/ - - name: liveness-probe - image: quay.io/k8scsi/livenessprobe:v2.0.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: plugin-dir - mountPath: /csi - volumes: - - name: registration-dir - hostPath: - path: /var/lib/kubelet/plugins_registry/ - type: DirectoryOrCreate - - name: plugin-dir - hostPath: - path: /var/lib/kubelet/plugins/csi.scaleway.com - type: DirectoryOrCreate - - name: kubelet-dir - hostPath: - path: /var/lib/kubelet - type: Directory - - name: device-dir - hostPath: - path: /dev ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: scaleway-csi-node - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar - namespace: kube-system -rules: - - apiGroups: [""] - resources: ["events"] - verbs: ["get", "list", "watch", "create", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar -subjects: - - kind: ServiceAccount - name: scaleway-csi-node - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-node-driver-registrar - apiGroup: rbac.authorization.k8s.io ---- -## volumesnapshotclasses CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.2.5 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/260" - creationTimestamp: null - name: volumesnapshotclasses.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .driver - name: Driver - type: string - - JSONPath: .deletionPolicy - description: Determines whether a VolumeSnapshotContent created through the VolumeSnapshotClass - should be deleted when its bound VolumeSnapshot is deleted. - name: DeletionPolicy - type: string - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotClass - listKind: VolumeSnapshotClassList - plural: volumesnapshotclasses - singular: volumesnapshotclass - preserveUnknownFields: false - scope: Cluster - subresources: {} - validation: - openAPIV3Schema: - description: VolumeSnapshotClass specifies parameters that a underlying storage - system uses when creating a volume snapshot. A specific VolumeSnapshotClass - is used by specifying its name in a VolumeSnapshot object. VolumeSnapshotClasses - are non-namespaced - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - deletionPolicy: - description: deletionPolicy determines whether a VolumeSnapshotContent created - through the VolumeSnapshotClass should be deleted when its bound VolumeSnapshot - is deleted. Supported values are "Retain" and "Delete". "Retain" means - that the VolumeSnapshotContent and its physical snapshot on underlying - storage system are kept. "Delete" means that the VolumeSnapshotContent - and its physical snapshot on underlying storage system are deleted. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the storage driver that handles this - VolumeSnapshotClass. Required. - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - parameters: - additionalProperties: - type: string - description: parameters is a key-value map with storage driver specific - parameters for creating snapshots. These values are opaque to Kubernetes. - type: object - required: - - deletionPolicy - - driver - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshotcontents CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.2.5 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/260" - creationTimestamp: null - name: volumesnapshotcontents.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .status.readyToUse - description: Indicates if a snapshot is ready to be used to restore a volume. - name: ReadyToUse - type: boolean - - JSONPath: .status.restoreSize - description: Represents the complete size of the snapshot in bytes - name: RestoreSize - type: integer - - JSONPath: .spec.deletionPolicy - description: Determines whether this VolumeSnapshotContent and its physical snapshot - on the underlying storage system should be deleted when its bound VolumeSnapshot - is deleted. - name: DeletionPolicy - type: string - - JSONPath: .spec.driver - description: Name of the CSI driver used to create the physical snapshot on the - underlying storage system. - name: Driver - type: string - - JSONPath: .spec.volumeSnapshotClassName - description: Name of the VolumeSnapshotClass to which this snapshot belongs. - name: VolumeSnapshotClass - type: string - - JSONPath: .spec.volumeSnapshotRef.name - description: Name of the VolumeSnapshot object to which this VolumeSnapshotContent - object is bound. - name: VolumeSnapshot - type: string - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotContent - listKind: VolumeSnapshotContentList - plural: volumesnapshotcontents - singular: volumesnapshotcontent - preserveUnknownFields: false - scope: Cluster - subresources: - status: {} - validation: - openAPIV3Schema: - description: VolumeSnapshotContent represents the actual "on-disk" snapshot - object in the underlying storage system - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: spec defines properties of a VolumeSnapshotContent created - by the underlying storage system. Required. - properties: - deletionPolicy: - description: deletionPolicy determines whether this VolumeSnapshotContent - and its physical snapshot on the underlying storage system should - be deleted when its bound VolumeSnapshot is deleted. Supported values - are "Retain" and "Delete". "Retain" means that the VolumeSnapshotContent - and its physical snapshot on underlying storage system are kept. "Delete" - means that the VolumeSnapshotContent and its physical snapshot on - underlying storage system are deleted. In dynamic snapshot creation - case, this field will be filled in with the "DeletionPolicy" field - defined in the VolumeSnapshotClass the VolumeSnapshot refers to. For - pre-existing snapshots, users MUST specify this field when creating - the VolumeSnapshotContent object. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the CSI driver used to create the - physical snapshot on the underlying storage system. This MUST be the - same as the name returned by the CSI GetPluginName() call for that - driver. Required. - type: string - source: - description: source specifies from where a snapshot will be created. - This field is immutable after creation. Required. - properties: - snapshotHandle: - description: snapshotHandle specifies the CSI "snapshot_id" of a - pre-existing snapshot on the underlying storage system. This field - is immutable. - type: string - volumeHandle: - description: volumeHandle specifies the CSI "volume_id" of the volume - from which a snapshot should be dynamically taken from. This field - is immutable. - type: string - type: object - volumeSnapshotClassName: - description: name of the VolumeSnapshotClass to which this snapshot - belongs. - type: string - volumeSnapshotRef: - description: volumeSnapshotRef specifies the VolumeSnapshot object to - which this VolumeSnapshotContent object is bound. VolumeSnapshot.Spec.VolumeSnapshotContentName - field must reference to this VolumeSnapshotContent's name for the - bidirectional binding to be valid. For a pre-existing VolumeSnapshotContent - object, name and namespace of the VolumeSnapshot object MUST be provided - for binding to happen. This field is immutable after creation. Required. - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object instead of an - entire object, this string should contain a valid JSON/Go field - access statement, such as desiredState.manifest.containers[2]. - For example, if the object reference is to a container within - a pod, this would take on a value like: "spec.containers{name}" - (where "name" refers to the name of the container that triggered - the event) or if no container name is specified "spec.containers[2]" - (container with index 2 in this pod). This syntax is chosen only - to have some well-defined way of referencing a part of an object. - TODO: this design is not final and this field is subject to change - in the future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this reference is - made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - required: - - deletionPolicy - - driver - - source - - volumeSnapshotRef - type: object - status: - description: status represents the current information of a snapshot. - properties: - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot - is taken by the underlying storage system. In dynamic snapshot creation - case, this field will be filled in with the "creation_time" value - returned from CSI "CreateSnapshotRequest" gRPC call. For a pre-existing - snapshot, this field will be filled with the "creation_time" value - returned from the CSI "ListSnapshots" gRPC call if the driver supports - it. If not specified, it indicates the creation time is unknown. The - format of this field is a Unix nanoseconds time encoded as an int64. - On Unix, the command `date +%s%N` returns the current time in nanoseconds - since 1970-01-01 00:00:00 UTC. - format: int64 - type: integer - error: - description: error is the latest observed error during snapshot creation, - if any. - properties: - message: - description: 'message is a string detailing the encountered error - during snapshot creation if specified. NOTE: message may be logged, - and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used - to restore a volume. In dynamic snapshot creation case, this field - will be filled in with the "ready_to_use" value returned from CSI - "CreateSnapshotRequest" gRPC call. For a pre-existing snapshot, this - field will be filled with the "ready_to_use" value returned from the - CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, - this field will be set to "True". If not specified, it means the readiness - of a snapshot is unknown. - type: boolean - restoreSize: - description: restoreSize represents the complete size of the snapshot - in bytes. In dynamic snapshot creation case, this field will be filled - in with the "size_bytes" value returned from CSI "CreateSnapshotRequest" - gRPC call. For a pre-existing snapshot, this field will be filled - with the "size_bytes" value returned from the CSI "ListSnapshots" - gRPC call if the driver supports it. When restoring a volume from - this snapshot, the size of the volume MUST NOT be smaller than the - restoreSize if it is specified, otherwise the restoration will fail. - If not specified, it indicates that the size is unknown. - format: int64 - minimum: 0 - type: integer - snapshotHandle: - description: snapshotHandle is the CSI "snapshot_id" of a snapshot on - the underlying storage system. If not specified, it indicates that - dynamic snapshot creation has either failed or it is still in progress. - type: string - type: object - required: - - spec - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshots CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.2.5 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/260" - creationTimestamp: null - name: volumesnapshots.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .status.readyToUse - description: Indicates if a snapshot is ready to be used to restore a volume. - name: ReadyToUse - type: boolean - - JSONPath: .spec.source.persistentVolumeClaimName - description: Name of the source PVC from where a dynamically taken snapshot will - be created. - name: SourcePVC - type: string - - JSONPath: .spec.source.volumeSnapshotContentName - description: Name of the VolumeSnapshotContent which represents a pre-provisioned - snapshot. - name: SourceSnapshotContent - type: string - - JSONPath: .status.restoreSize - description: Represents the complete size of the snapshot. - name: RestoreSize - type: string - - JSONPath: .spec.volumeSnapshotClassName - description: The name of the VolumeSnapshotClass requested by the VolumeSnapshot. - name: SnapshotClass - type: string - - JSONPath: .status.boundVolumeSnapshotContentName - description: The name of the VolumeSnapshotContent to which this VolumeSnapshot - is bound. - name: SnapshotContent - type: string - - JSONPath: .status.creationTime - description: Timestamp when the point-in-time snapshot is taken by the underlying - storage system. - name: CreationTime - type: date - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshot - listKind: VolumeSnapshotList - plural: volumesnapshots - singular: volumesnapshot - preserveUnknownFields: false - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - description: VolumeSnapshot is a user's request for either creating a point-in-time - snapshot of a persistent volume, or binding to a pre-existing snapshot. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: 'spec defines the desired characteristics of a snapshot requested - by a user. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshots#volumesnapshots - Required.' - properties: - source: - description: source specifies where a snapshot will be created from. - This field is immutable after creation. Required. - properties: - persistentVolumeClaimName: - description: persistentVolumeClaimName specifies the name of the - PersistentVolumeClaim object in the same namespace as the VolumeSnapshot - object where the snapshot should be dynamically taken from. This - field is immutable. - type: string - volumeSnapshotContentName: - description: volumeSnapshotContentName specifies the name of a pre-existing - VolumeSnapshotContent object. This field is immutable. - type: string - type: object - volumeSnapshotClassName: - description: 'volumeSnapshotClassName is the name of the VolumeSnapshotClass - requested by the VolumeSnapshot. If not specified, the default snapshot - class will be used if one exists. If not specified, and there is no - default snapshot class, dynamic snapshot creation will fail. Empty - string is not allowed for this field. TODO(xiangqian): a webhook validation - on empty string. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshot-classes' - type: string - required: - - source - type: object - status: - description: 'status represents the current information of a snapshot. NOTE: - status can be modified by sources other than system controllers, and must - not be depended upon for accuracy. Controllers should only use information - from the VolumeSnapshotContent object after verifying that the binding - is accurate and complete.' - properties: - boundVolumeSnapshotContentName: - description: 'boundVolumeSnapshotContentName represents the name of - the VolumeSnapshotContent object to which the VolumeSnapshot object - is bound. If not specified, it indicates that the VolumeSnapshot object - has not been successfully bound to a VolumeSnapshotContent object - yet. NOTE: Specified boundVolumeSnapshotContentName alone does not - mean binding is valid. Controllers MUST always verify bidirectional - binding between VolumeSnapshot and VolumeSnapshotContent to - avoid possible security issues.' - type: string - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot - is taken by the underlying storage system. In dynamic snapshot creation - case, this field will be filled in with the "creation_time" value - returned from CSI "CreateSnapshotRequest" gRPC call. For a pre-existing - snapshot, this field will be filled with the "creation_time" value - returned from the CSI "ListSnapshots" gRPC call if the driver supports - it. If not specified, it indicates that the creation time of the snapshot - is unknown. - format: date-time - type: string - error: - description: error is the last observed error during snapshot creation, - if any. This field could be helpful to upper level controllers(i.e., - application controller) to decide whether they should continue on - waiting for the snapshot to be created based on the type of error - reported. - properties: - message: - description: 'message is a string detailing the encountered error - during snapshot creation if specified. NOTE: message may be logged, - and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used - to restore a volume. In dynamic snapshot creation case, this field - will be filled in with the "ready_to_use" value returned from CSI - "CreateSnapshotRequest" gRPC call. For a pre-existing snapshot, this - field will be filled with the "ready_to_use" value returned from the - CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, - this field will be set to "True". If not specified, it means the readiness - of a snapshot is unknown. - type: boolean - restoreSize: - anyOf: - - type: integer - - type: string - description: restoreSize represents the complete size of the snapshot - in bytes. In dynamic snapshot creation case, this field will be filled - in with the "size_bytes" value returned from CSI "CreateSnapshotRequest" - gRPC call. For a pre-existing snapshot, this field will be filled - with the "size_bytes" value returned from the CSI "ListSnapshots" - gRPC call if the driver supports it. When restoring a volume from - this snapshot, the size of the volume MUST NOT be smaller than the - restoreSize if it is specified, otherwise the restoration will fail. - If not specified, it indicates that the size is unknown. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - required: - - spec - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: snapshot.storage.k8s.io/v1beta1 -kind: VolumeSnapshotClass -metadata: - name: scw-snapshot -driver: csi.scaleway.com -deletionPolicy: Delete ---- -kind: Deployment -apiVersion: apps/v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system -spec: - selector: - matchLabels: - app: scaleway-csi-controller - replicas: 1 - template: - metadata: - labels: - app: scaleway-csi-controller - spec: - priorityClassName: system-cluster-critical - serviceAccount: scaleway-csi-controller - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.1.4 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--mode=controller" - env: - - name: CSI_ENDPOINT - value: unix:///var/lib/csi/sockets/pluginproxy/csi.sock - envFrom: - - secretRef: - name: scaleway-secret - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - - name: csi-provisioner - image: quay.io/k8scsi/csi-provisioner:v1.6.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--enable-leader-election" - - "--leader-election-type=leases" - - "--feature-gates=Topology=true" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-attacher - image: quay.io/k8scsi/csi-attacher:v2.2.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-snapshotter - image: quay.io/k8scsi/csi-snapshotter:v2.1.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: snapshot-controller - image: quay.io/k8scsi/snapshot-controller:v2.1.0 - args: - - "--v=5" - - "--leader-election" - - name: csi-resizer - image: quay.io/k8scsi/csi-resizer:canary - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/mock.socket - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: liveness-probe - image: quay.io/k8scsi/livenessprobe:v2.0.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /csi - volumes: - - name: socket-dir - emptyDir: {} ---- -kind: ServiceAccount -apiVersion: v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-provisioner -rules: - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "create", "delete"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-controller -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-provisioner - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "update", "patch"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["volumeattachments"] - verbs: ["get", "list", "watch", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-attacher - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["create", "get", "list", "watch", "update", "delete"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots/status"] - verbs: ["update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents/status"] - verbs: ["update"] - - apiGroups: ["apiextensions.k8s.io"] - resources: ["customresourcedefinitions"] - verbs: ["create", "list", "watch", "delete", "get", "update"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-snapshotter - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: external-resizer -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "patch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["pods"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims/status"] - verbs: ["patch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: csi-resizer-role -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: external-resizer - apiGroup: rbac.authorization.k8s.io diff --git a/deploy/kubernetes/scaleway-csi-v0.1.5.yaml b/deploy/kubernetes/scaleway-csi-v0.1.5.yaml deleted file mode 100644 index e8d361f..0000000 --- a/deploy/kubernetes/scaleway-csi-v0.1.5.yaml +++ /dev/null @@ -1,944 +0,0 @@ -apiVersion: storage.k8s.io/v1beta1 -kind: CSIDriver -metadata: - name: csi.scaleway.com -spec: - attachRequired: true - podInfoOnMount: true ---- -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: scw-bssd - namespace: kube-system -provisioner: csi.scaleway.com -reclaimPolicy: Delete -allowVolumeExpansion: true ---- -kind: DaemonSet -apiVersion: apps/v1 -metadata: - name: scaleway-csi-node - namespace: kube-system - labels: -spec: - selector: - matchLabels: - app: scaleway-csi-node - template: - metadata: - labels: - app: scaleway-csi-node - role: csi - spec: - serviceAccount: scaleway-csi-node - nodeSelector: - kubernetes.io/os: linux - priorityClassName: system-node-critical - hostNetwork: true - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.1.5 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--v=4" - - "--mode=node" - env: - - name: CSI_ENDPOINT - value: unix:///csi/csi.sock - securityContext: - privileged: true - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - volumeMounts: - - name: plugin-dir - mountPath: /csi - - name: kubelet-dir - mountPath: /var/lib/kubelet - mountPropagation: "Bidirectional" - - name: device-dir - mountPath: /dev - - name: csi-node-driver-registrar - image: k8s.gcr.io/sig-storage/csi-node-driver-registrar:v2.0.1 - args: - - "--v=2" - - "--csi-address=$(CSI_ADDRESS)" - - "--kubelet-registration-path=$(KUBELET_REGISTRATION_PATH)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - - name: KUBELET_REGISTRATION_PATH - value: /var/lib/kubelet/plugins/csi.scaleway.com/csi.sock - - name: KUBE_NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - volumeMounts: - - name: plugin-dir - mountPath: /csi/ - - name: registration-dir - mountPath: /registration/ - - name: liveness-probe - image: k8s.gcr.io/sig-storage/livenessprobe:v2.2.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: plugin-dir - mountPath: /csi - volumes: - - name: registration-dir - hostPath: - path: /var/lib/kubelet/plugins_registry/ - type: DirectoryOrCreate - - name: plugin-dir - hostPath: - path: /var/lib/kubelet/plugins/csi.scaleway.com - type: DirectoryOrCreate - - name: kubelet-dir - hostPath: - path: /var/lib/kubelet - type: Directory - - name: device-dir - hostPath: - path: /dev ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: scaleway-csi-node - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar - namespace: kube-system -rules: - - apiGroups: [""] - resources: ["events"] - verbs: ["get", "list", "watch", "create", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar -subjects: - - kind: ServiceAccount - name: scaleway-csi-node - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-node-driver-registrar - apiGroup: rbac.authorization.k8s.io ---- -## volumesnapshotclasses CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.3.0 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/139" - creationTimestamp: null - name: volumesnapshotclasses.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .driver - name: Driver - type: string - - JSONPath: .deletionPolicy - description: Determines whether a VolumeSnapshotContent created through the VolumeSnapshotClass - should be deleted when its bound VolumeSnapshot is deleted. - name: DeletionPolicy - type: string - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotClass - listKind: VolumeSnapshotClassList - plural: volumesnapshotclasses - singular: volumesnapshotclass - preserveUnknownFields: false - scope: Cluster - subresources: {} - validation: - openAPIV3Schema: - description: VolumeSnapshotClass specifies parameters that a underlying storage - system uses when creating a volume snapshot. A specific VolumeSnapshotClass - is used by specifying its name in a VolumeSnapshot object. VolumeSnapshotClasses - are non-namespaced - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - deletionPolicy: - description: deletionPolicy determines whether a VolumeSnapshotContent created - through the VolumeSnapshotClass should be deleted when its bound VolumeSnapshot - is deleted. Supported values are "Retain" and "Delete". "Retain" means - that the VolumeSnapshotContent and its physical snapshot on underlying - storage system are kept. "Delete" means that the VolumeSnapshotContent - and its physical snapshot on underlying storage system are deleted. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the storage driver that handles this - VolumeSnapshotClass. Required. - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - parameters: - additionalProperties: - type: string - description: parameters is a key-value map with storage driver specific - parameters for creating snapshots. These values are opaque to Kubernetes. - type: object - required: - - deletionPolicy - - driver - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshotcontents CRD - copied from -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.3.0 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/139" - creationTimestamp: null - name: volumesnapshotcontents.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .status.readyToUse - description: Indicates if a snapshot is ready to be used to restore a volume. - name: ReadyToUse - type: boolean - - JSONPath: .status.restoreSize - description: Represents the complete size of the snapshot in bytes - name: RestoreSize - type: integer - - JSONPath: .spec.deletionPolicy - description: Determines whether this VolumeSnapshotContent and its physical snapshot - on the underlying storage system should be deleted when its bound VolumeSnapshot - is deleted. - name: DeletionPolicy - type: string - - JSONPath: .spec.driver - description: Name of the CSI driver used to create the physical snapshot on the - underlying storage system. - name: Driver - type: string - - JSONPath: .spec.volumeSnapshotClassName - description: Name of the VolumeSnapshotClass to which this snapshot belongs. - name: VolumeSnapshotClass - type: string - - JSONPath: .spec.volumeSnapshotRef.name - description: Name of the VolumeSnapshot object to which this VolumeSnapshotContent - object is bound. - name: VolumeSnapshot - type: string - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotContent - listKind: VolumeSnapshotContentList - plural: volumesnapshotcontents - singular: volumesnapshotcontent - preserveUnknownFields: false - scope: Cluster - subresources: - status: {} - validation: - openAPIV3Schema: - description: VolumeSnapshotContent represents the actual "on-disk" snapshot - object in the underlying storage system - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: spec defines properties of a VolumeSnapshotContent created - by the underlying storage system. Required. - properties: - deletionPolicy: - description: deletionPolicy determines whether this VolumeSnapshotContent - and its physical snapshot on the underlying storage system should - be deleted when its bound VolumeSnapshot is deleted. Supported values - are "Retain" and "Delete". "Retain" means that the VolumeSnapshotContent - and its physical snapshot on underlying storage system are kept. "Delete" - means that the VolumeSnapshotContent and its physical snapshot on - underlying storage system are deleted. In dynamic snapshot creation - case, this field will be filled in with the "DeletionPolicy" field - defined in the VolumeSnapshotClass the VolumeSnapshot refers to. For - pre-existing snapshots, users MUST specify this field when creating - the VolumeSnapshotContent object. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the CSI driver used to create the - physical snapshot on the underlying storage system. This MUST be the - same as the name returned by the CSI GetPluginName() call for that - driver. Required. - type: string - source: - description: source specifies from where a snapshot will be created. - This field is immutable after creation. Required. - properties: - snapshotHandle: - description: snapshotHandle specifies the CSI "snapshot_id" of a - pre-existing snapshot on the underlying storage system. This field - is immutable. - type: string - volumeHandle: - description: volumeHandle specifies the CSI "volume_id" of the volume - from which a snapshot should be dynamically taken from. This field - is immutable. - type: string - type: object - volumeSnapshotClassName: - description: name of the VolumeSnapshotClass to which this snapshot - belongs. - type: string - volumeSnapshotRef: - description: volumeSnapshotRef specifies the VolumeSnapshot object to - which this VolumeSnapshotContent object is bound. VolumeSnapshot.Spec.VolumeSnapshotContentName - field must reference to this VolumeSnapshotContent's name for the - bidirectional binding to be valid. For a pre-existing VolumeSnapshotContent - object, name and namespace of the VolumeSnapshot object MUST be provided - for binding to happen. This field is immutable after creation. Required. - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object instead of an - entire object, this string should contain a valid JSON/Go field - access statement, such as desiredState.manifest.containers[2]. - For example, if the object reference is to a container within - a pod, this would take on a value like: "spec.containers{name}" - (where "name" refers to the name of the container that triggered - the event) or if no container name is specified "spec.containers[2]" - (container with index 2 in this pod). This syntax is chosen only - to have some well-defined way of referencing a part of an object. - TODO: this design is not final and this field is subject to change - in the future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this reference is - made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - required: - - deletionPolicy - - driver - - source - - volumeSnapshotRef - type: object - status: - description: status represents the current information of a snapshot. - properties: - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot - is taken by the underlying storage system. In dynamic snapshot creation - case, this field will be filled in with the "creation_time" value - returned from CSI "CreateSnapshotRequest" gRPC call. For a pre-existing - snapshot, this field will be filled with the "creation_time" value - returned from the CSI "ListSnapshots" gRPC call if the driver supports - it. If not specified, it indicates the creation time is unknown. The - format of this field is a Unix nanoseconds time encoded as an int64. - On Unix, the command `date +%s%N` returns the current time in nanoseconds - since 1970-01-01 00:00:00 UTC. - format: int64 - type: integer - error: - description: error is the latest observed error during snapshot creation, - if any. - properties: - message: - description: 'message is a string detailing the encountered error - during snapshot creation if specified. NOTE: message may be logged, - and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used - to restore a volume. In dynamic snapshot creation case, this field - will be filled in with the "ready_to_use" value returned from CSI - "CreateSnapshotRequest" gRPC call. For a pre-existing snapshot, this - field will be filled with the "ready_to_use" value returned from the - CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, - this field will be set to "True". If not specified, it means the readiness - of a snapshot is unknown. - type: boolean - restoreSize: - description: restoreSize represents the complete size of the snapshot - in bytes. In dynamic snapshot creation case, this field will be filled - in with the "size_bytes" value returned from CSI "CreateSnapshotRequest" - gRPC call. For a pre-existing snapshot, this field will be filled - with the "size_bytes" value returned from the CSI "ListSnapshots" - gRPC call if the driver supports it. When restoring a volume from - this snapshot, the size of the volume MUST NOT be smaller than the - restoreSize if it is specified, otherwise the restoration will fail. - If not specified, it indicates that the size is unknown. - format: int64 - minimum: 0 - type: integer - snapshotHandle: - description: snapshotHandle is the CSI "snapshot_id" of a snapshot on - the underlying storage system. If not specified, it indicates that - dynamic snapshot creation has either failed or it is still in progress. - type: string - type: object - required: - - spec - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshots CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.3.0 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/139" - creationTimestamp: null - name: volumesnapshots.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .status.readyToUse - description: Indicates if a snapshot is ready to be used to restore a volume. - name: ReadyToUse - type: boolean - - JSONPath: .spec.source.persistentVolumeClaimName - description: Name of the source PVC from where a dynamically taken snapshot will - be created. - name: SourcePVC - type: string - - JSONPath: .spec.source.volumeSnapshotContentName - description: Name of the VolumeSnapshotContent which represents a pre-provisioned - snapshot. - name: SourceSnapshotContent - type: string - - JSONPath: .status.restoreSize - description: Represents the complete size of the snapshot. - name: RestoreSize - type: string - - JSONPath: .spec.volumeSnapshotClassName - description: The name of the VolumeSnapshotClass requested by the VolumeSnapshot. - name: SnapshotClass - type: string - - JSONPath: .status.boundVolumeSnapshotContentName - description: The name of the VolumeSnapshotContent to which this VolumeSnapshot - is bound. - name: SnapshotContent - type: string - - JSONPath: .status.creationTime - description: Timestamp when the point-in-time snapshot is taken by the underlying - storage system. - name: CreationTime - type: date - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshot - listKind: VolumeSnapshotList - plural: volumesnapshots - singular: volumesnapshot - preserveUnknownFields: false - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - description: VolumeSnapshot is a user's request for either creating a point-in-time - snapshot of a persistent volume, or binding to a pre-existing snapshot. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: 'spec defines the desired characteristics of a snapshot requested - by a user. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshots#volumesnapshots - Required.' - properties: - source: - description: source specifies where a snapshot will be created from. - This field is immutable after creation. Required. - properties: - persistentVolumeClaimName: - description: persistentVolumeClaimName specifies the name of the - PersistentVolumeClaim object in the same namespace as the VolumeSnapshot - object where the snapshot should be dynamically taken from. This - field is immutable. - type: string - volumeSnapshotContentName: - description: volumeSnapshotContentName specifies the name of a pre-existing - VolumeSnapshotContent object. This field is immutable. - type: string - type: object - volumeSnapshotClassName: - description: 'volumeSnapshotClassName is the name of the VolumeSnapshotClass - requested by the VolumeSnapshot. If not specified, the default snapshot - class will be used if one exists. If not specified, and there is no - default snapshot class, dynamic snapshot creation will fail. Empty - string is not allowed for this field. TODO(xiangqian): a webhook validation - on empty string. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshot-classes' - type: string - required: - - source - type: object - status: - description: 'status represents the current information of a snapshot. NOTE: - status can be modified by sources other than system controllers, and must - not be depended upon for accuracy. Controllers should only use information - from the VolumeSnapshotContent object after verifying that the binding - is accurate and complete.' - properties: - boundVolumeSnapshotContentName: - description: 'boundVolumeSnapshotContentName represents the name of - the VolumeSnapshotContent object to which the VolumeSnapshot object - is bound. If not specified, it indicates that the VolumeSnapshot object - has not been successfully bound to a VolumeSnapshotContent object - yet. NOTE: Specified boundVolumeSnapshotContentName alone does not - mean binding is valid. Controllers MUST always verify bidirectional - binding between VolumeSnapshot and VolumeSnapshotContent to - avoid possible security issues.' - type: string - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot - is taken by the underlying storage system. In dynamic snapshot creation - case, this field will be filled in with the "creation_time" value - returned from CSI "CreateSnapshotRequest" gRPC call. For a pre-existing - snapshot, this field will be filled with the "creation_time" value - returned from the CSI "ListSnapshots" gRPC call if the driver supports - it. If not specified, it indicates that the creation time of the snapshot - is unknown. - format: date-time - type: string - error: - description: error is the last observed error during snapshot creation, - if any. This field could be helpful to upper level controllers(i.e., - application controller) to decide whether they should continue on - waiting for the snapshot to be created based on the type of error - reported. - properties: - message: - description: 'message is a string detailing the encountered error - during snapshot creation if specified. NOTE: message may be logged, - and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used - to restore a volume. In dynamic snapshot creation case, this field - will be filled in with the "ready_to_use" value returned from CSI - "CreateSnapshotRequest" gRPC call. For a pre-existing snapshot, this - field will be filled with the "ready_to_use" value returned from the - CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, - this field will be set to "True". If not specified, it means the readiness - of a snapshot is unknown. - type: boolean - restoreSize: - type: string - description: restoreSize represents the complete size of the snapshot - in bytes. In dynamic snapshot creation case, this field will be filled - in with the "size_bytes" value returned from CSI "CreateSnapshotRequest" - gRPC call. For a pre-existing snapshot, this field will be filled - with the "size_bytes" value returned from the CSI "ListSnapshots" - gRPC call if the driver supports it. When restoring a volume from - this snapshot, the size of the volume MUST NOT be smaller than the - restoreSize if it is specified, otherwise the restoration will fail. - If not specified, it indicates that the size is unknown. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - required: - - spec - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: snapshot.storage.k8s.io/v1beta1 -kind: VolumeSnapshotClass -metadata: - name: scw-snapshot -driver: csi.scaleway.com -deletionPolicy: Delete ---- -kind: Deployment -apiVersion: apps/v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system -spec: - selector: - matchLabels: - app: scaleway-csi-controller - replicas: 1 - template: - metadata: - labels: - app: scaleway-csi-controller - spec: - priorityClassName: system-cluster-critical - serviceAccount: scaleway-csi-controller - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.1.5 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--mode=controller" - env: - - name: CSI_ENDPOINT - value: unix:///var/lib/csi/sockets/pluginproxy/csi.sock - envFrom: - - secretRef: - name: scaleway-secret - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - - name: csi-provisioner - image: k8s.gcr.io/sig-storage/csi-provisioner:v2.0.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - - "--feature-gates=Topology=true" - - "--default-fstype=ext4" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-attacher - image: k8s.gcr.io/sig-storage/csi-attacher:v3.0.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-snapshotter - image: k8s.gcr.io/sig-storage/csi-snapshotter:v2.1.1 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: snapshot-controller - image: k8s.gcr.io/sig-storage/snapshot-controller:v2.1.1 - args: - - "--v=5" - - "--leader-election" - - name: csi-resizer - image: k8s.gcr.io/sig-storage/csi-resizer:v1.0.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/mock.socket - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: liveness-probe - image: k8s.gcr.io/sig-storage/livenessprobe:v2.2.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /csi - volumes: - - name: socket-dir - emptyDir: {} ---- -kind: ServiceAccount -apiVersion: v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-provisioner -rules: - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "create", "delete"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-controller -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-provisioner - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "update", "patch"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["volumeattachments"] - verbs: ["get", "list", "watch", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-attacher - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["create", "get", "list", "watch", "update", "delete"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots/status"] - verbs: ["update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents/status"] - verbs: ["update"] - - apiGroups: ["apiextensions.k8s.io"] - resources: ["customresourcedefinitions"] - verbs: ["create", "list", "watch", "delete", "get", "update"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-snapshotter - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: external-resizer -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "patch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["pods"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims/status"] - verbs: ["patch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: csi-resizer-role -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: external-resizer - apiGroup: rbac.authorization.k8s.io diff --git a/deploy/kubernetes/scaleway-csi-v0.1.7.yaml b/deploy/kubernetes/scaleway-csi-v0.1.7.yaml deleted file mode 100644 index 6cd4dfa..0000000 --- a/deploy/kubernetes/scaleway-csi-v0.1.7.yaml +++ /dev/null @@ -1,947 +0,0 @@ -apiVersion: storage.k8s.io/v1beta1 -kind: CSIDriver -metadata: - name: csi.scaleway.com -spec: - attachRequired: true - podInfoOnMount: true ---- -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: scw-bssd - namespace: kube-system -provisioner: csi.scaleway.com -reclaimPolicy: Delete -allowVolumeExpansion: true ---- -kind: DaemonSet -apiVersion: apps/v1 -metadata: - name: scaleway-csi-node - namespace: kube-system - labels: -spec: - selector: - matchLabels: - app: scaleway-csi-node - template: - metadata: - labels: - app: scaleway-csi-node - role: csi - spec: - serviceAccount: scaleway-csi-node - nodeSelector: - kubernetes.io/os: linux - priorityClassName: system-node-critical - hostNetwork: true - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.1.7 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--v=4" - - "--mode=node" - env: - - name: CSI_ENDPOINT - value: unix:///csi/csi.sock - securityContext: - privileged: true - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - volumeMounts: - - name: plugin-dir - mountPath: /csi - - name: kubelet-dir - mountPath: /var/lib/kubelet - mountPropagation: "Bidirectional" - - name: device-dir - mountPath: /dev - - name: csi-node-driver-registrar - image: k8s.gcr.io/sig-storage/csi-node-driver-registrar:v2.0.1 - args: - - "--v=2" - - "--csi-address=$(CSI_ADDRESS)" - - "--kubelet-registration-path=$(KUBELET_REGISTRATION_PATH)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - - name: KUBELET_REGISTRATION_PATH - value: /var/lib/kubelet/plugins/csi.scaleway.com/csi.sock - - name: KUBE_NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - volumeMounts: - - name: plugin-dir - mountPath: /csi/ - - name: registration-dir - mountPath: /registration/ - - name: liveness-probe - image: k8s.gcr.io/sig-storage/livenessprobe:v2.2.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: plugin-dir - mountPath: /csi - volumes: - - name: registration-dir - hostPath: - path: /var/lib/kubelet/plugins_registry/ - type: DirectoryOrCreate - - name: plugin-dir - hostPath: - path: /var/lib/kubelet/plugins/csi.scaleway.com - type: DirectoryOrCreate - - name: kubelet-dir - hostPath: - path: /var/lib/kubelet - type: Directory - - name: device-dir - hostPath: - path: /dev ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: scaleway-csi-node - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar - namespace: kube-system -rules: - - apiGroups: [""] - resources: ["events"] - verbs: ["get", "list", "watch", "create", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar -subjects: - - kind: ServiceAccount - name: scaleway-csi-node - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-node-driver-registrar - apiGroup: rbac.authorization.k8s.io ---- -## volumesnapshotclasses CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.3.0 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/139" - creationTimestamp: null - name: volumesnapshotclasses.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .driver - name: Driver - type: string - - JSONPath: .deletionPolicy - description: Determines whether a VolumeSnapshotContent created through the VolumeSnapshotClass - should be deleted when its bound VolumeSnapshot is deleted. - name: DeletionPolicy - type: string - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotClass - listKind: VolumeSnapshotClassList - plural: volumesnapshotclasses - singular: volumesnapshotclass - preserveUnknownFields: false - scope: Cluster - subresources: {} - validation: - openAPIV3Schema: - description: VolumeSnapshotClass specifies parameters that a underlying storage - system uses when creating a volume snapshot. A specific VolumeSnapshotClass - is used by specifying its name in a VolumeSnapshot object. VolumeSnapshotClasses - are non-namespaced - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - deletionPolicy: - description: deletionPolicy determines whether a VolumeSnapshotContent created - through the VolumeSnapshotClass should be deleted when its bound VolumeSnapshot - is deleted. Supported values are "Retain" and "Delete". "Retain" means - that the VolumeSnapshotContent and its physical snapshot on underlying - storage system are kept. "Delete" means that the VolumeSnapshotContent - and its physical snapshot on underlying storage system are deleted. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the storage driver that handles this - VolumeSnapshotClass. Required. - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - parameters: - additionalProperties: - type: string - description: parameters is a key-value map with storage driver specific - parameters for creating snapshots. These values are opaque to Kubernetes. - type: object - required: - - deletionPolicy - - driver - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshotcontents CRD - copied from -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.3.0 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/139" - creationTimestamp: null - name: volumesnapshotcontents.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .status.readyToUse - description: Indicates if a snapshot is ready to be used to restore a volume. - name: ReadyToUse - type: boolean - - JSONPath: .status.restoreSize - description: Represents the complete size of the snapshot in bytes - name: RestoreSize - type: integer - - JSONPath: .spec.deletionPolicy - description: Determines whether this VolumeSnapshotContent and its physical snapshot - on the underlying storage system should be deleted when its bound VolumeSnapshot - is deleted. - name: DeletionPolicy - type: string - - JSONPath: .spec.driver - description: Name of the CSI driver used to create the physical snapshot on the - underlying storage system. - name: Driver - type: string - - JSONPath: .spec.volumeSnapshotClassName - description: Name of the VolumeSnapshotClass to which this snapshot belongs. - name: VolumeSnapshotClass - type: string - - JSONPath: .spec.volumeSnapshotRef.name - description: Name of the VolumeSnapshot object to which this VolumeSnapshotContent - object is bound. - name: VolumeSnapshot - type: string - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotContent - listKind: VolumeSnapshotContentList - plural: volumesnapshotcontents - singular: volumesnapshotcontent - preserveUnknownFields: false - scope: Cluster - subresources: - status: {} - validation: - openAPIV3Schema: - description: VolumeSnapshotContent represents the actual "on-disk" snapshot - object in the underlying storage system - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: spec defines properties of a VolumeSnapshotContent created - by the underlying storage system. Required. - properties: - deletionPolicy: - description: deletionPolicy determines whether this VolumeSnapshotContent - and its physical snapshot on the underlying storage system should - be deleted when its bound VolumeSnapshot is deleted. Supported values - are "Retain" and "Delete". "Retain" means that the VolumeSnapshotContent - and its physical snapshot on underlying storage system are kept. "Delete" - means that the VolumeSnapshotContent and its physical snapshot on - underlying storage system are deleted. In dynamic snapshot creation - case, this field will be filled in with the "DeletionPolicy" field - defined in the VolumeSnapshotClass the VolumeSnapshot refers to. For - pre-existing snapshots, users MUST specify this field when creating - the VolumeSnapshotContent object. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the CSI driver used to create the - physical snapshot on the underlying storage system. This MUST be the - same as the name returned by the CSI GetPluginName() call for that - driver. Required. - type: string - source: - description: source specifies from where a snapshot will be created. - This field is immutable after creation. Required. - properties: - snapshotHandle: - description: snapshotHandle specifies the CSI "snapshot_id" of a - pre-existing snapshot on the underlying storage system. This field - is immutable. - type: string - volumeHandle: - description: volumeHandle specifies the CSI "volume_id" of the volume - from which a snapshot should be dynamically taken from. This field - is immutable. - type: string - type: object - volumeSnapshotClassName: - description: name of the VolumeSnapshotClass to which this snapshot - belongs. - type: string - volumeSnapshotRef: - description: volumeSnapshotRef specifies the VolumeSnapshot object to - which this VolumeSnapshotContent object is bound. VolumeSnapshot.Spec.VolumeSnapshotContentName - field must reference to this VolumeSnapshotContent's name for the - bidirectional binding to be valid. For a pre-existing VolumeSnapshotContent - object, name and namespace of the VolumeSnapshot object MUST be provided - for binding to happen. This field is immutable after creation. Required. - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object instead of an - entire object, this string should contain a valid JSON/Go field - access statement, such as desiredState.manifest.containers[2]. - For example, if the object reference is to a container within - a pod, this would take on a value like: "spec.containers{name}" - (where "name" refers to the name of the container that triggered - the event) or if no container name is specified "spec.containers[2]" - (container with index 2 in this pod). This syntax is chosen only - to have some well-defined way of referencing a part of an object. - TODO: this design is not final and this field is subject to change - in the future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this reference is - made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - required: - - deletionPolicy - - driver - - source - - volumeSnapshotRef - type: object - status: - description: status represents the current information of a snapshot. - properties: - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot - is taken by the underlying storage system. In dynamic snapshot creation - case, this field will be filled in with the "creation_time" value - returned from CSI "CreateSnapshotRequest" gRPC call. For a pre-existing - snapshot, this field will be filled with the "creation_time" value - returned from the CSI "ListSnapshots" gRPC call if the driver supports - it. If not specified, it indicates the creation time is unknown. The - format of this field is a Unix nanoseconds time encoded as an int64. - On Unix, the command `date +%s%N` returns the current time in nanoseconds - since 1970-01-01 00:00:00 UTC. - format: int64 - type: integer - error: - description: error is the latest observed error during snapshot creation, - if any. - properties: - message: - description: 'message is a string detailing the encountered error - during snapshot creation if specified. NOTE: message may be logged, - and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used - to restore a volume. In dynamic snapshot creation case, this field - will be filled in with the "ready_to_use" value returned from CSI - "CreateSnapshotRequest" gRPC call. For a pre-existing snapshot, this - field will be filled with the "ready_to_use" value returned from the - CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, - this field will be set to "True". If not specified, it means the readiness - of a snapshot is unknown. - type: boolean - restoreSize: - description: restoreSize represents the complete size of the snapshot - in bytes. In dynamic snapshot creation case, this field will be filled - in with the "size_bytes" value returned from CSI "CreateSnapshotRequest" - gRPC call. For a pre-existing snapshot, this field will be filled - with the "size_bytes" value returned from the CSI "ListSnapshots" - gRPC call if the driver supports it. When restoring a volume from - this snapshot, the size of the volume MUST NOT be smaller than the - restoreSize if it is specified, otherwise the restoration will fail. - If not specified, it indicates that the size is unknown. - format: int64 - minimum: 0 - type: integer - snapshotHandle: - description: snapshotHandle is the CSI "snapshot_id" of a snapshot on - the underlying storage system. If not specified, it indicates that - dynamic snapshot creation has either failed or it is still in progress. - type: string - type: object - required: - - spec - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshots CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.3.0 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/139" - creationTimestamp: null - name: volumesnapshots.snapshot.storage.k8s.io -spec: - additionalPrinterColumns: - - JSONPath: .status.readyToUse - description: Indicates if a snapshot is ready to be used to restore a volume. - name: ReadyToUse - type: boolean - - JSONPath: .spec.source.persistentVolumeClaimName - description: Name of the source PVC from where a dynamically taken snapshot will - be created. - name: SourcePVC - type: string - - JSONPath: .spec.source.volumeSnapshotContentName - description: Name of the VolumeSnapshotContent which represents a pre-provisioned - snapshot. - name: SourceSnapshotContent - type: string - - JSONPath: .status.restoreSize - description: Represents the complete size of the snapshot. - name: RestoreSize - type: string - - JSONPath: .spec.volumeSnapshotClassName - description: The name of the VolumeSnapshotClass requested by the VolumeSnapshot. - name: SnapshotClass - type: string - - JSONPath: .status.boundVolumeSnapshotContentName - description: The name of the VolumeSnapshotContent to which this VolumeSnapshot - is bound. - name: SnapshotContent - type: string - - JSONPath: .status.creationTime - description: Timestamp when the point-in-time snapshot is taken by the underlying - storage system. - name: CreationTime - type: date - - JSONPath: .metadata.creationTimestamp - name: Age - type: date - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshot - listKind: VolumeSnapshotList - plural: volumesnapshots - singular: volumesnapshot - preserveUnknownFields: false - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - description: VolumeSnapshot is a user's request for either creating a point-in-time - snapshot of a persistent volume, or binding to a pre-existing snapshot. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: 'spec defines the desired characteristics of a snapshot requested - by a user. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshots#volumesnapshots - Required.' - properties: - source: - description: source specifies where a snapshot will be created from. - This field is immutable after creation. Required. - properties: - persistentVolumeClaimName: - description: persistentVolumeClaimName specifies the name of the - PersistentVolumeClaim object in the same namespace as the VolumeSnapshot - object where the snapshot should be dynamically taken from. This - field is immutable. - type: string - volumeSnapshotContentName: - description: volumeSnapshotContentName specifies the name of a pre-existing - VolumeSnapshotContent object. This field is immutable. - type: string - type: object - volumeSnapshotClassName: - description: 'volumeSnapshotClassName is the name of the VolumeSnapshotClass - requested by the VolumeSnapshot. If not specified, the default snapshot - class will be used if one exists. If not specified, and there is no - default snapshot class, dynamic snapshot creation will fail. Empty - string is not allowed for this field. TODO(xiangqian): a webhook validation - on empty string. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshot-classes' - type: string - required: - - source - type: object - status: - description: 'status represents the current information of a snapshot. NOTE: - status can be modified by sources other than system controllers, and must - not be depended upon for accuracy. Controllers should only use information - from the VolumeSnapshotContent object after verifying that the binding - is accurate and complete.' - properties: - boundVolumeSnapshotContentName: - description: 'boundVolumeSnapshotContentName represents the name of - the VolumeSnapshotContent object to which the VolumeSnapshot object - is bound. If not specified, it indicates that the VolumeSnapshot object - has not been successfully bound to a VolumeSnapshotContent object - yet. NOTE: Specified boundVolumeSnapshotContentName alone does not - mean binding is valid. Controllers MUST always verify bidirectional - binding between VolumeSnapshot and VolumeSnapshotContent to - avoid possible security issues.' - type: string - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot - is taken by the underlying storage system. In dynamic snapshot creation - case, this field will be filled in with the "creation_time" value - returned from CSI "CreateSnapshotRequest" gRPC call. For a pre-existing - snapshot, this field will be filled with the "creation_time" value - returned from the CSI "ListSnapshots" gRPC call if the driver supports - it. If not specified, it indicates that the creation time of the snapshot - is unknown. - format: date-time - type: string - error: - description: error is the last observed error during snapshot creation, - if any. This field could be helpful to upper level controllers(i.e., - application controller) to decide whether they should continue on - waiting for the snapshot to be created based on the type of error - reported. - properties: - message: - description: 'message is a string detailing the encountered error - during snapshot creation if specified. NOTE: message may be logged, - and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used - to restore a volume. In dynamic snapshot creation case, this field - will be filled in with the "ready_to_use" value returned from CSI - "CreateSnapshotRequest" gRPC call. For a pre-existing snapshot, this - field will be filled with the "ready_to_use" value returned from the - CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, - this field will be set to "True". If not specified, it means the readiness - of a snapshot is unknown. - type: boolean - restoreSize: - type: string - description: restoreSize represents the complete size of the snapshot - in bytes. In dynamic snapshot creation case, this field will be filled - in with the "size_bytes" value returned from CSI "CreateSnapshotRequest" - gRPC call. For a pre-existing snapshot, this field will be filled - with the "size_bytes" value returned from the CSI "ListSnapshots" - gRPC call if the driver supports it. When restoring a volume from - this snapshot, the size of the volume MUST NOT be smaller than the - restoreSize if it is specified, otherwise the restoration will fail. - If not specified, it indicates that the size is unknown. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - required: - - spec - type: object - version: v1beta1 - versions: - - name: v1beta1 - served: true - storage: true -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: snapshot.storage.k8s.io/v1beta1 -kind: VolumeSnapshotClass -metadata: - name: scw-snapshot -driver: csi.scaleway.com -deletionPolicy: Delete ---- -kind: Deployment -apiVersion: apps/v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system -spec: - selector: - matchLabels: - app: scaleway-csi-controller - replicas: 1 - template: - metadata: - labels: - app: scaleway-csi-controller - spec: - priorityClassName: system-cluster-critical - serviceAccount: scaleway-csi-controller - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.1.7 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--mode=controller" - env: - - name: CSI_ENDPOINT - value: unix:///var/lib/csi/sockets/pluginproxy/csi.sock - envFrom: - - secretRef: - name: scaleway-secret - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - - name: csi-provisioner - image: k8s.gcr.io/sig-storage/csi-provisioner:v2.0.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - - "--feature-gates=Topology=true" - - "--default-fstype=ext4" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-attacher - image: k8s.gcr.io/sig-storage/csi-attacher:v3.0.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-snapshotter - image: k8s.gcr.io/sig-storage/csi-snapshotter:v2.1.1 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: snapshot-controller - image: k8s.gcr.io/sig-storage/snapshot-controller:v2.1.1 - args: - - "--v=5" - - "--leader-election" - - name: csi-resizer - image: k8s.gcr.io/sig-storage/csi-resizer:v1.0.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/mock.socket - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: liveness-probe - image: k8s.gcr.io/sig-storage/livenessprobe:v2.2.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /csi - volumes: - - name: socket-dir - emptyDir: {} ---- -kind: ServiceAccount -apiVersion: v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-provisioner -rules: - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "create", "delete"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-controller -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-provisioner - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "update", "patch"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["volumeattachments"] - verbs: ["get", "list", "watch", "update", "patch"] - - apiGroups: ["storage.k8s.io"] - resources: ["volumeattachments/status"] - verbs: ["patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-attacher - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["create", "get", "list", "watch", "update", "delete"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots/status"] - verbs: ["update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents/status"] - verbs: ["update"] - - apiGroups: ["apiextensions.k8s.io"] - resources: ["customresourcedefinitions"] - verbs: ["create", "list", "watch", "delete", "get", "update"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-snapshotter - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: external-resizer -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "patch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["pods"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims/status"] - verbs: ["patch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: csi-resizer-role -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: external-resizer - apiGroup: rbac.authorization.k8s.io diff --git a/deploy/kubernetes/scaleway-csi-v0.1.8.yaml b/deploy/kubernetes/scaleway-csi-v0.1.8.yaml deleted file mode 100644 index 9673af9..0000000 --- a/deploy/kubernetes/scaleway-csi-v0.1.8.yaml +++ /dev/null @@ -1,1072 +0,0 @@ -apiVersion: storage.k8s.io/v1 -kind: CSIDriver -metadata: - name: csi.scaleway.com -spec: - attachRequired: true - podInfoOnMount: true ---- -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: scw-bssd - namespace: kube-system -provisioner: csi.scaleway.com -reclaimPolicy: Delete -allowVolumeExpansion: true ---- -kind: DaemonSet -apiVersion: apps/v1 -metadata: - name: scaleway-csi-node - namespace: kube-system - labels: -spec: - selector: - matchLabels: - app: scaleway-csi-node - template: - metadata: - labels: - app: scaleway-csi-node - role: csi - spec: - serviceAccount: scaleway-csi-node - nodeSelector: - kubernetes.io/os: linux - priorityClassName: system-node-critical - hostNetwork: true - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.1.7 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--v=4" - - "--mode=node" - env: - - name: CSI_ENDPOINT - value: unix:///csi/csi.sock - securityContext: - privileged: true - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - volumeMounts: - - name: plugin-dir - mountPath: /csi - - name: kubelet-dir - mountPath: /var/lib/kubelet - mountPropagation: "Bidirectional" - - name: device-dir - mountPath: /dev - - name: csi-node-driver-registrar - image: k8s.gcr.io/sig-storage/csi-node-driver-registrar:v2.0.1 - args: - - "--v=2" - - "--csi-address=$(CSI_ADDRESS)" - - "--kubelet-registration-path=$(KUBELET_REGISTRATION_PATH)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - - name: KUBELET_REGISTRATION_PATH - value: /var/lib/kubelet/plugins/csi.scaleway.com/csi.sock - - name: KUBE_NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - volumeMounts: - - name: plugin-dir - mountPath: /csi/ - - name: registration-dir - mountPath: /registration/ - - name: liveness-probe - image: k8s.gcr.io/sig-storage/livenessprobe:v2.2.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: plugin-dir - mountPath: /csi - volumes: - - name: registration-dir - hostPath: - path: /var/lib/kubelet/plugins_registry/ - type: DirectoryOrCreate - - name: plugin-dir - hostPath: - path: /var/lib/kubelet/plugins/csi.scaleway.com - type: DirectoryOrCreate - - name: kubelet-dir - hostPath: - path: /var/lib/kubelet - type: Directory - - name: device-dir - hostPath: - path: /dev ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: scaleway-csi-node - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar - namespace: kube-system -rules: - - apiGroups: [""] - resources: ["events"] - verbs: ["get", "list", "watch", "create", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar -subjects: - - kind: ServiceAccount - name: scaleway-csi-node - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-node-driver-registrar - apiGroup: rbac.authorization.k8s.io ---- -## volumesnapshotclasses CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.0 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/419" - creationTimestamp: null - name: volumesnapshotclasses.snapshot.storage.k8s.io -spec: - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotClass - listKind: VolumeSnapshotClassList - plural: volumesnapshotclasses - singular: volumesnapshotclass - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .driver - name: Driver - type: string - - description: Determines whether a VolumeSnapshotContent created through the VolumeSnapshotClass should be deleted when its bound VolumeSnapshot is deleted. - jsonPath: .deletionPolicy - name: DeletionPolicy - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1 - schema: - openAPIV3Schema: - description: VolumeSnapshotClass specifies parameters that a underlying storage system uses when creating a volume snapshot. A specific VolumeSnapshotClass is used by specifying its name in a VolumeSnapshot object. VolumeSnapshotClasses are non-namespaced - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - deletionPolicy: - description: deletionPolicy determines whether a VolumeSnapshotContent created through the VolumeSnapshotClass should be deleted when its bound VolumeSnapshot is deleted. Supported values are "Retain" and "Delete". "Retain" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are kept. "Delete" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are deleted. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the storage driver that handles this VolumeSnapshotClass. Required. - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - parameters: - additionalProperties: - type: string - description: parameters is a key-value map with storage driver specific parameters for creating snapshots. These values are opaque to Kubernetes. - type: object - required: - - deletionPolicy - - driver - type: object - served: true - storage: false - subresources: {} - - additionalPrinterColumns: - - jsonPath: .driver - name: Driver - type: string - - description: Determines whether a VolumeSnapshotContent created through the VolumeSnapshotClass should be deleted when its bound VolumeSnapshot is deleted. - jsonPath: .deletionPolicy - name: DeletionPolicy - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: VolumeSnapshotClass specifies parameters that a underlying storage system uses when creating a volume snapshot. A specific VolumeSnapshotClass is used by specifying its name in a VolumeSnapshot object. VolumeSnapshotClasses are non-namespaced - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - deletionPolicy: - description: deletionPolicy determines whether a VolumeSnapshotContent created through the VolumeSnapshotClass should be deleted when its bound VolumeSnapshot is deleted. Supported values are "Retain" and "Delete". "Retain" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are kept. "Delete" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are deleted. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the storage driver that handles this VolumeSnapshotClass. Required. - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - parameters: - additionalProperties: - type: string - description: parameters is a key-value map with storage driver specific parameters for creating snapshots. These values are opaque to Kubernetes. - type: object - required: - - deletionPolicy - - driver - type: object - served: true - storage: true - subresources: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshotcontents CRD - copied from -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.0 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/419" - creationTimestamp: null - name: volumesnapshotcontents.snapshot.storage.k8s.io -spec: - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotContent - listKind: VolumeSnapshotContentList - plural: volumesnapshotcontents - singular: volumesnapshotcontent - scope: Cluster - versions: - - additionalPrinterColumns: - - description: Indicates if the snapshot is ready to be used to restore a volume. - jsonPath: .status.readyToUse - name: ReadyToUse - type: boolean - - description: Represents the complete size of the snapshot in bytes - jsonPath: .status.restoreSize - name: RestoreSize - type: integer - - description: Determines whether this VolumeSnapshotContent and its physical snapshot on the underlying storage system should be deleted when its bound VolumeSnapshot is deleted. - jsonPath: .spec.deletionPolicy - name: DeletionPolicy - type: string - - description: Name of the CSI driver used to create the physical snapshot on the underlying storage system. - jsonPath: .spec.driver - name: Driver - type: string - - description: Name of the VolumeSnapshotClass to which this snapshot belongs. - jsonPath: .spec.volumeSnapshotClassName - name: VolumeSnapshotClass - type: string - - description: Name of the VolumeSnapshot object to which this VolumeSnapshotContent object is bound. - jsonPath: .spec.volumeSnapshotRef.name - name: VolumeSnapshot - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1 - schema: - openAPIV3Schema: - description: VolumeSnapshotContent represents the actual "on-disk" snapshot object in the underlying storage system - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: spec defines properties of a VolumeSnapshotContent created by the underlying storage system. Required. - properties: - deletionPolicy: - description: deletionPolicy determines whether this VolumeSnapshotContent and its physical snapshot on the underlying storage system should be deleted when its bound VolumeSnapshot is deleted. Supported values are "Retain" and "Delete". "Retain" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are kept. "Delete" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are deleted. For dynamically provisioned snapshots, this field will automatically be filled in by the CSI snapshotter sidecar with the "DeletionPolicy" field defined in the corresponding VolumeSnapshotClass. For pre-existing snapshots, users MUST specify this field when creating the VolumeSnapshotContent object. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the CSI driver used to create the physical snapshot on the underlying storage system. This MUST be the same as the name returned by the CSI GetPluginName() call for that driver. Required. - type: string - source: - description: source specifies whether the snapshot is (or should be) dynamically provisioned or already exists, and just requires a Kubernetes object representation. This field is immutable after creation. Required. - properties: - snapshotHandle: - description: snapshotHandle specifies the CSI "snapshot_id" of a pre-existing snapshot on the underlying storage system for which a Kubernetes object representation was (or should be) created. This field is immutable. - type: string - volumeHandle: - description: volumeHandle specifies the CSI "volume_id" of the volume from which a snapshot should be dynamically taken from. This field is immutable. - type: string - type: object - oneOf: - - required: ["snapshotHandle"] - - required: ["volumeHandle"] - volumeSnapshotClassName: - description: name of the VolumeSnapshotClass from which this snapshot was (or will be) created. Note that after provisioning, the VolumeSnapshotClass may be deleted or recreated with different set of values, and as such, should not be referenced post-snapshot creation. - type: string - volumeSnapshotRef: - description: volumeSnapshotRef specifies the VolumeSnapshot object to which this VolumeSnapshotContent object is bound. VolumeSnapshot.Spec.VolumeSnapshotContentName field must reference to this VolumeSnapshotContent's name for the bidirectional binding to be valid. For a pre-existing VolumeSnapshotContent object, name and namespace of the VolumeSnapshot object MUST be provided for binding to happen. This field is immutable after creation. Required. - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - required: - - deletionPolicy - - driver - - source - - volumeSnapshotRef - type: object - status: - description: status represents the current information of a snapshot. - properties: - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot is taken by the underlying storage system. In dynamic snapshot creation case, this field will be filled in by the CSI snapshotter sidecar with the "creation_time" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "creation_time" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. If not specified, it indicates the creation time is unknown. The format of this field is a Unix nanoseconds time encoded as an int64. On Unix, the command `date +%s%N` returns the current time in nanoseconds since 1970-01-01 00:00:00 UTC. - format: int64 - type: integer - error: - description: error is the last observed error during snapshot creation, if any. Upon success after retry, this error field will be cleared. - properties: - message: - description: 'message is a string detailing the encountered error during snapshot creation if specified. NOTE: message may be logged, and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used to restore a volume. In dynamic snapshot creation case, this field will be filled in by the CSI snapshotter sidecar with the "ready_to_use" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "ready_to_use" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, this field will be set to "True". If not specified, it means the readiness of a snapshot is unknown. - type: boolean - restoreSize: - description: restoreSize represents the complete size of the snapshot in bytes. In dynamic snapshot creation case, this field will be filled in by the CSI snapshotter sidecar with the "size_bytes" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "size_bytes" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. When restoring a volume from this snapshot, the size of the volume MUST NOT be smaller than the restoreSize if it is specified, otherwise the restoration will fail. If not specified, it indicates that the size is unknown. - format: int64 - minimum: 0 - type: integer - snapshotHandle: - description: snapshotHandle is the CSI "snapshot_id" of a snapshot on the underlying storage system. If not specified, it indicates that dynamic snapshot creation has either failed or it is still in progress. - type: string - type: object - required: - - spec - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - description: Indicates if the snapshot is ready to be used to restore a volume. - jsonPath: .status.readyToUse - name: ReadyToUse - type: boolean - - description: Represents the complete size of the snapshot in bytes - jsonPath: .status.restoreSize - name: RestoreSize - type: integer - - description: Determines whether this VolumeSnapshotContent and its physical snapshot on the underlying storage system should be deleted when its bound VolumeSnapshot is deleted. - jsonPath: .spec.deletionPolicy - name: DeletionPolicy - type: string - - description: Name of the CSI driver used to create the physical snapshot on the underlying storage system. - jsonPath: .spec.driver - name: Driver - type: string - - description: Name of the VolumeSnapshotClass to which this snapshot belongs. - jsonPath: .spec.volumeSnapshotClassName - name: VolumeSnapshotClass - type: string - - description: Name of the VolumeSnapshot object to which this VolumeSnapshotContent object is bound. - jsonPath: .spec.volumeSnapshotRef.name - name: VolumeSnapshot - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: VolumeSnapshotContent represents the actual "on-disk" snapshot object in the underlying storage system - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: spec defines properties of a VolumeSnapshotContent created by the underlying storage system. Required. - properties: - deletionPolicy: - description: deletionPolicy determines whether this VolumeSnapshotContent and its physical snapshot on the underlying storage system should be deleted when its bound VolumeSnapshot is deleted. Supported values are "Retain" and "Delete". "Retain" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are kept. "Delete" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are deleted. For dynamically provisioned snapshots, this field will automatically be filled in by the CSI snapshotter sidecar with the "DeletionPolicy" field defined in the corresponding VolumeSnapshotClass. For pre-existing snapshots, users MUST specify this field when creating the VolumeSnapshotContent object. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the CSI driver used to create the physical snapshot on the underlying storage system. This MUST be the same as the name returned by the CSI GetPluginName() call for that driver. Required. - type: string - source: - description: source specifies whether the snapshot is (or should be) dynamically provisioned or already exists, and just requires a Kubernetes object representation. This field is immutable after creation. Required. - properties: - snapshotHandle: - description: snapshotHandle specifies the CSI "snapshot_id" of a pre-existing snapshot on the underlying storage system for which a Kubernetes object representation was (or should be) created. This field is immutable. - type: string - volumeHandle: - description: volumeHandle specifies the CSI "volume_id" of the volume from which a snapshot should be dynamically taken from. This field is immutable. - type: string - type: object - volumeSnapshotClassName: - description: name of the VolumeSnapshotClass from which this snapshot was (or will be) created. Note that after provisioning, the VolumeSnapshotClass may be deleted or recreated with different set of values, and as such, should not be referenced post-snapshot creation. - type: string - volumeSnapshotRef: - description: volumeSnapshotRef specifies the VolumeSnapshot object to which this VolumeSnapshotContent object is bound. VolumeSnapshot.Spec.VolumeSnapshotContentName field must reference to this VolumeSnapshotContent's name for the bidirectional binding to be valid. For a pre-existing VolumeSnapshotContent object, name and namespace of the VolumeSnapshot object MUST be provided for binding to happen. This field is immutable after creation. Required. - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - required: - - deletionPolicy - - driver - - source - - volumeSnapshotRef - type: object - status: - description: status represents the current information of a snapshot. - properties: - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot is taken by the underlying storage system. In dynamic snapshot creation case, this field will be filled in by the CSI snapshotter sidecar with the "creation_time" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "creation_time" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. If not specified, it indicates the creation time is unknown. The format of this field is a Unix nanoseconds time encoded as an int64. On Unix, the command `date +%s%N` returns the current time in nanoseconds since 1970-01-01 00:00:00 UTC. - format: int64 - type: integer - error: - description: error is the last observed error during snapshot creation, if any. Upon success after retry, this error field will be cleared. - properties: - message: - description: 'message is a string detailing the encountered error during snapshot creation if specified. NOTE: message may be logged, and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used to restore a volume. In dynamic snapshot creation case, this field will be filled in by the CSI snapshotter sidecar with the "ready_to_use" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "ready_to_use" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, this field will be set to "True". If not specified, it means the readiness of a snapshot is unknown. - type: boolean - restoreSize: - description: restoreSize represents the complete size of the snapshot in bytes. In dynamic snapshot creation case, this field will be filled in by the CSI snapshotter sidecar with the "size_bytes" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "size_bytes" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. When restoring a volume from this snapshot, the size of the volume MUST NOT be smaller than the restoreSize if it is specified, otherwise the restoration will fail. If not specified, it indicates that the size is unknown. - format: int64 - minimum: 0 - type: integer - snapshotHandle: - description: snapshotHandle is the CSI "snapshot_id" of a snapshot on the underlying storage system. If not specified, it indicates that dynamic snapshot creation has either failed or it is still in progress. - type: string - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshots CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.0 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/419" - creationTimestamp: null - name: volumesnapshots.snapshot.storage.k8s.io -spec: - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshot - listKind: VolumeSnapshotList - plural: volumesnapshots - singular: volumesnapshot - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: Indicates if the snapshot is ready to be used to restore a volume. - jsonPath: .status.readyToUse - name: ReadyToUse - type: boolean - - description: If a new snapshot needs to be created, this contains the name of the source PVC from which this snapshot was (or will be) created. - jsonPath: .spec.source.persistentVolumeClaimName - name: SourcePVC - type: string - - description: If a snapshot already exists, this contains the name of the existing VolumeSnapshotContent object representing the existing snapshot. - jsonPath: .spec.source.volumeSnapshotContentName - name: SourceSnapshotContent - type: string - - description: Represents the minimum size of volume required to rehydrate from this snapshot. - jsonPath: .status.restoreSize - name: RestoreSize - type: string - - description: The name of the VolumeSnapshotClass requested by the VolumeSnapshot. - jsonPath: .spec.volumeSnapshotClassName - name: SnapshotClass - type: string - - description: Name of the VolumeSnapshotContent object to which the VolumeSnapshot object intends to bind to. Please note that verification of binding actually requires checking both VolumeSnapshot and VolumeSnapshotContent to ensure both are pointing at each other. Binding MUST be verified prior to usage of this object. - jsonPath: .status.boundVolumeSnapshotContentName - name: SnapshotContent - type: string - - description: Timestamp when the point-in-time snapshot was taken by the underlying storage system. - jsonPath: .status.creationTime - name: CreationTime - type: date - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1 - schema: - openAPIV3Schema: - description: VolumeSnapshot is a user's request for either creating a point-in-time snapshot of a persistent volume, or binding to a pre-existing snapshot. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: 'spec defines the desired characteristics of a snapshot requested by a user. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshots#volumesnapshots Required.' - properties: - source: - description: source specifies where a snapshot will be created from. This field is immutable after creation. Required. - properties: - persistentVolumeClaimName: - description: persistentVolumeClaimName specifies the name of the PersistentVolumeClaim object representing the volume from which a snapshot should be created. This PVC is assumed to be in the same namespace as the VolumeSnapshot object. This field should be set if the snapshot does not exists, and needs to be created. This field is immutable. - type: string - volumeSnapshotContentName: - description: volumeSnapshotContentName specifies the name of a pre-existing VolumeSnapshotContent object representing an existing volume snapshot. This field should be set if the snapshot already exists and only needs a representation in Kubernetes. This field is immutable. - type: string - type: object - oneOf: - - required: ["persistentVolumeClaimName"] - - required: ["volumeSnapshotContentName"] - volumeSnapshotClassName: - description: 'VolumeSnapshotClassName is the name of the VolumeSnapshotClass requested by the VolumeSnapshot. VolumeSnapshotClassName may be left nil to indicate that the default SnapshotClass should be used. A given cluster may have multiple default Volume SnapshotClasses: one default per CSI Driver. If a VolumeSnapshot does not specify a SnapshotClass, VolumeSnapshotSource will be checked to figure out what the associated CSI Driver is, and the default VolumeSnapshotClass associated with that CSI Driver will be used. If more than one VolumeSnapshotClass exist for a given CSI Driver and more than one have been marked as default, CreateSnapshot will fail and generate an event. Empty string is not allowed for this field.' - type: string - required: - - source - type: object - status: - description: status represents the current information of a snapshot. Consumers must verify binding between VolumeSnapshot and VolumeSnapshotContent objects is successful (by validating that both VolumeSnapshot and VolumeSnapshotContent point at each other) before using this object. - properties: - boundVolumeSnapshotContentName: - description: 'boundVolumeSnapshotContentName is the name of the VolumeSnapshotContent object to which this VolumeSnapshot object intends to bind to. If not specified, it indicates that the VolumeSnapshot object has not been successfully bound to a VolumeSnapshotContent object yet. NOTE: To avoid possible security issues, consumers must verify binding between VolumeSnapshot and VolumeSnapshotContent objects is successful (by validating that both VolumeSnapshot and VolumeSnapshotContent point at each other) before using this object.' - type: string - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot is taken by the underlying storage system. In dynamic snapshot creation case, this field will be filled in by the snapshot controller with the "creation_time" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "creation_time" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. If not specified, it may indicate that the creation time of the snapshot is unknown. - format: date-time - type: string - error: - description: error is the last observed error during snapshot creation, if any. This field could be helpful to upper level controllers(i.e., application controller) to decide whether they should continue on waiting for the snapshot to be created based on the type of error reported. The snapshot controller will keep retrying when an error occurrs during the snapshot creation. Upon success, this error field will be cleared. - properties: - message: - description: 'message is a string detailing the encountered error during snapshot creation if specified. NOTE: message may be logged, and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if the snapshot is ready to be used to restore a volume. In dynamic snapshot creation case, this field will be filled in by the snapshot controller with the "ready_to_use" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "ready_to_use" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, this field will be set to "True". If not specified, it means the readiness of a snapshot is unknown. - type: boolean - restoreSize: - type: string - description: restoreSize represents the minimum size of volume required to create a volume from this snapshot. In dynamic snapshot creation case, this field will be filled in by the snapshot controller with the "size_bytes" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "size_bytes" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. When restoring a volume from this snapshot, the size of the volume MUST NOT be smaller than the restoreSize if it is specified, otherwise the restoration will fail. If not specified, it indicates that the size is unknown. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - required: - - spec - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - description: Indicates if the snapshot is ready to be used to restore a volume. - jsonPath: .status.readyToUse - name: ReadyToUse - type: boolean - - description: If a new snapshot needs to be created, this contains the name of the source PVC from which this snapshot was (or will be) created. - jsonPath: .spec.source.persistentVolumeClaimName - name: SourcePVC - type: string - - description: If a snapshot already exists, this contains the name of the existing VolumeSnapshotContent object representing the existing snapshot. - jsonPath: .spec.source.volumeSnapshotContentName - name: SourceSnapshotContent - type: string - - description: Represents the minimum size of volume required to rehydrate from this snapshot. - jsonPath: .status.restoreSize - name: RestoreSize - type: string - - description: The name of the VolumeSnapshotClass requested by the VolumeSnapshot. - jsonPath: .spec.volumeSnapshotClassName - name: SnapshotClass - type: string - - description: Name of the VolumeSnapshotContent object to which the VolumeSnapshot object intends to bind to. Please note that verification of binding actually requires checking both VolumeSnapshot and VolumeSnapshotContent to ensure both are pointing at each other. Binding MUST be verified prior to usage of this object. - jsonPath: .status.boundVolumeSnapshotContentName - name: SnapshotContent - type: string - - description: Timestamp when the point-in-time snapshot was taken by the underlying storage system. - jsonPath: .status.creationTime - name: CreationTime - type: date - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: VolumeSnapshot is a user's request for either creating a point-in-time snapshot of a persistent volume, or binding to a pre-existing snapshot. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: 'spec defines the desired characteristics of a snapshot requested by a user. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshots#volumesnapshots Required.' - properties: - source: - description: source specifies where a snapshot will be created from. This field is immutable after creation. Required. - properties: - persistentVolumeClaimName: - description: persistentVolumeClaimName specifies the name of the PersistentVolumeClaim object representing the volume from which a snapshot should be created. This PVC is assumed to be in the same namespace as the VolumeSnapshot object. This field should be set if the snapshot does not exists, and needs to be created. This field is immutable. - type: string - volumeSnapshotContentName: - description: volumeSnapshotContentName specifies the name of a pre-existing VolumeSnapshotContent object representing an existing volume snapshot. This field should be set if the snapshot already exists and only needs a representation in Kubernetes. This field is immutable. - type: string - type: object - volumeSnapshotClassName: - description: 'VolumeSnapshotClassName is the name of the VolumeSnapshotClass requested by the VolumeSnapshot. VolumeSnapshotClassName may be left nil to indicate that the default SnapshotClass should be used. A given cluster may have multiple default Volume SnapshotClasses: one default per CSI Driver. If a VolumeSnapshot does not specify a SnapshotClass, VolumeSnapshotSource will be checked to figure out what the associated CSI Driver is, and the default VolumeSnapshotClass associated with that CSI Driver will be used. If more than one VolumeSnapshotClass exist for a given CSI Driver and more than one have been marked as default, CreateSnapshot will fail and generate an event. Empty string is not allowed for this field.' - type: string - required: - - source - type: object - status: - description: status represents the current information of a snapshot. Consumers must verify binding between VolumeSnapshot and VolumeSnapshotContent objects is successful (by validating that both VolumeSnapshot and VolumeSnapshotContent point at each other) before using this object. - properties: - boundVolumeSnapshotContentName: - description: 'boundVolumeSnapshotContentName is the name of the VolumeSnapshotContent object to which this VolumeSnapshot object intends to bind to. If not specified, it indicates that the VolumeSnapshot object has not been successfully bound to a VolumeSnapshotContent object yet. NOTE: To avoid possible security issues, consumers must verify binding between VolumeSnapshot and VolumeSnapshotContent objects is successful (by validating that both VolumeSnapshot and VolumeSnapshotContent point at each other) before using this object.' - type: string - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot is taken by the underlying storage system. In dynamic snapshot creation case, this field will be filled in by the snapshot controller with the "creation_time" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "creation_time" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. If not specified, it may indicate that the creation time of the snapshot is unknown. - format: date-time - type: string - error: - description: error is the last observed error during snapshot creation, if any. This field could be helpful to upper level controllers(i.e., application controller) to decide whether they should continue on waiting for the snapshot to be created based on the type of error reported. The snapshot controller will keep retrying when an error occurrs during the snapshot creation. Upon success, this error field will be cleared. - properties: - message: - description: 'message is a string detailing the encountered error during snapshot creation if specified. NOTE: message may be logged, and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if the snapshot is ready to be used to restore a volume. In dynamic snapshot creation case, this field will be filled in by the snapshot controller with the "ready_to_use" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "ready_to_use" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, this field will be set to "True". If not specified, it means the readiness of a snapshot is unknown. - type: boolean - restoreSize: - type: string - description: restoreSize represents the minimum size of volume required to create a volume from this snapshot. In dynamic snapshot creation case, this field will be filled in by the snapshot controller with the "size_bytes" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "size_bytes" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. When restoring a volume from this snapshot, the size of the volume MUST NOT be smaller than the restoreSize if it is specified, otherwise the restoration will fail. If not specified, it indicates that the size is unknown. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: snapshot.storage.k8s.io/v1 -kind: VolumeSnapshotClass -metadata: - name: scw-snapshot -driver: csi.scaleway.com -deletionPolicy: Delete ---- -kind: Deployment -apiVersion: apps/v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system -spec: - selector: - matchLabels: - app: scaleway-csi-controller - replicas: 1 - template: - metadata: - labels: - app: scaleway-csi-controller - spec: - priorityClassName: system-cluster-critical - serviceAccount: scaleway-csi-controller - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.1.8 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--mode=controller" - env: - - name: CSI_ENDPOINT - value: unix:///var/lib/csi/sockets/pluginproxy/csi.sock - envFrom: - - secretRef: - name: scaleway-secret - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - - name: csi-provisioner - image: k8s.gcr.io/sig-storage/csi-provisioner:v3.0.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - - "--feature-gates=Topology=true" - - "--default-fstype=ext4" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-attacher - image: k8s.gcr.io/sig-storage/csi-attacher:v3.3.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-snapshotter - image: k8s.gcr.io/sig-storage/csi-snapshotter:v4.2.1 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: snapshot-controller - image: k8s.gcr.io/sig-storage/snapshot-controller:v4.1.1 - args: - - "--v=5" - - "--leader-election" - - name: csi-resizer - image: k8s.gcr.io/sig-storage/csi-resizer:v1.3.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/mock.socket - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: liveness-probe - image: k8s.gcr.io/sig-storage/livenessprobe:v2.2.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /csi - volumes: - - name: socket-dir - emptyDir: {} ---- -kind: ServiceAccount -apiVersion: v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-provisioner -rules: - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "create", "delete"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-controller -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-provisioner - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "update", "patch"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["volumeattachments"] - verbs: ["get", "list", "watch", "update", "patch"] - - apiGroups: ["storage.k8s.io"] - resources: ["volumeattachments/status"] - verbs: ["patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-attacher - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["create", "get", "list", "watch", "update", "delete"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots/status"] - verbs: ["update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents/status"] - verbs: ["update"] - - apiGroups: ["apiextensions.k8s.io"] - resources: ["customresourcedefinitions"] - verbs: ["create", "list", "watch", "delete", "get", "update"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-snapshotter - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: external-resizer -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "patch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["pods"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims/status"] - verbs: ["patch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: csi-resizer-role -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: external-resizer - apiGroup: rbac.authorization.k8s.io diff --git a/deploy/kubernetes/scaleway-csi-v0.2.0.yaml b/deploy/kubernetes/scaleway-csi-v0.2.0.yaml deleted file mode 100644 index f21b3f8..0000000 --- a/deploy/kubernetes/scaleway-csi-v0.2.0.yaml +++ /dev/null @@ -1,1072 +0,0 @@ -apiVersion: storage.k8s.io/v1 -kind: CSIDriver -metadata: - name: csi.scaleway.com -spec: - attachRequired: true - podInfoOnMount: true ---- -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: scw-bssd - namespace: kube-system -provisioner: csi.scaleway.com -reclaimPolicy: Delete -allowVolumeExpansion: true ---- -kind: DaemonSet -apiVersion: apps/v1 -metadata: - name: scaleway-csi-node - namespace: kube-system - labels: -spec: - selector: - matchLabels: - app: scaleway-csi-node - template: - metadata: - labels: - app: scaleway-csi-node - role: csi - spec: - serviceAccount: scaleway-csi-node - nodeSelector: - kubernetes.io/os: linux - priorityClassName: system-node-critical - hostNetwork: true - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.2.0 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--v=4" - - "--mode=node" - env: - - name: CSI_ENDPOINT - value: unix:///csi/csi.sock - securityContext: - privileged: true - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - volumeMounts: - - name: plugin-dir - mountPath: /csi - - name: kubelet-dir - mountPath: /var/lib/kubelet - mountPropagation: "Bidirectional" - - name: device-dir - mountPath: /dev - - name: csi-node-driver-registrar - image: k8s.gcr.io/sig-storage/csi-node-driver-registrar:v2.0.1 - args: - - "--v=2" - - "--csi-address=$(CSI_ADDRESS)" - - "--kubelet-registration-path=$(KUBELET_REGISTRATION_PATH)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - - name: KUBELET_REGISTRATION_PATH - value: /var/lib/kubelet/plugins/csi.scaleway.com/csi.sock - - name: KUBE_NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - volumeMounts: - - name: plugin-dir - mountPath: /csi/ - - name: registration-dir - mountPath: /registration/ - - name: liveness-probe - image: k8s.gcr.io/sig-storage/livenessprobe:v2.2.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: plugin-dir - mountPath: /csi - volumes: - - name: registration-dir - hostPath: - path: /var/lib/kubelet/plugins_registry/ - type: DirectoryOrCreate - - name: plugin-dir - hostPath: - path: /var/lib/kubelet/plugins/csi.scaleway.com - type: DirectoryOrCreate - - name: kubelet-dir - hostPath: - path: /var/lib/kubelet - type: Directory - - name: device-dir - hostPath: - path: /dev ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: scaleway-csi-node - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar - namespace: kube-system -rules: - - apiGroups: [""] - resources: ["events"] - verbs: ["get", "list", "watch", "create", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-node-driver-registrar -subjects: - - kind: ServiceAccount - name: scaleway-csi-node - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-node-driver-registrar - apiGroup: rbac.authorization.k8s.io ---- -## volumesnapshotclasses CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.0 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/419" - creationTimestamp: null - name: volumesnapshotclasses.snapshot.storage.k8s.io -spec: - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotClass - listKind: VolumeSnapshotClassList - plural: volumesnapshotclasses - singular: volumesnapshotclass - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .driver - name: Driver - type: string - - description: Determines whether a VolumeSnapshotContent created through the VolumeSnapshotClass should be deleted when its bound VolumeSnapshot is deleted. - jsonPath: .deletionPolicy - name: DeletionPolicy - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1 - schema: - openAPIV3Schema: - description: VolumeSnapshotClass specifies parameters that a underlying storage system uses when creating a volume snapshot. A specific VolumeSnapshotClass is used by specifying its name in a VolumeSnapshot object. VolumeSnapshotClasses are non-namespaced - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - deletionPolicy: - description: deletionPolicy determines whether a VolumeSnapshotContent created through the VolumeSnapshotClass should be deleted when its bound VolumeSnapshot is deleted. Supported values are "Retain" and "Delete". "Retain" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are kept. "Delete" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are deleted. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the storage driver that handles this VolumeSnapshotClass. Required. - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - parameters: - additionalProperties: - type: string - description: parameters is a key-value map with storage driver specific parameters for creating snapshots. These values are opaque to Kubernetes. - type: object - required: - - deletionPolicy - - driver - type: object - served: true - storage: false - subresources: {} - - additionalPrinterColumns: - - jsonPath: .driver - name: Driver - type: string - - description: Determines whether a VolumeSnapshotContent created through the VolumeSnapshotClass should be deleted when its bound VolumeSnapshot is deleted. - jsonPath: .deletionPolicy - name: DeletionPolicy - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: VolumeSnapshotClass specifies parameters that a underlying storage system uses when creating a volume snapshot. A specific VolumeSnapshotClass is used by specifying its name in a VolumeSnapshot object. VolumeSnapshotClasses are non-namespaced - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - deletionPolicy: - description: deletionPolicy determines whether a VolumeSnapshotContent created through the VolumeSnapshotClass should be deleted when its bound VolumeSnapshot is deleted. Supported values are "Retain" and "Delete". "Retain" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are kept. "Delete" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are deleted. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the storage driver that handles this VolumeSnapshotClass. Required. - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - parameters: - additionalProperties: - type: string - description: parameters is a key-value map with storage driver specific parameters for creating snapshots. These values are opaque to Kubernetes. - type: object - required: - - deletionPolicy - - driver - type: object - served: true - storage: true - subresources: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshotcontents CRD - copied from -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.0 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/419" - creationTimestamp: null - name: volumesnapshotcontents.snapshot.storage.k8s.io -spec: - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshotContent - listKind: VolumeSnapshotContentList - plural: volumesnapshotcontents - singular: volumesnapshotcontent - scope: Cluster - versions: - - additionalPrinterColumns: - - description: Indicates if the snapshot is ready to be used to restore a volume. - jsonPath: .status.readyToUse - name: ReadyToUse - type: boolean - - description: Represents the complete size of the snapshot in bytes - jsonPath: .status.restoreSize - name: RestoreSize - type: integer - - description: Determines whether this VolumeSnapshotContent and its physical snapshot on the underlying storage system should be deleted when its bound VolumeSnapshot is deleted. - jsonPath: .spec.deletionPolicy - name: DeletionPolicy - type: string - - description: Name of the CSI driver used to create the physical snapshot on the underlying storage system. - jsonPath: .spec.driver - name: Driver - type: string - - description: Name of the VolumeSnapshotClass to which this snapshot belongs. - jsonPath: .spec.volumeSnapshotClassName - name: VolumeSnapshotClass - type: string - - description: Name of the VolumeSnapshot object to which this VolumeSnapshotContent object is bound. - jsonPath: .spec.volumeSnapshotRef.name - name: VolumeSnapshot - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1 - schema: - openAPIV3Schema: - description: VolumeSnapshotContent represents the actual "on-disk" snapshot object in the underlying storage system - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: spec defines properties of a VolumeSnapshotContent created by the underlying storage system. Required. - properties: - deletionPolicy: - description: deletionPolicy determines whether this VolumeSnapshotContent and its physical snapshot on the underlying storage system should be deleted when its bound VolumeSnapshot is deleted. Supported values are "Retain" and "Delete". "Retain" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are kept. "Delete" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are deleted. For dynamically provisioned snapshots, this field will automatically be filled in by the CSI snapshotter sidecar with the "DeletionPolicy" field defined in the corresponding VolumeSnapshotClass. For pre-existing snapshots, users MUST specify this field when creating the VolumeSnapshotContent object. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the CSI driver used to create the physical snapshot on the underlying storage system. This MUST be the same as the name returned by the CSI GetPluginName() call for that driver. Required. - type: string - source: - description: source specifies whether the snapshot is (or should be) dynamically provisioned or already exists, and just requires a Kubernetes object representation. This field is immutable after creation. Required. - properties: - snapshotHandle: - description: snapshotHandle specifies the CSI "snapshot_id" of a pre-existing snapshot on the underlying storage system for which a Kubernetes object representation was (or should be) created. This field is immutable. - type: string - volumeHandle: - description: volumeHandle specifies the CSI "volume_id" of the volume from which a snapshot should be dynamically taken from. This field is immutable. - type: string - type: object - oneOf: - - required: ["snapshotHandle"] - - required: ["volumeHandle"] - volumeSnapshotClassName: - description: name of the VolumeSnapshotClass from which this snapshot was (or will be) created. Note that after provisioning, the VolumeSnapshotClass may be deleted or recreated with different set of values, and as such, should not be referenced post-snapshot creation. - type: string - volumeSnapshotRef: - description: volumeSnapshotRef specifies the VolumeSnapshot object to which this VolumeSnapshotContent object is bound. VolumeSnapshot.Spec.VolumeSnapshotContentName field must reference to this VolumeSnapshotContent's name for the bidirectional binding to be valid. For a pre-existing VolumeSnapshotContent object, name and namespace of the VolumeSnapshot object MUST be provided for binding to happen. This field is immutable after creation. Required. - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - required: - - deletionPolicy - - driver - - source - - volumeSnapshotRef - type: object - status: - description: status represents the current information of a snapshot. - properties: - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot is taken by the underlying storage system. In dynamic snapshot creation case, this field will be filled in by the CSI snapshotter sidecar with the "creation_time" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "creation_time" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. If not specified, it indicates the creation time is unknown. The format of this field is a Unix nanoseconds time encoded as an int64. On Unix, the command `date +%s%N` returns the current time in nanoseconds since 1970-01-01 00:00:00 UTC. - format: int64 - type: integer - error: - description: error is the last observed error during snapshot creation, if any. Upon success after retry, this error field will be cleared. - properties: - message: - description: 'message is a string detailing the encountered error during snapshot creation if specified. NOTE: message may be logged, and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used to restore a volume. In dynamic snapshot creation case, this field will be filled in by the CSI snapshotter sidecar with the "ready_to_use" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "ready_to_use" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, this field will be set to "True". If not specified, it means the readiness of a snapshot is unknown. - type: boolean - restoreSize: - description: restoreSize represents the complete size of the snapshot in bytes. In dynamic snapshot creation case, this field will be filled in by the CSI snapshotter sidecar with the "size_bytes" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "size_bytes" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. When restoring a volume from this snapshot, the size of the volume MUST NOT be smaller than the restoreSize if it is specified, otherwise the restoration will fail. If not specified, it indicates that the size is unknown. - format: int64 - minimum: 0 - type: integer - snapshotHandle: - description: snapshotHandle is the CSI "snapshot_id" of a snapshot on the underlying storage system. If not specified, it indicates that dynamic snapshot creation has either failed or it is still in progress. - type: string - type: object - required: - - spec - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - description: Indicates if the snapshot is ready to be used to restore a volume. - jsonPath: .status.readyToUse - name: ReadyToUse - type: boolean - - description: Represents the complete size of the snapshot in bytes - jsonPath: .status.restoreSize - name: RestoreSize - type: integer - - description: Determines whether this VolumeSnapshotContent and its physical snapshot on the underlying storage system should be deleted when its bound VolumeSnapshot is deleted. - jsonPath: .spec.deletionPolicy - name: DeletionPolicy - type: string - - description: Name of the CSI driver used to create the physical snapshot on the underlying storage system. - jsonPath: .spec.driver - name: Driver - type: string - - description: Name of the VolumeSnapshotClass to which this snapshot belongs. - jsonPath: .spec.volumeSnapshotClassName - name: VolumeSnapshotClass - type: string - - description: Name of the VolumeSnapshot object to which this VolumeSnapshotContent object is bound. - jsonPath: .spec.volumeSnapshotRef.name - name: VolumeSnapshot - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: VolumeSnapshotContent represents the actual "on-disk" snapshot object in the underlying storage system - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: spec defines properties of a VolumeSnapshotContent created by the underlying storage system. Required. - properties: - deletionPolicy: - description: deletionPolicy determines whether this VolumeSnapshotContent and its physical snapshot on the underlying storage system should be deleted when its bound VolumeSnapshot is deleted. Supported values are "Retain" and "Delete". "Retain" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are kept. "Delete" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are deleted. For dynamically provisioned snapshots, this field will automatically be filled in by the CSI snapshotter sidecar with the "DeletionPolicy" field defined in the corresponding VolumeSnapshotClass. For pre-existing snapshots, users MUST specify this field when creating the VolumeSnapshotContent object. Required. - enum: - - Delete - - Retain - type: string - driver: - description: driver is the name of the CSI driver used to create the physical snapshot on the underlying storage system. This MUST be the same as the name returned by the CSI GetPluginName() call for that driver. Required. - type: string - source: - description: source specifies whether the snapshot is (or should be) dynamically provisioned or already exists, and just requires a Kubernetes object representation. This field is immutable after creation. Required. - properties: - snapshotHandle: - description: snapshotHandle specifies the CSI "snapshot_id" of a pre-existing snapshot on the underlying storage system for which a Kubernetes object representation was (or should be) created. This field is immutable. - type: string - volumeHandle: - description: volumeHandle specifies the CSI "volume_id" of the volume from which a snapshot should be dynamically taken from. This field is immutable. - type: string - type: object - volumeSnapshotClassName: - description: name of the VolumeSnapshotClass from which this snapshot was (or will be) created. Note that after provisioning, the VolumeSnapshotClass may be deleted or recreated with different set of values, and as such, should not be referenced post-snapshot creation. - type: string - volumeSnapshotRef: - description: volumeSnapshotRef specifies the VolumeSnapshot object to which this VolumeSnapshotContent object is bound. VolumeSnapshot.Spec.VolumeSnapshotContentName field must reference to this VolumeSnapshotContent's name for the bidirectional binding to be valid. For a pre-existing VolumeSnapshotContent object, name and namespace of the VolumeSnapshot object MUST be provided for binding to happen. This field is immutable after creation. Required. - properties: - apiVersion: - description: API version of the referent. - type: string - fieldPath: - description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' - type: string - kind: - description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' - type: string - resourceVersion: - description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' - type: string - uid: - description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' - type: string - type: object - required: - - deletionPolicy - - driver - - source - - volumeSnapshotRef - type: object - status: - description: status represents the current information of a snapshot. - properties: - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot is taken by the underlying storage system. In dynamic snapshot creation case, this field will be filled in by the CSI snapshotter sidecar with the "creation_time" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "creation_time" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. If not specified, it indicates the creation time is unknown. The format of this field is a Unix nanoseconds time encoded as an int64. On Unix, the command `date +%s%N` returns the current time in nanoseconds since 1970-01-01 00:00:00 UTC. - format: int64 - type: integer - error: - description: error is the last observed error during snapshot creation, if any. Upon success after retry, this error field will be cleared. - properties: - message: - description: 'message is a string detailing the encountered error during snapshot creation if specified. NOTE: message may be logged, and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if a snapshot is ready to be used to restore a volume. In dynamic snapshot creation case, this field will be filled in by the CSI snapshotter sidecar with the "ready_to_use" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "ready_to_use" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, this field will be set to "True". If not specified, it means the readiness of a snapshot is unknown. - type: boolean - restoreSize: - description: restoreSize represents the complete size of the snapshot in bytes. In dynamic snapshot creation case, this field will be filled in by the CSI snapshotter sidecar with the "size_bytes" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "size_bytes" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. When restoring a volume from this snapshot, the size of the volume MUST NOT be smaller than the restoreSize if it is specified, otherwise the restoration will fail. If not specified, it indicates that the size is unknown. - format: int64 - minimum: 0 - type: integer - snapshotHandle: - description: snapshotHandle is the CSI "snapshot_id" of a snapshot on the underlying storage system. If not specified, it indicates that dynamic snapshot creation has either failed or it is still in progress. - type: string - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -## volumesnapshots CRD - copied from https://github.com/kubernetes-csi/external-snapshotter/blob/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.0 - api-approved.kubernetes.io: "https://github.com/kubernetes-csi/external-snapshotter/pull/419" - creationTimestamp: null - name: volumesnapshots.snapshot.storage.k8s.io -spec: - group: snapshot.storage.k8s.io - names: - kind: VolumeSnapshot - listKind: VolumeSnapshotList - plural: volumesnapshots - singular: volumesnapshot - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: Indicates if the snapshot is ready to be used to restore a volume. - jsonPath: .status.readyToUse - name: ReadyToUse - type: boolean - - description: If a new snapshot needs to be created, this contains the name of the source PVC from which this snapshot was (or will be) created. - jsonPath: .spec.source.persistentVolumeClaimName - name: SourcePVC - type: string - - description: If a snapshot already exists, this contains the name of the existing VolumeSnapshotContent object representing the existing snapshot. - jsonPath: .spec.source.volumeSnapshotContentName - name: SourceSnapshotContent - type: string - - description: Represents the minimum size of volume required to rehydrate from this snapshot. - jsonPath: .status.restoreSize - name: RestoreSize - type: string - - description: The name of the VolumeSnapshotClass requested by the VolumeSnapshot. - jsonPath: .spec.volumeSnapshotClassName - name: SnapshotClass - type: string - - description: Name of the VolumeSnapshotContent object to which the VolumeSnapshot object intends to bind to. Please note that verification of binding actually requires checking both VolumeSnapshot and VolumeSnapshotContent to ensure both are pointing at each other. Binding MUST be verified prior to usage of this object. - jsonPath: .status.boundVolumeSnapshotContentName - name: SnapshotContent - type: string - - description: Timestamp when the point-in-time snapshot was taken by the underlying storage system. - jsonPath: .status.creationTime - name: CreationTime - type: date - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1 - schema: - openAPIV3Schema: - description: VolumeSnapshot is a user's request for either creating a point-in-time snapshot of a persistent volume, or binding to a pre-existing snapshot. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: 'spec defines the desired characteristics of a snapshot requested by a user. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshots#volumesnapshots Required.' - properties: - source: - description: source specifies where a snapshot will be created from. This field is immutable after creation. Required. - properties: - persistentVolumeClaimName: - description: persistentVolumeClaimName specifies the name of the PersistentVolumeClaim object representing the volume from which a snapshot should be created. This PVC is assumed to be in the same namespace as the VolumeSnapshot object. This field should be set if the snapshot does not exists, and needs to be created. This field is immutable. - type: string - volumeSnapshotContentName: - description: volumeSnapshotContentName specifies the name of a pre-existing VolumeSnapshotContent object representing an existing volume snapshot. This field should be set if the snapshot already exists and only needs a representation in Kubernetes. This field is immutable. - type: string - type: object - oneOf: - - required: ["persistentVolumeClaimName"] - - required: ["volumeSnapshotContentName"] - volumeSnapshotClassName: - description: 'VolumeSnapshotClassName is the name of the VolumeSnapshotClass requested by the VolumeSnapshot. VolumeSnapshotClassName may be left nil to indicate that the default SnapshotClass should be used. A given cluster may have multiple default Volume SnapshotClasses: one default per CSI Driver. If a VolumeSnapshot does not specify a SnapshotClass, VolumeSnapshotSource will be checked to figure out what the associated CSI Driver is, and the default VolumeSnapshotClass associated with that CSI Driver will be used. If more than one VolumeSnapshotClass exist for a given CSI Driver and more than one have been marked as default, CreateSnapshot will fail and generate an event. Empty string is not allowed for this field.' - type: string - required: - - source - type: object - status: - description: status represents the current information of a snapshot. Consumers must verify binding between VolumeSnapshot and VolumeSnapshotContent objects is successful (by validating that both VolumeSnapshot and VolumeSnapshotContent point at each other) before using this object. - properties: - boundVolumeSnapshotContentName: - description: 'boundVolumeSnapshotContentName is the name of the VolumeSnapshotContent object to which this VolumeSnapshot object intends to bind to. If not specified, it indicates that the VolumeSnapshot object has not been successfully bound to a VolumeSnapshotContent object yet. NOTE: To avoid possible security issues, consumers must verify binding between VolumeSnapshot and VolumeSnapshotContent objects is successful (by validating that both VolumeSnapshot and VolumeSnapshotContent point at each other) before using this object.' - type: string - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot is taken by the underlying storage system. In dynamic snapshot creation case, this field will be filled in by the snapshot controller with the "creation_time" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "creation_time" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. If not specified, it may indicate that the creation time of the snapshot is unknown. - format: date-time - type: string - error: - description: error is the last observed error during snapshot creation, if any. This field could be helpful to upper level controllers(i.e., application controller) to decide whether they should continue on waiting for the snapshot to be created based on the type of error reported. The snapshot controller will keep retrying when an error occurrs during the snapshot creation. Upon success, this error field will be cleared. - properties: - message: - description: 'message is a string detailing the encountered error during snapshot creation if specified. NOTE: message may be logged, and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if the snapshot is ready to be used to restore a volume. In dynamic snapshot creation case, this field will be filled in by the snapshot controller with the "ready_to_use" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "ready_to_use" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, this field will be set to "True". If not specified, it means the readiness of a snapshot is unknown. - type: boolean - restoreSize: - type: string - description: restoreSize represents the minimum size of volume required to create a volume from this snapshot. In dynamic snapshot creation case, this field will be filled in by the snapshot controller with the "size_bytes" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "size_bytes" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. When restoring a volume from this snapshot, the size of the volume MUST NOT be smaller than the restoreSize if it is specified, otherwise the restoration will fail. If not specified, it indicates that the size is unknown. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - required: - - spec - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - description: Indicates if the snapshot is ready to be used to restore a volume. - jsonPath: .status.readyToUse - name: ReadyToUse - type: boolean - - description: If a new snapshot needs to be created, this contains the name of the source PVC from which this snapshot was (or will be) created. - jsonPath: .spec.source.persistentVolumeClaimName - name: SourcePVC - type: string - - description: If a snapshot already exists, this contains the name of the existing VolumeSnapshotContent object representing the existing snapshot. - jsonPath: .spec.source.volumeSnapshotContentName - name: SourceSnapshotContent - type: string - - description: Represents the minimum size of volume required to rehydrate from this snapshot. - jsonPath: .status.restoreSize - name: RestoreSize - type: string - - description: The name of the VolumeSnapshotClass requested by the VolumeSnapshot. - jsonPath: .spec.volumeSnapshotClassName - name: SnapshotClass - type: string - - description: Name of the VolumeSnapshotContent object to which the VolumeSnapshot object intends to bind to. Please note that verification of binding actually requires checking both VolumeSnapshot and VolumeSnapshotContent to ensure both are pointing at each other. Binding MUST be verified prior to usage of this object. - jsonPath: .status.boundVolumeSnapshotContentName - name: SnapshotContent - type: string - - description: Timestamp when the point-in-time snapshot was taken by the underlying storage system. - jsonPath: .status.creationTime - name: CreationTime - type: date - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: VolumeSnapshot is a user's request for either creating a point-in-time snapshot of a persistent volume, or binding to a pre-existing snapshot. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - spec: - description: 'spec defines the desired characteristics of a snapshot requested by a user. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshots#volumesnapshots Required.' - properties: - source: - description: source specifies where a snapshot will be created from. This field is immutable after creation. Required. - properties: - persistentVolumeClaimName: - description: persistentVolumeClaimName specifies the name of the PersistentVolumeClaim object representing the volume from which a snapshot should be created. This PVC is assumed to be in the same namespace as the VolumeSnapshot object. This field should be set if the snapshot does not exists, and needs to be created. This field is immutable. - type: string - volumeSnapshotContentName: - description: volumeSnapshotContentName specifies the name of a pre-existing VolumeSnapshotContent object representing an existing volume snapshot. This field should be set if the snapshot already exists and only needs a representation in Kubernetes. This field is immutable. - type: string - type: object - volumeSnapshotClassName: - description: 'VolumeSnapshotClassName is the name of the VolumeSnapshotClass requested by the VolumeSnapshot. VolumeSnapshotClassName may be left nil to indicate that the default SnapshotClass should be used. A given cluster may have multiple default Volume SnapshotClasses: one default per CSI Driver. If a VolumeSnapshot does not specify a SnapshotClass, VolumeSnapshotSource will be checked to figure out what the associated CSI Driver is, and the default VolumeSnapshotClass associated with that CSI Driver will be used. If more than one VolumeSnapshotClass exist for a given CSI Driver and more than one have been marked as default, CreateSnapshot will fail and generate an event. Empty string is not allowed for this field.' - type: string - required: - - source - type: object - status: - description: status represents the current information of a snapshot. Consumers must verify binding between VolumeSnapshot and VolumeSnapshotContent objects is successful (by validating that both VolumeSnapshot and VolumeSnapshotContent point at each other) before using this object. - properties: - boundVolumeSnapshotContentName: - description: 'boundVolumeSnapshotContentName is the name of the VolumeSnapshotContent object to which this VolumeSnapshot object intends to bind to. If not specified, it indicates that the VolumeSnapshot object has not been successfully bound to a VolumeSnapshotContent object yet. NOTE: To avoid possible security issues, consumers must verify binding between VolumeSnapshot and VolumeSnapshotContent objects is successful (by validating that both VolumeSnapshot and VolumeSnapshotContent point at each other) before using this object.' - type: string - creationTime: - description: creationTime is the timestamp when the point-in-time snapshot is taken by the underlying storage system. In dynamic snapshot creation case, this field will be filled in by the snapshot controller with the "creation_time" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "creation_time" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. If not specified, it may indicate that the creation time of the snapshot is unknown. - format: date-time - type: string - error: - description: error is the last observed error during snapshot creation, if any. This field could be helpful to upper level controllers(i.e., application controller) to decide whether they should continue on waiting for the snapshot to be created based on the type of error reported. The snapshot controller will keep retrying when an error occurrs during the snapshot creation. Upon success, this error field will be cleared. - properties: - message: - description: 'message is a string detailing the encountered error during snapshot creation if specified. NOTE: message may be logged, and it should not contain sensitive information.' - type: string - time: - description: time is the timestamp when the error was encountered. - format: date-time - type: string - type: object - readyToUse: - description: readyToUse indicates if the snapshot is ready to be used to restore a volume. In dynamic snapshot creation case, this field will be filled in by the snapshot controller with the "ready_to_use" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "ready_to_use" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, this field will be set to "True". If not specified, it means the readiness of a snapshot is unknown. - type: boolean - restoreSize: - type: string - description: restoreSize represents the minimum size of volume required to create a volume from this snapshot. In dynamic snapshot creation case, this field will be filled in by the snapshot controller with the "size_bytes" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "size_bytes" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. When restoring a volume from this snapshot, the size of the volume MUST NOT be smaller than the restoreSize if it is specified, otherwise the restoration will fail. If not specified, it indicates that the size is unknown. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: snapshot.storage.k8s.io/v1 -kind: VolumeSnapshotClass -metadata: - name: scw-snapshot -driver: csi.scaleway.com -deletionPolicy: Delete ---- -kind: Deployment -apiVersion: apps/v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system -spec: - selector: - matchLabels: - app: scaleway-csi-controller - replicas: 1 - template: - metadata: - labels: - app: scaleway-csi-controller - spec: - priorityClassName: system-cluster-critical - serviceAccount: scaleway-csi-controller - containers: - - name: scaleway-csi-plugin - image: scaleway/scaleway-csi:v0.2.0 - args : - - "--endpoint=$(CSI_ENDPOINT)" - - "--mode=controller" - env: - - name: CSI_ENDPOINT - value: unix:///var/lib/csi/sockets/pluginproxy/csi.sock - envFrom: - - secretRef: - name: scaleway-secret - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - ports: - - name: healthz - containerPort: 9808 - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: healthz - initialDelaySeconds: 10 - timeoutSeconds: 3 - periodSeconds: 2 - failureThreshold: 5 - - name: csi-provisioner - image: k8s.gcr.io/sig-storage/csi-provisioner:v3.0.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - - "--feature-gates=Topology=true" - - "--default-fstype=ext4" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-attacher - image: k8s.gcr.io/sig-storage/csi-attacher:v3.4.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: csi-snapshotter - image: k8s.gcr.io/sig-storage/csi-snapshotter:v4.2.1 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: snapshot-controller - image: k8s.gcr.io/sig-storage/snapshot-controller:v4.1.1 - args: - - "--v=5" - - "--leader-election" - - name: csi-resizer - image: k8s.gcr.io/sig-storage/csi-resizer:v1.3.0 - args: - - "--v=5" - - "--csi-address=$(CSI_ADDRESS)" - - "--leader-election" - env: - - name: CSI_ADDRESS - value: /var/lib/csi/sockets/pluginproxy/mock.socket - volumeMounts: - - name: socket-dir - mountPath: /var/lib/csi/sockets/pluginproxy/ - - name: liveness-probe - image: k8s.gcr.io/sig-storage/livenessprobe:v2.6.0 - args: - - "--csi-address=$(CSI_ADDRESS)" - env: - - name: CSI_ADDRESS - value: /csi/csi.sock - volumeMounts: - - name: socket-dir - mountPath: /csi - volumes: - - name: socket-dir - emptyDir: {} ---- -kind: ServiceAccount -apiVersion: v1 -metadata: - name: scaleway-csi-controller - namespace: kube-system ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-provisioner -rules: - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "create", "delete"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-controller -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-provisioner - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "update", "patch"] - - apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["csinodes"] - verbs: ["get", "list", "watch"] - - apiGroups: ["storage.k8s.io"] - resources: ["volumeattachments"] - verbs: ["get", "list", "watch", "update", "patch"] - - apiGroups: ["storage.k8s.io"] - resources: ["volumeattachments/status"] - verbs: ["patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-attacher -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-attacher - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] - - apiGroups: [""] - resources: ["secrets"] - verbs: ["get", "list"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents"] - verbs: ["create", "get", "list", "watch", "update", "delete"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshots/status"] - verbs: ["update"] - - apiGroups: ["snapshot.storage.k8s.io"] - resources: ["volumesnapshotcontents/status"] - verbs: ["update"] - - apiGroups: ["apiextensions.k8s.io"] - resources: ["customresourcedefinitions"] - verbs: ["create", "list", "watch", "delete", "get", "update"] - - apiGroups: ["coordination.k8s.io"] - resources: ["leases"] - verbs: ["get", "watch", "list", "delete", "update", "create"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: scaleway-csi-snapshotter -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: scaleway-csi-snapshotter - apiGroup: rbac.authorization.k8s.io ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: external-resizer -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "patch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["pods"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims/status"] - verbs: ["patch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["list", "watch", "create", "update", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: csi-resizer-role -subjects: - - kind: ServiceAccount - name: scaleway-csi-controller - namespace: kube-system -roleRef: - kind: ClusterRole - name: external-resizer - apiGroup: rbac.authorization.k8s.io diff --git a/deploy/kubernetes/scaleway-secret.yaml b/deploy/kubernetes/scaleway-secret.yaml deleted file mode 100644 index 936880a..0000000 --- a/deploy/kubernetes/scaleway-secret.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Here is an example about how you can have the secrets required to run the scaleway-csi driver. -# To generate an access/secret, see https://www.scaleway.com/en/docs/generate-an-api-token/ ---- -apiVersion: v1 -kind: Secret -metadata: - name: scaleway-secret - namespace: kube-system -type: Opaque -stringData: - SCW_ACCESS_KEY: "YOUR-ACCESS-KEY" - SCW_SECRET_KEY: "YOUR-SECRET-KEY" - # Project ID could also be an Organization ID - SCW_DEFAULT_PROJECT_ID: "YOUR-PROJECT-ID" - # The default zone where the block volumes will be created, ex: fr-par-1 - SCW_DEFAULT_ZONE: "fr-par-1" diff --git a/driver/controller.go b/driver/controller.go deleted file mode 100644 index bef1a63..0000000 --- a/driver/controller.go +++ /dev/null @@ -1,867 +0,0 @@ -package driver - -import ( - "context" - "fmt" - "os" - "strconv" - "strings" - "sync" - - "github.com/container-storage-interface/spec/lib/go/csi" - "github.com/scaleway/scaleway-csi/scaleway" - "github.com/scaleway/scaleway-sdk-go/api/instance/v1" - "github.com/scaleway/scaleway-sdk-go/scw" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/timestamppb" - "k8s.io/klog/v2" -) - -var ( - // controllerCapabilities represents the capabilites of the Scaleway Block Volumes - controllerCapabilities = []csi.ControllerServiceCapability_RPC_Type{ - csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, - csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME, - csi.ControllerServiceCapability_RPC_LIST_VOLUMES, - csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT, - csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS, - csi.ControllerServiceCapability_RPC_EXPAND_VOLUME, - csi.ControllerServiceCapability_RPC_LIST_VOLUMES_PUBLISHED_NODES, - csi.ControllerServiceCapability_RPC_GET_VOLUME, - csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER, - } - - // supportedAccessModes represents the supported access modes for the Scaleway Block Volumes - supportedAccessModes = []csi.VolumeCapability_AccessMode{ - csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, - }, - csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER, - }, - } - - scwVolumeID = DriverName + "/volume-id" - scwVolumeName = DriverName + "/volume-name" - scwVolumeZone = DriverName + "/volume-zone" - - volumeTypeKey = "type" - encryptedKey = "encrypted" -) - -type controllerService struct { - scaleway *scaleway.Scaleway - config *DriverConfig - mux sync.Mutex -} - -func newControllerService(config *DriverConfig) controllerService { - userAgent := fmt.Sprintf("%s %s (%s)", DriverName, driverVersion, gitCommit) - if extraUA := os.Getenv(ExtraUserAgentEnv); extraUA != "" { - userAgent = userAgent + " " + extraUA - } - - return controllerService{ - config: config, - scaleway: scaleway.NewScaleway(userAgent), - } -} - -// CreateVolume creates a new volume with the given CreateVolumeRequest. -// This function is idempotent -func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { - klog.V(4).Infof("CreateVolume: called with %s", stripSecretFromReq(*req)) - - volumeName := req.GetName() - if volumeName == "" { - return nil, status.Error(codes.InvalidArgument, "name not provided") - } - - volumeCapabilities := req.GetVolumeCapabilities() - if len(volumeCapabilities) == 0 { - return nil, status.Error(codes.InvalidArgument, "volumeCapabilities not provided") - } - - err := validateVolumeCapabilities(volumeCapabilities) - if err != nil { - return nil, status.Errorf(codes.InvalidArgument, "volumeCapabilities not supported: %s", err) - } - - encrypted := false - - volumeType := scaleway.DefaultVolumeType - for key, value := range req.GetParameters() { - switch strings.ToLower(key) { - case volumeTypeKey: - volumeType = instance.VolumeVolumeType(value) - case encryptedKey: - encryptedValue, err := strconv.ParseBool(value) - if err != nil { - return nil, status.Errorf(codes.InvalidArgument, "invalid bool value (%s) for parameter %s: %v", value, key, err) - } - // TODO check if this value has changed? - encrypted = encryptedValue - default: - return nil, status.Errorf(codes.InvalidArgument, "invalid parameter key %s", key) - } - } - - minSize, maxSize, err := d.scaleway.GetVolumeLimits(string(volumeType)) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - - size, err := getVolumeRequestCapacity(minSize, maxSize, req.GetCapacityRange()) - if err != nil { - return nil, status.Errorf(codes.OutOfRange, "capacityRange invalid: %s", err) - } - - scwVolumeName := d.config.Prefix + volumeName - // TODO check all zones - volume, err := d.scaleway.GetVolumeByName(scwVolumeName, size, volumeType) - if err != nil { - switch err { - case scaleway.ErrVolumeNotFound: // all good - case scaleway.ErrDifferentSize: - return nil, status.Error(codes.AlreadyExists, err.Error()) - case scaleway.ErrMultipleVolumes: - return nil, status.Error(codes.Internal, err.Error()) - default: - return nil, status.Error(codes.Internal, err.Error()) - } - } else { // volume exists - return &csi.CreateVolumeResponse{ - Volume: &csi.Volume{ - VolumeId: volume.Zone.String() + "/" + volume.ID, - CapacityBytes: int64(volume.Size), - AccessibleTopology: newAccessibleTopology(volume.Zone), - VolumeContext: map[string]string{ - encryptedKey: strconv.FormatBool(encrypted), - }, - }, - }, nil - } - - var contentSource *csi.VolumeContentSource - var snapshotID *string - var snapshotZone scw.Zone - if req.GetVolumeContentSource() != nil { - if _, ok := req.GetVolumeContentSource().GetType().(*csi.VolumeContentSource_Snapshot); !ok { - return nil, status.Error(codes.InvalidArgument, "unsupported volumeContentSource type") - } - sourceSnapshot := req.GetVolumeContentSource().GetSnapshot() - if sourceSnapshot == nil { - return nil, status.Error(codes.Internal, "error retrieving snapshot from the volumeContentSource") - } - - sourceSnapshotID, sourceSnapshotZone, err := getSnapshotIDAndZone(sourceSnapshot.GetSnapshotId()) - if err != nil { - return nil, err - } - - // TODO check all zones - snapshotResp, err := d.scaleway.GetSnapshot(&instance.GetSnapshotRequest{ - SnapshotID: sourceSnapshotID, - Zone: sourceSnapshotZone, - }) - if err != nil { - if _, ok := err.(*scw.ResourceNotFoundError); ok { - return nil, status.Errorf(codes.NotFound, "snapshot %s not found", sourceSnapshotID) - } - return nil, status.Error(codes.Internal, err.Error()) - } - snapshotID = &sourceSnapshotID - snapshotZone = snapshotResp.Snapshot.Zone - contentSource = &csi.VolumeContentSource{ - Type: &csi.VolumeContentSource_Snapshot{ - Snapshot: &csi.VolumeContentSource_SnapshotSource{ - SnapshotId: scaleway.ExpandSnapshotID(snapshotResp.Snapshot), - }, - }, - } - } - - chosenZones, err := chooseZones(req.GetAccessibilityRequirements(), snapshotZone) - if err != nil { - return nil, err - } - if len(chosenZones) == 0 { - chosenZones = append(chosenZones, scw.Zone("")) // this will use the default zone of the client - } - - volumeSize := scw.Size(size) - volumeRequest := &instance.CreateVolumeRequest{ - Name: scwVolumeName, - VolumeType: volumeType, - } - if contentSource != nil { - volumeRequest.BaseSnapshot = snapshotID - } else { - volumeRequest.Size = &volumeSize - } - - if len(chosenZones) == 1 { // either it's with an empty zone, the snapshot zone, or just one classic zone - if chosenZones[0] != scw.Zone("") { - volumeRequest.Zone = chosenZones[0] - } - volumeResp, err := d.scaleway.CreateVolume(volumeRequest) - if err != nil { - if _, ok := err.(*scw.ResourceNotFoundError); ok { - return nil, status.Error(codes.NotFound, err.Error()) - } - return nil, status.Error(codes.Internal, err.Error()) - } - segments := map[string]string{ - ZoneTopologyKey: string(volumeResp.Volume.Zone), - } - - return &csi.CreateVolumeResponse{ - Volume: &csi.Volume{ - VolumeId: volumeResp.Volume.Zone.String() + "/" + volumeResp.Volume.ID, - ContentSource: contentSource, - CapacityBytes: int64(volumeResp.Volume.Size), - AccessibleTopology: []*csi.Topology{ - { - Segments: segments, - }, - }, - VolumeContext: map[string]string{ - encryptedKey: strconv.FormatBool(encrypted), - }, - }, - }, nil - } - - var errors []string - for _, zone := range chosenZones { // if we multiple wanted zone, we try each one - volumeRequest.Zone = zone - volumeResp, err := d.scaleway.CreateVolume(volumeRequest) - if err != nil { - errors = append(errors, err.Error()) - continue - } - - segments := map[string]string{ - ZoneTopologyKey: string(volumeResp.Volume.Zone), - } - - return &csi.CreateVolumeResponse{ - Volume: &csi.Volume{ - VolumeId: volumeResp.Volume.Zone.String() + "/" + volumeResp.Volume.ID, - ContentSource: contentSource, - CapacityBytes: int64(volumeResp.Volume.Size), - AccessibleTopology: []*csi.Topology{ - { - Segments: segments, - }, - }, - VolumeContext: map[string]string{ - encryptedKey: strconv.FormatBool(encrypted), - }, - }, - }, nil - } - - // here errors is not empty - return nil, status.Errorf(codes.Internal, "multiple error while trying different zones: %s", strings.Join(errors, "; ")) -} - -// DeleteVolume deprovision a volume. -// This operation MUST be idempotent. -func (d *controllerService) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) { - klog.V(4).Infof("DeleteVolume called with %s", stripSecretFromReq(*req)) - volumeID, volumeZone, err := getVolumeIDAndZone(req.GetVolumeId()) - if err != nil { - return nil, err - } - - volumeResp, err := d.scaleway.GetVolume(&instance.GetVolumeRequest{ - VolumeID: volumeID, - Zone: volumeZone, - }) - if err != nil { - if _, ok := err.(*scw.ResourceNotFoundError); ok { - klog.V(4).Infof("volume with ID %s not found", volumeID) - return &csi.DeleteVolumeResponse{}, nil - } - - return nil, status.Error(codes.Internal, err.Error()) - } - if volumeResp.Volume.Server != nil { - return nil, status.Error(codes.FailedPrecondition, "volume is still atached to a server") - } - - klog.V(4).Infof("deleting volume with ID %s", volumeID) - err = d.scaleway.DeleteVolume(&instance.DeleteVolumeRequest{ - VolumeID: volumeResp.Volume.ID, - Zone: volumeResp.Volume.Zone, - }) - if err != nil { - if _, ok := err.(*scw.ResourceNotFoundError); ok { - klog.V(4).Infof("volume with ID %s not found", volumeID) - return &csi.DeleteVolumeResponse{}, nil - } - - return nil, status.Error(codes.Internal, err.Error()) - } - klog.V(4).Infof("volume with ID %s deleted", volumeID) - return &csi.DeleteVolumeResponse{}, nil -} - -// ControllerPublishVolume perform the work that is necessary for making the volume available on the given node. -// This operation MUST be idempotent. -func (d *controllerService) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { - klog.V(4).Infof("ControllerPublishVolume called with %s", stripSecretFromReq(*req)) - - volumeID, volumeZone, err := getVolumeIDAndZone(req.GetVolumeId()) - if err != nil { - return nil, err - } - - nodeID, nodeZone, err := getNodeIDAndZone(req.GetNodeId()) - if err != nil { - return nil, err - } - - volumeCapability := req.GetVolumeCapability() - if volumeCapability == nil { - return nil, status.Error(codes.InvalidArgument, "volumeCapability is not provided") - } - - err = validateVolumeCapabilities([]*csi.VolumeCapability{volumeCapability}) - if err != nil { - return nil, status.Errorf(codes.InvalidArgument, "volumeCapability not supported: %s", err) - } - - volumeResp, err := d.scaleway.GetVolume(&instance.GetVolumeRequest{ - VolumeID: volumeID, - Zone: volumeZone, - }) - if err != nil { - if _, ok := err.(*scw.ResourceNotFoundError); ok { - return nil, status.Errorf(codes.NotFound, "volume %s not found", volumeID) - } - return nil, status.Error(codes.Internal, err.Error()) - } - - serverResp, err := d.scaleway.GetServer(&instance.GetServerRequest{ - ServerID: nodeID, - Zone: nodeZone, - }) - if err != nil { - if _, ok := err.(*scw.ResourceNotFoundError); ok { - return nil, status.Errorf(codes.NotFound, "instance %s not found", volumeID) - } - return nil, status.Error(codes.Internal, err.Error()) - } - - if volumeResp.Volume.Server != nil { - if volumeResp.Volume.Server.ID == serverResp.Server.ID { - return &csi.ControllerPublishVolumeResponse{ - PublishContext: map[string]string{ - scwVolumeName: volumeResp.Volume.Name, - scwVolumeID: volumeResp.Volume.ID, - scwVolumeZone: volumeResp.Volume.Zone.String(), - }, - }, nil - } - return nil, status.Errorf(codes.FailedPrecondition, "volume %s already attached to another node %s", volumeID, volumeResp.Volume.Server.ID) - } - - volumesCount := len(serverResp.Server.Volumes) - - if volumesCount == maxVolumesPerNode { - return nil, status.Error(codes.ResourceExhausted, "max number of volumes for this instance") - } - - if volumeResp.Volume.Zone != serverResp.Server.Zone { - return nil, status.Error(codes.InvalidArgument, "volume and node are not in the same zone") - } - - d.mux.Lock() - defer d.mux.Unlock() - _, err = d.scaleway.AttachVolume(&instance.AttachVolumeRequest{ - ServerID: nodeID, - VolumeID: volumeID, - Zone: volumeResp.Volume.Zone, - }) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - - return &csi.ControllerPublishVolumeResponse{ - PublishContext: map[string]string{ - scwVolumeName: volumeResp.Volume.Name, - scwVolumeID: volumeResp.Volume.ID, - scwVolumeZone: volumeResp.Volume.Zone.String(), - }, - }, nil -} - -// ControllerUnpublishVolume is the reverse operation of ControllerPublishVolume -// This operation MUST be idempotent. -func (d *controllerService) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) { - klog.V(4).Infof("ControllerUnpublishVolume called with %s", stripSecretFromReq(*req)) - - volumeID, volumeZone, err := getVolumeIDAndZone(req.GetVolumeId()) - if err != nil { - return nil, err - } - - nodeID, nodeZone, err := getNodeIDAndZone(req.GetNodeId()) - if err != nil { - return nil, err - } - - volumeResp, err := d.scaleway.GetVolume(&instance.GetVolumeRequest{ - VolumeID: volumeID, - Zone: volumeZone, - }) - if err != nil { - if _, ok := err.(*scw.ResourceNotFoundError); ok { - return &csi.ControllerUnpublishVolumeResponse{}, nil - } - return nil, status.Error(codes.Internal, err.Error()) - } - - if volumeResp.Volume.Server == nil { - return &csi.ControllerUnpublishVolumeResponse{}, nil - } - - _, err = d.scaleway.GetServer(&instance.GetServerRequest{ - ServerID: nodeID, - Zone: nodeZone, - }) - if err != nil { - if _, ok := err.(*scw.ResourceNotFoundError); ok { - return &csi.ControllerUnpublishVolumeResponse{}, nil - } - return nil, status.Error(codes.Internal, err.Error()) - } - - d.mux.Lock() - defer d.mux.Unlock() - _, err = d.scaleway.DetachVolume(&instance.DetachVolumeRequest{ - VolumeID: volumeID, - Zone: volumeResp.Volume.Zone, - }) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - - return &csi.ControllerUnpublishVolumeResponse{}, nil -} - -// ValidateVolumeCapabilities check if a pre-provisioned volume has all the capabilities -// that the CO wants. This RPC call SHALL return confirmed only if all the -// volume capabilities specified in the request are supported. -// This operation MUST be idempotent. -func (d *controllerService) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) { - klog.V(4).Infof("ValidateVolumeCapabilities called with %s", stripSecretFromReq(*req)) - volumeID, volumeZone, err := getVolumeIDAndZone(req.GetVolumeId()) - if err != nil { - return nil, err - } - - volumeCapabilities := req.GetVolumeCapabilities() - if volumeCapabilities == nil { - return nil, status.Error(codes.InvalidArgument, "volumeCapabilities is not provided") - } - - _, err = d.scaleway.GetVolume(&instance.GetVolumeRequest{ - VolumeID: volumeID, - Zone: volumeZone, - }) - if err != nil { - if _, ok := err.(*scw.ResourceNotFoundError); ok { - return nil, status.Errorf(codes.NotFound, "volume %s not found", volumeID) - } - - return nil, status.Error(codes.Internal, err.Error()) - } - // TODO check stuff - return &csi.ValidateVolumeCapabilitiesResponse{ - Confirmed: &csi.ValidateVolumeCapabilitiesResponse_Confirmed{ - VolumeCapabilities: []*csi.VolumeCapability{ - { - AccessMode: &supportedAccessModes[0], // TODO refactor - }, - }, - }, - }, nil -} - -// ListVolumes returns the list of the requested volumes -func (d *controllerService) ListVolumes(ctx context.Context, req *csi.ListVolumesRequest) (*csi.ListVolumesResponse, error) { - klog.V(4).Infof("ListVolumes called with %s", stripSecretFromReq(*req)) - var numberResults int - var err error - - startingToken := req.GetStartingToken() - if startingToken != "" { - numberResults, err = strconv.Atoi(startingToken) - if err != nil { - return nil, status.Error(codes.Aborted, "invalid startingToken") - } - } - - volumesResp, err := d.scaleway.ListVolumes(&instance.ListVolumesRequest{}, scw.WithContext(ctx), scw.WithAllPages()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - volumes := volumesResp.Volumes - - nextPage := "" - maxEntries := req.GetMaxEntries() - if maxEntries == 0 { - if numberResults != 0 { - volumes = volumes[numberResults:] - } - } else { - if int(maxEntries) > (len(volumes) - numberResults) { - volumes = volumes[numberResults:] - } else { - volumes = volumes[numberResults : numberResults+int(maxEntries)] - nextPage = strconv.Itoa(numberResults + int(maxEntries)) - } - } - - var volumesEntries []*csi.ListVolumesResponse_Entry - for _, volume := range volumes { - var serversID []string - if volume.Server != nil { - serversID = append(serversID, volume.Zone.String()+"/"+volume.Server.ID) - } - volumesEntries = append(volumesEntries, &csi.ListVolumesResponse_Entry{ - Volume: &csi.Volume{ - VolumeId: scaleway.ExpandVolumeID(volume), - CapacityBytes: int64(volume.Size), - }, - Status: &csi.ListVolumesResponse_VolumeStatus{ - PublishedNodeIds: serversID, - }, - }) - } - - return &csi.ListVolumesResponse{ - Entries: volumesEntries, - NextToken: nextPage, - }, nil -} - -// GetCapacity returns the capacity of the storage pool from which the controller provisions volumes. -func (d *controllerService) GetCapacity(ctx context.Context, req *csi.GetCapacityRequest) (*csi.GetCapacityResponse, error) { - klog.V(4).Infof("GetCapacity is not yet implemented") - return nil, status.Error(codes.Unimplemented, "GetCapacity is not yet implemented") -} - -// ControllerGetCapabilities returns the supported capabilities of controller service provided by the Plugin. -func (d *controllerService) ControllerGetCapabilities(ctx context.Context, req *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) { - klog.V(4).Infof("ControllerGetCapabilities called with %v", stripSecretFromReq(*req)) - var capabilities []*csi.ControllerServiceCapability - for _, capability := range controllerCapabilities { - capabilities = append(capabilities, &csi.ControllerServiceCapability{ - Type: &csi.ControllerServiceCapability_Rpc{ - Rpc: &csi.ControllerServiceCapability_RPC{ - Type: capability, - }, - }, - }) - } - return &csi.ControllerGetCapabilitiesResponse{Capabilities: capabilities}, nil -} - -// CreateSnapshot creates a snapshot of the given volume -func (d *controllerService) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) { - klog.V(4).Infof("CreateSnapshot called with %v", stripSecretFromReq(*req)) - sourceVolumeID, sourceVolumeZone, err := getSourceVolumeIDAndZone(req.GetSourceVolumeId()) - if err != nil { - return nil, err - } - - name := req.GetName() - if name == "" { - return nil, status.Error(codes.InvalidArgument, "name not provided") - } - - snapshot, err := d.scaleway.GetSnapshotByName(name, sourceVolumeID, sourceVolumeZone) - if err != nil { - switch err { - case scaleway.ErrSnapshotNotFound: // all good - case scaleway.ErrSnapshotSameName: - return nil, status.Errorf(codes.AlreadyExists, "a snapshot with the name %s already exists", name) - default: - return nil, status.Error(codes.Internal, err.Error()) - } - } - - if snapshot != nil { - snapshotResp := &csi.Snapshot{ - SizeBytes: int64(snapshot.Size), // TODO(pcyvoct) ugly cast - SnapshotId: scaleway.ExpandSnapshotID(snapshot), - SourceVolumeId: sourceVolumeZone.String() + "/" + sourceVolumeID, - ReadyToUse: snapshot.State == instance.SnapshotStateAvailable, - } - - if snapshot.CreationDate != nil { - snapshotResp.CreationTime = timestamppb.New(*snapshot.CreationDate) - } - - return &csi.CreateSnapshotResponse{ - Snapshot: snapshotResp, - }, nil - - } - - snapshotResp, err := d.scaleway.CreateSnapshot(&instance.CreateSnapshotRequest{ - VolumeID: &sourceVolumeID, - Name: name, - Zone: sourceVolumeZone, - }) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - - snapshotProtoResp := &csi.Snapshot{ - SizeBytes: int64(snapshotResp.Snapshot.Size), // TODO(pcyvoct) ugly cast - SnapshotId: scaleway.ExpandSnapshotID(snapshotResp.Snapshot), - SourceVolumeId: sourceVolumeZone.String() + "/" + sourceVolumeID, - ReadyToUse: snapshotResp.Snapshot.State == instance.SnapshotStateAvailable, - } - - if snapshotResp.Snapshot.CreationDate != nil { - snapshotProtoResp.CreationTime = timestamppb.New(*snapshotResp.Snapshot.CreationDate) - } - - return &csi.CreateSnapshotResponse{ - Snapshot: snapshotProtoResp, - }, nil -} - -// DeleteSnapshot deletes the given snapshot -func (d *controllerService) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) { - klog.V(4).Infof("DeleteSnapshot called with %s", stripSecretFromReq(*req)) - snapshotID, snapshotZone, err := getSnapshotIDAndZone(req.GetSnapshotId()) - if err != nil { - return nil, err - } - - err = d.scaleway.DeleteSnapshot(&instance.DeleteSnapshotRequest{ - SnapshotID: snapshotID, - Zone: snapshotZone, - }) - if err != nil { - if _, ok := err.(*scw.ResourceNotFoundError); ok { - klog.V(4).Infof("snapshot with ID %s not found", snapshotID) - return &csi.DeleteSnapshotResponse{}, nil - } - - return nil, status.Error(codes.Internal, err.Error()) - } - return &csi.DeleteSnapshotResponse{}, nil -} - -// ListSnapshots return the information about all snapshots on the -// storage system within the given parameters regardless of how -// they were created. ListSnapshots SHALL NOT list a snapshot that -// is being created but has not been cut successfully yet. -func (d *controllerService) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) { - klog.V(4).Infof("ListSnapshots called with %s", stripSecretFromReq(*req)) - var numberResults int - var err error - - startingToken := req.GetStartingToken() - if startingToken != "" { - numberResults, err = strconv.Atoi(startingToken) - if err != nil { - return nil, status.Error(codes.Aborted, "invalid startingToken") - } - } - - // TODO fix zones - snapshotID, snapshotZone, _ := getSnapshotIDAndZone(req.GetSnapshotId()) - sourceVolumeID, sourceVolumeZone, _ := getSourceVolumeIDAndZone(req.GetSourceVolumeId()) - - snapshots := []*instance.Snapshot{} - switch { - case req.SnapshotId != "": - snapshotResp, err := d.scaleway.GetSnapshot(&instance.GetSnapshotRequest{ - SnapshotID: snapshotID, - Zone: snapshotZone, - }, scw.WithContext(ctx)) - if err != nil { - // not found should return empty list - if _, ok := err.(*scw.ResourceNotFoundError); ok { - return &csi.ListSnapshotsResponse{ - Entries: []*csi.ListSnapshotsResponse_Entry{}, - }, nil - } - return nil, status.Error(codes.Internal, err.Error()) - } - snapshots = []*instance.Snapshot{snapshotResp.Snapshot} - case sourceVolumeID != "": - snapshotsResp, err := d.scaleway.ListSnapshots(&instance.ListSnapshotsRequest{ - BaseVolumeID: &sourceVolumeID, - Zone: sourceVolumeZone, - }, scw.WithContext(ctx), scw.WithAllPages()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - snapshots = snapshotsResp.Snapshots - default: - snapshotsResp, err := d.scaleway.ListSnapshots(&instance.ListSnapshotsRequest{}, scw.WithContext(ctx), scw.WithAllPages()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - snapshots = snapshotsResp.Snapshots - } - - nextPage := "" - maxEntries := req.GetMaxEntries() - if maxEntries == 0 { - if numberResults != 0 { - snapshots = snapshots[numberResults:] - } - } else { - if int(maxEntries) > (len(snapshots) - numberResults) { - snapshots = snapshots[numberResults:] - } else { - snapshots = snapshots[numberResults : numberResults+int(maxEntries)] - nextPage = strconv.Itoa(numberResults + int(maxEntries)) - } - } - - var snapshotsEntries []*csi.ListSnapshotsResponse_Entry - for _, snap := range snapshots { - sourceID := "" - if snap.BaseVolume != nil { - sourceID = snap.Zone.String() + "/" + snap.BaseVolume.ID - } - - snapshotProtoResp := &csi.Snapshot{ - SizeBytes: int64(snap.Size), // TODO(pcyvoct) ugly cast - SnapshotId: scaleway.ExpandSnapshotID(snap), - SourceVolumeId: sourceID, - ReadyToUse: snap.State == instance.SnapshotStateAvailable, - } - - if snap.CreationDate != nil { - snapshotProtoResp.CreationTime = timestamppb.New(*snap.CreationDate) - } - - snapshotsEntries = append(snapshotsEntries, &csi.ListSnapshotsResponse_Entry{ - Snapshot: snapshotProtoResp, - }) - } - - return &csi.ListSnapshotsResponse{ - Entries: snapshotsEntries, - NextToken: nextPage, - }, nil -} - -// ControllerExpandVolume expands the given volume -func (d *controllerService) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) { - klog.V(4).Infof("ControllerExpandVolume called with %s", stripSecretFromReq(*req)) - volumeID, volumeZone, err := getVolumeIDAndZone(req.GetVolumeId()) - if err != nil { - return nil, err - } - - nodeExpansionRequired := true - - volumeCapability := req.GetVolumeCapability() - if volumeCapability != nil { - err := validateVolumeCapability(volumeCapability) - if err != nil { - return nil, status.Errorf(codes.InvalidArgument, "volumeCapabilities not supported: %s", err) - } - switch volumeCapability.GetAccessType().(type) { - case *csi.VolumeCapability_Block: - nodeExpansionRequired = false - } - } - - volumeResp, err := d.scaleway.GetVolume(&instance.GetVolumeRequest{ - VolumeID: volumeID, - Zone: volumeZone, - }) - if err != nil { - if _, ok := err.(*scw.ResourceNotFoundError); ok { - return nil, status.Errorf(codes.NotFound, "volume %s not found", volumeID) - } - return nil, status.Error(codes.Internal, err.Error()) - } - - minSize, maxSize, err := d.scaleway.GetVolumeLimits(string(volumeResp.Volume.VolumeType)) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - - newSize, err := getVolumeRequestCapacity(minSize, maxSize, req.GetCapacityRange()) - if err != nil { - return nil, status.Errorf(codes.OutOfRange, "capacityRange invalid: %s", err) - } - - if newSize < int64(volumeResp.Volume.Size) { - return nil, status.Error(codes.InvalidArgument, "the new size of the volume will be less than the actual size") - } - - _, err = d.scaleway.UpdateVolume(&instance.UpdateVolumeRequest{ - Zone: volumeZone, - VolumeID: volumeID, - Size: scw.SizePtr(scw.Size(newSize)), - }) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - - vol, err := d.scaleway.WaitForVolume(&instance.WaitForVolumeRequest{ - VolumeID: volumeID, - Zone: volumeZone, - }) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - if vol.State != instance.VolumeStateAvailable { - return nil, status.Errorf(codes.Internal, "volume %s is in state %s", volumeID, vol.State) - } - - return &csi.ControllerExpandVolumeResponse{CapacityBytes: newSize, NodeExpansionRequired: nodeExpansionRequired}, nil -} - -// ControllerGetVolume gets a specific volume. -func (d *controllerService) ControllerGetVolume(ctx context.Context, req *csi.ControllerGetVolumeRequest) (*csi.ControllerGetVolumeResponse, error) { - klog.V(4).Infof("ControllerGetVolume called with %s", stripSecretFromReq(*req)) - volumeID, volumeZone, err := getVolumeIDAndZone(req.GetVolumeId()) - if err != nil { - return nil, err - } - - volumeResp, err := d.scaleway.GetVolume(&instance.GetVolumeRequest{ - VolumeID: volumeID, - Zone: volumeZone, - }) - if err != nil { - if _, ok := err.(*scw.ResourceNotFoundError); ok { - return nil, status.Errorf(codes.NotFound, "volume %s not found", volumeID) - } - return nil, status.Error(codes.Internal, err.Error()) - } - - var serversID []string - if volumeResp.Volume.Server != nil { - serversID = append(serversID, volumeResp.Volume.Zone.String()+"/"+volumeResp.Volume.Server.ID) - } - - return &csi.ControllerGetVolumeResponse{ - Volume: &csi.Volume{ - VolumeId: volumeResp.Volume.Zone.String() + "/" + volumeResp.Volume.ID, - CapacityBytes: int64(volumeResp.Volume.Size), - }, - Status: &csi.ControllerGetVolumeResponse_VolumeStatus{ - PublishedNodeIds: serversID, - }, - }, nil -} diff --git a/driver/errors.go b/driver/errors.go deleted file mode 100644 index 6ce2fe7..0000000 --- a/driver/errors.go +++ /dev/null @@ -1,28 +0,0 @@ -package driver - -import ( - "errors" -) - -var ( - errSchemeNotSupported = errors.New("scheme not supported for endpoint") - errRemovingSocket = errors.New("error removing existing socket") - errProjectIDNotSet = errors.New("projectID must be set") - - errTargetPathEmpty = errors.New("target path empty") - errTargetNotSharedMounter = errors.New("target is not shared mounter") - errTargetNotMounterOnRightDevice = errors.New("target is not mounted on the right device") - errFsTypeEmpty = errors.New("filesystem type is empty") - errDevicePathIsNotDevice = errors.New("device path does not point on a block device") - - errVolumeCapabilitiesIsNil = errors.New("volume capabilites is nil") - errVolumeCapabilityIsNil = errors.New("volume capability is nil") - errBothMountBlockVolumes = errors.New("both mount and block volume type specified") - errAccessModeNotSupported = errors.New("access mode not supported") - - errLimitBytesLessThanRequiredBytes = errors.New("limit size is less than required size") - errRequiredBytesLessThanMinimun = errors.New("required size is less than the minimun size") - errLimitBytesLessThanMinimum = errors.New("limit size is less than the minimun size") - errRequiredBytesGreaterThanMaximun = errors.New("required size is greater than the maximum size") - errLimitBytesGreaterThanMaximum = errors.New("limit size is greater than the maximum size") -) diff --git a/driver/helpers.go b/driver/helpers.go deleted file mode 100644 index b022dfa..0000000 --- a/driver/helpers.go +++ /dev/null @@ -1,284 +0,0 @@ -package driver - -import ( - "fmt" - "os" - "path/filepath" - "reflect" - "strings" - - "github.com/container-storage-interface/spec/lib/go/csi" - "github.com/scaleway/scaleway-sdk-go/scw" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "k8s.io/klog/v2" -) - -func getSnapshotIDAndZone(id string) (string, scw.Zone, error) { - return extractIDAndZone(id, "snapshotID") -} - -func getSourceVolumeIDAndZone(id string) (string, scw.Zone, error) { - return extractIDAndZone(id, "sourceVolumeID") -} - -func getVolumeIDAndZone(id string) (string, scw.Zone, error) { - return extractIDAndZone(id, "volumeID") -} - -func getNodeIDAndZone(id string) (string, scw.Zone, error) { - return extractIDAndZone(id, "nodeID") -} - -func extractIDAndZone(id string, name string) (string, scw.Zone, error) { - if id == "" { - return "", scw.Zone(""), status.Errorf(codes.InvalidArgument, "%s is not provided", name) - } - splitID := strings.Split(id, "/") - if len(splitID) > 2 { - return "", scw.Zone(""), status.Errorf(codes.InvalidArgument, "wrong format for %s", name) - } else if len(splitID) == 1 { - return splitID[0], scw.Zone(""), nil - } else { // id like zone/uuid - zone, err := scw.ParseZone(splitID[0]) - if err != nil { - klog.Warningf("wrong zone in %s, will try default zone", name) - return splitID[1], scw.Zone(""), nil - } - return splitID[1], zone, nil - } -} - -func chooseZones(accessibilityRequirements *csi.TopologyRequirement, snapshotZone scw.Zone) ([]scw.Zone, error) { - if accessibilityRequirements != nil { - requestedZones := map[string]scw.Zone{} - for _, req := range accessibilityRequirements.GetRequisite() { - topologyKeys := req.GetSegments() - for topologyKey, topologyValue := range topologyKeys { - switch topologyKey { - case ZoneTopologyKey: - zone, err := scw.ParseZone(topologyValue) - if err != nil { - klog.Warningf("the given value for requisite %s: %s is not a valid zone", ZoneTopologyKey, topologyValue) - continue - } - if snapshotZone == scw.Zone("") || snapshotZone == zone { - requestedZones[topologyValue] = zone - } - default: - klog.Warningf("unknow topology key %s for requisite", topologyKey) - } - } - } - - preferredZones := []scw.Zone{} - preferredZonesMap := map[string]scw.Zone{} - for _, pref := range accessibilityRequirements.GetPreferred() { - topologyKeys := pref.GetSegments() - for topologyKey, topologyValue := range topologyKeys { - switch topologyKey { - case ZoneTopologyKey: - zone, err := scw.ParseZone(topologyValue) - if err != nil { - klog.Warningf("the given value for preferred %s: %s is not a valid zone", ZoneTopologyKey, topologyValue) - continue - } - if snapshotZone == scw.Zone("") || snapshotZone == zone { - if _, ok := preferredZonesMap[topologyValue]; !ok { - if accessibilityRequirements.GetRequisite() != nil { - if _, ok := requestedZones[topologyValue]; !ok { - return nil, status.Errorf(codes.InvalidArgument, "%s: %s is specified in preferred but not in requisite", topologyKey, topologyValue) - } - delete(requestedZones, topologyValue) - } - - preferredZonesMap[topologyValue] = zone - preferredZones = append(preferredZones, zone) - } - } - default: - klog.Warningf("unknow topology key %s for preferred", topologyKey) - } - } - } - - for _, requestedZone := range requestedZones { - preferredZones = append(preferredZones, requestedZone) - } - - if snapshotZone != scw.Zone("") && len(preferredZones) != 1 { - return nil, status.Error(codes.ResourceExhausted, "desired volume content source and desired topology are not compatible, different zones") - } - return preferredZones, nil - } - - if snapshotZone != scw.Zone("") { - return []scw.Zone{snapshotZone}, nil - } - - return []scw.Zone{}, nil -} - -func validateVolumeCapabilities(volumeCapabilities []*csi.VolumeCapability) error { - if volumeCapabilities == nil { - return errVolumeCapabilitiesIsNil - } - - block := false - mount := false - - for _, volumeCapability := range volumeCapabilities { - err := validateVolumeCapability(volumeCapability) - if err != nil { - return err - } - if volumeCapability.GetBlock() != nil { - block = true - } - - if volumeCapability.GetMount() != nil { - mount = true - } - } - - if mount && block { - return errBothMountBlockVolumes - } - return nil -} - -func validateVolumeCapability(volumeCapability *csi.VolumeCapability) error { - if volumeCapability == nil { - return errVolumeCapabilityIsNil - } - - for _, accessMode := range supportedAccessModes { - if accessMode.Mode == volumeCapability.GetAccessMode().GetMode() { - return nil - } - } - return errAccessModeNotSupported -} - -func getVolumeRequestCapacity(minSize int64, maxSize int64, capacityRange *csi.CapacityRange) (int64, error) { - if capacityRange == nil { - return minSize, nil - } - - requiredBytes := capacityRange.GetRequiredBytes() - requiredBytesSet := requiredBytes > 0 - - limitBytes := capacityRange.GetLimitBytes() - limitBytesSet := limitBytes > 0 - - if !requiredBytesSet && !limitBytesSet { - return minSize, nil - } - - if requiredBytesSet && limitBytesSet && limitBytes < requiredBytes { - return 0, errLimitBytesLessThanRequiredBytes - } - - if requiredBytesSet && !limitBytesSet && requiredBytes < minSize { - return 0, errRequiredBytesLessThanMinimun - } - - if limitBytesSet && limitBytes < minSize { - return 0, errLimitBytesLessThanMinimum - } - - if requiredBytesSet && requiredBytes > maxSize { - return 0, errRequiredBytesGreaterThanMaximun - } - - if !requiredBytesSet && limitBytesSet && limitBytes > maxSize { - return 0, errLimitBytesGreaterThanMaximum - } - - if requiredBytesSet && limitBytesSet && requiredBytes == limitBytes { - return requiredBytes, nil - } - - if requiredBytesSet { - return requiredBytes, nil - } - - if limitBytesSet { - return limitBytes, nil - } - - return minSize, nil -} - -func newAccessibleTopology(zone scw.Zone) []*csi.Topology { - return []*csi.Topology{ - { - Segments: map[string]string{ZoneTopologyKey: zone.String()}, - }, - } -} - -func createMountPoint(path string, file bool) error { - _, err := os.Stat(path) - if err != nil { - if !os.IsNotExist(err) { - return err - } - } - - if file { - dir := filepath.Dir(path) - err := os.MkdirAll(dir, os.FileMode(0755)) - if err != nil { - return err - } - file, err := os.OpenFile(path, os.O_CREATE, os.FileMode(0644)) - defer file.Close() - if err != nil { - return err - } - } else { - err := os.MkdirAll(path, os.FileMode(0755)) - if err != nil { - return err - } - } - return nil -} - -var secretsField = "Secrets" - -func stripSecretFromReq(req interface{}) string { - ret := "{" - - reqValue := reflect.ValueOf(req) - reqType := reqValue.Type() - if reqType.Kind() == reflect.Struct { - for i := 0; i < reqValue.NumField(); i++ { - field := reqType.Field(i) - value := reqValue.Field(i) - - valueToPrint := fmt.Sprintf("%+v", value.Interface()) - - if field.Name == secretsField && value.Kind() == reflect.Map { - valueToPrint = "[" - for j := 0; j < len(value.MapKeys()); j++ { - valueToPrint += fmt.Sprintf("%s:", value.MapKeys()[j].String()) - if j != len(value.MapKeys())-1 { - valueToPrint += " " - } - } - valueToPrint += "]" - } - - ret += fmt.Sprintf("%s:%s", field.Name, valueToPrint) - if i != reqValue.NumField()-1 { - ret += " " - } - } - } - - ret += "}" - - return ret -} diff --git a/driver/helpers_test.go b/driver/helpers_test.go deleted file mode 100644 index 7973d31..0000000 --- a/driver/helpers_test.go +++ /dev/null @@ -1,651 +0,0 @@ -package driver - -import ( - "fmt" - "path/filepath" - "reflect" - "runtime" - "testing" - - "github.com/container-storage-interface/spec/lib/go/csi" - "github.com/scaleway/scaleway-sdk-go/scw" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -// AssertTrue fails the test if is not true. -func AssertTrue(tb testing.TB, test bool) { - if !test { - _, file, line, _ := runtime.Caller(1) - fmt.Printf("\033[31m%s:%d: unexpected result: %t (wanted true)\033[39m\n", filepath.Base(file), line, test) - tb.FailNow() - } -} - -// AssertFalse fails the test if is not false. -func AssertFalse(tb testing.TB, test bool) { - if test { - _, file, line, _ := runtime.Caller(1) - fmt.Printf("\033[31m%s:%d: unexpected result: %t (wanted false)\033[39m\n", filepath.Base(file), line, test) - tb.FailNow() - } -} - -// AssertNoError fails the test if an err is not nil. -func AssertNoError(tb testing.TB, err error) { - if err != nil { - _, file, line, _ := runtime.Caller(1) - fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n", filepath.Base(file), line, err.Error()) - tb.FailNow() - } -} - -// Equals fails the test if exp is not equal to act. -func Equals(tb testing.TB, exp, act interface{}) { - if !reflect.DeepEqual(exp, act) { - _, file, line, _ := runtime.Caller(1) - fmt.Printf("\033[31m%s:%d: unexpected result\nexp: %#v\ngot: %#v\033[39m\n", filepath.Base(file), line, exp, act) - tb.FailNow() - } -} - -func Test_extractIDAndZone(t *testing.T) { - t.Run("simpleID", func(t *testing.T) { - id, zone, err := extractIDAndZone("testID", "") - AssertNoError(t, err) - Equals(t, "testID", id) - Equals(t, scw.Zone(""), zone) - }) - t.Run("idAndZone", func(t *testing.T) { - id, zone, err := extractIDAndZone("fr-par-1/testID", "") - AssertNoError(t, err) - Equals(t, "testID", id) - Equals(t, scw.ZoneFrPar1, zone) - }) - t.Run("idAndBadZone", func(t *testing.T) { - id, zone, err := extractIDAndZone("blabla/testID", "") - AssertNoError(t, err) - Equals(t, "testID", id) - Equals(t, scw.Zone(""), zone) - }) - t.Run("idAndWrongZone", func(t *testing.T) { - id, zone, err := extractIDAndZone("fr-ams-1/testID", "") - AssertNoError(t, err) - Equals(t, "testID", id) - Equals(t, scw.Zone("fr-ams-1"), zone) - }) - t.Run("emptyID", func(t *testing.T) { - id, zone, err := extractIDAndZone("", "test") - Equals(t, status.Errorf(codes.InvalidArgument, "test is not provided"), err) - Equals(t, "", id) - Equals(t, scw.Zone(""), zone) - }) - t.Run("wrongFormat", func(t *testing.T) { - id, zone, err := extractIDAndZone("a/b/c", "test") - Equals(t, status.Errorf(codes.InvalidArgument, "wrong format for test"), err) - Equals(t, "", id) - Equals(t, scw.Zone(""), zone) - }) -} - -func Test_chooseZones(t *testing.T) { - testsBench := []struct { - req *csi.TopologyRequirement - zone scw.Zone - expected []scw.Zone - err error - }{ - { - req: nil, - zone: scw.Zone(""), - expected: []scw.Zone{}, - err: nil, - }, - { - req: nil, - zone: scw.ZoneFrPar1, - expected: []scw.Zone{scw.ZoneFrPar1}, - err: nil, - }, - { - req: &csi.TopologyRequirement{}, - zone: scw.Zone(""), - expected: []scw.Zone{}, - err: nil, - }, - { - req: &csi.TopologyRequirement{ - Requisite: []*csi.Topology{ - { - Segments: map[string]string{}, - }, - }, - }, - zone: scw.Zone(""), - expected: []scw.Zone{}, - err: nil, - }, - { - req: &csi.TopologyRequirement{ - Preferred: []*csi.Topology{ - { - Segments: map[string]string{}, - }, - }, - }, - zone: scw.Zone(""), - expected: []scw.Zone{}, - err: nil, - }, - { - req: &csi.TopologyRequirement{ - Preferred: []*csi.Topology{ - { - Segments: map[string]string{}, - }, - }, - Requisite: []*csi.Topology{ - { - Segments: map[string]string{}, - }, - }, - }, - zone: scw.Zone(""), - expected: []scw.Zone{}, - err: nil, - }, - { - req: &csi.TopologyRequirement{ - Preferred: []*csi.Topology{ - { - Segments: map[string]string{}, - }, - }, - Requisite: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - }, - }, - zone: scw.Zone(""), - expected: []scw.Zone{scw.ZoneFrPar1}, - err: nil, - }, - { - req: &csi.TopologyRequirement{ - Preferred: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - }, - Requisite: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - }, - }, - zone: scw.Zone(""), - expected: []scw.Zone{scw.ZoneFrPar1}, - err: nil, - }, - { - req: &csi.TopologyRequirement{ - Preferred: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - }, - Requisite: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar2), - }, - }, - }, - }, - zone: scw.Zone(""), - expected: []scw.Zone{scw.ZoneFrPar1, scw.ZoneFrPar2}, - err: nil, - }, - { - req: &csi.TopologyRequirement{ - Preferred: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar2), - }, - }, - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - }, - Requisite: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar2), - }, - }, - }, - }, - zone: scw.Zone(""), - expected: []scw.Zone{scw.ZoneFrPar2, scw.ZoneFrPar1}, - err: nil, - }, - { - req: &csi.TopologyRequirement{ - Preferred: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: "fr-par-4", - }, - }, - { - Segments: map[string]string{ - ZoneTopologyKey: "fr-par-3", - }, - }, - }, - Requisite: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: "fr-par-4", - }, - }, - { - Segments: map[string]string{ - ZoneTopologyKey: "fr-par-3", - }, - }, - }, - }, - zone: scw.Zone(""), - expected: []scw.Zone{"fr-par-4", "fr-par-3"}, - err: nil, - }, - { - req: &csi.TopologyRequirement{ - Preferred: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: "fr-ams", - }, - }, - }, - Requisite: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: "fr-par", - }, - }, - }, - }, - zone: scw.Zone(""), - expected: []scw.Zone{}, - err: nil, - }, - { - req: &csi.TopologyRequirement{ - Preferred: []*csi.Topology{ - { - Segments: map[string]string{ - "test": "fr-ams", - }, - }, - }, - Requisite: []*csi.Topology{ - { - Segments: map[string]string{ - "testagain": "fr-par", - }, - }, - }, - }, - zone: scw.Zone(""), - expected: []scw.Zone{}, - err: nil, - }, - { - req: &csi.TopologyRequirement{ - Preferred: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - }, - }, - zone: scw.Zone(""), - expected: []scw.Zone{scw.ZoneFrPar1}, - err: nil, - }, - { - req: &csi.TopologyRequirement{ - Preferred: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - }, - Requisite: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar2), - }, - }, - }, - }, - zone: scw.ZoneFrPar1, - expected: []scw.Zone{scw.ZoneFrPar1}, - err: nil, - }, - { - req: &csi.TopologyRequirement{ - Preferred: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - }, - Requisite: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - }, - }, - zone: scw.ZoneFrPar1, - expected: []scw.Zone{scw.ZoneFrPar1}, - err: nil, - }, - { - req: &csi.TopologyRequirement{ - Preferred: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar2), - }, - }, - }, - Requisite: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar2), - }, - }, - }, - }, - zone: scw.ZoneFrPar1, - expected: []scw.Zone{scw.ZoneFrPar1}, - err: nil, - }, - { - req: &csi.TopologyRequirement{ - Preferred: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar2), - }, - }, - }, - Requisite: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar2), - }, - }, - }, - }, - zone: scw.ZoneNlAms1, - expected: nil, - err: status.Error(codes.ResourceExhausted, "desired volume content source and desired topology are not compatible, different zones"), - }, - { - req: &csi.TopologyRequirement{ - Preferred: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar2), - }, - }, - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar1), - }, - }, - }, - Requisite: []*csi.Topology{ - { - Segments: map[string]string{ - ZoneTopologyKey: string(scw.ZoneFrPar2), - }, - }, - }, - }, - zone: scw.Zone(""), - expected: nil, - err: status.Errorf(codes.InvalidArgument, "%s: %s is specified in preferred but not in requisite", ZoneTopologyKey, scw.ZoneFrPar1), - }, - } - - for _, test := range testsBench { - zones, err := chooseZones(test.req, test.zone) - Equals(t, test.expected, zones) - Equals(t, test.err, err) - } -} - -func Test_validateVolumeCapabilities(t *testing.T) { - testsBench := []struct { - volCaps []*csi.VolumeCapability - err error - }{ - { - volCaps: nil, - err: errVolumeCapabilitiesIsNil, - }, - { - volCaps: []*csi.VolumeCapability{ - { - AccessType: &csi.VolumeCapability_Block{}, - }, - }, - err: errAccessModeNotSupported, - }, - { - volCaps: []*csi.VolumeCapability{ - { - AccessType: &csi.VolumeCapability_Block{}, - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, - }, - }, - }, - err: errAccessModeNotSupported, - }, - { - volCaps: []*csi.VolumeCapability{ - { - AccessType: &csi.VolumeCapability_Block{}, - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, - }, - }, - }, - err: nil, - }, - { - volCaps: []*csi.VolumeCapability{ - { - AccessType: &csi.VolumeCapability_Mount{}, - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, - }, - }, - }, - err: nil, - }, - } - - for _, test := range testsBench { - err := validateVolumeCapabilities(test.volCaps) - Equals(t, test.err, err) - } -} - -func Test_getVolumeRequestCapacity(t *testing.T) { - var min int64 = 1000 - var max int64 = 1000000000 - testsBench := []struct { - capRange *csi.CapacityRange - res int64 - err error - }{ - { - capRange: &csi.CapacityRange{ - RequiredBytes: 0, - LimitBytes: 0, - }, - res: min, - err: nil, - }, - { - capRange: &csi.CapacityRange{ - RequiredBytes: min + 10, - LimitBytes: 0, - }, - res: min + 10, - err: nil, - }, - { - capRange: &csi.CapacityRange{ - RequiredBytes: 0, - LimitBytes: min + 10, - }, - res: min + 10, - err: nil, - }, - { - capRange: &csi.CapacityRange{ - RequiredBytes: min - 10, - LimitBytes: 0, - }, - res: 0, - err: errRequiredBytesLessThanMinimun, - }, - { - capRange: &csi.CapacityRange{ - RequiredBytes: 0, - LimitBytes: min - 10, - }, - res: 0, - err: errLimitBytesLessThanMinimum, - }, - { - capRange: &csi.CapacityRange{ - RequiredBytes: min + 10, - LimitBytes: min + 5, - }, - res: 0, - err: errLimitBytesLessThanRequiredBytes, - }, - { - capRange: &csi.CapacityRange{ - RequiredBytes: min + 10, - LimitBytes: min + 5, - }, - res: 0, - err: errLimitBytesLessThanRequiredBytes, - }, - { - capRange: &csi.CapacityRange{ - RequiredBytes: max + 10, - LimitBytes: 0, - }, - res: 0, - err: errRequiredBytesGreaterThanMaximun, - }, - { - capRange: &csi.CapacityRange{ - RequiredBytes: 0, - LimitBytes: max + 10, - }, - res: 0, - err: errLimitBytesGreaterThanMaximum, - }, - { - capRange: &csi.CapacityRange{ - RequiredBytes: min + 10, - LimitBytes: min + 10, - }, - res: min + 10, - err: nil, - }, - { - capRange: &csi.CapacityRange{ - RequiredBytes: min + 10, - LimitBytes: min + 20, - }, - res: min + 10, - err: nil, - }, - } - - for _, test := range testsBench { - res, err := getVolumeRequestCapacity(min, max, test.capRange) - Equals(t, test.err, err) - Equals(t, test.res, res) - } -} diff --git a/driver/sanity_test.go b/driver/sanity_test.go deleted file mode 100644 index 15f3880..0000000 --- a/driver/sanity_test.go +++ /dev/null @@ -1,515 +0,0 @@ -package driver - -import ( - "fmt" - "os" - "path" - "strconv" - "strings" - "testing" - "time" - - "github.com/google/uuid" - "github.com/kubernetes-csi/csi-test/v5/pkg/sanity" - "github.com/scaleway/scaleway-sdk-go/api/instance/v1" - "github.com/scaleway/scaleway-sdk-go/scw" - "golang.org/x/sys/unix" - kmount "k8s.io/mount-utils" - kexec "k8s.io/utils/exec" - utilsio "k8s.io/utils/io" - - "github.com/scaleway/scaleway-csi/scaleway" -) - -type fakeHelper struct { - fakeDiskUtils - fakeInstanceAPI -} - -func TestSanityCSI(t *testing.T) { - endpoint := "/tmp/csi-testing.sock" - nodeID := "fb094b6a-a732-4d5f-8283-bd6726ff5938" - defaultVol := &instance.Volume{ - ID: "fb094b6a-b73b-4d5f-8283-bd6726ff5938", - VolumeType: instance.VolumeVolumeTypeLSSD, - Zone: scw.ZoneFrPar1, - Name: "local", - } - volumesMap := map[string]*instance.Volume{ - defaultVol.ID: defaultVol, - } - serversMap := map[string]*instance.Server{ - nodeID: &instance.Server{ - ID: nodeID, - Volumes: map[string]*instance.VolumeServer{ - "0": ConvertVolumeVolumeServer(defaultVol), - }, - Zone: scw.ZoneFrPar1, - }, - } - snapshotsMap := make(map[string]*instance.Snapshot) - diskUtilsDevices := make(map[string]*mountpoint) - - driverConfig := &DriverConfig{ - Endpoint: fmt.Sprintf("unix://%s", endpoint), - Mode: AllMode, - } - fakeInstance := &fakeInstanceAPI{ - volumesMap: volumesMap, - serversMap: serversMap, - snapshotsMap: snapshotsMap, - defaultZone: scw.ZoneFrPar1, - } - fakeDiskUtils := &fakeDiskUtils{ - kMounter: &kmount.SafeFormatAndMount{ - Interface: kmount.New(""), - Exec: kexec.New(), - }, - devices: diskUtilsDevices, - } - fakeHelper := &fakeHelper{ - fakeDiskUtils: *fakeDiskUtils, - fakeInstanceAPI: *fakeInstance, - } - - driver := &Driver{ - config: driverConfig, - controllerService: controllerService{ - scaleway: &scaleway.Scaleway{ - InstanceAPI: fakeHelper, - }, - config: driverConfig, - }, - nodeService: nodeService{ - nodeID: nodeID, - nodeZone: scw.ZoneFrPar1, - diskUtils: fakeHelper, - }, - } - - go driver.Run() // an error here would fail the test anyway since the grpc server would not be started - - config := sanity.NewTestConfig() - config.Address = endpoint - config.TestNodeVolumeAttachLimit = true - config.TestVolumeExpandSize = config.TestVolumeSize * 2 - config.RemoveTargetPath = func(path string) error { - return os.RemoveAll(path) - } - config.RemoveStagingPath = func(path string) error { - return os.RemoveAll(path) - } - sanity.Test(t, config) - driver.srv.GracefulStop() - os.RemoveAll(endpoint) -} - -func ConvertVolumeVolumeServer(vol *instance.Volume) *instance.VolumeServer { - return &instance.VolumeServer{ - ID: vol.ID, - Name: vol.Name, - Organization: vol.Organization, - Size: vol.Size, - VolumeType: instance.VolumeServerVolumeType(vol.VolumeType), - CreationDate: vol.CreationDate, - ModificationDate: vol.ModificationDate, - State: instance.VolumeServerStateAvailable, - Project: vol.Project, - Boot: false, - Zone: vol.Zone, - } -} - -type fakeInstanceAPI struct { - volumesMap map[string]*instance.Volume - serversMap map[string]*instance.Server - snapshotsMap map[string]*instance.Snapshot - defaultZone scw.Zone -} - -func (s *fakeHelper) ListVolumesTypes(req *instance.ListVolumesTypesRequest, opts ...scw.RequestOption) (*instance.ListVolumesTypesResponse, error) { - return &instance.ListVolumesTypesResponse{ - Volumes: map[string]*instance.VolumeType{ - instance.VolumeVolumeTypeBSSD.String(): { - Constraints: &instance.VolumeTypeConstraints{ - Max: 10 * 1000 * 1000 * 1000 * 1000, - Min: 1 * 1000 * 1000 * 1000, - }, - }, - }, - }, nil -} - -func (s *fakeHelper) ListVolumes(req *instance.ListVolumesRequest, opts ...scw.RequestOption) (*instance.ListVolumesResponse, error) { - volumes := make([]*instance.Volume, 0) - for _, v := range s.volumesMap { - if req.Name == nil || strings.Contains(v.Name, *req.Name) { - volumes = append(volumes, v) - } - } - return &instance.ListVolumesResponse{Volumes: volumes, TotalCount: uint32(len(volumes))}, nil -} - -func (s *fakeHelper) CreateVolume(req *instance.CreateVolumeRequest, opts ...scw.RequestOption) (*instance.CreateVolumeResponse, error) { - if req.Zone == "" { - req.Zone = s.defaultZone - } - volume := &instance.Volume{} - volume.ID = uuid.New().String() - volume.Zone = req.Zone - volume.VolumeType = req.VolumeType - if req.Size != nil { - volume.Size = *req.Size - } else if req.BaseVolume != nil { - baseVol, ok := s.volumesMap[*req.BaseVolume] - if !ok { - return nil, &scw.ResourceNotFoundError{} - } - volume.Size = baseVol.Size - } else if req.BaseSnapshot != nil { - baseSnap, ok := s.snapshotsMap[*req.BaseSnapshot] - if !ok { - return nil, &scw.ResourceNotFoundError{} - } - volume.Size = baseSnap.Size - } else { - return nil, &scw.ResponseError{StatusCode: 400} - } - volume.State = instance.VolumeStateAvailable - volume.Name = req.Name - - s.volumesMap[volume.ID] = volume - return &instance.CreateVolumeResponse{Volume: volume}, nil -} - -func (s *fakeHelper) GetVolume(req *instance.GetVolumeRequest, opts ...scw.RequestOption) (*instance.GetVolumeResponse, error) { - if vol, ok := s.volumesMap[req.VolumeID]; ok { - return &instance.GetVolumeResponse{Volume: vol}, nil - } - return nil, &scw.ResourceNotFoundError{} -} - -func (s *fakeHelper) UpdateVolume(req *instance.UpdateVolumeRequest, opts ...scw.RequestOption) (*instance.UpdateVolumeResponse, error) { - vol, ok := s.volumesMap[req.VolumeID] - if !ok { - return nil, &scw.ResourceNotFoundError{} - } - - if req.Name != nil { - vol.Name = *req.Name - } - // TODO add size - return &instance.UpdateVolumeResponse{ - Volume: vol, - }, nil -} - -func (s *fakeHelper) DeleteVolume(req *instance.DeleteVolumeRequest, opts ...scw.RequestOption) error { - if _, ok := s.volumesMap[req.VolumeID]; ok { - delete(s.volumesMap, req.VolumeID) - return nil - } - return &scw.ResourceNotFoundError{} -} - -func (s *fakeHelper) GetServer(req *instance.GetServerRequest, opts ...scw.RequestOption) (*instance.GetServerResponse, error) { - if srv, ok := s.serversMap[req.ServerID]; ok { - return &instance.GetServerResponse{Server: srv}, nil - } - return nil, &scw.ResourceNotFoundError{} -} - -func (s *fakeHelper) AttachVolume(req *instance.AttachVolumeRequest, opts ...scw.RequestOption) (*instance.AttachVolumeResponse, error) { - if vol, ok := s.volumesMap[req.VolumeID]; ok { - if srv, ok := s.serversMap[req.ServerID]; ok { - // emulate instance error if volume is already attached to server - for i := 0; i < maxVolumesPerNode; i++ { - key := fmt.Sprintf("%d", i) - if existingVol, ok := srv.Volumes[key]; ok && existingVol.ID == vol.ID { - return nil, &scw.InvalidArgumentsError{} - } - } - - // add volume to volumes list - // We loop through all the possible volume keys (0 to len(volumes)) - // to find a non existing key and assign it to the requested volume. - // A key should always be found. However we return an error if no keys were found. - found := false - for i := 0; i < maxVolumesPerNode; i++ { - key := fmt.Sprintf("%d", i) - if _, ok := srv.Volumes[key]; !ok { - vol.Server = &instance.ServerSummary{ - ID: req.ServerID, - } - srv.Volumes[key] = ConvertVolumeVolumeServer(vol) - found = true - break - } - } - if !found { - return nil, fmt.Errorf("could not find key to attach volume %s", req.VolumeID) - } - - s.devices[path.Join(diskByIDPath, diskSCWPrefix+req.VolumeID)] = &mountpoint{ - block: true, - } - return &instance.AttachVolumeResponse{Server: srv}, nil - } - } - return nil, &scw.ResourceNotFoundError{} -} - -func (s *fakeHelper) DetachVolume(req *instance.DetachVolumeRequest, opts ...scw.RequestOption) (*instance.DetachVolumeResponse, error) { - if vol, ok := s.volumesMap[req.VolumeID]; ok { - if srv, ok := s.serversMap[vol.Server.ID]; ok { - // remove volume from volumes list - for key, volume := range srv.Volumes { - if volume.ID == req.VolumeID { - vol.Server = nil - delete(srv.Volumes, key) - break - } - } - - delete(s.devices, path.Join(diskByIDPath, diskSCWPrefix+req.VolumeID)) - return &instance.DetachVolumeResponse{Server: srv}, nil - } - } - return nil, &scw.ResourceNotFoundError{} -} - -func (s *fakeHelper) WaitForVolume(req *instance.WaitForVolumeRequest, opts ...scw.RequestOption) (*instance.Volume, error) { - if vol, ok := s.volumesMap[req.VolumeID]; ok { - return vol, nil - } - return nil, &scw.ResourceNotFoundError{} -} - -func (s *fakeHelper) GetSnapshot(req *instance.GetSnapshotRequest, opts ...scw.RequestOption) (*instance.GetSnapshotResponse, error) { - snapshot, ok := s.snapshotsMap[req.SnapshotID] - if !ok { - return nil, &scw.ResourceNotFoundError{} - } - return &instance.GetSnapshotResponse{ - Snapshot: snapshot, - }, nil -} - -func (s *fakeHelper) ListSnapshots(req *instance.ListSnapshotsRequest, opts ...scw.RequestOption) (*instance.ListSnapshotsResponse, error) { - snapshots := make([]*instance.Snapshot, 0) - for _, snap := range s.snapshotsMap { - if req.BaseVolumeID != nil && (snap.BaseVolume == nil || *req.BaseVolumeID != snap.BaseVolume.ID) { - continue - } - - if req.Name != nil && !strings.Contains(snap.Name, *req.Name) { - continue - } - - if snap.State == instance.SnapshotStateSnapshotting { - snap.State = instance.SnapshotStateAvailable - } - snapshots = append(snapshots, snap) - } - return &instance.ListSnapshotsResponse{Snapshots: snapshots, TotalCount: uint32(len(snapshots))}, nil -} - -func (s *fakeHelper) CreateSnapshot(req *instance.CreateSnapshotRequest, opts ...scw.RequestOption) (*instance.CreateSnapshotResponse, error) { - if req.Zone == "" { - req.Zone = s.defaultZone - } - - volume, ok := s.volumesMap[*req.VolumeID] - if !ok { - return nil, &scw.ResourceNotFoundError{} - } - snapshot := &instance.Snapshot{} - snapshot.ID = uuid.New().String() - snapshot.Zone = req.Zone - snapshot.Name = req.Name - snapshot.VolumeType = volume.VolumeType - snapshot.Size = volume.Size - snapshot.State = instance.SnapshotStateSnapshotting - snapshot.BaseVolume = &instance.SnapshotBaseVolume{ - ID: volume.ID, - Name: volume.Name, - } - snapshot.CreationDate = scw.TimePtr(time.Now()) - s.snapshotsMap[snapshot.ID] = snapshot - - return &instance.CreateSnapshotResponse{ - Snapshot: snapshot, - }, nil -} - -func (s *fakeHelper) DeleteSnapshot(req *instance.DeleteSnapshotRequest, opts ...scw.RequestOption) error { - if _, ok := s.snapshotsMap[req.SnapshotID]; ok { - delete(s.snapshotsMap, req.SnapshotID) - return nil - } - return &scw.ResourceNotFoundError{} -} - -type mountpoint struct { - targetPath string - fsType string - mountOptions []string - block bool -} - -type fakeDiskUtils struct { - kMounter *kmount.SafeFormatAndMount - devices map[string]*mountpoint -} - -// FormatAndMount is only used for non block devices -func (s *fakeHelper) FormatAndMount(targetPath string, devicePath string, fsType string, mountOptions []string) error { - if fsType == "" { - fsType = defaultFSType - } - - s.devices[devicePath] = &mountpoint{ - targetPath: targetPath, - fsType: fsType, - mountOptions: mountOptions, - block: false, - } - return nil -} - -func (s *fakeHelper) Unmount(target string) error { - return kmount.CleanupMountPoint(target, s.kMounter, true) -} - -func (s *fakeHelper) MountToTarget(sourcePath, targetPath, fsType string, mountOptions []string) error { - if fsType == "" { - fsType = defaultFSType - } - - s.devices[sourcePath] = &mountpoint{ - targetPath: targetPath, - fsType: fsType, - mountOptions: mountOptions, - block: strings.HasPrefix(sourcePath, diskByIDPath), - } - return nil -} - -func (s *fakeHelper) GetDevicePath(volumeID string) (string, error) { - if _, ok := s.devices[path.Join(diskByIDPath, diskSCWPrefix+volumeID)]; ok { - return path.Join(diskByIDPath, diskSCWPrefix+volumeID), nil - } - - return "", os.ErrNotExist -} - -func (s *fakeHelper) IsSharedMounted(targetPath string, devicePath string) (bool, error) { - if targetPath == "" { - return false, errTargetPathEmpty - } - if d, ok := s.devices[devicePath]; ok { - return d.targetPath == targetPath, nil - } - - for _, tp := range s.devices { - if tp.targetPath == targetPath { - return true, nil - } - } - - return false, nil -} - -// taken from https://github.com/kubernetes/kubernetes/blob/master/pkg/util/mount/mount_linux.go -func (s *fakeHelper) GetMountInfo(targetPath string) (*mountInfo, error) { - content, err := utilsio.ConsistentRead(procMountInfoPath, procMountInfoMaxListTries) - if err != nil { - return &mountInfo{}, err - } - contentStr := string(content) - - for _, line := range strings.Split(contentStr, "\n") { - if line == "" { - // the last split() item is empty string following the last \n - continue - } - // See `man proc` for authoritative description of format of the file. - fields := strings.Fields(line) - if len(fields) < expectedAtLeastNumFieldsPerMountInfo { - return nil, fmt.Errorf("wrong number of fields in (expected at least %d, got %d): %s", expectedAtLeastNumFieldsPerMountInfo, len(fields), line) - } - if fields[4] != targetPath { - continue - } - id, err := strconv.Atoi(fields[0]) - if err != nil { - return nil, err - } - parentID, err := strconv.Atoi(fields[1]) - if err != nil { - return nil, err - } - info := &mountInfo{ - id: id, - parentID: parentID, - majorMinor: fields[2], - root: fields[3], - mountPoint: fields[4], - mountOptions: strings.Split(fields[5], ","), - } - // All fields until "-" are "optional fields". - i := 6 - for ; i < len(fields) && fields[i] != "-"; i++ { - info.optionalFields = append(info.optionalFields, fields[i]) - } - // Parse the rest 3 fields. - i++ - if len(fields)-i < 3 { - return nil, fmt.Errorf("expect 3 fields in %s, got %d", line, len(fields)-i) - } - info.fsType = fields[i] - info.source = fields[i+1] - info.superOptions = strings.Split(fields[i+2], ",") - return info, nil - } - return &mountInfo{}, nil -} - -func (s *fakeHelper) IsBlockDevice(path string) (bool, error) { - for _, mp := range s.devices { - if mp.targetPath == path { - return mp.block, nil - } - } - return false, fmt.Errorf("not found") // enough for csi sanity? -} - -func (s *fakeHelper) GetStatfs(path string) (*unix.Statfs_t, error) { - return &unix.Statfs_t{ - Blocks: 1000, - Bsize: 4, - Bfree: 500, - Files: 1000, - Ffree: 500, - }, nil -} - -func (s *fakeHelper) Resize(targetPath string, devicePath, passphrase string) error { - return nil -} - -func (s *fakeHelper) IsEncrypted(devicePath string) (bool, error) { - return false, nil -} - -func (s *fakeHelper) EncryptAndOpenDevice(volumeID string, passphrase string) (string, error) { - return "", nil -} - -func (s *fakeHelper) CloseDevice(volumeID string) error { - return nil -} - -func (s *fakeHelper) GetMappedDevicePath(volumeID string) (string, error) { - return "", nil -} diff --git a/examples/kubernetes/README.md b/examples/kubernetes/README.md index 8f86a2d..13f5983 100644 --- a/examples/kubernetes/README.md +++ b/examples/kubernetes/README.md @@ -1,39 +1,50 @@ # Kubernetes examples -You can find in this directory some examples about how to use the Scaleway CSI driver inside Kubernetes. +You can find in this directory some examples about how to use the Scaleway CSI +driver inside Kubernetes. -It will cover [Persistent Volumes/Persistent Volume Claim (PV/PVC)](https://kubernetes.io/docs/concepts/storage/persistent-volumes/), [Storage Class](https://kubernetes.io/docs/concepts/storage/storage-classes/) and [Volume Snapshots](https://kubernetes.io/docs/concepts/storage/volume-snapshots/). +It will cover [Persistent Volumes/Persistent Volume Claims (PV/PVC)](https://kubernetes.io/docs/concepts/storage/persistent-volumes/), +[Storage Classes](https://kubernetes.io/docs/concepts/storage/storage-classes/) +and [Volume Snapshots](https://kubernetes.io/docs/concepts/storage/volume-snapshots/). -If a [StorageClass](https://kubernetes.io/docs/concepts/storage/storage-classes/) is not provided in the examples, the `scw-bssd` storage class will be used. +If a [StorageClass](https://kubernetes.io/docs/concepts/storage/storage-classes/) +is not provided in the examples, the `sbs-default` storage class will be used. ## PVC & Deployment -We will create a [PersistentVolumeClaim](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) and use it as a volume inside the pod of a deployment, to store nginx's logs. +We will create a [PersistentVolumeClaim](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) +and use it as a volume inside the pod of a deployment, to store nginx's logs. First, we will create a 3Gi volume: + ```bash -$ kubectl apply -f pvc-deployment/pvc.yaml +kubectl apply -f pvc-deployment/pvc.yaml ``` Now we can create the deployment that will use this volume: + ```bash -$ kubectl apply -f pvc-deployment/deployment.yaml +kubectl apply -f pvc-deployment/deployment.yaml ``` ## Raw Block Volumes -We will create a block volume and make it available in the pod as a raw block device. In order to do so, `volumeMode` must be set to `Block`. -First, create the volume: +We will create a block volume and make it available in the pod as a raw block device. +In order to do so, `volumeMode` must be set to `Block`. First, create the volume: + ```bash -$ kubectl apply -f raw-volume/pvc.yaml +kubectl apply -f raw-volume/pvc.yaml ``` -Now we can create a pod that will use this raw volume. In order to do so, `volumesDevices` must be used, instead of the traditional `volumeMounts`: +Now we can create a pod that will use this raw volume. In order to do so, +`volumesDevices` must be used, instead of the usual `volumeMounts`: + ```bash -$ kubectl apply -f raw-volume/pod.yaml +kubectl apply -f raw-volume/pod.yaml ``` You can now exec into the container and use the volume as a classic block device: -```bash + +```console $ kubectl exec -it my-awesome-block-volume-app sh / # ls -al /dev/xvda brw-rw---- 1 root disk 8, 32 Mar 23 12:34 /dev/xvda @@ -45,7 +56,9 @@ brw-rw---- 1 root disk 8, 32 Mar 23 12:34 /dev/xvda ## Importing existing Scaleway volumes -If you have an already existing volume, with the ID `11111111-1111-1111-111111111111` in the zone `fr-par-1`, you can import it by creating the following PV: +If you have an already existing volume, with the ID `11111111-1111-1111-111111111111` +in the zone `fr-par-1`, you can import it by creating the following PV: + ```yaml apiVersion: v1 kind: PersistentVolume @@ -57,7 +70,7 @@ spec: volumeMode: Filesystem accessModes: - ReadWriteOnce - storageClassName: scw-bssd + storageClassName: sbs-default csi: driver: csi.scaleway.com volumeHandle: fr-par-1/11111111-1111-1111-111111111111 @@ -71,45 +84,56 @@ spec: - fr-par-1 ``` -Once the PV is created, create a PVC with the same attributes (here `scw-bssd` as storage class and a size of 5Gi): +Once the PV is created, create a PVC with the same attributes (here `sbs-default` +as storage class and a size of 5Gi): + ```bash -$ kubectl apply -f importing/pvc.yaml +kubectl apply -f importing/pvc.yaml ``` And finally create a pod that uses this volume: + ```bash -$ kubectl apply -f importing/pod.yaml +kubectl apply -f importing/pod.yaml ``` ## Volume Snapshots -In Kubernetes, it is possible to create snapshots via the [VolumeSnapshot](https://kubernetes.io/docs/concepts/storage/volume-snapshots/) object. +In Kubernetes, it is possible to create snapshots via the +[VolumeSnapshot](https://kubernetes.io/docs/concepts/storage/volume-snapshots/) object. Volumes can be snapshotted, and restored from snapshots. First let's create a PVC: + ```bash -$ kubctl apply -f snapshots/pvc.yaml +kubctl apply -f snapshots/pvc.yaml ``` We can now snapshot it: + ```bash -$ kubectl apply -f snapshots/snapshot.yaml +kubectl apply -f snapshots/snapshot.yaml ``` And it should be marked as ready: -```bash + +```console $ kubectl get volumesnapshot.snapshot.storage.k8s.io NAME READYTOUSE SOURCEPVC SOURCESNAPSHOTCONTENT RESTORESIZE SNAPSHOTCLASS SNAPSHOTCONTENT CREATIONTIME AGE my-snapshot true my-soon-to-be-snapshotted-pvc 5Gi scw-snapshot snapcontent-7146128a-c9f2-4050-856f-8ae1590eb436 113s 114s ``` When ready, you can create a new PVC from this snapshot: + ```bash -$ kubectl apply -f snapshots/restored-snapshot.yaml +kubectl apply -f snapshots/restored-snapshot.yaml ``` ### Importing snapshots -It is also possible, as for the volumes, to import snapshots. Let's say you have a snapshot in `fr-par-1` with the ID `11111111-1111-1111-111111111111`. You must first import the `VolumeSnapshotContent` as followed: +It is also possible, as for the volumes, to import snapshots. Let's say you have +a snapshot in `fr-par-1` with the ID `11111111-1111-1111-111111111111`. +You must first import the `VolumeSnapshotContent` as followed: + ```yaml apiVersion: snapshot.storage.k8s.io/v1beta1 kind: VolumeSnapshotContent @@ -127,6 +151,7 @@ spec: ``` Once it's done, we can now create the `VolumeSnapshot` with: + ```yaml apiVersion: snapshot.storage.k8s.io/v1beta1 kind: VolumeSnapshot @@ -139,22 +164,28 @@ spec: ``` And now, it's available to use: -```bash -$ kubectl get volumesnapshot.snapshot.storage.k8s.io + +```console +$ kubectl get volumesnapshot.snapshot.storage.k8s.io NAME READYTOUSE SOURCEPVC SOURCESNAPSHOTCONTENT RESTORESIZE SNAPSHOTCLASS SNAPSHOTCONTENT CREATIONTIME AGE my-imported-snapshot true my-imported-snapshot-content 3Gi scw-snapshot my-imported-snapshot-content 46d 3m32s ``` ## Different StorageClass -[StorageClasses](https://kubernetes.io/docs/concepts/storage/storage-classes/) offer a way to easily create different types of [Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/). +[StorageClasses](https://kubernetes.io/docs/concepts/storage/storage-classes/) +offer a way to easily create different types of [Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/). -In the installation guide, a basic storage class is deployed: `scw-bssd` that will provision standard Scaleway Block Volumes. We will see here how to customize different storage class. -The provisioner will always be `csi.scaleway.com`. +In the installation guide, a basic storage class is deployed: `sbs-default` that +will provision standard Scaleway Block Volumes. We will see here how to customize +different storage class. The provisioner will always be `csi.scaleway.com`. ### Set a default storage class -In order to have a default storage class (ie not having to specify the `storageClassName` for each PVC), you must add the `storageclass.kubernetes.io/is-default-class: "true"` annotation to the storage class: +In order to have a default storage class (i.e. not having to specify the`storageClassName` +for each PVC), you must add the `storageclass.kubernetes.io/is-default-class: "true"` +annotation to the storage class: + ```yaml kind: StorageClass apiVersion: storage.k8s.io/v1 @@ -162,54 +193,63 @@ metadata: name: my-default-storage-class annotations: storageclass.kubernetes.io/is-default-class: "true" -provisioner: csi.scaleway.com +provisioner: csi.scaleway.com reclaimPolicy: Delete ``` ### Choose the filesystem type -In order to change the filesystem type used to format the volume (it's `ext4` by default), you must add the `csi.storage.k8s.io/fstype` parameter to the storage class: +In order to change the filesystem type used to format the volume (it's `ext4` by default), +you must add the `csi.storage.k8s.io/fstype` parameter to the storage class: + ```yaml kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: my-ext3-storage-class -provisioner: csi.scaleway.com +provisioner: csi.scaleway.com reclaimPolicy: Delete parameters: csi.storage.k8s.io/fstype: ext3 ``` -### Choose the type of Scaleway Block Volume +### Specify in which zone the volumes are going to be created + +By default, the Scaleway CSI plugin uses the `SCW_DEFAULT_ZONE` environment +variable to get the zone where the volumes will be provisioned. If you want to +override this value, you must use the `allowedTopologies` field of the storage +class to specify a zone: -When a new type of Scaleway Block Volume will be available, let's say it's called `b_ssd+`, you will need to add the `type` parameter to the storage class: ```yaml kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: - name: my-bssdplus-storage-class -provisioner: csi.scaleway.com + name: my-ams-storage-class +provisioner: csi.scaleway.com reclaimPolicy: Delete -parameters: - type: b_ssd+ +allowedTopologies: +- matchLabelExpressions: + - key: topology.csi.scaleway.com/zone + values: + - nl-ams-1 ``` -### Specify in which zone the volumes are going to be created +### Choose the number of IOPS + +If you don't set the `iops` parameter, the Scaleway Block Storage API will create +volumes with 5000 IOPS. To create volumes with higher IOPS, you can set the `iops` +parameter to the requested number of IOPS in your `StorageClass`. Please refer to +the Scaleway Block Storage documentation for a list of allowed IOPS. For example: -By default, the Scaleway CSI plugin uses the `SCW_DEFAULT_ZONE` environment variable to get the zone where the volumes will be provisioned. -If you want to override this value, you must use the `allowedTopologies` field of the storage class to specify a zone: ```yaml kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: - name: my-ams-storage-class -provisioner: csi.scaleway.com + name: my-15k-iops-storage-class +provisioner: csi.scaleway.com reclaimPolicy: Delete -allowedTopologies: -- matchLabelExpressions: - - key: topology.csi.scaleway.com/zone - values: - - nl-ams-1 +parameters: + iops: "15000" ``` ## Encrypting Volumes @@ -218,22 +258,27 @@ This plugin supports at rest encryption of the volumes with Cryptsetup/LUKS. ### Storage Class parameters -In order to have an encrypted volume, `encrypted: true` needs to be added to the StorageClass parameters. -You will also need a passphrase to encrypt/decrypt the volume, which is taken from the secrets passed to the `NodeStageVolume` and `NodeExpandVolume` method. +In order to have an encrypted volume, `encrypted: true` needs to be added to the +StorageClass parameters. You will also need a passphrase to encrypt/decrypt the volume, +which is taken from the secrets passed to the `NodeStageVolume` and `NodeExpandVolume` method. -The [external-provisioner](https://github.com/kubernetes-csi/external-provisioner) can be used to [pass down the wanted secret to the CSI plugin](https://kubernetes-csi.github.io/docs/secrets-and-credentials-storage-class.html) (v1.0.1+). +The [external-provisioner](https://github.com/kubernetes-csi/external-provisioner) +can be used to [pass down the wanted secret to the CSI plugin](https://kubernetes-csi.github.io/docs/secrets-and-credentials-storage-class.html) (v1.0.1+). Some additional parameters are needed on the StorageClass: + - `csi.storage.k8s.io/node-stage-secret-name`: The name of the secret - `csi.storage.k8s.io/node-stage-secret-namespace`: The namespace of the secret - `csi.storage.k8s.io/node-expand-secret-name`: The name of the secret (see note below). - `csi.storage.k8s.io/node-expand-secret-namespace`: The namespace of the secret (see note below). -> Volume expansion for encrypted volumes is only supported with the `CSINodeExpandSecret` feature gate which is available since `v1.25.0` and by default since `v1.27.0`. +> Volume expansion for encrypted volumes is only supported with the `CSINodeExpandSecret` +> feature gate which is available since `v1.25.0` and by default since `v1.27.0`. The secret needs to have the passphrase in the entry with the key `encryptionPassphrase`. For instance with the following secret: + ```yaml apiVersion: v1 kind: Secret @@ -246,13 +291,14 @@ data: ``` and the following StorageClass: + ```yaml # Volume expansion is supported with CSINodeExpandSecret feature gate since v1.25.0 or by default since v1.27.0 allowVolumeExpansion: true apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: - name: "scw-bssd-enc" + name: "sbs-default-enc" provisioner: csi.scaleway.com reclaimPolicy: Delete volumeBindingMode: Immediate @@ -265,9 +311,15 @@ parameters: csi.storage.k8s.io/node-expand-secret-namespace: "default" ``` -all the PVC created with the StorageClass `scw-bssd-enc` will be encrypted at rest with the passphrase `myawesomepassphrase`. +all the PVC created with the StorageClass `sbs-default-enc` will be encrypted at +rest with the passphrase `myawesomepassphrase`. -The [Per Volume Secret](https://kubernetes-csi.github.io/docs/secrets-and-credentials-storage-class.html#per-volume-secrets) can also be used to avoid having one passphrase per StorageClass. +The [Per Volume Secret](https://kubernetes-csi.github.io/docs/secrets-and-credentials-storage-class.html#per-volume-secrets) +can also be used to avoid having one passphrase per StorageClass. -Please note that prior to `v0.2.1` the expansion of encrypted volume was not possible, `PV` created without the `csi.storage.k8s.io/node-stage-secret` annotations will need to be patched by hand if expansion is needed. -Be sure to be extra carefull doing so as the needed fields are immutable and you'll need to force the patch (backup any data, switch the `reclaimPolicy` of the volume to `Retain`, ...). +> **Note** +> Please note that prior to `v0.2.1` the expansion of encrypted volume was not possible, +> `PersistentVolumes` created without the `csi.storage.k8s.io/node-stage-secret` annotations +> will need to be patched manually if expansion is needed. Be sure to be extra careful doing +> so as the needed fields are immutable and you'll need to force the patch (backup any data, +> switch the `reclaimPolicy` of the volume to `Retain`, etc.). diff --git a/examples/kubernetes/block-volume/pvc.yaml b/examples/kubernetes/block-volume/pvc.yaml index e6c714b..277a0c2 100644 --- a/examples/kubernetes/block-volume/pvc.yaml +++ b/examples/kubernetes/block-volume/pvc.yaml @@ -8,5 +8,5 @@ spec: resources: requests: storage: 10Gi - storageClassName: scw-bssd + storageClassName: sbs-default volumeMode: Block diff --git a/examples/kubernetes/importing/pv.yaml b/examples/kubernetes/importing/pv.yaml index 0693497..416cce2 100644 --- a/examples/kubernetes/importing/pv.yaml +++ b/examples/kubernetes/importing/pv.yaml @@ -8,7 +8,7 @@ spec: volumeMode: Filesystem accessModes: - ReadWriteOnce - storageClassName: scw-bssd + storageClassName: sbs-default csi: driver: csi.scaleway.com volumeHandle: fr-par-1/11111111-1111-1111-111111111111 diff --git a/examples/kubernetes/importing/pvc.yaml b/examples/kubernetes/importing/pvc.yaml index d61f34a..4f18497 100644 --- a/examples/kubernetes/importing/pvc.yaml +++ b/examples/kubernetes/importing/pvc.yaml @@ -8,4 +8,4 @@ spec: resources: requests: storage: 5Gi - storageClassName: scw-bssd + storageClassName: sbs-default diff --git a/examples/kubernetes/pvc-deployment/pvc.yaml b/examples/kubernetes/pvc-deployment/pvc.yaml index 94ce328..ef42d1b 100644 --- a/examples/kubernetes/pvc-deployment/pvc.yaml +++ b/examples/kubernetes/pvc-deployment/pvc.yaml @@ -8,4 +8,4 @@ spec: resources: requests: storage: 3Gi - storageClassName: scw-bssd + storageClassName: sbs-default diff --git a/examples/kubernetes/snapshots/pvc.yaml b/examples/kubernetes/snapshots/pvc.yaml index 02929de..638b898 100644 --- a/examples/kubernetes/snapshots/pvc.yaml +++ b/examples/kubernetes/snapshots/pvc.yaml @@ -8,4 +8,4 @@ spec: resources: requests: storage: 5Gi - storageClassName: scw-bssd + storageClassName: sbs-default diff --git a/examples/kubernetes/snapshots/restored-snapshot.yaml b/examples/kubernetes/snapshots/restored-snapshot.yaml index 5b7a346..8e562e9 100644 --- a/examples/kubernetes/snapshots/restored-snapshot.yaml +++ b/examples/kubernetes/snapshots/restored-snapshot.yaml @@ -5,7 +5,7 @@ metadata: spec: accessModes: - ReadWriteOnce - storageClassName: scw-bssd + storageClassName: sbs-default resources: requests: storage: 5Gi diff --git a/go.mod b/go.mod index ebc1198..72078f2 100644 --- a/go.mod +++ b/go.mod @@ -3,30 +3,37 @@ module github.com/scaleway/scaleway-csi go 1.20 require ( - github.com/container-storage-interface/spec v1.6.0 + github.com/container-storage-interface/spec v1.8.0 github.com/golang/protobuf v1.5.3 - github.com/google/uuid v1.3.0 - github.com/kubernetes-csi/csi-test/v5 v5.0.0 - github.com/scaleway/scaleway-sdk-go v1.0.0-beta.21.0.20230918151823-4f048611ed7c - golang.org/x/sys v0.9.0 - google.golang.org/grpc v1.56.1 - google.golang.org/protobuf v1.30.0 + github.com/google/uuid v1.3.1 + github.com/kubernetes-csi/csi-test/v5 v5.1.0 + github.com/onsi/ginkgo/v2 v2.13.0 + github.com/onsi/gomega v1.28.0 + github.com/pkg/sftp v1.13.6 + github.com/scaleway/scaleway-sdk-go v1.0.0-beta.21.0.20231010165413-fd6263b48233 + golang.org/x/crypto v0.14.0 + golang.org/x/exp v0.0.0-20231006140011-7918f672742d + golang.org/x/sys v0.13.0 + google.golang.org/grpc v1.58.3 + google.golang.org/protobuf v1.31.0 k8s.io/klog/v2 v2.100.1 - k8s.io/mount-utils v0.27.3 - k8s.io/utils v0.0.0-20230505201702-9f6742963106 + k8s.io/mount-utils v0.28.2 + k8s.io/utils v0.0.0-20230726121419-3b25d923346b + oya.to/namedlocker v1.0.0 ) require ( - github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/go-cmp v0.5.9 // indirect + github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect + github.com/kr/fs v0.1.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/moby/sys/mountinfo v0.6.2 // indirect - github.com/onsi/ginkgo/v2 v2.1.4 // indirect - github.com/onsi/gomega v1.20.0 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect - golang.org/x/net v0.9.0 // indirect - golang.org/x/text v0.9.0 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + golang.org/x/net v0.16.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 900de1b..6db53fc 100644 --- a/go.sum +++ b/go.sum @@ -1,251 +1,119 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/container-storage-interface/spec v1.6.0 h1:vwN9uCciKygX/a0toYryoYD5+qI9ZFeAMuhEEKO+JBA= -github.com/container-storage-interface/spec v1.6.0/go.mod h1:8K96oQNkJ7pFcC2R9Z1ynGGBB1I93kcS6PGg3SsOk8s= +github.com/container-storage-interface/spec v1.8.0 h1:D0vhF3PLIZwlwZEf2eNbpujGCNwspwTYf2idJRJx4xI= +github.com/container-storage-interface/spec v1.8.0/go.mod h1:ROLik+GhPslwwWRNFF1KasPzroNARibH2rfz1rkg4H0= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -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/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kubernetes-csi/csi-test/v5 v5.0.0 h1:GJ0M+ppcKgWhafXH3B2Ssfw1Egzly9GlMx3JOQApekM= -github.com/kubernetes-csi/csi-test/v5 v5.0.0/go.mod h1:jVEIqf8Nv1roo/4zhl/r6Tc68MAgRX/OQSQK0azTHyo= +github.com/kubernetes-csi/csi-test/v5 v5.1.0 h1:8UxFRH0W8C4RbppKYYJeOJ506C7ybngKZA5GabGgJec= +github.com/kubernetes-csi/csi-test/v5 v5.1.0/go.mod h1:LoAh2XHbXcKnCoM1WgEyviUXiLmTeCmFTsjzaNloL3k= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= -github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q= -github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= +github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= +github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= +github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.21.0.20230918151823-4f048611ed7c h1:HM3dPr4NWDAAJDt3mmJGLZ+1SqvQNbRM0zBvBB4UHmU= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.21.0.20230918151823-4f048611ed7c/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.21.0.20231010165413-fd6263b48233 h1:1y6zYYJ6q0G3UlWTnaTVu3LN3y2kLBOgIr05QtNic5Q= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.21.0.20231010165413-fd6263b48233/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +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-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -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= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20201209185603-f92720507ed4/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ= -google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/mount-utils v0.27.3 h1:oubkDKLTZUneW27wgyOmp8a1AAZj04vGmtq+YW8wdvY= -k8s.io/mount-utils v0.27.3/go.mod h1:vmcjYdi2Vg1VTWY7KkhvwJVY6WDHxb/QQhiQKkR8iNs= -k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU= -k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/mount-utils v0.28.2 h1:sIdMH7fRhcU48V1oYJ9cLmLm/TG+2jLhhe8eS3I+FWg= +k8s.io/mount-utils v0.28.2/go.mod h1:AyP8LmZSLgpGdFQr+vzHTerlPiGvXUdP99n98Er47jw= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +oya.to/namedlocker v1.0.0 h1:+HKiEvhKSzBtboeqiT8vK10aVUTASnneKpw+j79FQwA= +oya.to/namedlocker v1.0.0/go.mod h1:+eBYtjcKHBxsdm/HAofHTTnSq6H96THcxHYG1A6WKX0= diff --git a/pkg/driver/controller.go b/pkg/driver/controller.go new file mode 100644 index 0000000..5786465 --- /dev/null +++ b/pkg/driver/controller.go @@ -0,0 +1,550 @@ +package driver + +import ( + "context" + "strconv" + "sync" + + "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/scaleway/scaleway-csi/pkg/scaleway" + block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1" + "github.com/scaleway/scaleway-sdk-go/scw" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "k8s.io/klog/v2" + "oya.to/namedlocker" +) + +const ( + // scwVolumeIDKey is the key for the volumeID in the publishContext. + scwVolumeIDKey = DriverName + "/volume-id" + // scwVolumeNameKey is the key for the volumeName in the publishContext. + scwVolumeNameKey = DriverName + "/volume-name" + // scwVolumeZoneKey is the key for the volumeZone in the publishContext. + scwVolumeZoneKey = DriverName + "/volume-zone" + + // volumeTypeKey is the key of the volume type parameter. + volumeTypeKey = "type" + // encryptedKey is the key of the encrypted parameter. + encryptedKey = "encrypted" + // volumeIOPSKey is the key of the iops parameter. + volumeIOPSKey = "iops" +) + +var ( + // controllerCapabilities represents the capabilites of the controller. + controllerCapabilities = []csi.ControllerServiceCapability_RPC_Type{ + csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, + csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME, + csi.ControllerServiceCapability_RPC_LIST_VOLUMES, + csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT, + csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS, + csi.ControllerServiceCapability_RPC_EXPAND_VOLUME, + csi.ControllerServiceCapability_RPC_GET_VOLUME, + csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER, + csi.ControllerServiceCapability_RPC_LIST_VOLUMES_PUBLISHED_NODES, + } + + // supportedAccessModes represents the supported access modes for the Scaleway Block Volumes. + supportedAccessModes = []csi.VolumeCapability_AccessMode_Mode{ + csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, + csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY, + csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER, + csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER, + } +) + +// controllerService implements csi.ControllerServer. +var _ csi.ControllerServer = &controllerService{} + +// controllerService implements csi.ControllerServer. +type controllerService struct { + scaleway scaleway.Interface + config *DriverConfig + // Global attach/detach mutex. + mux sync.Mutex + // Volume locks ensures we don't run parallel operations on volumes (e.g. + // detaching a volume and taking a snapshot). + locks namedlocker.Store +} + +func newControllerService(config *DriverConfig) (*controllerService, error) { + scw, err := scaleway.New(UserAgent()) + if err != nil { + return nil, err //nolint:wrapcheck + } + + return &controllerService{ + config: config, + scaleway: scw, + }, nil +} + +// CreateVolume creates a new volume with the given CreateVolumeRequest. +// This function is idempotent +func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { + klog.V(4).Infof("CreateVolume: called with %s", stripSecretFromReq(*req)) + + volumeName := req.GetName() + if volumeName == "" { + return nil, status.Error(codes.InvalidArgument, "name not provided") + } + + if err := validateVolumeCapabilities(req.GetVolumeCapabilities(), false); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "volumeCapabilities not supported: %s", err) + } + + perfIOPS, encrypted, err := parseCreateVolumeParams(req.GetParameters()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid parameters: %s:", err) + } + + size, err := getVolumeRequestCapacity(req.GetCapacityRange()) + if err != nil { + return nil, status.Errorf(codes.OutOfRange, "capacityRange invalid: %s", err) + } + + scwVolumeName := d.config.Prefix + volumeName + + var snapshotID string + var snapshotZone scw.Zone + if req.GetVolumeContentSource() != nil { + if _, ok := req.GetVolumeContentSource().GetType().(*csi.VolumeContentSource_Snapshot); !ok { + return nil, status.Error(codes.InvalidArgument, "unsupported volumeContentSource type") + } + sourceSnapshot := req.GetVolumeContentSource().GetSnapshot() + if sourceSnapshot == nil { + return nil, status.Error(codes.Internal, "error retrieving snapshot from the volumeContentSource") + } + + snapshotID, snapshotZone, err = ExtractIDAndZone(sourceSnapshot.GetSnapshotId()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter snapshotID: %s", err) + } + } + + chosenZones, err := chooseZones(req.GetAccessibilityRequirements(), snapshotZone) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "unable to choose zone from accessibilityRequirements: %s", err) + } + + volume, err := d.getOrCreateVolume(ctx, scwVolumeName, snapshotID, size, perfIOPS, chosenZones) + if err != nil { + return nil, status.Errorf(codeFromScalewayError(err), "could not get or create volume: %s", err) + } + + cv := csiVolume(volume) + cv.VolumeContext = map[string]string{ + encryptedKey: strconv.FormatBool(encrypted), + } + + return &csi.CreateVolumeResponse{ + Volume: cv, + }, nil +} + +// DeleteVolume deprovisions a volume. +// This operation MUST be idempotent. +func (d *controllerService) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) { + klog.V(4).Infof("DeleteVolume called with %s", stripSecretFromReq(*req)) + volumeID, volumeZone, err := ExtractIDAndZone(req.GetVolumeId()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter volumeID: %s", err) + } + + klog.V(4).Infof("Deleting volume with ID %s", volumeID) + if err := d.scaleway.DeleteVolume(ctx, volumeID, volumeZone); err != nil { + code := codeFromScalewayError(err) + if code == codes.NotFound { + klog.V(4).Infof("volume with ID %s not found", volumeID) + return &csi.DeleteVolumeResponse{}, nil + } + + return nil, status.Errorf(code, "failed to delete volume: %s", err) + } + + klog.V(4).Infof("volume with ID %s deleted", volumeID) + return &csi.DeleteVolumeResponse{}, nil +} + +// ControllerPublishVolume perform the work that is necessary for making the volume available on the given node. +// This operation MUST be idempotent. +func (d *controllerService) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { + klog.V(4).Infof("ControllerPublishVolume called with %s", stripSecretFromReq(*req)) + + volumeID, volumeZone, err := ExtractIDAndZone(req.GetVolumeId()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter volumeID: %s", err) + } + + nodeID, nodeZone, err := ExtractIDAndZone(req.GetNodeId()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter nodeID: %s", err) + } + + d.locks.Lock(volumeID) + defer d.locks.Unlock(volumeID) + + if err := validateVolumeCapabilities([]*csi.VolumeCapability{req.GetVolumeCapability()}, false); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "volumeCapability not supported: %s", err) + } + + volumeResp, err := d.scaleway.GetVolume(ctx, volumeID, volumeZone) + if err != nil { + return nil, status.Errorf(codeFromScalewayError(err), "unable to get volume to publish: %s", err) + } + + server, err := d.scaleway.GetServer(ctx, nodeID, nodeZone) + if err != nil { + return nil, status.Errorf(codeFromScalewayError(err), "unable to get server where to publish the volume: %s", err) + } + + publishContext := map[string]string{ + scwVolumeNameKey: volumeResp.Name, + scwVolumeIDKey: volumeResp.ID, + scwVolumeZoneKey: volumeResp.Zone.String(), + } + + // Is the volume already attached? + if volumeServerIDs := publishedNodeIDs(volumeResp); len(volumeServerIDs) != 0 { + if volumeServerIDs[0] == expandZonalID(server.ID, server.Zone) { + return &csi.ControllerPublishVolumeResponse{ + PublishContext: publishContext, + }, nil + } + + return nil, status.Errorf(codes.FailedPrecondition, "volume %s already attached to another node %s", volumeID, volumeServerIDs[0]) + } + + if len(server.Volumes) == scaleway.MaxVolumesPerNode { + return nil, status.Errorf(codes.ResourceExhausted, "max number of volumes reached for instance %s", nodeID) + } + + d.mux.Lock() + defer d.mux.Unlock() + if err := d.scaleway.AttachVolume(ctx, nodeID, volumeID, volumeZone); err != nil { + return nil, status.Errorf(codes.Internal, "failed to attach volume to instance: %s", err) + } + + return &csi.ControllerPublishVolumeResponse{ + PublishContext: publishContext, + }, nil +} + +// ControllerUnpublishVolume is the reverse operation of ControllerPublishVolume +// This operation MUST be idempotent. +func (d *controllerService) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) { + klog.V(4).Infof("ControllerUnpublishVolume called with %s", stripSecretFromReq(*req)) + + volumeID, volumeZone, err := ExtractIDAndZone(req.GetVolumeId()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter volumeID: %s", err) + } + + nodeID, nodeZone, err := ExtractIDAndZone(req.GetNodeId()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter nodeID: %s", err) + } + + d.locks.Lock(volumeID) + defer d.locks.Unlock(volumeID) + + volume, err := d.scaleway.GetVolume(ctx, volumeID, volumeZone) + if err != nil { + code := codeFromScalewayError(err) + if code == codes.NotFound { + return &csi.ControllerUnpublishVolumeResponse{}, nil + } + + return nil, status.Errorf(code, "failed to get volume to unpublish: %s", err) + } + + // Skip if volume is not attached. + if len(publishedNodeIDs(volume)) == 0 { + return &csi.ControllerUnpublishVolumeResponse{}, nil + } + + if _, err := d.scaleway.GetServer(ctx, nodeID, nodeZone); err != nil { + code := codeFromScalewayError(err) + if code == codes.NotFound { + return &csi.ControllerUnpublishVolumeResponse{}, nil + } + + return nil, status.Errorf(code, "failed to get server where to unpublish volume: %s", err) + } + + d.mux.Lock() + defer d.mux.Unlock() + if err := d.scaleway.DetachVolume(ctx, volumeID, volumeZone); err != nil { + return nil, status.Errorf(codes.Internal, "failed to detach volume: %s", err) + } + + return &csi.ControllerUnpublishVolumeResponse{}, nil +} + +// ValidateVolumeCapabilities check if a pre-provisioned volume has all the capabilities +// that the CO wants. This RPC call SHALL return confirmed only if all the +// volume capabilities specified in the request are supported. +// This operation MUST be idempotent. +func (d *controllerService) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) { + klog.V(4).Infof("ValidateVolumeCapabilities called with %s", stripSecretFromReq(*req)) + volumeID, volumeZone, err := ExtractIDAndZone(req.GetVolumeId()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter volumeID: %s", err) + } + + if _, err = d.scaleway.GetVolume(ctx, volumeID, volumeZone); err != nil { + return nil, status.Errorf(codeFromScalewayError(err), "failed to get volume: %s", err) + } + + if err := validateVolumeCapabilities(req.GetVolumeCapabilities(), false); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "unsupported capabilities: %s", err) + } + + return &csi.ValidateVolumeCapabilitiesResponse{ + Confirmed: &csi.ValidateVolumeCapabilitiesResponse_Confirmed{ + VolumeCapabilities: req.GetVolumeCapabilities(), + }, + }, nil +} + +// ListVolumes returns the list of the requested volumes +func (d *controllerService) ListVolumes(ctx context.Context, req *csi.ListVolumesRequest) (*csi.ListVolumesResponse, error) { + klog.V(4).Infof("ListVolumes called with %s", stripSecretFromReq(*req)) + + start, err := parseStartingToken(req.GetStartingToken()) + if err != nil { + return nil, status.Errorf(codes.Aborted, "invalid startingToken: %s", err) + } + + volumes, next, err := d.scaleway.ListVolumes(ctx, start, uint32(req.GetMaxEntries())) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to list volumes: %s", err) + } + + volumesEntries := make([]*csi.ListVolumesResponse_Entry, 0, len(volumes)) + for _, volume := range volumes { + volumesEntries = append(volumesEntries, &csi.ListVolumesResponse_Entry{ + Volume: csiVolume(volume), + Status: &csi.ListVolumesResponse_VolumeStatus{ + PublishedNodeIds: publishedNodeIDs(volume), + }, + }) + } + + return &csi.ListVolumesResponse{ + Entries: volumesEntries, + NextToken: next, + }, nil +} + +// GetCapacity returns the capacity of the storage pool from which the controller provisions volumes. +func (d *controllerService) GetCapacity(ctx context.Context, req *csi.GetCapacityRequest) (*csi.GetCapacityResponse, error) { + klog.V(4).Infof("GetCapacity is not yet implemented") + return nil, status.Error(codes.Unimplemented, "GetCapacity is not yet implemented") +} + +// ControllerGetCapabilities returns the supported capabilities of controller service provided by the Plugin. +func (d *controllerService) ControllerGetCapabilities(ctx context.Context, req *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) { + klog.V(4).Infof("ControllerGetCapabilities called with %s", stripSecretFromReq(*req)) + + capabilities := make([]*csi.ControllerServiceCapability, 0, len(controllerCapabilities)) + for _, capability := range controllerCapabilities { + capabilities = append(capabilities, &csi.ControllerServiceCapability{ + Type: &csi.ControllerServiceCapability_Rpc{ + Rpc: &csi.ControllerServiceCapability_RPC{ + Type: capability, + }, + }, + }) + } + + return &csi.ControllerGetCapabilitiesResponse{Capabilities: capabilities}, nil +} + +// CreateSnapshot creates a snapshot of the given volume +func (d *controllerService) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) { + klog.V(4).Infof("CreateSnapshot called with %s", stripSecretFromReq(*req)) + sourceVolumeID, sourceVolumeZone, err := ExtractIDAndZone(req.GetSourceVolumeId()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter sourceVolumeID: %s", err) + } + + d.locks.Lock(sourceVolumeID) + defer d.locks.Unlock(sourceVolumeID) + + name := req.GetName() + if name == "" { + return nil, status.Error(codes.InvalidArgument, "name not provided") + } + + snapshot, err := d.getOrCreateSnapshot(ctx, name, sourceVolumeID, sourceVolumeZone) + if err != nil { + return nil, status.Errorf(codeFromScalewayError(err), "unable to get or create snapshot: %s", err) + } + + // Fail if the snapshot is not in an expected state. + if snapshot.Status != block.SnapshotStatusAvailable && snapshot.Status != block.SnapshotStatusInUse { + return nil, status.Errorf(codes.Internal, "snapshot %s has an unexpected status: %s", snapshot.ID, snapshot.Status) + } + + return &csi.CreateSnapshotResponse{ + Snapshot: csiSnapshot(snapshot), + }, nil +} + +// DeleteSnapshot deletes the given snapshot +func (d *controllerService) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) { + klog.V(4).Infof("DeleteSnapshot called with %s", stripSecretFromReq(*req)) + snapshotID, snapshotZone, err := ExtractIDAndZone(req.GetSnapshotId()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter snapshotID: %s", err) + } + + if err := d.scaleway.DeleteSnapshot(ctx, snapshotID, snapshotZone); err != nil { + code := codeFromScalewayError(err) + if code == codes.NotFound { + klog.V(4).Infof("snapshot with ID %s not found", snapshotID) + return &csi.DeleteSnapshotResponse{}, nil + } + + return nil, status.Errorf(code, "unable to delete snapshot: %s", err) + } + + return &csi.DeleteSnapshotResponse{}, nil +} + +// ListSnapshots return the information about all snapshots on the +// storage system within the given parameters regardless of how +// they were created. ListSnapshots SHALL NOT list a snapshot that +// is being created but has not been cut successfully yet. +func (d *controllerService) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) { + klog.V(4).Infof("ListSnapshots called with %s", stripSecretFromReq(*req)) + + start, err := parseStartingToken(req.GetStartingToken()) + if err != nil { + return nil, status.Errorf(codes.Aborted, "invalid startingToken: %s", err) + } + + var snapshots []*block.SnapshotSummary + var next string + + switch { + case req.GetSnapshotId() != "": + snapshotID, snapshotZone, err := ExtractIDAndZone(req.GetSnapshotId()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter snapshotID: %s", err) + } + + snapshot, err := d.scaleway.GetSnapshot(ctx, snapshotID, snapshotZone) + if err != nil { + // If volume doesn't exist, simply return an empty list. + if code := codeFromScalewayError(err); code != codes.NotFound { + return nil, status.Errorf(code, "failed to get snapshot %q: %s", req.GetSnapshotId(), err) + } + } + + if snapshot != nil { + snapshots = append(snapshots, snapshot) + } + case req.GetSourceVolumeId() != "": + sourceVolumeID, sourceVolumeZone, err := ExtractIDAndZone(req.GetSourceVolumeId()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter sourceVolumeID: %s", err) + } + + s, n, err := d.scaleway.ListSnapshotsBySourceVolume(ctx, start, uint32(req.GetMaxEntries()), sourceVolumeID, sourceVolumeZone) + if err != nil { + return nil, status.Errorf(codeFromScalewayError(err), "failed to get snapshots for volume %q: %s", req.GetSourceVolumeId(), err) + } + + snapshots = append(snapshots, s...) + next = n + default: + s, n, err := d.scaleway.ListSnapshots(ctx, start, uint32(req.GetMaxEntries())) + if err != nil { + return nil, status.Errorf(codeFromScalewayError(err), "failed to list snapshots: %s", err) + } + + snapshots = append(snapshots, s...) + next = n + } + + snapshotsEntries := make([]*csi.ListSnapshotsResponse_Entry, 0, len(snapshots)) + for _, snap := range snapshots { + snapshotsEntries = append(snapshotsEntries, &csi.ListSnapshotsResponse_Entry{ + Snapshot: csiSnapshot(snap), + }) + } + + return &csi.ListSnapshotsResponse{ + Entries: snapshotsEntries, + NextToken: next, + }, nil +} + +// ControllerExpandVolume expands the given volume +func (d *controllerService) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) { + klog.V(4).Infof("ControllerExpandVolume called with %s", stripSecretFromReq(*req)) + volumeID, volumeZone, err := ExtractIDAndZone(req.GetVolumeId()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter volumeID: %s", err) + } + + d.locks.Lock(volumeID) + defer d.locks.Unlock(volumeID) + + nodeExpansionRequired := true + + if volumeCapability := req.GetVolumeCapability(); volumeCapability != nil { + block, _, err := validateVolumeCapability(volumeCapability) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "volumeCapabilities not supported: %s", err) + } + if block { + nodeExpansionRequired = false + } + } + + volumeResp, err := d.scaleway.GetVolume(ctx, volumeID, volumeZone) + if err != nil { + return nil, status.Errorf(codeFromScalewayError(err), "failed to get volume that will be expanded: %s", err) + } + + newSize, err := getVolumeRequestCapacity(req.GetCapacityRange()) + if err != nil { + return nil, status.Errorf(codes.OutOfRange, "capacityRange invalid: %s", err) + } + + if int64(volumeResp.Size) >= newSize { + // Volume is already larger than or equal to the target capacity. + return &csi.ControllerExpandVolumeResponse{CapacityBytes: int64(volumeResp.Size), NodeExpansionRequired: nodeExpansionRequired}, nil + } + + if err = d.scaleway.ResizeVolume(ctx, volumeID, volumeZone, newSize); err != nil { + return nil, status.Errorf(codeFromScalewayError(err), "failed to resize volume: %s", err) + } + + return &csi.ControllerExpandVolumeResponse{CapacityBytes: newSize, NodeExpansionRequired: nodeExpansionRequired}, nil +} + +// ControllerGetVolume gets a specific volume. +func (d *controllerService) ControllerGetVolume(ctx context.Context, req *csi.ControllerGetVolumeRequest) (*csi.ControllerGetVolumeResponse, error) { + klog.V(4).Infof("ControllerGetVolume called with %s", stripSecretFromReq(*req)) + volumeID, volumeZone, err := ExtractIDAndZone(req.GetVolumeId()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter volumeID: %s", err) + } + + volume, err := d.scaleway.GetVolume(ctx, volumeID, volumeZone) + if err != nil { + return nil, status.Errorf(codeFromScalewayError(err), "failed to get volume: %s", err) + } + + return &csi.ControllerGetVolumeResponse{ + Volume: csiVolume(volume), + Status: &csi.ControllerGetVolumeResponse_VolumeStatus{ + PublishedNodeIds: publishedNodeIDs(volume), + }, + }, nil +} diff --git a/driver/diskutils.go b/pkg/driver/diskutils.go similarity index 51% rename from driver/diskutils.go rename to pkg/driver/diskutils.go index d4d9700..4c02901 100644 --- a/driver/diskutils.go +++ b/pkg/driver/diskutils.go @@ -1,19 +1,18 @@ package driver import ( + "errors" "fmt" + "io/fs" "os" - "os/exec" "path" "path/filepath" - "strconv" "strings" "golang.org/x/sys/unix" "k8s.io/klog/v2" kmount "k8s.io/mount-utils" kexec "k8s.io/utils/exec" - utilsio "k8s.io/utils/io" ) const ( @@ -23,72 +22,66 @@ const ( diskLuksMapperPath = "/dev/mapper/" defaultFSType = "ext4" - - procMountInfoMaxListTries = 3 - procMountsExpectedNumFieldsPerLine = 6 - procMountInfoExpectedAtLeastNumFields = 10 - procMountsPath = "/proc/mounts" - procMountInfoPath = "/proc/self/mountinfo" - expectedAtLeastNumFieldsPerMountInfo = 10 ) type DiskUtils interface { // FormatAndMount tries to mount `devicePath` on `targetPath` as `fsType` with `mountOptions` - // If it fails it will try to format `devicePath` as `fsType` first and retry + // If it fails it will try to format `devicePath` as `fsType` first and retry. FormatAndMount(targetPath string, devicePath string, fsType string, mountOptions []string) error - // Unmount unmounts the given target + // Unmount unmounts the given target. Unmount(target string) error - // MountToTarget tries to mount `sourcePath` on `targetPath` as `fsType` with `mountOptions` + // MountToTarget tries to mount `sourcePath` on `targetPath` as `fsType` with `mountOptions`. MountToTarget(sourcePath, targetPath, fsType string, mountOptions []string) error - // IsBlockDevice returns true if `path` is a block device + // IsBlockDevice returns true if `path` is a block device. IsBlockDevice(path string) (bool, error) - // GetDevicePath returns the path for the specified volumeID + // GetDevicePath returns the path for the specified volumeID. GetDevicePath(volumeID string) (string, error) - // IsSharedMounted returns true is `devicePath` is shared mounted on `targetPath` - IsSharedMounted(targetPath string, devicePath string) (bool, error) - - // GetMountInfo returns a mount informations for `targetPath` - // taken from https://github.com/kubernetes/kubernetes/blob/master/pkg/util/mount/mount_linux.go - GetMountInfo(targetPath string) (*mountInfo, error) + // IsMounted returns true is `devicePath` has a device mounted. + IsMounted(targetPath string) bool - // GetStatfs return the statfs struct for the given path + // GetStatfs return the statfs struct for the given path. GetStatfs(path string) (*unix.Statfs_t, error) - // Resize resizes the given volumes, it will try to resize the LUKS device first if the passphrase is provided + // Resize resizes the given volumes, it will try to resize the LUKS device first if the passphrase is provided. Resize(targetPath string, devicePath, passphrase string) error - // IsEncrypted returns true if the device with the given path is encrypted with LUKS + // IsEncrypted returns true if the device with the given path is encrypted with LUKS. IsEncrypted(devicePath string) (bool, error) - // EncryptAndOpenDevice encrypts the volume with the given ID with the given passphrase and open it - // If the device is already encrypted (LUKS header present), it will only open the device + // EncryptAndOpenDevice encrypts the volume with the given ID with the given passphrase and opens it + // If the device is already encrypted (LUKS header present), it will only open the device. EncryptAndOpenDevice(volumeID string, passphrase string) (string, error) - // CloseDevice closes the encrypted device with the given ID + // CloseDevice closes the encrypted device with the given ID. CloseDevice(volumeID string) error - // GetMappedDevicePath returns the path on where the encrypted device with the given ID is mapped + // GetMappedDevicePath returns the path on where the encrypted device with the given ID is mapped. GetMappedDevicePath(volumeID string) (string, error) } type diskUtils struct { kMounter *kmount.SafeFormatAndMount + kResizer *kmount.ResizeFs } func newDiskUtils() *diskUtils { return &diskUtils{ - kMounter: &kmount.SafeFormatAndMount{ - Interface: kmount.New(""), - Exec: kexec.New(), - }, + kMounter: kmount.NewSafeFormatAndMount(kmount.New(""), kexec.New()), + kResizer: kmount.NewResizeFs(kexec.New()), } } +func devicePath(volumeID string) string { + return path.Join(diskByIDPath, diskSCWPrefix+volumeID) +} + +// EncryptAndOpenDevice encrypts the volume with the given ID with the given passphrase and opens it +// If the device is already encrypted (LUKS header present), it will only open the device. func (d *diskUtils) EncryptAndOpenDevice(volumeID string, passphrase string) (string, error) { encryptedDevicePath, err := d.GetMappedDevicePath(volumeID) if err != nil { @@ -105,6 +98,7 @@ func (d *diskUtils) EncryptAndOpenDevice(volumeID string, passphrase string) (st if err != nil { return "", fmt.Errorf("error getting device path for volume %s: %w", volumeID, err) } + isLuks, err := luksIsLuks(devicePath) if err != nil { return "", fmt.Errorf("error checking if device %s is a luks device: %w", devicePath, err) @@ -112,19 +106,19 @@ func (d *diskUtils) EncryptAndOpenDevice(volumeID string, passphrase string) (st if !isLuks { // need to format the device - err = luksFormat(devicePath, passphrase) - if err != nil { + if err = luksFormat(devicePath, passphrase); err != nil { return "", fmt.Errorf("error formating device %s: %w", devicePath, err) } } - err = luksOpen(devicePath, diskLuksMapperPrefix+volumeID, passphrase) - if err != nil { + if err = luksOpen(devicePath, diskLuksMapperPrefix+volumeID, passphrase); err != nil { return "", fmt.Errorf("error luks opening device %s: %w", devicePath, err) } + return diskLuksMapperPath + diskLuksMapperPrefix + volumeID, nil } +// CloseDevice closes the encrypted device with the given ID. func (d *diskUtils) CloseDevice(volumeID string) error { encryptedDevicePath, err := d.GetMappedDevicePath(volumeID) if err != nil { @@ -141,12 +135,12 @@ func (d *diskUtils) CloseDevice(volumeID string) error { return nil } +// GetMappedDevicePath returns the path on where the encrypted device with the given ID is mapped func (d *diskUtils) GetMappedDevicePath(volumeID string) (string, error) { mappedPath := diskLuksMapperPath + diskLuksMapperPrefix + volumeID - _, err := os.Stat(mappedPath) - if err != nil { + if _, err := os.Stat(mappedPath); err != nil { // if the mapped device does not exist on disk, it's not open - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return "", nil } return "", fmt.Errorf("error checking stat on %s: %w", mappedPath, err) @@ -176,6 +170,8 @@ func (d *diskUtils) GetMappedDevicePath(volumeID string) (string, error) { return mappedPath, nil } +// FormatAndMount tries to mount `devicePath` on `targetPath` as `fsType` with `mountOptions` +// If it fails it will try to format `devicePath` as `fsType` first and retry. func (d *diskUtils) FormatAndMount(targetPath string, devicePath string, fsType string, mountOptions []string) error { if fsType == "" { fsType = defaultFSType @@ -190,163 +186,66 @@ func (d *diskUtils) FormatAndMount(targetPath string, devicePath string, fsType return nil } +// Unmount unmounts the given target. func (d *diskUtils) Unmount(target string) error { - return kmount.CleanupMountPoint(target, d.kMounter, true) + if err := kmount.CleanupMountPoint(target, d.kMounter, true); err != nil { + return fmt.Errorf("failed to unmount target: %w", err) + } + + return nil } +// MountToTarget tries to mount `sourcePath` on `targetPath` as `fsType` with `mountOptions`. func (d *diskUtils) MountToTarget(sourcePath, targetPath, fsType string, mountOptions []string) error { if fsType == "" { fsType = defaultFSType } if err := d.kMounter.Mount(sourcePath, targetPath, fsType, mountOptions); err != nil { - return err + return fmt.Errorf("failed to mount to target: %w", err) } return nil } func (d *diskUtils) GetDevicePath(volumeID string) (string, error) { - devicePath := path.Join(diskByIDPath, diskSCWPrefix+volumeID) + devicePath := devicePath(volumeID) realDevicePath, err := filepath.EvalSymlinks(devicePath) if err != nil { - return "", err + return "", fmt.Errorf("failed to get real device path: %w", err) } deviceInfo, err := os.Stat(realDevicePath) if err != nil { - return "", err + return "", fmt.Errorf("failed to get device info: %w", err) } deviceMode := deviceInfo.Mode() if os.ModeDevice != deviceMode&os.ModeDevice || os.ModeCharDevice == deviceMode&os.ModeCharDevice { - return "", errDevicePathIsNotDevice + return "", errors.New("device path does not point on a block device") } return devicePath, nil } -func (d *diskUtils) IsSharedMounted(targetPath string, devicePath string) (bool, error) { - if targetPath == "" { - return false, errTargetPathEmpty - } - - mountInfo, err := d.GetMountInfo(targetPath) - if err != nil { - return false, err - } - - if mountInfo == nil { - return false, nil - } - - sharedMounted := false - for _, optionalField := range mountInfo.optionalFields { - tag := strings.Split(optionalField, ":") - if tag != nil && tag[0] == "shared" { - sharedMounted = true - } - } - if !sharedMounted { - return false, errTargetNotSharedMounter - } - - if devicePath != "" && mountInfo.source != devicePath { - return false, errTargetNotMounterOnRightDevice - } - - return true, nil -} - -// taken from https://github.com/kubernetes/kubernetes/blob/master/pkg/util/mount/mount_linux.go -// This represents a single line in /proc//mountinfo. -type mountInfo struct { - // Unique ID for the mount (maybe reused after umount). - id int - // The ID of the parent mount (or of self for the root of this mount namespace's mount tree). - parentID int - // The value of `st_dev` for files on this filesystem. - majorMinor string - // The pathname of the directory in the filesystem which forms the root of this mount. - root string - // Mount source, filesystem-specific information. e.g. device, tmpfs name. - source string - // Mount point, the pathname of the mount point. - mountPoint string - // Optional fieds, zero or more fields of the form "tag[:value]". - optionalFields []string - // The filesystem type in the form "type[.subtype]". - fsType string - // Per-mount options. - mountOptions []string - // Per-superblock options. - superOptions []string -} - -// taken from https://github.com/kubernetes/kubernetes/blob/master/pkg/util/mount/mount_linux.go -func (d *diskUtils) GetMountInfo(targetPath string) (*mountInfo, error) { - content, err := utilsio.ConsistentRead(procMountInfoPath, procMountInfoMaxListTries) +func (d *diskUtils) IsMounted(targetPath string) bool { + notMnt, err := d.kMounter.IsLikelyNotMountPoint(targetPath) if err != nil { - return &mountInfo{}, err + return false } - contentStr := string(content) - for _, line := range strings.Split(contentStr, "\n") { - if line == "" { - // the last split() item is empty string following the last \n - continue - } - // See `man proc` for authoritative description of format of the file. - fields := strings.Fields(line) - if len(fields) < expectedAtLeastNumFieldsPerMountInfo { - return nil, fmt.Errorf("wrong number of fields in (expected at least %d, got %d): %s", expectedAtLeastNumFieldsPerMountInfo, len(fields), line) - } - if fields[4] != targetPath { - continue - } - id, err := strconv.Atoi(fields[0]) - if err != nil { - return nil, err - } - parentID, err := strconv.Atoi(fields[1]) - if err != nil { - return nil, err - } - info := &mountInfo{ - id: id, - parentID: parentID, - majorMinor: fields[2], - root: fields[3], - mountPoint: fields[4], - mountOptions: strings.Split(fields[5], ","), - } - // All fields until "-" are "optional fields". - i := 6 - for ; i < len(fields) && fields[i] != "-"; i++ { - info.optionalFields = append(info.optionalFields, fields[i]) - } - // Parse the rest 3 fields. - i++ - if len(fields)-i < 3 { - return nil, fmt.Errorf("expect 3 fields in %s, got %d", line, len(fields)-i) - } - info.fsType = fields[i] - info.source = fields[i+1] - info.superOptions = strings.Split(fields[i+2], ",") - return info, nil - } - return nil, nil + return !notMnt } func (d *diskUtils) IsBlockDevice(path string) (bool, error) { realPath, err := filepath.EvalSymlinks(path) if err != nil { - return false, err + return false, fmt.Errorf("failed to get real path: %w", err) } deviceInfo, err := os.Stat(realPath) if err != nil { - return false, err + return false, fmt.Errorf("failed to get device info: %w", err) } deviceMode := deviceInfo.Mode() @@ -360,8 +259,11 @@ func (d *diskUtils) IsBlockDevice(path string) (bool, error) { func (d *diskUtils) GetStatfs(path string) (*unix.Statfs_t, error) { fs := &unix.Statfs_t{} - err := unix.Statfs(path, fs) - return fs, err + if err := unix.Statfs(path, fs); err != nil { + return nil, fmt.Errorf("failed to statfs: %w", err) + } + + return fs, nil } func (d *diskUtils) IsEncrypted(devicePath string) (bool, error) { @@ -369,11 +271,6 @@ func (d *diskUtils) IsEncrypted(devicePath string) (bool, error) { } func (d *diskUtils) Resize(targetPath string, devicePath, passphrase string) error { - mountInfo, err := d.GetMountInfo(targetPath) - if err != nil { - return err - } - if passphrase != "" { klog.V(4).Infof("resizing LUKS device %s", devicePath) if err := luksResize(devicePath, passphrase); err != nil { @@ -381,24 +278,18 @@ func (d *diskUtils) Resize(targetPath string, devicePath, passphrase string) err } } - klog.V(4).Infof("resizing filesystem %s on %s", mountInfo.fsType, devicePath) + klog.V(4).Infof("resizing %s", devicePath) - switch mountInfo.fsType { - case "ext3", "ext4": - resize2fsPath, err := exec.LookPath("resize2fs") - if err != nil { - return err - } - resize2fsArgs := []string{devicePath} - return exec.Command(resize2fsPath, resize2fsArgs...).Run() - case "xfs": - xfsGrowfsPath, err := exec.LookPath("xfs_growfs") - if err != nil { - return err + needResize, err := d.kResizer.NeedResize(devicePath, targetPath) + if err != nil { + return fmt.Errorf("failed to check if resize is needed: %w", err) + } + + if needResize { + if _, err := d.kResizer.Resize(devicePath, targetPath); err != nil { + return fmt.Errorf("failed to resize volume: %w", err) } - xfsGrowfsArgs := []string{"-d", targetPath} - return exec.Command(xfsGrowfsPath, xfsGrowfsArgs...).Run() } - return fmt.Errorf("filesystem %s does not support resizing", mountInfo.fsType) + return nil } diff --git a/pkg/driver/diskutils_fake.go b/pkg/driver/diskutils_fake.go new file mode 100644 index 0000000..e88bc07 --- /dev/null +++ b/pkg/driver/diskutils_fake.go @@ -0,0 +1,192 @@ +package driver + +import ( + "fmt" + "os" + "strings" + "sync" + + "github.com/scaleway/scaleway-sdk-go/api/instance/v1" + kmount "k8s.io/mount-utils" + + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" + "golang.org/x/sys/unix" +) + +type mountpoint struct { + targetPath string + fsType string + mountOptions []string + block bool +} + +type fakeDiskUtils struct { + // Pointer to a server. When a NodeController publishes or unpublishes a + // volume, it must update the volumes in this server. + server *instance.Server + + devices map[string]*mountpoint + + mux sync.Mutex +} + +func newFakeDiskUtils(server *instance.Server) *fakeDiskUtils { + return &fakeDiskUtils{ + server: server, + devices: make(map[string]*mountpoint), + } +} + +func (f *fakeDiskUtils) refreshDevices() { + // Remove devices no longer present in server object. + for p := range f.devices { + if strings.HasPrefix(p, diskByIDPath) && + !slices.ContainsFunc(maps.Values(f.server.Volumes), func(v *instance.VolumeServer) bool { + return devicePath(v.ID) == p + }) { + delete(f.devices, p) + } + } + + // Add new devices + for _, v := range f.server.Volumes { + devPath := devicePath(v.ID) + if _, ok := f.devices[devPath]; !ok { + f.devices[devPath] = &mountpoint{ + block: true, + } + } + } +} + +func (f *fakeDiskUtils) FormatAndMount(targetPath string, devicePath string, fsType string, mountOptions []string) error { + f.mux.Lock() + defer f.mux.Unlock() + + f.refreshDevices() + + if fsType == "" { + fsType = defaultFSType + } + + f.devices[devicePath] = &mountpoint{ + targetPath: targetPath, + fsType: fsType, + mountOptions: mountOptions, + block: false, + } + + return nil +} + +// Unmount unmounts the given target +func (f *fakeDiskUtils) Unmount(target string) error { + if err := kmount.CleanupMountPoint(target, kmount.New(""), true); err != nil { + return fmt.Errorf("failed to unmount target: %w", err) + } + + return nil +} + +// MountToTarget tries to mount `sourcePath` on `targetPath` as `fsType` with `mountOptions` +func (f *fakeDiskUtils) MountToTarget(sourcePath, targetPath, fsType string, mountOptions []string) error { + f.mux.Lock() + defer f.mux.Unlock() + + f.refreshDevices() + + if fsType == "" { + fsType = defaultFSType + } + + f.devices[sourcePath] = &mountpoint{ + targetPath: targetPath, + fsType: fsType, + mountOptions: mountOptions, + block: strings.HasPrefix(sourcePath, diskByIDPath), + } + + return nil +} + +// IsBlockDevice returns true if `path` is a block device +func (f *fakeDiskUtils) IsBlockDevice(path string) (bool, error) { + f.mux.Lock() + defer f.mux.Unlock() + + f.refreshDevices() + + for _, mp := range f.devices { + if mp.targetPath == path { + return mp.block, nil + } + } + + return false, fmt.Errorf("not found") // enough for csi sanity? +} + +// GetDevicePath returns the path for the specified volumeID +func (f *fakeDiskUtils) GetDevicePath(volumeID string) (string, error) { + f.mux.Lock() + defer f.mux.Unlock() + + f.refreshDevices() + + devPath := devicePath(volumeID) + + if _, ok := f.devices[devPath]; ok { + return devPath, nil + } + + return "", os.ErrNotExist +} + +func (f *fakeDiskUtils) IsMounted(targetPath string) bool { + f.mux.Lock() + defer f.mux.Unlock() + + f.refreshDevices() + + for _, tp := range f.devices { + if tp.targetPath == targetPath { + return true + } + } + + return false +} + +// GetStatfs return the statfs struct for the given path +func (f *fakeDiskUtils) GetStatfs(path string) (*unix.Statfs_t, error) { + return &unix.Statfs_t{ + Blocks: 1000, + Bsize: 4, + Bfree: 500, + Files: 1000, + Ffree: 500, + }, nil +} + +func (f *fakeDiskUtils) Resize(targetPath string, devicePath, passphrase string) error { + return nil +} + +// IsEncrypted returns true if the device with the given path is encrypted with LUKS +func (f *fakeDiskUtils) IsEncrypted(devicePath string) (bool, error) { + return false, nil +} + +func (f *fakeDiskUtils) EncryptAndOpenDevice(volumeID string, passphrase string) (string, error) { + return "", nil +} + +// CloseDevice closes the encrypted device with the given ID +func (f *fakeDiskUtils) CloseDevice(volumeID string) error { + return nil +} + +// GetMappedDevicePath returns the path on where the encrypted device with the given ID is mapped +func (f *fakeDiskUtils) GetMappedDevicePath(volumeID string) (string, error) { + return "", nil +} diff --git a/driver/driver.go b/pkg/driver/driver.go similarity index 52% rename from driver/driver.go rename to pkg/driver/driver.go index b66628a..221df38 100644 --- a/driver/driver.go +++ b/pkg/driver/driver.go @@ -2,7 +2,9 @@ package driver import ( "context" + "errors" "fmt" + "io/fs" "net" "net/url" "os" @@ -17,44 +19,72 @@ import ( ) const ( - // DriverName is the official name for the Scaleway CSI plugin - DriverName = "csi.scaleway.com" + // DriverName is the official name for the Scaleway CSI plugin. + DriverName = "csi.scaleway.com" + + // ZoneTopologyKey is the topology key used to provision volumes. ZoneTopologyKey = "topology." + DriverName + "/zone" - // ExtraUserAgentEnv is the environment variable that adds some string at the end of the user agent + // ExtraUserAgentEnv is the environment variable that adds some string at the end of the user agent. ExtraUserAgentEnv = "EXTRA_USER_AGENT" ) -// Mode represents the mode in which the CSI driver started +// Mode represents the mode in which the CSI driver started. type Mode string const ( - // ControllerMode represents the controller mode + // ControllerMode represents the controller mode. ControllerMode Mode = "controller" - // NodeMode represents the node mode + // NodeMode represents the node mode. NodeMode Mode = "node" - // AllMode represents the the controller and the node mode at the same time + // AllMode represents the the controller and the node mode at the same time. AllMode Mode = "all" ) // DriverConfig is used to configure a new Driver type DriverConfig struct { + // Endpoint is the path to the CSI endpoint. Endpoint string - Prefix string - Mode Mode + // Prefix added to the name of newly created volumes. + Prefix string + // Plugin mode. + Mode Mode } -// Driver implements the interfaces csi.IdentityServer, csi.ControllerServer and csi.NodeServer +// Driver implements the interfaces csi.IdentityServer, csi.ControllerServer and csi.NodeServer. type Driver struct { - controllerService - nodeService + // controllerService implements the ControllerServer. + *controllerService + + // nodeService implements the NodeServer. + *nodeService + // User config. config *DriverConfig + // grpc server. srv *grpc.Server } -// NewDriver returns a CSI plugin +// mode parses the current mode and returns whether the controller/node services +// should be started. +func mode(mode Mode) (controller bool, node bool, err error) { + switch mode { + case ControllerMode: + controller = true + case NodeMode: + node = true + case AllMode: + controller = true + node = true + default: + err = fmt.Errorf("unknown mode for driver: %s", mode) + } + + return +} + +// NewDriver returns a CSI plugin. func NewDriver(config *DriverConfig) (*Driver, error) { klog.Infof("Driver: %s Version: %s", DriverName, driverVersion) @@ -62,16 +92,26 @@ func NewDriver(config *DriverConfig) (*Driver, error) { config: config, } - switch config.Mode { - case ControllerMode: - driver.controllerService = newControllerService(config) - case NodeMode: - driver.nodeService = newNodeService() - case AllMode: - driver.controllerService = newControllerService(config) - driver.nodeService = newNodeService() - default: - return nil, fmt.Errorf("unknown mode for driver: %s", config.Mode) + controller, node, err := mode(config.Mode) + if err != nil { + return nil, err + } + + if controller { + ctrl, err := newControllerService(config) + if err != nil { + return nil, err + } + driver.controllerService = ctrl + } + + if node { + nodeService, err := newNodeService() + if err != nil { + return nil, err + } + + driver.nodeService = nodeService } return driver, nil @@ -81,33 +121,32 @@ func NewDriver(config *DriverConfig) (*Driver, error) { func (d *Driver) Run() error { endpointURL, err := url.Parse(d.config.Endpoint) if err != nil { - return err + return fmt.Errorf("failed to parse endpoint URL: %w", err) } if endpointURL.Scheme != "unix" { klog.Errorf("only unix domain sockets are supported, not %s", endpointURL.Scheme) - return errSchemeNotSupported + return errors.New("scheme not supported for endpoint") } addr := path.Join(endpointURL.Host, filepath.FromSlash(endpointURL.Path)) klog.Infof("Removing existing socket if existing") - if err := os.Remove(addr); err != nil && !os.IsNotExist(err) { + if err := os.Remove(addr); err != nil && !errors.Is(err, fs.ErrNotExist) { klog.Errorf("error removing existing socket") - return errRemovingSocket + return errors.New("error removing existing socket") } dir := filepath.Dir(addr) - if _, err := os.Stat(dir); os.IsNotExist(err) { - err = os.MkdirAll(dir, os.ModePerm) - if err != nil { - return err + if _, err := os.Stat(dir); errors.Is(err, fs.ErrNotExist) { + if err = os.MkdirAll(dir, os.ModePerm); err != nil { + return fmt.Errorf("failed to create dir for socket: %w", err) } } listener, err := net.Listen(endpointURL.Scheme, addr) if err != nil { - return err + return fmt.Errorf("failed to create listener: %w", err) } // log error through a grpc unary interceptor @@ -119,25 +158,21 @@ func (d *Driver) Run() error { return resp, err } - opts := []grpc.ServerOption{ - grpc.UnaryInterceptor(logErrorHandler), - } - - d.srv = grpc.NewServer(opts...) + d.srv = grpc.NewServer(grpc.UnaryInterceptor(logErrorHandler)) csi.RegisterIdentityServer(d.srv, d) - switch d.config.Mode { - case ControllerMode: - csi.RegisterControllerServer(d.srv, d) - case NodeMode: - csi.RegisterNodeServer(d.srv, d) - case AllMode: + controller, node, err := mode(d.config.Mode) + if err != nil { + return err + } + + if controller { csi.RegisterControllerServer(d.srv, d) - csi.RegisterNodeServer(d.srv, d) - default: - return fmt.Errorf("unknown mode for driver: %s", d.config.Mode) // should never happen though + } + if node { + csi.RegisterNodeServer(d.srv, d) } // graceful shutdown @@ -149,5 +184,5 @@ func (d *Driver) Run() error { }() klog.Infof("CSI server started on %s", d.config.Endpoint) - return d.srv.Serve(listener) + return d.srv.Serve(listener) //nolint: wrapcheck } diff --git a/pkg/driver/helpers.go b/pkg/driver/helpers.go new file mode 100644 index 0000000..9c41ba7 --- /dev/null +++ b/pkg/driver/helpers.go @@ -0,0 +1,494 @@ +package driver + +import ( + "context" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "reflect" + "strconv" + "strings" + + "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/scaleway/scaleway-csi/pkg/scaleway" + block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1" + "github.com/scaleway/scaleway-sdk-go/scw" + "golang.org/x/exp/slices" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" + "k8s.io/klog/v2" +) + +// UserAgent returns the CSI driver user-agent. +func UserAgent() (userAgent string) { + userAgent = fmt.Sprintf("%s %s (%s)", DriverName, driverVersion, gitCommit) + if extraUA := os.Getenv(ExtraUserAgentEnv); extraUA != "" { + userAgent = userAgent + " " + extraUA + } + + return +} + +// expandZonalID concatenates the ID and zone of a resource to create a zonal ID. +func expandZonalID(id string, zone scw.Zone) string { + return fmt.Sprintf("%s/%s", zone, id) +} + +// ExtractIDAndZone takes a zonal ID and returns the ID and zone of a resource. +func ExtractIDAndZone(id string) (string, scw.Zone, error) { + if id == "" { + return "", scw.Zone(""), status.Errorf(codes.InvalidArgument, "ID must not be empty") + } + + splitID := strings.Split(id, "/") + if len(splitID) > 2 { + return "", scw.Zone(""), status.Errorf(codes.InvalidArgument, "ID %q is not correctly formatted", id) + } else if len(splitID) == 1 { + return splitID[0], scw.Zone(""), nil + } else { // id like zone/uuid + zone, err := scw.ParseZone(splitID[0]) + if err != nil { + klog.Warningf("wrong zone in ID %q, will try default zone", id) + return splitID[1], scw.Zone(""), nil //nolint:nilerr + } + return splitID[1], zone, nil + } +} + +// chooseZones returns the most appropriate zones according to the accessibility +// requirements and snapshot zone. +func chooseZones(accessibilityRequirements *csi.TopologyRequirement, snapshotZone scw.Zone) ([]scw.Zone, error) { + if accessibilityRequirements != nil { + requestedZones := map[string]scw.Zone{} + for _, req := range accessibilityRequirements.GetRequisite() { + topologyKeys := req.GetSegments() + for topologyKey, topologyValue := range topologyKeys { + switch topologyKey { + case ZoneTopologyKey: + zone, err := scw.ParseZone(topologyValue) + if err != nil { + klog.Warningf("the given value for requisite %s: %s is not a valid zone", ZoneTopologyKey, topologyValue) + continue + } + if snapshotZone == scw.Zone("") || snapshotZone == zone { + requestedZones[topologyValue] = zone + } + default: + klog.Warningf("unknow topology key %s for requisite", topologyKey) + } + } + } + + preferredZones := []scw.Zone{} + preferredZonesMap := map[string]scw.Zone{} + for _, pref := range accessibilityRequirements.GetPreferred() { + topologyKeys := pref.GetSegments() + for topologyKey, topologyValue := range topologyKeys { + switch topologyKey { + case ZoneTopologyKey: + zone, err := scw.ParseZone(topologyValue) + if err != nil { + klog.Warningf("the given value for preferred %s: %s is not a valid zone", ZoneTopologyKey, topologyValue) + continue + } + if snapshotZone == scw.Zone("") || snapshotZone == zone { + if _, ok := preferredZonesMap[topologyValue]; !ok { + if accessibilityRequirements.GetRequisite() != nil { + if _, ok := requestedZones[topologyValue]; !ok { + return nil, status.Errorf(codes.InvalidArgument, "%s: %s is specified in preferred but not in requisite", topologyKey, topologyValue) + } + delete(requestedZones, topologyValue) + } + + preferredZonesMap[topologyValue] = zone + preferredZones = append(preferredZones, zone) + } + } + default: + klog.Warningf("unknow topology key %s for preferred", topologyKey) + } + } + } + + for _, requestedZone := range requestedZones { + preferredZones = append(preferredZones, requestedZone) + } + + if snapshotZone != scw.Zone("") && len(preferredZones) != 1 { + return nil, status.Error(codes.ResourceExhausted, "desired volume content source and desired topology are not compatible, different zones") + } + return preferredZones, nil + } + + if snapshotZone != scw.Zone("") { + return []scw.Zone{snapshotZone}, nil + } + + return []scw.Zone{}, nil +} + +// validateVolumeCapabilities makes sure the provided volume capabilities are +// valid and supported by the driver. If optional is false and no volumeCapabilities +// are provided, an error is returned. +func validateVolumeCapabilities(volumeCapabilities []*csi.VolumeCapability, optional bool) error { + if !optional && len(volumeCapabilities) == 0 { + return errors.New("no volumeCapabilities were provided") + } + + for i, volumeCapability := range volumeCapabilities { + if _, _, err := validateVolumeCapability(volumeCapability); err != nil { + return fmt.Errorf("unsupported volume capability at index %d: %w", i, err) + } + } + + return nil +} + +// validateVolumeCapability validates a single volume capacity. +func validateVolumeCapability(volumeCapability *csi.VolumeCapability) (block bool, mount bool, err error) { + mode := volumeCapability.GetAccessMode().GetMode() + if !slices.Contains(supportedAccessModes, mode) { + return false, false, fmt.Errorf("mode %q not supported", mode.String()) + } + + block = volumeCapability.GetBlock() != nil + mount = volumeCapability.GetMount() != nil + + // It should be impossible for block and mount to be true at the same time. + if block && mount { + return false, false, errors.New("both mount and block volume type specified") + } + + if !block && !mount { + return false, false, errors.New("one of block or mount access type is not specified") + } + + return block, mount, nil +} + +// getVolumeRequestCapacity returns the volume capacity that will be requested +// to the Scaleway block storage according to the provided capacity range. +func getVolumeRequestCapacity(capacityRange *csi.CapacityRange) (int64, error) { + if capacityRange == nil { + return scaleway.MinVolumeSize, nil + } + + requiredBytes := capacityRange.GetRequiredBytes() + requiredBytesSet := requiredBytes > 0 + + limitBytes := capacityRange.GetLimitBytes() + limitBytesSet := limitBytes > 0 + + if !requiredBytesSet && !limitBytesSet { + return scaleway.MinVolumeSize, nil + } + + if requiredBytesSet && limitBytesSet && limitBytes < requiredBytes { + return 0, errors.New("limit size is less than required size") + } + + if requiredBytesSet && !limitBytesSet && requiredBytes < scaleway.MinVolumeSize { + return 0, errors.New("required size is less than the minimum size") + } + + if limitBytesSet && limitBytes < scaleway.MinVolumeSize { + return 0, errors.New("limit size is less than the minimum size") + } + + if requiredBytesSet && limitBytesSet && requiredBytes == limitBytes { + return requiredBytes, nil + } + + if requiredBytesSet { + return requiredBytes, nil + } + + if limitBytesSet { + return limitBytes, nil + } + + return scaleway.MinVolumeSize, nil +} + +// createMountPoint creates a mount point to the specified path. When file is +// set to true, it creates a file mount point (needed to bind a block volume). +func createMountPoint(path string, file bool) error { + _, err := os.Stat(path) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("failed to stat mountpoint: %w", err) + } + + if file { + if err := os.MkdirAll(filepath.Dir(path), os.FileMode(0755)); err != nil { + return fmt.Errorf("failed to create dir for mountpoint: %w", err) + } + + file, err := os.OpenFile(path, os.O_CREATE, os.FileMode(0644)) + if err != nil { + return fmt.Errorf("failed to create mountpoint file: %w", err) + } + defer file.Close() + } else { + if err := os.MkdirAll(path, os.FileMode(0755)); err != nil { + return fmt.Errorf("failed to create mountpoint dir: %w", err) + } + } + + return nil +} + +// secretsField is the name of the field that contains a map with secrets. +const secretsField = "Secrets" + +// stripSecretFromReq returns a CSI request as a string after stripping all secrets. +func stripSecretFromReq(req any) string { + ret := "{" + + reqValue := reflect.ValueOf(req) + reqType := reqValue.Type() + if reqType.Kind() == reflect.Struct { + for i := 0; i < reqValue.NumField(); i++ { + field := reqType.Field(i) + value := reqValue.Field(i) + + valueToPrint := fmt.Sprintf("%+v", value.Interface()) + + if field.Name == secretsField && value.Kind() == reflect.Map { + valueToPrint = "[" + for j := 0; j < len(value.MapKeys()); j++ { + valueToPrint += fmt.Sprintf("%s:", value.MapKeys()[j].String()) + if j != len(value.MapKeys())-1 { + valueToPrint += " " + } + } + valueToPrint += "]" + } + + ret += fmt.Sprintf("%s:%s", field.Name, valueToPrint) + if i != reqValue.NumField()-1 { + ret += " " + } + } + } + + ret += "}" + + return ret +} + +// getOrCreateVolume gets a volume by name or creates it if it does not exist. +func (d *controllerService) getOrCreateVolume(ctx context.Context, name, snapshotID string, size int64, perfIOPS *uint32, zones []scw.Zone) (*block.Volume, error) { + if len(zones) == 0 { + zones = append(zones, scw.Zone("")) + } + + for _, zone := range zones { + volume, err := d.scaleway.GetVolumeByName(ctx, name, scw.Size(size), zone) + if err != nil && !errors.Is(err, scaleway.ErrVolumeNotFound) { + return nil, fmt.Errorf("failed to try to get existing volume %q: %w", name, err) + } + + if volume != nil { + // volume exists. + return volume, nil + } + } + + var errs []error + for _, zone := range zones { + volume, err := d.scaleway.CreateVolume(ctx, name, snapshotID, size, perfIOPS, zone) + if err != nil { + errs = append(errs, err) + continue + } + + return volume, nil + } + + return nil, fmt.Errorf("failed to create volume: %w", errors.Join(errs...)) +} + +// getOrCreateSnapshot gets a snapshot by name or creates it if it does not exist. +func (d *controllerService) getOrCreateSnapshot(ctx context.Context, name, sourceVolumeID string, zone scw.Zone) (*block.SnapshotSummary, error) { + snapshot, err := d.scaleway.GetSnapshotByName(ctx, name, sourceVolumeID, zone) + if err != nil && !errors.Is(err, scaleway.ErrSnapshotNotFound) { + return nil, fmt.Errorf("failed to try to get existing snapshot: %w", err) + } + + if snapshot == nil { + // Create snapshot if it does not exist. + snapshot, err = d.scaleway.CreateSnapshot(ctx, name, sourceVolumeID, zone) + if err != nil { + return nil, fmt.Errorf("failed to create snapshot: %w", err) + } + } + + // Wait for snapshot to be cut. + snapshot, err = d.scaleway.WaitForSnapshot(ctx, snapshot.ID, zone) + if err != nil { + return nil, fmt.Errorf("wait for snapshot ended with an error: %w", err) + } + + return snapshot, nil +} + +// parseCreateVolumeParams parses the params sent by the client during the +// creation of a volume. It returns the requested number of IOPS if specified. +// The second return value is true if the volume should be encrypted. +func parseCreateVolumeParams(params map[string]string) (*uint32, bool, error) { + var ( + encrypted bool + perfIOPS *uint32 + volumeType string + ) + + for key, value := range params { + switch strings.ToLower(key) { + case volumeTypeKey: + if value != scaleway.LegacyDefaultVolumeType { + return nil, false, fmt.Errorf("invalid value (%s) for parameter %s: unknown volume type", value, key) + } + + volumeType = value + case encryptedKey: + encryptedValue, err := strconv.ParseBool(value) + if err != nil { + return nil, false, fmt.Errorf("invalid bool value (%s) for parameter %s: %s", value, key, err) + } + encrypted = encryptedValue + + case volumeIOPSKey: + iops, err := strconv.Atoi(value) + if err != nil { + return nil, false, fmt.Errorf("invalid value (%s) for parameter %s: %s", value, key, err) + } + + perfIOPS = scw.Uint32Ptr(uint32(iops)) + default: + return nil, false, fmt.Errorf("invalid parameter key %s", key) + } + } + + // Params are invalid if LegacyDefaultVolumeType is set but number of IOPS is + // different than what is supported. + if volumeType == scaleway.LegacyDefaultVolumeType && perfIOPS != nil && + *perfIOPS != scaleway.LegacyDefaultVolumeTypeIOPS { + return nil, false, fmt.Errorf("volume type %s only supports %d iops", + scaleway.LegacyDefaultVolumeType, scaleway.LegacyDefaultVolumeTypeIOPS) + } + + return perfIOPS, encrypted, nil +} + +// csiVolume returns a CSI Volume from a Scaleway Volume spec. +func csiVolume(volume *block.Volume) *csi.Volume { + var contentSource *csi.VolumeContentSource + if volume.ParentSnapshotID != nil { + contentSource = &csi.VolumeContentSource{ + Type: &csi.VolumeContentSource_Snapshot{ + Snapshot: &csi.VolumeContentSource_SnapshotSource{ + SnapshotId: expandZonalID(*volume.ParentSnapshotID, volume.Zone), + }, + }, + } + } + + return &csi.Volume{ + VolumeId: expandZonalID(volume.ID, volume.Zone), + CapacityBytes: int64(volume.Size), + AccessibleTopology: []*csi.Topology{ + { + Segments: map[string]string{ZoneTopologyKey: volume.Zone.String()}, + }, + }, + ContentSource: contentSource, + } +} + +// publishedNodeIDs returns the ID of the node that the volume is attached to. +// There will be either one or zero ID in the returned slice as the volume can +// be attached to at most one server. +func publishedNodeIDs(volume *block.Volume) []string { + ids := make([]string, 0, len(volume.References)) + + for _, v := range volume.References { + if v.ProductResourceType == scaleway.InstanceServerProductResourceType { + ids = append(ids, expandZonalID(v.ProductResourceID, volume.Zone)) + break + } + } + + return ids +} + +// csiSnapshot returns a CSI Snapshot from a SnapshotSummary. +func csiSnapshot(snapshot *block.SnapshotSummary) *csi.Snapshot { + snap := &csi.Snapshot{ + SizeBytes: int64(snapshot.Size), + SnapshotId: expandZonalID(snapshot.ID, snapshot.Zone), + ReadyToUse: snapshot.Status == block.SnapshotStatusAvailable, + } + + if snapshot.ParentVolume != nil { + snap.SourceVolumeId = expandZonalID(snapshot.ParentVolume.ID, snapshot.Zone) + } + + if snapshot.CreatedAt != nil { + snap.CreationTime = timestamppb.New(*snapshot.CreatedAt) + } + + return snap +} + +// parseStartingToken parses a numeric starting token. +func parseStartingToken(token string) (uint32, error) { + if token == "" { + return 0, nil + } + + start, err := strconv.Atoi(token) + if err != nil { + return 0, fmt.Errorf("failed to parse token into a number: %w", err) + } + + if start < 0 { + return 0, nil + } + + return uint32(start), nil +} + +// isVolumeEncrypted returns true if the volume context specifies that the volume +// should be encrypted. +func isVolumeEncrypted(volumeContext map[string]string) (bool, error) { + encrypted := false + if encryptedValueString, ok := volumeContext[encryptedKey]; ok { + encryptedValue, err := strconv.ParseBool(encryptedValueString) + if err != nil { + return false, fmt.Errorf("failed to check if volume is encrypted from volume context: %w", err) + } + + encrypted = encryptedValue + } + + return encrypted, nil +} + +// codeFromError takes an error and returns the most appropriate GRPC error code +// according to the CSI spec. +func codeFromScalewayError(err error) codes.Code { + switch { + case errors.Is(err, scaleway.ErrVolumeDifferentSize), errors.Is(err, scaleway.ErrSnapshotExists): + return codes.AlreadyExists + case scaleway.IsNotFoundError(err), scaleway.IsGoneError(err): + return codes.NotFound + case scaleway.IsPreconditionFailedError(err): + // Most likely when trying to delete a volume that is already attached. + return codes.FailedPrecondition + default: + return codes.Internal + } +} diff --git a/pkg/driver/helpers_test.go b/pkg/driver/helpers_test.go new file mode 100644 index 0000000..e03ab21 --- /dev/null +++ b/pkg/driver/helpers_test.go @@ -0,0 +1,1177 @@ +package driver + +import ( + "reflect" + "strconv" + "testing" + + "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/scaleway/scaleway-csi/pkg/scaleway" + block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1" + "github.com/scaleway/scaleway-sdk-go/scw" +) + +func TestExtractIDAndZone(t *testing.T) { + t.Parallel() + type args struct { + id string + } + tests := []struct { + name string + args args + want string + want1 scw.Zone + wantErr bool + }{ + { + name: "empty ID", + args: args{ + id: "", + }, + want: "", + want1: scw.Zone(""), + wantErr: true, + }, + { + name: "invalid ID format", + args: args{ + id: "fr-par-1/4d33a22f-9794-4f29-a92e-083c03d60681/abcd", + }, + want: "", + want1: scw.Zone(""), + wantErr: true, + }, + { + name: "ID without zone", + args: args{ + id: "4d33a22f-9794-4f29-a92e-083c03d60681", + }, + want: "4d33a22f-9794-4f29-a92e-083c03d60681", + want1: scw.Zone(""), + wantErr: false, + }, + { + name: "ID and zone", + args: args{ + id: "fr-par-1/4d33a22f-9794-4f29-a92e-083c03d60681", + }, + want: "4d33a22f-9794-4f29-a92e-083c03d60681", + want1: scw.ZoneFrPar1, + wantErr: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, got1, err := ExtractIDAndZone(tt.args.id) + if (err != nil) != tt.wantErr { + t.Errorf("ExtractIDAndZone() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ExtractIDAndZone() got = %v, want %v", got, tt.want) + } + if !reflect.DeepEqual(got1, tt.want1) { + t.Errorf("ExtractIDAndZone() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func Test_chooseZones(t *testing.T) { + t.Parallel() + type args struct { + accessibilityRequirements *csi.TopologyRequirement + snapshotZone scw.Zone + } + tests := []struct { + name string + args args + want []scw.Zone + wantErr bool + }{ + { + name: "nothing should return empty zone", + args: args{ + snapshotZone: scw.Zone(""), + }, + want: []scw.Zone{}, + wantErr: false, + }, + { + name: "nothing (non-nil) should return empty zone", + args: args{ + accessibilityRequirements: &csi.TopologyRequirement{}, + snapshotZone: scw.Zone(""), + }, + want: []scw.Zone{}, + wantErr: false, + }, + { + name: "snapshot from fr-par-1", + args: args{ + snapshotZone: scw.ZoneFrPar1, + }, + want: []scw.Zone{scw.ZoneFrPar1}, + wantErr: false, + }, + { + name: "empty preferred", + args: args{ + accessibilityRequirements: &csi.TopologyRequirement{ + Preferred: []*csi.Topology{ + { + Segments: map[string]string{}, + }, + }, + }, + snapshotZone: scw.Zone(""), + }, + want: []scw.Zone{}, + wantErr: false, + }, + { + name: "empty requisite", + args: args{ + accessibilityRequirements: &csi.TopologyRequirement{ + Requisite: []*csi.Topology{ + { + Segments: map[string]string{}, + }, + }, + }, + snapshotZone: scw.Zone(""), + }, + want: []scw.Zone{}, + wantErr: false, + }, + { + name: "empty preferred and requisite", + args: args{ + accessibilityRequirements: &csi.TopologyRequirement{ + Preferred: []*csi.Topology{ + { + Segments: map[string]string{}, + }, + }, + Requisite: []*csi.Topology{ + { + Segments: map[string]string{}, + }, + }, + }, + snapshotZone: scw.Zone(""), + }, + want: []scw.Zone{}, + wantErr: false, + }, + { + name: "fr-par-1 requisite", + args: args{ + accessibilityRequirements: &csi.TopologyRequirement{ + Preferred: []*csi.Topology{ + { + Segments: map[string]string{}, + }, + }, + Requisite: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + }, + }, + snapshotZone: scw.Zone(""), + }, + want: []scw.Zone{scw.ZoneFrPar1}, + wantErr: false, + }, + { + name: "fr-par-1 preferred and requisite", + args: args{ + accessibilityRequirements: &csi.TopologyRequirement{ + Preferred: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + }, + Requisite: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + }, + }, + snapshotZone: scw.Zone(""), + }, + want: []scw.Zone{scw.ZoneFrPar1}, + wantErr: false, + }, + { + name: "fr-par-1 preferred and requisite, fr-par2 requisite", + args: args{ + accessibilityRequirements: &csi.TopologyRequirement{ + Preferred: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + }, + Requisite: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar2), + }, + }, + }, + }, + snapshotZone: scw.Zone(""), + }, + want: []scw.Zone{scw.ZoneFrPar1, scw.ZoneFrPar2}, + wantErr: false, + }, + { + name: "fr-par-1/fr-par-2 preferred and requisite", + args: args{ + accessibilityRequirements: &csi.TopologyRequirement{ + Preferred: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar2), + }, + }, + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + }, + Requisite: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar2), + }, + }, + }, + }, + snapshotZone: scw.Zone(""), + }, + want: []scw.Zone{scw.ZoneFrPar2, scw.ZoneFrPar1}, + wantErr: false, + }, + { + name: "fr-par-4/fr-par-3 preferred and requisite", + args: args{ + accessibilityRequirements: &csi.TopologyRequirement{ + Preferred: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string("fr-par-4"), + }, + }, + { + Segments: map[string]string{ + ZoneTopologyKey: string("fr-par-3"), + }, + }, + }, + Requisite: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string("fr-par-4"), + }, + }, + { + Segments: map[string]string{ + ZoneTopologyKey: string("fr-par-3"), + }, + }, + }, + }, + snapshotZone: scw.Zone(""), + }, + want: []scw.Zone{"fr-par-4", "fr-par-3"}, + wantErr: false, + }, + { + name: "invalid topology values should be ignored", + args: args{ + accessibilityRequirements: &csi.TopologyRequirement{ + Preferred: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string("fr-ams"), + }, + }, + }, + Requisite: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string("fr-par"), + }, + }, + }, + }, + snapshotZone: scw.Zone(""), + }, + want: []scw.Zone{}, + wantErr: false, + }, + { + name: "invalid topology keys should be ignored", + args: args{ + accessibilityRequirements: &csi.TopologyRequirement{ + Preferred: []*csi.Topology{ + { + Segments: map[string]string{ + "test": string("fr-ams"), + }, + }, + }, + Requisite: []*csi.Topology{ + { + Segments: map[string]string{ + "testagain": string("fr-par"), + }, + }, + }, + }, + snapshotZone: scw.Zone(""), + }, + want: []scw.Zone{}, + wantErr: false, + }, + { + name: "fr-par-1 preferred", + args: args{ + accessibilityRequirements: &csi.TopologyRequirement{ + Preferred: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + }, + }, + snapshotZone: scw.Zone(""), + }, + want: []scw.Zone{scw.ZoneFrPar1}, + wantErr: false, + }, + { + name: "fr-par-1 preferred and requisite, fr-par-2 requisite, snapshot in fr-par-1", + args: args{ + accessibilityRequirements: &csi.TopologyRequirement{ + Preferred: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + }, + Requisite: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar2), + }, + }, + }, + }, + snapshotZone: scw.ZoneFrPar1, + }, + want: []scw.Zone{scw.ZoneFrPar1}, + wantErr: false, + }, + { + name: "fr-par-1 preferred (with duplicate) and requisite, snapshot in fr-par-1", + args: args{ + accessibilityRequirements: &csi.TopologyRequirement{ + Preferred: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + }, + Requisite: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + }, + }, + snapshotZone: scw.ZoneFrPar1, + }, + want: []scw.Zone{scw.ZoneFrPar1}, + wantErr: false, + }, + { + name: "fr-par-1/fr-par-2 preferred and requisite, snapshot in fr-par-1", + args: args{ + accessibilityRequirements: &csi.TopologyRequirement{ + Preferred: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar2), + }, + }, + }, + Requisite: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar2), + }, + }, + }, + }, + snapshotZone: scw.ZoneFrPar1, + }, + want: []scw.Zone{scw.ZoneFrPar1}, + wantErr: false, + }, + { + name: "fr-par-1/fr-par-2 preferred and requisite, snapshot in nl-ams-1 should error", + args: args{ + accessibilityRequirements: &csi.TopologyRequirement{ + Preferred: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar2), + }, + }, + }, + Requisite: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar2), + }, + }, + }, + }, + snapshotZone: scw.ZoneNlAms1, + }, + want: nil, + wantErr: true, + }, + { + name: "fr-par-1/fr-par-2 preferred and fr-par-2 requisite should error", + args: args{ + accessibilityRequirements: &csi.TopologyRequirement{ + Preferred: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar2), + }, + }, + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar1), + }, + }, + }, + Requisite: []*csi.Topology{ + { + Segments: map[string]string{ + ZoneTopologyKey: string(scw.ZoneFrPar2), + }, + }, + }, + }, + snapshotZone: scw.ZoneFrPar1, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := chooseZones(tt.args.accessibilityRequirements, tt.args.snapshotZone) + if (err != nil) != tt.wantErr { + t.Errorf("chooseZones() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("chooseZones() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_validateVolumeCapability(t *testing.T) { + t.Parallel() + type args struct { + volumeCapability *csi.VolumeCapability + } + tests := []struct { + name string + args args + wantBlock bool + wantMount bool + wantErr bool + }{ + { + name: "multi node is not supported", + args: args{ + volumeCapability: &csi.VolumeCapability{ + AccessType: &csi.VolumeCapability_Block{}, + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, + }, + }, + }, + wantBlock: false, + wantMount: false, + wantErr: true, + }, + { + name: "single node as block", + args: args{ + volumeCapability: &csi.VolumeCapability{ + AccessType: &csi.VolumeCapability_Block{Block: &csi.VolumeCapability_BlockVolume{}}, + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, + }, + }, + }, + wantBlock: true, + wantMount: false, + wantErr: false, + }, + { + name: "single node as mount", + args: args{ + volumeCapability: &csi.VolumeCapability{ + AccessType: &csi.VolumeCapability_Mount{Mount: &csi.VolumeCapability_MountVolume{}}, + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, + }, + }, + }, + wantBlock: false, + wantMount: true, + wantErr: false, + }, + { + name: "empty access type should error", + args: args{ + volumeCapability: &csi.VolumeCapability{ + AccessType: nil, + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, + }, + }, + }, + wantBlock: false, + wantMount: false, + wantErr: true, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + gotBlock, gotMount, err := validateVolumeCapability(tt.args.volumeCapability) + if (err != nil) != tt.wantErr { + t.Errorf("validateVolumeCapability() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotBlock != tt.wantBlock { + t.Errorf("validateVolumeCapability() gotBlock = %v, want %v", gotBlock, tt.wantBlock) + } + if gotMount != tt.wantMount { + t.Errorf("validateVolumeCapability() gotMount = %v, want %v", gotMount, tt.wantMount) + } + }) + } +} + +func Test_validateVolumeCapabilities(t *testing.T) { + t.Parallel() + type args struct { + volumeCapabilities []*csi.VolumeCapability + optional bool + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty volumeCapabilities should error when not optional", + args: args{ + volumeCapabilities: []*csi.VolumeCapability{}, + optional: false, + }, + wantErr: true, + }, + { + name: "empty volumeCapabilities should not error when optional", + args: args{ + volumeCapabilities: []*csi.VolumeCapability{}, + optional: true, + }, + wantErr: false, + }, + { + name: "valid volumeCapabilities", + args: args{ + volumeCapabilities: []*csi.VolumeCapability{ + { + AccessType: &csi.VolumeCapability_Block{Block: &csi.VolumeCapability_BlockVolume{}}, + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, + }, + }, + { + AccessType: &csi.VolumeCapability_Mount{Mount: &csi.VolumeCapability_MountVolume{}}, + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "second volumeCapability is invalid", + args: args{ + volumeCapabilities: []*csi.VolumeCapability{ + { + AccessType: &csi.VolumeCapability_Block{Block: &csi.VolumeCapability_BlockVolume{}}, + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, + }, + }, + { + AccessType: &csi.VolumeCapability_Mount{Mount: &csi.VolumeCapability_MountVolume{}}, + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, + }, + }, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if err := validateVolumeCapabilities(tt.args.volumeCapabilities, tt.args.optional); (err != nil) != tt.wantErr { + t.Errorf("validateVolumeCapabilities() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_isVolumeEncrypted(t *testing.T) { + t.Parallel() + type args struct { + volumeContext map[string]string + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + { + name: "volume should be encrypted", + args: args{ + volumeContext: map[string]string{ + encryptedKey: "true", + }, + }, + want: true, + wantErr: false, + }, + { + name: "volume should not be encrypted", + args: args{ + volumeContext: map[string]string{ + encryptedKey: "false", + }, + }, + want: false, + wantErr: false, + }, + { + name: "volume should not be encrypted by default", + args: args{ + volumeContext: map[string]string{}, + }, + want: false, + wantErr: false, + }, + { + name: "invalid encrypted value", + args: args{ + volumeContext: map[string]string{ + encryptedKey: "invalid", + }, + }, + want: false, + wantErr: true, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := isVolumeEncrypted(tt.args.volumeContext) + if (err != nil) != tt.wantErr { + t.Errorf("isVolumeEncrypted() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("isVolumeEncrypted() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_expandZonalID(t *testing.T) { + type args struct { + id string + zone scw.Zone + } + tests := []struct { + name string + args args + want string + }{ + { + name: "fr-par-1/4d33a22f-9794-4f29-a92e-083c03d60681", + args: args{ + id: "4d33a22f-9794-4f29-a92e-083c03d60681", + zone: scw.ZoneFrPar1, + }, + want: "fr-par-1/4d33a22f-9794-4f29-a92e-083c03d60681", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := expandZonalID(tt.args.id, tt.args.zone); got != tt.want { + t.Errorf("expandZonalID() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getVolumeRequestCapacity(t *testing.T) { + t.Parallel() + type args struct { + capacityRange *csi.CapacityRange + } + tests := []struct { + name string + args args + want int64 + wantErr bool + }{ + { + name: "nil capacity range, should default to driver MinVolumeSize", + args: args{ + capacityRange: nil, + }, + want: scaleway.MinVolumeSize, + wantErr: false, + }, + { + name: "empty capacity range, should default to driver MinVolumeSize", + args: args{ + capacityRange: &csi.CapacityRange{}, + }, + want: scaleway.MinVolumeSize, + wantErr: false, + }, + { + name: "limit less than required", + args: args{ + capacityRange: &csi.CapacityRange{ + LimitBytes: 1000000000, + RequiredBytes: 2000000000, + }, + }, + want: 0, + wantErr: true, + }, + { + name: "required less than driver MinVolumeSize", + args: args{ + capacityRange: &csi.CapacityRange{ + RequiredBytes: scaleway.MinVolumeSize - 100, + }, + }, + want: 0, + wantErr: true, + }, + { + name: "limit less than driver MinVolumeSize", + args: args{ + capacityRange: &csi.CapacityRange{ + LimitBytes: scaleway.MinVolumeSize - 100, + }, + }, + want: 0, + wantErr: true, + }, + { + name: "limit == required", + args: args{ + capacityRange: &csi.CapacityRange{ + LimitBytes: 2000000000, + RequiredBytes: 2000000000, + }, + }, + want: 2000000000, + wantErr: false, + }, + { + name: "when required and limit are set, should return required", + args: args{ + capacityRange: &csi.CapacityRange{ + LimitBytes: 3000000000, + RequiredBytes: 2000000000, + }, + }, + want: 2000000000, + wantErr: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := getVolumeRequestCapacity(tt.args.capacityRange) + if (err != nil) != tt.wantErr { + t.Errorf("getVolumeRequestCapacity() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("getVolumeRequestCapacity() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_parseCreateVolumeParams(t *testing.T) { + t.Parallel() + type args struct { + params map[string]string + } + tests := []struct { + name string + args args + want *uint32 + want1 bool + wantErr bool + }{ + { + name: "no params", + args: args{ + params: map[string]string{}, + }, + want: nil, + want1: false, + wantErr: false, + }, + { + name: "unknown param", + args: args{ + params: map[string]string{ + "unknown_param": "unknown_param_value", + }, + }, + want: nil, + want1: false, + wantErr: true, + }, + { + name: "encryption set to true", + args: args{ + params: map[string]string{ + encryptedKey: "true", + }, + }, + want: nil, + want1: true, + wantErr: false, + }, + { + name: "encryption set to false", + args: args{ + params: map[string]string{ + encryptedKey: "false", + }, + }, + want: nil, + want1: false, + wantErr: false, + }, + { + name: "encryption set to non-boolean value should error", + args: args{ + params: map[string]string{ + encryptedKey: "abcd", + }, + }, + want: nil, + want1: false, + wantErr: true, + }, + { + name: "legacy default volume type should return nil iops", + args: args{ + params: map[string]string{ + volumeTypeKey: scaleway.LegacyDefaultVolumeType, + }, + }, + want: nil, + want1: false, + wantErr: false, + }, + { + name: "legacy default volume type compatible with iops param set to 5K", + args: args{ + params: map[string]string{ + volumeTypeKey: scaleway.LegacyDefaultVolumeType, + volumeIOPSKey: strconv.Itoa(scaleway.LegacyDefaultVolumeTypeIOPS), + }, + }, + want: scw.Uint32Ptr(scaleway.LegacyDefaultVolumeTypeIOPS), + want1: false, + wantErr: false, + }, + { + name: "legacy default volume type not compatible with iops param set to something else than 5K", + args: args{ + params: map[string]string{ + volumeTypeKey: scaleway.LegacyDefaultVolumeType, + volumeIOPSKey: "1234", + }, + }, + want: nil, + want1: false, + wantErr: true, + }, + { + name: "unknown volume type", + args: args{ + params: map[string]string{ + volumeTypeKey: "abcd", + }, + }, + want: nil, + want1: false, + wantErr: true, + }, + { + name: "iops not a number", + args: args{ + params: map[string]string{ + volumeIOPSKey: "abcd", + }, + }, + want: nil, + want1: false, + wantErr: true, + }, + { + name: "iops and encryption set", + args: args{ + params: map[string]string{ + volumeIOPSKey: "15000", + encryptedKey: "true", + }, + }, + want: scw.Uint32Ptr(15000), + want1: true, + wantErr: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, got1, err := parseCreateVolumeParams(tt.args.params) + if (err != nil) != tt.wantErr { + t.Errorf("parseCreateVolumeParams() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("parseCreateVolumeParams() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("parseCreateVolumeParams() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func Test_parseStartingToken(t *testing.T) { + t.Parallel() + type args struct { + token string + } + tests := []struct { + name string + args args + want uint32 + wantErr bool + }{ + { + name: "not a number", + args: args{ + token: "abcd", + }, + want: 0, + wantErr: true, + }, + { + name: "defaults to 0", + args: args{ + token: "", + }, + want: 0, + wantErr: false, + }, + { + name: "min 0", + args: args{ + token: "-2", + }, + want: 0, + wantErr: false, + }, + { + name: "123", + args: args{ + token: "123", + }, + want: 123, + wantErr: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := parseStartingToken(tt.args.token) + if (err != nil) != tt.wantErr { + t.Errorf("parseStartingToken() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("parseStartingToken() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_publishedNodeIDs(t *testing.T) { + t.Parallel() + type args struct { + volume *block.Volume + } + tests := []struct { + name string + args args + want []string + }{ + { + name: "volume is not attached to anything", + args: args{ + volume: &block.Volume{}, + }, + want: []string{}, + }, + { + name: "volume is attached to one server", + args: args{ + volume: &block.Volume{ + Zone: scw.ZoneFrPar1, + References: []*block.Reference{ + { + ProductResourceType: scaleway.InstanceServerProductResourceType, + ProductResourceID: "618712ec-3bdd-4497-ae44-a91bb2569ef1", + }, + }, + }, + }, + want: []string{"fr-par-1/618712ec-3bdd-4497-ae44-a91bb2569ef1"}, + }, + { + name: "volume is attached to another product", + args: args{ + volume: &block.Volume{ + References: []*block.Reference{ + { + ProductResourceType: "another_product", + ProductResourceID: "618712ec-3bdd-4497-ae44-a91bb2569ef1", + }, + }, + }, + }, + want: []string{}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := publishedNodeIDs(tt.args.volume); !reflect.DeepEqual(got, tt.want) { + t.Errorf("publishedNodeIDs() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/driver/identity.go b/pkg/driver/identity.go similarity index 99% rename from driver/identity.go rename to pkg/driver/identity.go index fadab4a..e88adc3 100644 --- a/driver/identity.go +++ b/pkg/driver/identity.go @@ -53,7 +53,7 @@ func (d *Driver) GetPluginCapabilities(ctx context.Context, req *csi.GetPluginCa // Probe allows to verify that the plugin is in a healthy and ready state func (d *Driver) Probe(ctx context.Context, req *csi.ProbeRequest) (*csi.ProbeResponse, error) { - //TODO + // TODO return &csi.ProbeResponse{ Ready: &wrappers.BoolValue{ Value: true, diff --git a/driver/luks_utils.go b/pkg/driver/luks_utils.go similarity index 80% rename from driver/luks_utils.go rename to pkg/driver/luks_utils.go index df14983..8121e68 100644 --- a/driver/luks_utils.go +++ b/pkg/driver/luks_utils.go @@ -8,7 +8,7 @@ import ( "strings" ) -var ( +const ( cryptsetupCmd = "cryptsetup" defaultLuksHash = "sha256" defaultLuksCipher = "aes-xts-plain64" @@ -29,7 +29,11 @@ func luksFormat(devicePath string, passphrase string) error { luksFormatCmd := exec.Command(cryptsetupCmd, args...) luksFormatCmd.Stdin = strings.NewReader(passphrase) - return luksFormatCmd.Run() + if err := luksFormatCmd.Run(); err != nil { + return fmt.Errorf("luksFormat failed: %w", err) + } + + return nil } func luksOpen(devicePath string, mapperFile string, passphrase string) error { @@ -43,7 +47,11 @@ func luksOpen(devicePath string, mapperFile string, passphrase string) error { luksOpenCmd := exec.Command(cryptsetupCmd, args...) luksOpenCmd.Stdin = strings.NewReader(passphrase) - return luksOpenCmd.Run() + if err := luksOpenCmd.Run(); err != nil { + return fmt.Errorf("luksOpen failed: %w", err) + } + + return nil } func luksClose(mapperFile string) error { @@ -54,7 +62,11 @@ func luksClose(mapperFile string) error { luksCloseCmd := exec.Command(cryptsetupCmd, args...) - return luksCloseCmd.Run() + if err := luksCloseCmd.Run(); err != nil { + return fmt.Errorf("luksClose failed: %w", err) + } + + return nil } func luksResize(mapperFile, passphrase string) error { @@ -73,7 +85,7 @@ func luksResize(mapperFile, passphrase string) error { luksResizeCmd.Stderr = e if err := luksResizeCmd.Run(); err != nil { - return fmt.Errorf("luks resize failed: %v, stdout: %s, stderr: %s", err, o.String(), e.String()) + return fmt.Errorf("luks resize failed: %s, stdout: %s, stderr: %s", err, o.String(), e.String()) } return nil } @@ -89,9 +101,8 @@ func luksStatus(mapperFile string) ([]byte, error) { luksStatusCmd := exec.Command(cryptsetupCmd, args...) luksStatusCmd.Stdout = &stdout - err := luksStatusCmd.Run() - if err != nil { - return nil, err + if err := luksStatusCmd.Run(); err != nil { + return nil, fmt.Errorf("failed to get luks status: %w", err) } return stdout.Bytes(), nil @@ -105,15 +116,15 @@ func luksIsLuks(devicePath string) (bool, error) { luksIsLuksCmd := exec.Command(cryptsetupCmd, args...) - err := luksIsLuksCmd.Run() - if err != nil { + if err := luksIsLuksCmd.Run(); err != nil { var exitErr *exec.ExitError if ok := errors.As(err, &exitErr); ok { if exitErr.ExitCode() == 1 { // not a luks device return false, nil } } - return false, err + return false, fmt.Errorf("failed to check if device is luks: %w", err) } + return true, nil } diff --git a/driver/node.go b/pkg/driver/node.go similarity index 65% rename from driver/node.go rename to pkg/driver/node.go index 9764ffe..389b0de 100644 --- a/driver/node.go +++ b/pkg/driver/node.go @@ -2,27 +2,26 @@ package driver import ( "context" + "errors" + "fmt" + "io/fs" "os" - "strconv" "strings" "github.com/container-storage-interface/spec/lib/go/csi" "github.com/scaleway/scaleway-sdk-go/scw" - "golang.org/x/sys/unix" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "k8s.io/klog/v2" - "github.com/scaleway/scaleway-csi/scaleway" + "github.com/scaleway/scaleway-csi/pkg/scaleway" ) -const ( - // maximum of volumes per node - maxVolumesPerNode = 16 +// name of the secret for the encryption passphrase. +const encryptionPassphraseKey = "encryptionPassphrase" - // name of the secret for the encryption passphrase - encryptionPassphraseKey = "encryptionPassphrase" -) +// nodeService implements csi.NodeServer. +var _ csi.NodeServer = &nodeService{} type nodeService struct { diskUtils DiskUtils @@ -31,22 +30,22 @@ type nodeService struct { nodeZone scw.Zone } -func newNodeService() nodeService { - metadata, err := scaleway.NewMetadata().GetMetadata() +func newNodeService() (*nodeService, error) { + metadata, err := scaleway.GetMetadata() if err != nil { - panic(err) + return nil, fmt.Errorf("unable to fetch Scaleway metadata: %w", err) } zone, err := scw.ParseZone(metadata.Location.ZoneID) if err != nil { - panic(err) + return nil, fmt.Errorf("invalid zone in metadata: %w", err) } - return nodeService{ + return &nodeService{ diskUtils: newDiskUtils(), nodeID: metadata.ID, nodeZone: zone, - } + }, nil } // NodeStageVolume is called by the CO prior to the volume being consumed @@ -61,19 +60,14 @@ func (d *nodeService) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol klog.V(4).Infof("NodeStageVolume called with %s", stripSecretFromReq(*req)) // check arguments - volumeID, _, err := getVolumeIDAndZone(req.GetVolumeId()) + volumeID, _, err := ExtractIDAndZone(req.GetVolumeId()) if err != nil { - return nil, err + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter volumeID: %s", err) } - encrypted := false - if encryptedValueString, ok := req.GetVolumeContext()[encryptedKey]; ok { - encryptedValue, err := strconv.ParseBool(encryptedValueString) - if err != nil { - // should never happen but better safe than sorry, let's panic - panic(err) - } - encrypted = encryptedValue + encrypted, err := isVolumeEncrypted(req.GetVolumeContext()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid volumeContext: %s", err) } stagingTargetPath := req.GetStagingTargetPath() @@ -86,24 +80,24 @@ func (d *nodeService) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol return nil, status.Error(codes.InvalidArgument, "volumeCapability not provided") } - err = validateVolumeCapabilities([]*csi.VolumeCapability{volumeCapability}) + block, _, err := validateVolumeCapability(volumeCapability) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "volumeCapability not supported: %s", err) } - volumeName, ok := req.GetPublishContext()[scwVolumeName] + volumeName, ok := req.GetPublishContext()[scwVolumeNameKey] if !ok || volumeName == "" { - return nil, status.Errorf(codes.InvalidArgument, "%s not found in publish context of volume %s", scwVolumeName, volumeID) + return nil, status.Errorf(codes.InvalidArgument, "%s not found in publish context of volume %s", scwVolumeNameKey, volumeID) } - scwVolumeID, ok := req.GetPublishContext()[scwVolumeID] + scwVolumeID, ok := req.GetPublishContext()[scwVolumeIDKey] if !ok { - return nil, status.Errorf(codes.InvalidArgument, "%s not found in publish context of volume %s", scwVolumeID, volumeID) + return nil, status.Errorf(codes.InvalidArgument, "%s not found in publish context of volume %s", scwVolumeIDKey, volumeID) } devicePath, err := d.diskUtils.GetDevicePath(scwVolumeID) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return nil, status.Errorf(codes.NotFound, "volume %s is not mounted on node yet", volumeID) } return nil, status.Errorf(codes.Internal, "error getting device path for volume with ID %s: %s", volumeID, err.Error()) @@ -121,18 +115,11 @@ func (d *nodeService) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol } } - switch volumeCapability.GetAccessType().(type) { - // no need to mount if it's in block mode - case *csi.VolumeCapability_Block: + if block { return &csi.NodeStageVolumeResponse{}, nil } - isMounted, err := d.diskUtils.IsSharedMounted(stagingTargetPath, devicePath) - if err != nil { - return nil, status.Errorf(codes.Internal, "error checking mount point of volume %s on path %s: %s", volumeID, stagingTargetPath, err.Error()) - } - - if isMounted { + if d.diskUtils.IsMounted(stagingTargetPath) { blockDevice, err := d.diskUtils.IsBlockDevice(stagingTargetPath) if err != nil { return nil, status.Errorf(codes.Internal, "error checking stat for %s: %s", stagingTargetPath, err.Error()) @@ -154,16 +141,28 @@ func (d *nodeService) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol mountOptions := mountCap.GetMountFlags() fsType := mountCap.GetFsType() + // See https://github.com/container-storage-interface/spec/issues/482. + if fsType == "xfs" { + mountOptions = append(mountOptions, "nouuid") + } + klog.V(4).Infof("Volume %s with ID %s will be mounted on %s with type %s and options %s", volumeName, volumeID, stagingTargetPath, fsType, strings.Join(mountOptions, ",")) // format and mounting volume - err = d.diskUtils.FormatAndMount(stagingTargetPath, devicePath, fsType, mountOptions) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to format and mount device from (%q) to (%q) with fstype (%q) and options (%q): %v", + if err := d.diskUtils.FormatAndMount(stagingTargetPath, devicePath, fsType, mountOptions); err != nil { + return nil, status.Errorf(codes.Internal, "failed to format and mount device from (%q) to (%q) with fstype (%q) and options (%q): %s", devicePath, stagingTargetPath, fsType, mountOptions, err) } + klog.V(4).Infof("Volume %s with ID %s has been mounted on %s with type %s and options %s", volumeName, volumeID, stagingTargetPath, fsType, strings.Join(mountOptions, ",")) + // Try expanding the volume if it's created from a snapshot. We provide an + // empty password as we don't expect the size of an encrypted (or not) volume + // to change between the moment we open it and now, so luks resizing is useless. + if err := d.diskUtils.Resize(stagingTargetPath, devicePath, ""); err != nil { + return nil, status.Errorf(codes.Internal, "failed to resize volume: %s", err) + } + return &csi.NodeStageVolumeResponse{}, nil } @@ -173,9 +172,9 @@ func (d *nodeService) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstag klog.V(4).Infof("NodeUnstageVolume called with %s", stripSecretFromReq(*req)) // check arguments - volumeID, _, err := getVolumeIDAndZone(req.GetVolumeId()) + volumeID, _, err := ExtractIDAndZone(req.GetVolumeId()) if err != nil { - return nil, err + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter volumeID: %s", err) } stagingTargetPath := req.GetStagingTargetPath() @@ -183,24 +182,18 @@ func (d *nodeService) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstag return nil, status.Error(codes.InvalidArgument, "stagingTargetPath not provided") } - _, err = d.diskUtils.GetDevicePath(volumeID) - if err != nil { - if os.IsNotExist(err) { + if _, err = d.diskUtils.GetDevicePath(volumeID); err != nil { + if errors.Is(err, fs.ErrNotExist) { return nil, status.Errorf(codes.NotFound, "volume with ID %s not found", volumeID) } return nil, status.Errorf(codes.Internal, "error getting device path for volume with ID %s: %s", volumeID, err.Error()) } - if _, err := os.Stat(stagingTargetPath); os.IsNotExist(err) { + if _, err := os.Stat(stagingTargetPath); errors.Is(err, fs.ErrNotExist) { return nil, status.Errorf(codes.NotFound, "volume with ID %s not found on node", volumeID) } - isMounted, err := d.diskUtils.IsSharedMounted(stagingTargetPath, "") - if err != nil { - return nil, status.Errorf(codes.Internal, "error checking if target is mounted: %s", err.Error()) - } - - if isMounted { + if d.diskUtils.IsMounted(stagingTargetPath) { klog.V(4).Infof("Volume with ID %s is mounted on %s, umounting it", volumeID, stagingTargetPath) err = d.diskUtils.Unmount(stagingTargetPath) if err != nil { @@ -208,8 +201,7 @@ func (d *nodeService) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstag } } - err = d.diskUtils.CloseDevice(volumeID) - if err != nil { + if err := d.diskUtils.CloseDevice(volumeID); err != nil { return nil, status.Errorf(codes.Internal, "error closing device with ID %s: %s", volumeID, err.Error()) } @@ -224,9 +216,9 @@ func (d *nodeService) NodePublishVolume(ctx context.Context, req *csi.NodePublis klog.V(4).Infof("NodePublishVolume called with %s", stripSecretFromReq(*req)) // check arguments - volumeID, _, err := getVolumeIDAndZone(req.GetVolumeId()) + volumeID, _, err := ExtractIDAndZone(req.GetVolumeId()) if err != nil { - return nil, err + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter volumeID: %s", err) } targetPath := req.GetTargetPath() @@ -239,7 +231,7 @@ func (d *nodeService) NodePublishVolume(ctx context.Context, req *csi.NodePublis return nil, status.Error(codes.InvalidArgument, "volumeCapability not provided") } - err = validateVolumeCapabilities([]*csi.VolumeCapability{volumeCapability}) + block, mount, err := validateVolumeCapability(volumeCapability) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "volumeCapability not supported: %s", err) } @@ -249,14 +241,14 @@ func (d *nodeService) NodePublishVolume(ctx context.Context, req *csi.NodePublis return nil, status.Error(codes.FailedPrecondition, "stagingTargetPath not provided") } - scwVolumeID, ok := req.GetPublishContext()[scwVolumeID] + scwVolumeID, ok := req.GetPublishContext()[scwVolumeIDKey] if !ok { - return nil, status.Errorf(codes.InvalidArgument, "%s not found for volume with ID %s", scwVolumeID, volumeID) + return nil, status.Errorf(codes.InvalidArgument, "%s not found for volume with ID %s", scwVolumeIDKey, volumeID) } - volumeName, ok := req.GetPublishContext()[scwVolumeName] + volumeName, ok := req.GetPublishContext()[scwVolumeNameKey] if !ok { - return nil, status.Errorf(codes.InvalidArgument, "%s not provided in publishContext", scwVolumeName) + return nil, status.Errorf(codes.InvalidArgument, "%s not provided in publishContext", scwVolumeNameKey) } devicePath, err := d.diskUtils.GetDevicePath(scwVolumeID) @@ -264,131 +256,57 @@ func (d *nodeService) NodePublishVolume(ctx context.Context, req *csi.NodePublis return nil, status.Errorf(codes.NotFound, "volume %s not found: %s", volumeID, err.Error()) } - encrypted := false - if encryptedValueString, ok := req.GetVolumeContext()[encryptedKey]; ok { - encryptedValue, err := strconv.ParseBool(encryptedValueString) - if err != nil { - // should never happen but better safe than sorry, let's panic - panic(err) - } - encrypted = encryptedValue + encrypted, err := isVolumeEncrypted(req.GetVolumeContext()) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid volumeContext: %s", err) } - // TODO check volumeID - isMounted, err := d.diskUtils.IsSharedMounted(targetPath, devicePath) - if err != nil { - return nil, status.Errorf(codes.Internal, "error checking mount point of volume %s on path %s: %v", volumeID, stagingTargetPath, err) + if encrypted { + devicePath, err = d.diskUtils.GetMappedDevicePath(scwVolumeID) + if err != nil { + return nil, status.Errorf(codes.Internal, "error getting mapped device for encrypted device %s: %s", devicePath, err.Error()) + } } - if isMounted { + if d.diskUtils.IsMounted(targetPath) { blockDevice, err := d.diskUtils.IsBlockDevice(targetPath) if err != nil { return nil, status.Errorf(codes.Internal, "error checking stat for %s: %s", targetPath, err.Error()) } - if blockDevice && volumeCapability.GetMount() != nil || !blockDevice && volumeCapability.GetBlock() != nil { + if blockDevice && mount || !blockDevice && block { return nil, status.Error(codes.AlreadyExists, "cannot change volumeCapability type") } - if volumeCapability.GetBlock() != nil { - // if block device is encrypted, we should use the mapped path as the source path - if encrypted { - devicePath, err = d.diskUtils.GetMappedDevicePath(scwVolumeID) - if err != nil { - return nil, status.Errorf(codes.Internal, "error getting mapped device for encrypted device %s: %s", devicePath, err.Error()) - } - } - - // unix specific, will error if not unix - fd, err := unix.Openat(unix.AT_FDCWD, devicePath, unix.O_RDONLY, uint32(0)) - defer unix.Close(fd) - if err != nil { - return nil, status.Errorf(codes.Internal, "error opening block device %s: %s", devicePath, err.Error()) - } - ro, err := unix.IoctlGetInt(fd, unix.BLKROGET) - if err != nil { - return nil, status.Errorf(codes.Internal, "error getting BLKROGET for block device %s: %s", devicePath, err.Error()) - } - - if (ro == 1) == req.GetReadonly() { - klog.V(4).Infof("Volume %s with ID %s is already mounted as a raw device on %s", volumeName, volumeID, targetPath) - return &csi.NodePublishVolumeResponse{}, nil - } - return nil, status.Errorf(codes.AlreadyExists, "volume with ID %s does not match the given mount mode for the request", volumeID) - } - - mountInfo, err := d.diskUtils.GetMountInfo(targetPath) - if err != nil { - return nil, status.Errorf(codes.Internal, "error getting mount information of path %s: %s", targetPath, err.Error()) - } - - isReadOnly := false - if mountInfo != nil { - for _, opt := range mountInfo.mountOptions { - if opt == "rw" { - break - } else if opt == "ro" { - isReadOnly = true - break - } - } - } - - if isReadOnly != req.GetReadonly() { - return nil, status.Errorf(codes.AlreadyExists, "volume with ID %s does not match the given mount mode for the request", volumeID) - } - klog.V(4).Infof("Volume %s with ID %s is already mounted on %s", volumeName, volumeID, stagingTargetPath) return &csi.NodePublishVolumeResponse{}, nil } - var sourcePath string - var fsType string - var mountOptions []string - mount := volumeCapability.GetMount() - if mount == nil { - if volumeCapability.GetBlock() != nil { - sourcePath = devicePath - if req.GetReadonly() { - fd, err := unix.Openat(unix.AT_FDCWD, devicePath, unix.O_RDONLY, uint32(0)) - if err != nil { - return nil, status.Errorf(codes.Internal, "error opening block device %s: %s", devicePath, err.Error()) - } - err = unix.IoctlSetPointerInt(fd, unix.BLKROSET, 1) - unix.Close(fd) - if err != nil { - return nil, status.Errorf(codes.Internal, "error setting BLKROSET for block device %s: %s", devicePath, err.Error()) - } - } - - // if block device is encrypted, we should use the mapped path as the source path - if encrypted { - sourcePath, err = d.diskUtils.GetMappedDevicePath(scwVolumeID) - if err != nil { - return nil, status.Errorf(codes.Internal, "error getting mapped device for encrypted device with ID %s: %s", scwVolumeID, err.Error()) - } - } - } + var ( + sourcePath string + fsType string + mountOptions = []string{"bind"} + ) + + if block { + sourcePath = devicePath } else { sourcePath = stagingTargetPath - fsType = mount.GetFsType() - mountOptions = mount.GetMountFlags() + fsType = req.GetVolumeCapability().GetMount().GetFsType() + mountOptions = append(mountOptions, req.GetVolumeCapability().GetMount().GetMountFlags()...) } - mountOptions = append(mountOptions, "bind") - if req.GetReadonly() { mountOptions = append(mountOptions, "ro") } - err = createMountPoint(targetPath, volumeCapability.GetBlock() != nil) - if err != nil { + if err := createMountPoint(targetPath, block); err != nil { return nil, status.Errorf(codes.Internal, "error creating mount point %s for volume with ID %s", targetPath, volumeID) } - err = d.diskUtils.MountToTarget(sourcePath, targetPath, fsType, mountOptions) - if err != nil { + if err := d.diskUtils.MountToTarget(sourcePath, targetPath, fsType, mountOptions); err != nil { return nil, status.Errorf(codes.Internal, "error mounting source %s to target %s with fs of type %s : %s", sourcePath, targetPath, fsType, err.Error()) } + return &csi.NodePublishVolumeResponse{}, nil } @@ -402,8 +320,7 @@ func (d *nodeService) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpu return nil, status.Error(codes.InvalidArgument, "targetPath not provided") } - err := d.diskUtils.Unmount(targetPath) - if err != nil { + if err := d.diskUtils.Unmount(targetPath); err != nil { return nil, status.Errorf(codes.Internal, "error unmounting target path: %s", err.Error()) } @@ -414,9 +331,9 @@ func (d *nodeService) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpu func (d *nodeService) NodeGetVolumeStats(ctx context.Context, req *csi.NodeGetVolumeStatsRequest) (*csi.NodeGetVolumeStatsResponse, error) { klog.V(4).Infof("NodeGetVolumeStats called with %s", stripSecretFromReq(*req)) - volumeID, _, err := getVolumeIDAndZone(req.GetVolumeId()) + volumeID, _, err := ExtractIDAndZone(req.GetVolumeId()) if err != nil { - return nil, err + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter volumeID: %s", err) } volumePath := req.GetVolumePath() @@ -429,18 +346,12 @@ func (d *nodeService) NodeGetVolumeStats(ctx context.Context, req *csi.NodeGetVo volumePath = stagingPath } - isMounted, err := d.diskUtils.IsSharedMounted(volumePath, "") - if err != nil { - return nil, status.Errorf(codes.Internal, "error checking mount point of path %s for volume %s: %s", volumePath, volumeID, err.Error()) - } - - if !isMounted { - return nil, status.Errorf(codes.NotFound, "volume with ID %s not found", volumeID) + if !d.diskUtils.IsMounted(volumePath) { + return nil, status.Errorf(codes.NotFound, "volume with ID %s not mounted to %s", volumeID, req.GetVolumePath()) } - _, err = d.diskUtils.GetDevicePath(volumeID) - if err != nil { - if os.IsNotExist(err) { + if _, err := d.diskUtils.GetDevicePath(volumeID); err != nil { + if errors.Is(err, fs.ErrNotExist) { return nil, status.Errorf(codes.NotFound, "volume with ID %s not found", volumeID) } return nil, status.Errorf(codes.Internal, "error getting device path for volume with ID %s: %s", volumeID, err.Error()) @@ -485,28 +396,28 @@ func (d *nodeService) NodeGetVolumeStats(ctx context.Context, req *csi.NodeGetVo func (d *nodeService) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) { return &csi.NodeGetCapabilitiesResponse{ Capabilities: []*csi.NodeServiceCapability{ - &csi.NodeServiceCapability{ + { Type: &csi.NodeServiceCapability_Rpc{ Rpc: &csi.NodeServiceCapability_RPC{ Type: csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME, }, }, }, - &csi.NodeServiceCapability{ + { Type: &csi.NodeServiceCapability_Rpc{ Rpc: &csi.NodeServiceCapability_RPC{ Type: csi.NodeServiceCapability_RPC_GET_VOLUME_STATS, }, }, }, - &csi.NodeServiceCapability{ + { Type: &csi.NodeServiceCapability_Rpc{ Rpc: &csi.NodeServiceCapability_RPC{ Type: csi.NodeServiceCapability_RPC_EXPAND_VOLUME, }, }, }, - &csi.NodeServiceCapability{ + { Type: &csi.NodeServiceCapability_Rpc{ Rpc: &csi.NodeServiceCapability_RPC{ Type: csi.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER, @@ -521,7 +432,7 @@ func (d *nodeService) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetC func (d *nodeService) NodeGetInfo(ctx context.Context, req *csi.NodeGetInfoRequest) (*csi.NodeGetInfoResponse, error) { return &csi.NodeGetInfoResponse{ NodeId: d.nodeZone.String() + "/" + d.nodeID, - MaxVolumesPerNode: maxVolumesPerNode - 1, // One is already used by the l_ssd root volume + MaxVolumesPerNode: scaleway.MaxVolumesPerNode - 1, // One is already used by the l_ssd or b_ssd root volume AccessibleTopology: &csi.Topology{ Segments: map[string]string{ ZoneTopologyKey: d.nodeZone.String(), @@ -533,9 +444,10 @@ func (d *nodeService) NodeGetInfo(ctx context.Context, req *csi.NodeGetInfoReque // NodeExpandVolume expands the given volume func (d *nodeService) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolumeRequest) (*csi.NodeExpandVolumeResponse, error) { klog.V(4).Infof("NodeExpandVolume called with %s", stripSecretFromReq(*req)) - volumeID, _, err := getVolumeIDAndZone(req.GetVolumeId()) + + volumeID, _, err := ExtractIDAndZone(req.GetVolumeId()) if err != nil { - return nil, err + return nil, status.Errorf(codes.InvalidArgument, "invalid parameter volumeID: %s", err) } volumePath := req.GetVolumePath() @@ -545,10 +457,10 @@ func (d *nodeService) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandV devicePath, err := d.diskUtils.GetDevicePath(volumeID) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return nil, status.Errorf(codes.NotFound, "volume %s is not mounted on node", volumeID) } - return nil, status.Errorf(codes.Internal, "failed to get device path for volume %s: %v", volumeID, err) + return nil, status.Errorf(codes.Internal, "failed to get device path for volume %s: %s", volumeID, err) } isBlock, err := d.diskUtils.IsBlockDevice(volumePath) @@ -558,15 +470,9 @@ func (d *nodeService) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandV volumeCapability := req.GetVolumeCapability() if volumeCapability != nil { - err = validateVolumeCapabilities([]*csi.VolumeCapability{volumeCapability}) - if err != nil { + if isBlock, _, err = validateVolumeCapability(volumeCapability); err != nil { return nil, status.Errorf(codes.InvalidArgument, "volumeCapability not supported: %s", err) } - - switch volumeCapability.GetAccessType().(type) { - case *csi.VolumeCapability_Block: - isBlock = true - } } // no need to resize if it's in block mode @@ -593,7 +499,7 @@ func (d *nodeService) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandV } if err = d.diskUtils.Resize(volumePath, devicePath, passphrase); err != nil { - return nil, status.Errorf(codes.Internal, "failed to resize volume %s mounted on %s: %v", volumeID, volumePath, err) + return nil, status.Errorf(codes.Internal, "failed to resize volume %s mounted on %s: %s", volumeID, volumePath, err) } return &csi.NodeExpandVolumeResponse{}, nil diff --git a/pkg/driver/sanity_test.go b/pkg/driver/sanity_test.go new file mode 100644 index 0000000..00280e4 --- /dev/null +++ b/pkg/driver/sanity_test.go @@ -0,0 +1,64 @@ +package driver + +import ( + "fmt" + "os" + "testing" + + "github.com/kubernetes-csi/csi-test/v5/pkg/sanity" + "github.com/scaleway/scaleway-csi/pkg/scaleway" + "github.com/scaleway/scaleway-sdk-go/api/instance/v1" + "github.com/scaleway/scaleway-sdk-go/scw" +) + +func TestSanityCSI(t *testing.T) { + zone := scw.ZoneFrPar1 + endpoint := "/tmp/csi-testing.sock" + server := &instance.Server{ + ID: "fb094b6a-a732-4d5f-8283-bd6726ff5938", + Name: "test", + Volumes: map[string]*instance.VolumeServer{ + "0": { + ID: "c3be79a0-aa3f-4189-aac2-ac7f41eda819", + VolumeType: instance.VolumeServerVolumeTypeBSSD, + }, + }, + Zone: zone, + } + + fake := scaleway.NewFake([]*instance.Server{server}, zone) + + driverConfig := &DriverConfig{ + Endpoint: fmt.Sprintf("unix://%s", endpoint), + Mode: AllMode, + } + + driver := &Driver{ + config: driverConfig, + controllerService: &controllerService{ + scaleway: fake, + config: driverConfig, + }, + nodeService: &nodeService{ + nodeID: server.ID, + nodeZone: zone, + diskUtils: newFakeDiskUtils(server), + }, + } + + go driver.Run() //nolint:errcheck // an error here would fail the test anyway since the grpc server would not be started + + config := sanity.NewTestConfig() + config.Address = endpoint + config.TestNodeVolumeAttachLimit = true + config.TestVolumeExpandSize = config.TestVolumeSize * 2 + config.RemoveTargetPath = func(path string) error { + return os.RemoveAll(path) //nolint: wrapcheck + } + config.RemoveStagingPath = func(path string) error { + return os.RemoveAll(path) //nolint: wrapcheck + } + sanity.Test(t, config) + driver.srv.GracefulStop() + os.RemoveAll(endpoint) +} diff --git a/driver/version.go b/pkg/driver/version.go similarity index 78% rename from driver/version.go rename to pkg/driver/version.go index 4e208bb..d89814a 100644 --- a/driver/version.go +++ b/pkg/driver/version.go @@ -1,7 +1,6 @@ package driver import ( - "encoding/json" "fmt" "runtime" ) @@ -36,13 +35,3 @@ func GetVersion() VersionInfo { Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), } } - -// GetVersionJSON returns the current running version in JSON -func GetVersionJSON() (string, error) { - info := GetVersion() - marshalled, err := json.MarshalIndent(&info, "", " ") - if err != nil { - return "", err - } - return string(marshalled), nil -} diff --git a/pkg/scaleway/block.go b/pkg/scaleway/block.go new file mode 100644 index 0000000..b692830 --- /dev/null +++ b/pkg/scaleway/block.go @@ -0,0 +1,293 @@ +package scaleway + +import ( + "context" + "fmt" + + block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1" + "github.com/scaleway/scaleway-sdk-go/scw" +) + +// InstanceServerProductResourceType is the ProductResourceType of an instance +// in the reference of a volume. +const InstanceServerProductResourceType = "instance_server" + +// GetVolumeByName is a helper to find a volume by its name and size in the provided zone. +// It returns ErrVolumeDifferentSize if the volume does not have the expected size. +func (s *Scaleway) GetVolumeByName(ctx context.Context, name string, size scw.Size, zone scw.Zone) (*block.Volume, error) { + volumesResp, err := s.block.ListVolumes(&block.ListVolumesRequest{ + Name: scw.StringPtr(name), + Zone: zone, + }, scw.WithContext(ctx), scw.WithAllPages()) + if err != nil { + return nil, fmt.Errorf("failed to list volumes with provided name: %w", err) + } + + for _, vol := range volumesResp.Volumes { + if vol.Size != size { + return nil, fmt.Errorf("%w: %s", ErrVolumeDifferentSize, vol.ID) + } + + if vol.Name == name { + return vol, nil + } + } + + return nil, ErrVolumeNotFound +} + +// GetSnapshotByName is a helper to find a snapshot by its name, its sourceVolumeID and zone. +func (s *Scaleway) GetSnapshotByName(ctx context.Context, name string, sourceVolumeID string, zone scw.Zone) (*block.SnapshotSummary, error) { + snapshotsResp, err := s.block.ListSnapshots(&block.ListSnapshotsRequest{ + Name: scw.StringPtr(name), + Zone: zone, + }, scw.WithContext(ctx), scw.WithAllPages()) + if err != nil { + return nil, fmt.Errorf("failed to list snapshots with provided name: %w", err) + } + + for _, snap := range snapshotsResp.Snapshots { + if snap.Name == name { + if snap.ParentVolume != nil && snap.ParentVolume.ID != sourceVolumeID { + return nil, ErrSnapshotExists + } + return snap, nil + } + } + + return nil, ErrSnapshotNotFound +} + +// ListVolumes lists all volumes in all zones of the region where the driver is +// deployed. Results are paginated, use the returned token to fetch the next results. +func (s *Scaleway) ListVolumes(ctx context.Context, start, max uint32) ([]*block.Volume, string, error) { + return paginatedList(func(page int32, pageSize uint32) ([]*block.Volume, error) { + volumesResp, err := s.block.ListVolumes(&block.ListVolumesRequest{ + Page: scw.Int32Ptr(page), + PageSize: scw.Uint32Ptr(pageSize), + Zone: scw.ZoneFrPar1, // Do not remove this, it's needed for zones that are not part of the SDK. + }, scw.WithContext(ctx), scw.WithZones(s.zones...)) + if err != nil { + return nil, fmt.Errorf("failed to list volumes: %w", err) + } + + return volumesResp.Volumes, nil + }, start, max) +} + +// ListVolumes lists all snapshots in all zones of the region where the driver is +// deployed. Results are paginated, use the returned token to fetch the next results. +func (s *Scaleway) ListSnapshots(ctx context.Context, start, max uint32) ([]*block.SnapshotSummary, string, error) { + return paginatedList(func(page int32, pageSize uint32) ([]*block.SnapshotSummary, error) { + snapshotsResp, err := s.block.ListSnapshots(&block.ListSnapshotsRequest{ + Page: scw.Int32Ptr(page), + PageSize: scw.Uint32Ptr(pageSize), + Zone: scw.ZoneFrPar1, // Do not remove this, it's needed for zones that are not part of the SDK. + }, scw.WithContext(ctx), scw.WithZones(s.zones...)) + if err != nil { + return nil, fmt.Errorf("failed to list snapshots: %w", err) + } + + return snapshotsResp.Snapshots, nil + }, start, max) +} + +// ListVolumes lists all snapshots that match the specified sourceVolumeID in all +// zones of the region where the driver is deployed. Results are paginated, use +// the returned token to fetch the next results. +func (s *Scaleway) ListSnapshotsBySourceVolume( + ctx context.Context, + start, max uint32, + sourceVolumeID string, + sourceVolumeZone scw.Zone, +) ([]*block.SnapshotSummary, string, error) { + // Return nothing if sourceVolumeID is not a valid UUID. + if !isValidUUID(sourceVolumeID) { + return nil, "", nil + } + + return paginatedList(func(page int32, pageSize uint32) ([]*block.SnapshotSummary, error) { + snapshotsResp, err := s.block.ListSnapshots(&block.ListSnapshotsRequest{ + Page: scw.Int32Ptr(page), + PageSize: scw.Uint32Ptr(pageSize), + VolumeID: scw.StringPtr(sourceVolumeID), + Zone: sourceVolumeZone, + }, scw.WithContext(ctx)) + if err != nil { + return nil, fmt.Errorf("failed to list snapshots: %w", err) + } + + return snapshotsResp.Snapshots, nil + }, start, max) +} + +// DeleteVolume deletes a volume by ID and zone. +func (s *Scaleway) DeleteVolume(ctx context.Context, volumeID string, zone scw.Zone) error { + // Return not found if volumeID is not a valid UUID. + if !isValidUUID(volumeID) { + return &scw.ResourceNotFoundError{Resource: volumeResource, ResourceID: volumeID} + } + + if err := s.block.DeleteVolume(&block.DeleteVolumeRequest{ + VolumeID: volumeID, + Zone: zone, + }, scw.WithContext(ctx)); err != nil { + return fmt.Errorf("failed to delete volume: %w", err) + } + + return nil +} + +// GetVolume gets an existing volume by ID and zone. +func (s *Scaleway) GetVolume(ctx context.Context, volumeID string, zone scw.Zone) (*block.Volume, error) { + // Return not found if volumeID is not a valid UUID. + if !isValidUUID(volumeID) { + return nil, &scw.ResourceNotFoundError{Resource: volumeResource, ResourceID: volumeID} + } + + volume, err := s.block.GetVolume(&block.GetVolumeRequest{ + VolumeID: volumeID, + Zone: zone, + }, scw.WithContext(ctx)) + if err != nil { + return nil, fmt.Errorf("failed to get volume: %w", err) + } + + return volume, nil +} + +// DeleteSnapshot deletes a snapshot by ID and zone. +func (s *Scaleway) DeleteSnapshot(ctx context.Context, snapshotID string, zone scw.Zone) error { + // Return not found if snapshotID is not a valid UUID. + if !isValidUUID(snapshotID) { + return &scw.ResourceNotFoundError{Resource: snapshotResource, ResourceID: snapshotID} + } + + if err := s.block.DeleteSnapshot(&block.DeleteSnapshotRequest{ + SnapshotID: snapshotID, + Zone: zone, + }, scw.WithContext(ctx)); err != nil { + return fmt.Errorf("failed to delete snapshot: %w", err) + } + + return nil +} + +// GetSnapshot returns the snapshot that has the provided ID and zone. +func (s *Scaleway) GetSnapshot(ctx context.Context, snapshotID string, zone scw.Zone) (*block.SnapshotSummary, error) { + // Return not found if snapshotID is not a valid UUID. + if !isValidUUID(snapshotID) { + return nil, &scw.ResourceNotFoundError{Resource: snapshotResource, ResourceID: snapshotID} + } + + snapshot, err := s.block.GetSnapshot(&block.GetSnapshotRequest{ + SnapshotID: snapshotID, + Zone: zone, + }, scw.WithContext(ctx)) + if err != nil { + return nil, fmt.Errorf("failed to get snapshot: %w", err) + } + + return snapshotToSnapshotSummary(snapshot), nil +} + +// ResizeVolume updates the size of a volume. It waits until the volume is successfully +// resized. +func (s *Scaleway) ResizeVolume(ctx context.Context, volumeID string, zone scw.Zone, size int64) error { + // Return not found if volumeID is not a valid UUID. + if !isValidUUID(volumeID) { + return &scw.ResourceNotFoundError{Resource: snapshotResource, ResourceID: volumeID} + } + + if _, err := s.block.UpdateVolume(&block.UpdateVolumeRequest{ + VolumeID: volumeID, + Zone: zone, + Size: scw.SizePtr(scw.Size(size)), + }, scw.WithContext(ctx)); err != nil { + return fmt.Errorf("failed to update volume with new size: %w", err) + } + + vol, err := s.block.WaitForVolume(&block.WaitForVolumeRequest{ + VolumeID: volumeID, + Zone: zone, + }, scw.WithContext(ctx)) + if err != nil { + return fmt.Errorf("wait for volume ended with an error: %w", err) + } + + if vol.Status != block.VolumeStatusAvailable && vol.Status != block.VolumeStatusInUse { + return fmt.Errorf("volume %s is in state %s", volumeID, vol.Status) + } + + return nil +} + +// CreateVolume creates a volume with the given parameters. If snapshotID is not +// empty, the size parameter is ignored and the volume is created from the snapshot. +// If perfIOPS is nil, the block API will decide how many iops are associated to the volume. +func (s *Scaleway) CreateVolume(ctx context.Context, name, snapshotID string, size int64, perfIOPS *uint32, zone scw.Zone) (*block.Volume, error) { + req := &block.CreateVolumeRequest{ + Name: name, + PerfIops: perfIOPS, + Zone: zone, + } + + if snapshotID != "" { + if !isValidUUID(snapshotID) { + return nil, &scw.ResourceNotFoundError{Resource: snapshotResource, ResourceID: snapshotID} + } + + req.FromSnapshot = &block.CreateVolumeRequestFromSnapshot{ + SnapshotID: snapshotID, + Size: scw.SizePtr(scw.Size(size)), + } + } else { + req.FromEmpty = &block.CreateVolumeRequestFromEmpty{ + Size: scw.Size(size), + } + } + + volume, err := s.block.CreateVolume(req, scw.WithContext(ctx)) + if err != nil { + return nil, fmt.Errorf("failed to create volume: %w", err) + } + + return volume, nil +} + +// CreateSnapshot creates a snapshot with the given parameters. +func (s *Scaleway) CreateSnapshot(ctx context.Context, name, volumeID string, zone scw.Zone) (*block.SnapshotSummary, error) { + // Return not found if volumeID is not a valid UUID. + if !isValidUUID(volumeID) { + return nil, &scw.ResourceNotFoundError{Resource: volumeResource, ResourceID: volumeID} + } + + snapshot, err := s.block.CreateSnapshot(&block.CreateSnapshotRequest{ + Name: name, + VolumeID: volumeID, + Zone: zone, + }, scw.WithContext(ctx)) + if err != nil { + return nil, fmt.Errorf("failed to create snapshot: %w", err) + } + + return snapshotToSnapshotSummary(snapshot), nil +} + +// WaitForSnapshot waits for a snapshot to be in a terminal state. +func (s *Scaleway) WaitForSnapshot(ctx context.Context, snapshotID string, zone scw.Zone) (*block.SnapshotSummary, error) { + // Return not found if volumeID is not a valid UUID. + if !isValidUUID(snapshotID) { + return nil, &scw.ResourceNotFoundError{Resource: snapshotResource, ResourceID: snapshotID} + } + + snap, err := s.block.WaitForSnapshot(&block.WaitForSnapshotRequest{ + SnapshotID: snapshotID, + Zone: zone, + }, scw.WithContext(ctx)) + if err != nil { + return nil, fmt.Errorf("wait for snapshot error: %w", err) + } + + return snapshotToSnapshotSummary(snap), nil +} diff --git a/pkg/scaleway/errors.go b/pkg/scaleway/errors.go new file mode 100644 index 0000000..2ca10d6 --- /dev/null +++ b/pkg/scaleway/errors.go @@ -0,0 +1,45 @@ +package scaleway + +import ( + "errors" + "net/http" + + "github.com/scaleway/scaleway-sdk-go/scw" +) + +var ( + // ErrVolumeNotFound is the error returned when the volume was not found when + // getting by name. + ErrVolumeNotFound = errors.New("volume not found") + + // ErrSnapshotNotFound is the error returned when the snapshot was not found + // when getting by name. + ErrSnapshotNotFound = errors.New("snapshot not found") + + // ErrSnapshotExists is returned when there is already a snapshot with the same + // name but it has a different sourceVolumeID. + ErrSnapshotExists = errors.New("snapshot exists but has a different sourceVolumeID") + + // ErrVolumeDifferentSize is returned when there is already a volume with the + // same name but it has a different size. + ErrVolumeDifferentSize = errors.New("volume already exists with a different size") +) + +// IsNotFoundError returns true if the provided error is a ResourceNotFoundError. +func IsNotFoundError(err error) bool { + var notFound *scw.ResourceNotFoundError + return errors.As(err, ¬Found) +} + +// IsPreconditionFailedError returns true if the provided error is a PreconditionFailedError. +func IsPreconditionFailedError(err error) bool { + var precondition *scw.PreconditionFailedError + return errors.As(err, &precondition) +} + +// IsGoneError returns true if the provided is an HTTP Gone error. Scaleway Block +// API returns this error when trying to get a resource that was deleted. +func IsGoneError(err error) bool { + var internal *scw.ResponseError + return errors.As(err, &internal) && internal.StatusCode == http.StatusGone +} diff --git a/pkg/scaleway/fake.go b/pkg/scaleway/fake.go new file mode 100644 index 0000000..9aecf55 --- /dev/null +++ b/pkg/scaleway/fake.go @@ -0,0 +1,415 @@ +package scaleway + +import ( + "context" + "errors" + "fmt" + "sort" + "strconv" + "sync" + "time" + + "github.com/google/uuid" + block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1" + "github.com/scaleway/scaleway-sdk-go/api/instance/v1" + "github.com/scaleway/scaleway-sdk-go/scw" + "golang.org/x/exp/maps" +) + +// Enforce interface. +var _ Interface = &Fake{} + +// Fake is a fake Scaleway client. It implements the same interface as the real +// Scaleway client but stores data in-memory. +type Fake struct { + snapshots map[string]*block.Snapshot + volumes map[string]*block.Volume + servers map[string]*instance.Server + defaultZone scw.Zone + mux sync.Mutex +} + +// NewFake returns a new fake Scaleway client. +func NewFake(servers []*instance.Server, zone scw.Zone) *Fake { + if zone == scw.Zone("") { + zone = scw.ZoneFrPar1 + } + + f := &Fake{ + snapshots: make(map[string]*block.Snapshot), + volumes: make(map[string]*block.Volume), + servers: make(map[string]*instance.Server), + defaultZone: zone, + } + + for _, s := range servers { + f.servers[s.ID] = s + } + + return f +} + +// mapPaginatedRange returns elements of a map according to the pagination parameters +// start (index of the first element) and max (amount of elements to return). +// The f callback function allows to filter elements. +func mapPaginatedRange[T any](m map[string]T, f func(T) bool, start, max uint32) (list []T, next string) { + keys := maps.Keys(m) + sort.Strings(keys) + + // Return now if start index is above max index or we want no element. + if int(start) >= len(keys) { + return + } + + for i, key := range keys[start:] { + if max != 0 && len(list) == int(max) { + next = strconv.Itoa(int(start) + i) + break + } + + if f(m[key]) { + list = append(list, m[key]) + } + } + + return +} + +func (f *Fake) AttachVolume(ctx context.Context, serverID string, volumeID string, zone scw.Zone) error { + f.mux.Lock() + defer f.mux.Unlock() + + if zone == scw.Zone("") { + zone = f.defaultZone + } + + s, ok := f.servers[serverID] + if !ok || ok && s.Zone != zone { + return &scw.ResourceNotFoundError{Resource: serverResource, ResourceID: serverID} + } + + v, ok := f.volumes[volumeID] + if !ok || ok && v.Zone != zone { + return &scw.ResourceNotFoundError{Resource: volumeResource, ResourceID: volumeID} + } + + if len(s.Volumes) == MaxVolumesPerNode { + return errors.New("server has reached max volume capacity") + } + + if v.Status != block.VolumeStatusAvailable { + return errors.New("volume is not available") + } + + v.References = append(v.References, &block.Reference{ + ID: uuid.NewString(), + ProductResourceType: InstanceServerProductResourceType, + ProductResourceID: serverID, + }) + + v.Status = block.VolumeStatusInUse + + for i := 0; i <= len(s.Volumes); i++ { + key := fmt.Sprintf("%d", i) + if _, ok := s.Volumes[key]; !ok { + s.Volumes[key] = &instance.VolumeServer{ + ID: volumeID, + VolumeType: instance.VolumeServerVolumeType("sbs_volume"), + } + break + } + } + + return nil +} + +func (f *Fake) CreateSnapshot(ctx context.Context, name string, volumeID string, zone scw.Zone) (*block.SnapshotSummary, error) { + f.mux.Lock() + defer f.mux.Unlock() + + if zone == scw.Zone("") { + zone = f.defaultZone + } + + v, ok := f.volumes[volumeID] + if !ok || ok && v.Zone != zone { + return nil, errors.New("source volume not found") + } + + snapshot := &block.Snapshot{ + ID: uuid.NewString(), + Name: name, + ParentVolume: &block.SnapshotParentVolume{ + ID: volumeID, + Name: v.Name, + Type: v.Type, + }, + Size: v.Size, + CreatedAt: scw.TimePtr(time.Now()), + UpdatedAt: scw.TimePtr(time.Now()), + Status: block.SnapshotStatusAvailable, + Zone: zone, + } + + f.snapshots[snapshot.ID] = snapshot + + return snapshotToSnapshotSummary(snapshot), nil +} + +func (f *Fake) CreateVolume(ctx context.Context, name string, snapshotID string, size int64, perfIOPS *uint32, zone scw.Zone) (*block.Volume, error) { + f.mux.Lock() + defer f.mux.Unlock() + + if zone == scw.Zone("") { + zone = f.defaultZone + } + + volume := &block.Volume{ + ID: uuid.NewString(), + Name: name, + Type: "nvme_5k", + CreatedAt: scw.TimePtr(time.Now()), + UpdatedAt: scw.TimePtr(time.Now()), + Status: block.VolumeStatusAvailable, + Zone: zone, + Specs: &block.VolumeSpecifications{ + Class: block.StorageClassSbs, + }, + } + + if perfIOPS == nil { + volume.Specs.PerfIops = scw.Uint32Ptr(5000) + } + + if snapshotID != "" { + s, ok := f.snapshots[snapshotID] + if !ok || ok && s.Zone != zone { + return nil, &scw.ResourceNotFoundError{Resource: snapshotResource, ResourceID: snapshotID} + } + + volume.ParentSnapshotID = scw.StringPtr(snapshotID) + volume.Size = s.Size + } else { + volume.Size = scw.Size(size) + } + + f.volumes[volume.ID] = volume + + return volume, nil +} + +func (f *Fake) DeleteSnapshot(ctx context.Context, snapshotID string, zone scw.Zone) error { + f.mux.Lock() + defer f.mux.Unlock() + + if zone == scw.Zone("") { + zone = f.defaultZone + } + + if s, ok := f.snapshots[snapshotID]; ok && s.Zone == zone { + delete(f.snapshots, snapshotID) + } + + return &scw.ResourceNotFoundError{Resource: snapshotResource, ResourceID: snapshotID} +} + +func (f *Fake) DeleteVolume(ctx context.Context, volumeID string, zone scw.Zone) error { + f.mux.Lock() + defer f.mux.Unlock() + + if zone == scw.Zone("") { + zone = f.defaultZone + } + + if s, ok := f.volumes[volumeID]; ok && s.Zone == zone { + delete(f.volumes, volumeID) + } + + return &scw.ResourceNotFoundError{Resource: volumeResource, ResourceID: volumeID} +} + +func (f *Fake) DetachVolume(ctx context.Context, volumeID string, zone scw.Zone) error { + f.mux.Lock() + defer f.mux.Unlock() + + if zone == scw.Zone("") { + zone = f.defaultZone + } + + v, ok := f.volumes[volumeID] + if !ok || ok && v.Zone != zone { + return &scw.ResourceNotFoundError{Resource: volumeResource, ResourceID: volumeID} + } + + if v.Status != block.VolumeStatusInUse || len(v.References) == 0 { + return errors.New("volume not in required state, it is not in use") + } + + s, ok := f.servers[v.References[0].ProductResourceID] + if !ok || ok && s.Zone != zone { + return &scw.ResourceNotFoundError{Resource: serverResource, ResourceID: v.References[0].ProductResourceID} + } + + for k, vol := range s.Volumes { + if vol.ID == volumeID { + delete(s.Volumes, k) + break + } + } + + v.References = nil + v.Status = block.VolumeStatusAvailable + + return nil +} + +func (f *Fake) GetServer(ctx context.Context, serverID string, zone scw.Zone) (*instance.Server, error) { + f.mux.Lock() + defer f.mux.Unlock() + + if zone == scw.Zone("") { + zone = f.defaultZone + } + + if s, ok := f.servers[serverID]; ok && s.Zone == zone { + return f.servers[serverID], nil + } + + return nil, &scw.ResourceNotFoundError{Resource: serverResource, ResourceID: serverID} +} + +func (f *Fake) GetSnapshot(ctx context.Context, snapshotID string, zone scw.Zone) (*block.SnapshotSummary, error) { + f.mux.Lock() + defer f.mux.Unlock() + + if zone == scw.Zone("") { + zone = f.defaultZone + } + + if s, ok := f.snapshots[snapshotID]; ok && s.Zone == zone { + return snapshotToSnapshotSummary(f.snapshots[snapshotID]), nil + } + + return nil, &scw.ResourceNotFoundError{Resource: snapshotResource, ResourceID: snapshotID} +} + +func (f *Fake) GetSnapshotByName(ctx context.Context, name string, sourceVolumeID string, zone scw.Zone) (*block.SnapshotSummary, error) { + f.mux.Lock() + defer f.mux.Unlock() + + if zone == scw.Zone("") { + zone = f.defaultZone + } + + for _, s := range f.snapshots { + if s.Name == name && s.Zone == zone { + if s.ParentVolume != nil && s.ParentVolume.ID != sourceVolumeID { + return nil, ErrSnapshotExists + } + + return snapshotToSnapshotSummary(s), nil + } + } + + return nil, ErrSnapshotNotFound +} + +func (f *Fake) GetVolume(ctx context.Context, volumeID string, zone scw.Zone) (*block.Volume, error) { + f.mux.Lock() + defer f.mux.Unlock() + + if zone == scw.Zone("") { + zone = f.defaultZone + } + + if s, ok := f.volumes[volumeID]; ok && s.Zone == zone { + return f.volumes[volumeID], nil + } + + return nil, &scw.ResourceNotFoundError{Resource: volumeResource, ResourceID: volumeID} +} + +func (f *Fake) GetVolumeByName(ctx context.Context, name string, size scw.Size, zone scw.Zone) (*block.Volume, error) { + f.mux.Lock() + defer f.mux.Unlock() + + if zone == scw.Zone("") { + zone = f.defaultZone + } + + for _, v := range f.volumes { + if v.Name == name && v.Zone == zone { + if v.Size != size { + return nil, ErrVolumeDifferentSize + } + + return v, nil + } + } + + return nil, ErrVolumeNotFound +} + +func (f *Fake) ListSnapshots(ctx context.Context, start uint32, max uint32) ([]*block.SnapshotSummary, string, error) { + f.mux.Lock() + defer f.mux.Unlock() + + results, next := mapPaginatedRange(f.snapshots, func(_ *block.Snapshot) bool { return true }, start, max) + + snapshots := make([]*block.SnapshotSummary, 0, len(results)) + for _, r := range results { + snapshots = append(snapshots, snapshotToSnapshotSummary(r)) + } + + return snapshots, next, nil +} + +func (f *Fake) ListSnapshotsBySourceVolume(ctx context.Context, start uint32, max uint32, sourceVolumeID string, sourceVolumeZone scw.Zone) ([]*block.SnapshotSummary, string, error) { + f.mux.Lock() + defer f.mux.Unlock() + + results, next := mapPaginatedRange(f.snapshots, func(s *block.Snapshot) bool { + return s.ParentVolume != nil && s.ParentVolume.ID == sourceVolumeID && s.Zone == sourceVolumeZone + }, start, max) + + snapshots := make([]*block.SnapshotSummary, 0, len(results)) + for _, r := range results { + snapshots = append(snapshots, snapshotToSnapshotSummary(r)) + } + + return snapshots, next, nil +} + +func (f *Fake) ListVolumes(ctx context.Context, start uint32, max uint32) ([]*block.Volume, string, error) { + f.mux.Lock() + defer f.mux.Unlock() + + results, next := mapPaginatedRange(f.volumes, func(v *block.Volume) bool { return true }, start, max) + + return results, next, nil +} + +func (f *Fake) ResizeVolume(ctx context.Context, volumeID string, zone scw.Zone, size int64) error { + f.mux.Lock() + defer f.mux.Unlock() + + if zone == scw.Zone("") { + zone = f.defaultZone + } + + if s, ok := f.volumes[volumeID]; ok && s.Zone == zone { + if size < int64(f.volumes[volumeID].Size) { + return errors.New("new volume size is less than current volume size") + } + + f.volumes[volumeID].Size = scw.Size(size) + + return nil + } + + return &scw.ResourceNotFoundError{Resource: volumeResource, ResourceID: volumeID} +} + +func (f *Fake) WaitForSnapshot(ctx context.Context, snapshotID string, zone scw.Zone) (*block.SnapshotSummary, error) { + return f.GetSnapshot(ctx, snapshotID, zone) +} diff --git a/pkg/scaleway/helpers.go b/pkg/scaleway/helpers.go new file mode 100644 index 0000000..e7ef3ed --- /dev/null +++ b/pkg/scaleway/helpers.go @@ -0,0 +1,119 @@ +package scaleway + +import ( + "fmt" + "math" + "strconv" + + "github.com/google/uuid" + block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1" + "github.com/scaleway/scaleway-sdk-go/scw" +) + +// maxPageSize for paginated lists. +const maxPageSize = 50 + +// paginatedList allows to list resources with a start and max pagination options. +func paginatedList[T any](query func(page int32, pageSize uint32) ([]T, error), start, max uint32) (elements []T, next string, err error) { + // Reduce page size if needed. + listPageSize := maxPageSize + if int(max) < listPageSize && max != 0 { + listPageSize = int(max) + } + + var ( + // first page to query (must start at 1). + page = int32(math.Floor(float64(start)/float64(listPageSize))) + 1 + // first element index. + first = int(start % uint32(listPageSize)) + ) + + for { + var resp []T + resp, err = query(page, uint32(listPageSize)) + if err != nil { + return nil, "", err + } + + respCount := len(resp) + + if first >= respCount { + return + } + + if max == 0 { + elements = append(elements, resp[first:]...) + } else { + elements = append(elements, resp[first:min(respCount, first+int(max)-len(elements))]...) + + // reached max elements. + if len(elements) >= int(max) { + if respCount == listPageSize { + next = strconv.Itoa(int(start + max)) + } + + return + } + } + + // less results than page size. + if respCount != listPageSize { + return + } + + first = 0 + page++ + } +} + +// min returns the smaller of a or b. +func min(a, b int) int { + return int(math.Min(float64(a), float64(b))) +} + +// clientZones returns the zones of the region where the client is configured. +func clientZones(client *scw.Client) ([]scw.Zone, error) { + if defaultZone, ok := client.GetDefaultZone(); ok { + region, err := defaultZone.Region() + if err != nil { + return nil, fmt.Errorf("failed to parse region from default zone: %w", err) + } + + if len(region.GetZones()) > 0 { + return region.GetZones(), nil + } + + return []scw.Zone{defaultZone}, nil + } + + if region, ok := client.GetDefaultRegion(); ok { + if len(region.GetZones()) > 0 { + return region.GetZones(), nil + } + } + + return nil, fmt.Errorf("no zone/region was provided, please set the SCW_DEFAULT_ZONE environment variable") +} + +// snapshotToSnapshotSummary converts a Snapshot to a SnapshotSummary. +func snapshotToSnapshotSummary(snapshot *block.Snapshot) *block.SnapshotSummary { + return &block.SnapshotSummary{ + ID: snapshot.ID, + Name: snapshot.Name, + ParentVolume: snapshot.ParentVolume, + Size: snapshot.Size, + ProjectID: snapshot.ProjectID, + CreatedAt: snapshot.CreatedAt, + UpdatedAt: snapshot.UpdatedAt, + Status: snapshot.Status, + Tags: snapshot.Tags, + Zone: snapshot.Zone, + Class: snapshot.Class, + } +} + +// isValidUUID returns true if the provided value is a valid UUID. +func isValidUUID(u string) bool { + _, err := uuid.Parse(u) + return err == nil +} diff --git a/pkg/scaleway/instance.go b/pkg/scaleway/instance.go new file mode 100644 index 0000000..77187aa --- /dev/null +++ b/pkg/scaleway/instance.go @@ -0,0 +1,118 @@ +package scaleway + +import ( + "context" + "fmt" + + block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1" + "github.com/scaleway/scaleway-sdk-go/api/instance/v1" + "github.com/scaleway/scaleway-sdk-go/scw" +) + +// GetServer returns the server associated to the provided serverID. +func (s *Scaleway) GetServer(ctx context.Context, serverID string, zone scw.Zone) (*instance.Server, error) { + if !isValidUUID(serverID) { + return nil, &scw.ResourceNotFoundError{Resource: serverResource, ResourceID: serverID} + } + + resp, err := s.instance.GetServer(&instance.GetServerRequest{ + ServerID: serverID, + Zone: zone, + }, scw.WithContext(ctx)) + if err != nil { + return nil, fmt.Errorf("failed to get server: %w", err) + } + + return resp.Server, nil +} + +// AttachVolume attaches the provided volume to the specified server. It then +// waits for the volume to be effectively attached. +func (s *Scaleway) AttachVolume(ctx context.Context, serverID, volumeID string, zone scw.Zone) error { + if !isValidUUID(serverID) { + return &scw.ResourceNotFoundError{Resource: "server", ResourceID: serverID} + } + if !isValidUUID(volumeID) { + return &scw.ResourceNotFoundError{Resource: "volume", ResourceID: volumeID} + } + + if _, err := s.instance.AttachVolume(&instance.AttachVolumeRequest{ + Zone: zone, + ServerID: serverID, + VolumeID: volumeID, + }, scw.WithContext(ctx)); err != nil { + return fmt.Errorf("failed to attach volume: %w", err) + } + + if err := s.waitForVolumeAndReferences( + ctx, volumeID, zone, block.VolumeStatusInUse, block.ReferenceStatusAttached, + ); err != nil { + return err + } + + return nil +} + +// DetachVolume detaches the provided volume. It then waits for the volume to be +// effectively detached. +func (s *Scaleway) DetachVolume(ctx context.Context, volumeID string, zone scw.Zone) error { + if _, err := s.instance.DetachVolume(&instance.DetachVolumeRequest{ + Zone: zone, + VolumeID: volumeID, + IsBlockVolume: scw.BoolPtr(true), + }, scw.WithContext(ctx)); err != nil { + return fmt.Errorf("failed to detach volume: %w", err) + } + + if err := s.waitForVolumeAndReferences( + ctx, volumeID, zone, block.VolumeStatusAvailable, block.ReferenceStatusDetached, + ); err != nil { + return err + } + + return nil + +} + +func (s *Scaleway) waitForVolumeAndReferences( + ctx context.Context, + volumeID string, + zone scw.Zone, + volumeStatus block.VolumeStatus, + referenceStatus block.ReferenceStatus, +) error { + volume, err := s.block.WaitForVolumeAndReferences(&block.WaitForVolumeAndReferencesRequest{ + VolumeID: volumeID, + Zone: zone, + VolumeTerminalStatus: &volumeStatus, + ReferenceTerminalStatus: &referenceStatus, + }, scw.WithContext(ctx)) + if err != nil { + return fmt.Errorf("failed to wait for volume: %w", err) + } + + if volume.Status != volumeStatus { + return fmt.Errorf("volume is in state %s which is not expected", volume.Status) + } + + for _, ref := range volume.References { + if ref.Status != referenceStatus { + return fmt.Errorf("volume reference %s is in state %s which is not expected", ref.ID, ref.Status) + } + } + + return nil +} + +// GetLegacyVolume gets an Instance volume by its ID and zone. +func (s *Scaleway) GetLegacyVolume(ctx context.Context, volumeID string, zone scw.Zone) (*instance.Volume, error) { + resp, err := s.instance.GetVolume(&instance.GetVolumeRequest{ + VolumeID: volumeID, + Zone: zone, + }, scw.WithContext(ctx)) + if err != nil { + return nil, fmt.Errorf("failed to get legacy volume: %w", err) + } + + return resp.Volume, nil +} diff --git a/pkg/scaleway/interface.go b/pkg/scaleway/interface.go new file mode 100644 index 0000000..35c429a --- /dev/null +++ b/pkg/scaleway/interface.go @@ -0,0 +1,29 @@ +package scaleway + +import ( + "context" + + block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1" + "github.com/scaleway/scaleway-sdk-go/api/instance/v1" + "github.com/scaleway/scaleway-sdk-go/scw" +) + +// Interface is the Scaleway wrapper interface. +type Interface interface { + AttachVolume(ctx context.Context, serverID string, volumeID string, zone scw.Zone) error + CreateSnapshot(ctx context.Context, name string, volumeID string, zone scw.Zone) (*block.SnapshotSummary, error) + CreateVolume(ctx context.Context, name string, snapshotID string, size int64, perfIOPS *uint32, zone scw.Zone) (*block.Volume, error) + DeleteSnapshot(ctx context.Context, snapshotID string, zone scw.Zone) error + DeleteVolume(ctx context.Context, volumeID string, zone scw.Zone) error + DetachVolume(ctx context.Context, volumeID string, zone scw.Zone) error + GetServer(ctx context.Context, serverID string, zone scw.Zone) (*instance.Server, error) + GetSnapshot(ctx context.Context, snapshotID string, zone scw.Zone) (*block.SnapshotSummary, error) + GetSnapshotByName(ctx context.Context, name string, sourceVolumeID string, zone scw.Zone) (*block.SnapshotSummary, error) + GetVolume(ctx context.Context, volumeID string, zone scw.Zone) (*block.Volume, error) + GetVolumeByName(ctx context.Context, name string, size scw.Size, zone scw.Zone) (*block.Volume, error) + ListSnapshots(ctx context.Context, start uint32, max uint32) ([]*block.SnapshotSummary, string, error) + ListSnapshotsBySourceVolume(ctx context.Context, start uint32, max uint32, sourceVolumeID string, sourceVolumeZone scw.Zone) ([]*block.SnapshotSummary, string, error) + ListVolumes(ctx context.Context, start uint32, max uint32) ([]*block.Volume, string, error) + ResizeVolume(ctx context.Context, volumeID string, zone scw.Zone, size int64) error + WaitForSnapshot(ctx context.Context, snapshotID string, zone scw.Zone) (*block.SnapshotSummary, error) +} diff --git a/pkg/scaleway/metadata.go b/pkg/scaleway/metadata.go new file mode 100644 index 0000000..499f0d3 --- /dev/null +++ b/pkg/scaleway/metadata.go @@ -0,0 +1,107 @@ +package scaleway + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "os" + "time" + + "github.com/scaleway/scaleway-sdk-go/api/instance/v1" + "github.com/scaleway/scaleway-sdk-go/scw" + "k8s.io/klog/v2" +) + +const ( + metadataAPIURL = "http://169.254.42.42/conf?format=json" + metadataRequestTimeout = 10 * time.Second + cloudInitDataFile = "/run/cloud-init/instance-data.json" +) + +var metadataSources = []metadataSource{cloudInitMetadataSource{}, apiMetadataSource{}} + +// GetMetadata gets metadata of the instance that runs this function. It tries +// each metadata source successively (cloud-init, api) until one responds successfully. +func GetMetadata() (*instance.Metadata, error) { + for _, src := range metadataSources { + md, err := src.Get() + if err != nil { + klog.Errorf("failed to get Metadata from source %T: %s", src, err) + continue + } + + return md, nil + } + + return nil, errors.New("no metadata source responded successfully") +} + +// metadataSource is a generic Instance metadata source. +type metadataSource interface { + Get() (*instance.Metadata, error) +} + +// apiMetadataSource retrieves Instance metadata from the Instance metadata API. +type apiMetadataSource struct{} + +func (apiMetadataSource) Get() (m *instance.Metadata, err error) { + ctx, cancel := context.WithTimeout(context.TODO(), metadataRequestTimeout) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, metadataAPIURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create metadata request") + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to get metadata from API: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("metadata API did not return 200: got %d", resp.StatusCode) + } + + metadata := &instance.Metadata{} + if err = json.NewDecoder(resp.Body).Decode(metadata); err != nil { + return nil, fmt.Errorf("error decoding metadata: %w", err) + } + + return metadata, nil +} + +// apiMetadataSource retrieves Instance metadata from the cloud-init metadata. +type cloudInitMetadataSource struct{} + +func (cloudInitMetadataSource) Get() (*instance.Metadata, error) { + f, err := os.Open(cloudInitDataFile) + if err != nil { + return nil, fmt.Errorf("failed to open cloud-init data file: %w", err) + } + defer f.Close() + + data := cloudInitInstanceData{} + if err = json.NewDecoder(f).Decode(&data); err != nil { + return nil, fmt.Errorf("error decoding cloud-init data file: %w", err) + } + + // Validate data. + if data.DS.Metadata == nil { + return nil, errors.New("missing metadata in cloud-init data file") + } + + if _, err = scw.ParseZone(data.DS.Metadata.Location.ZoneID); err != nil { + return nil, fmt.Errorf("zone is not valid in .location.zone_id: %w", err) + } + + return data.DS.Metadata, nil +} + +type cloudInitInstanceData struct { + DS struct { + Metadata *instance.Metadata `json:"meta_data,omitempty"` + } `json:"ds"` +} diff --git a/pkg/scaleway/scaleway.go b/pkg/scaleway/scaleway.go new file mode 100644 index 0000000..ad14a0e --- /dev/null +++ b/pkg/scaleway/scaleway.go @@ -0,0 +1,71 @@ +package scaleway + +import ( + "fmt" + + block "github.com/scaleway/scaleway-sdk-go/api/block/v1alpha1" + "github.com/scaleway/scaleway-sdk-go/api/instance/v1" + "github.com/scaleway/scaleway-sdk-go/scw" +) + +const ( + // MaxVolumesPerNode represents the number max of volumes attached to one node. + MaxVolumesPerNode = 16 + + // LegacyDefaultVolumeType is the legacy default type for Scaleway Block volumes + // that use Instance API. We keep it for backward compatibility. It is equivalent + // to the new 5K IOPS volumes. + LegacyDefaultVolumeType = "b_ssd" + + // LegacyDefaultVolumeTypeIOPS is the number of IOPS for the LegacyDefaultVolumeType. + LegacyDefaultVolumeTypeIOPS = 5000 + + // MinVolumeSize is the minimum size of a volume. + MinVolumeSize = 1000000000 // 1 GB. +) + +const ( + volumeResource = "volume" + serverResource = "server" + snapshotResource = "snapshot" +) + +// Scaleway is the struct used to communicate with the Scaleway provider. +type Scaleway struct { + // scaleway client. + client *scw.Client + // zones of the region where the client is configured. + zones []scw.Zone + + // instance API. + instance *instance.API + + // block API. + block *block.API +} + +// Enforce interface. +var _ Interface = &Scaleway{} + +// New returns a new Scaleway object which will use the given user agent. +func New(userAgent string) (*Scaleway, error) { + client, err := scw.NewClient( + scw.WithEnv(), + scw.WithUserAgent(userAgent), + ) + if err != nil { + return nil, fmt.Errorf("failed to create Scaleway client: %w", err) + } + + zones, err := clientZones(client) + if err != nil { + return nil, err + } + + return &Scaleway{ + client: client, + zones: zones, + instance: instance.NewAPI(client), + block: block.NewAPI(client), + }, nil +} diff --git a/scaleway/scaleway.go b/scaleway/scaleway.go deleted file mode 100644 index 6899b3d..0000000 --- a/scaleway/scaleway.go +++ /dev/null @@ -1,163 +0,0 @@ -package scaleway - -import ( - "errors" - "fmt" - - "github.com/scaleway/scaleway-sdk-go/api/instance/v1" - "github.com/scaleway/scaleway-sdk-go/scw" -) - -const ( - // MaxVolumesPerNode represents the number max of volumes attached to one node - MaxVolumesPerNode = 16 - - // DefaultVolumeType is the default type for Scaleway Block volumes - DefaultVolumeType = instance.VolumeVolumeTypeBSSD -) - -var ( - // ErrMultipleVolumes is the error returned when multiples volumes exists with the same name - ErrMultipleVolumes = errors.New("multiple volumes exists with the same name") - // ErrDifferentSize is the error returned when a volume match the given name, but the size doesn't match - ErrDifferentSize = errors.New("volume exists with a different size") - // ErrVolumeNotFound is the error returned when the volume was not found - ErrVolumeNotFound = errors.New("volume not found") - - // ErrSnapshotNotFound is the error returned when the snapshot was not found - ErrSnapshotNotFound = errors.New("snapshot not found") - // ErrSnapshotSameName is the error returned when a snapshot with the same name already exists - ErrSnapshotSameName = errors.New("a snapshot with the same name exists") - // ErrSnapshotStillSnapshotting is the error returned when a snapshot is still snapshotting - ErrSnapshotStillSnapshotting = errors.New("snapshot is still snapshotting") -) - -// Scaleway is the struct used to communicate withe the Scaleway provider -type Scaleway struct { - InstanceAPI -} - -// NewScaleway returns a new Scaleway object which will use the given user agent -func NewScaleway(userAgent string) *Scaleway { - client, err := scw.NewClient( - scw.WithEnv(), - scw.WithUserAgent(userAgent), - ) - if err != nil { - panic(err) - } - api := instance.NewAPI(client) - return &Scaleway{api} -} - -// Metadata is an interface for the instance metadata -type Metadata interface { - GetMetadata() (m *instance.Metadata, err error) -} - -// NewMetadata returns a new Metadata object to be used from a Scaleway instance -func NewMetadata() Metadata { - return instance.NewMetadataAPI() -} - -// InstanceAPI is an interface for the Scaleway Go SDK for instance -type InstanceAPI interface { - // ListVolumes is an interface for the SDK ListVolumes method - ListVolumes(req *instance.ListVolumesRequest, opts ...scw.RequestOption) (*instance.ListVolumesResponse, error) - - // CreateVolume is an interface for the SDK CreateVolume method - CreateVolume(req *instance.CreateVolumeRequest, opts ...scw.RequestOption) (*instance.CreateVolumeResponse, error) - - // GetVolume is an interface for the SDK GetVolume method - GetVolume(req *instance.GetVolumeRequest, opts ...scw.RequestOption) (*instance.GetVolumeResponse, error) - - // DeleteVolume is an interface for the SDK DeleteVolume method - DeleteVolume(req *instance.DeleteVolumeRequest, opts ...scw.RequestOption) error - - // GetServer is an interface for the SDK GetServer method - GetServer(req *instance.GetServerRequest, opts ...scw.RequestOption) (*instance.GetServerResponse, error) - - // UpdateVolume is an interface for the SDK UpdateVolume method - UpdateVolume(req *instance.UpdateVolumeRequest, opts ...scw.RequestOption) (*instance.UpdateVolumeResponse, error) - - // AttachVolume is an interface for the SDK AttachVolume method - AttachVolume(req *instance.AttachVolumeRequest, opts ...scw.RequestOption) (*instance.AttachVolumeResponse, error) - - // DetachVolume is an interface for the SDK DetachVolume method - DetachVolume(req *instance.DetachVolumeRequest, opts ...scw.RequestOption) (*instance.DetachVolumeResponse, error) - - // WaitForVolume is an interface for the SDK WaitForVolume method - WaitForVolume(req *instance.WaitForVolumeRequest, opts ...scw.RequestOption) (*instance.Volume, error) - - // GetSnapshot is an interface for the SDK GetSnapshot method - GetSnapshot(req *instance.GetSnapshotRequest, opts ...scw.RequestOption) (*instance.GetSnapshotResponse, error) - - // ListSnapshots is an interface for the SDK ListSnapshots method - ListSnapshots(req *instance.ListSnapshotsRequest, opts ...scw.RequestOption) (*instance.ListSnapshotsResponse, error) - - // CreateSnapshot is an interface for the SDK CreateSnapshot method - CreateSnapshot(req *instance.CreateSnapshotRequest, opts ...scw.RequestOption) (*instance.CreateSnapshotResponse, error) - - // DeleteSnapshot is an interface for the SDK CreateSnapshot method - DeleteSnapshot(req *instance.DeleteSnapshotRequest, opts ...scw.RequestOption) error - - // ListVolumesTypes is an interface for the SDK ListVolumesTypes method - ListVolumesTypes(req *instance.ListVolumesTypesRequest, opts ...scw.RequestOption) (*instance.ListVolumesTypesResponse, error) -} - -func (s *Scaleway) GetVolumeLimits(volumeType string) (int64, int64, error) { - volumeTypes, err := s.ListVolumesTypes(&instance.ListVolumesTypesRequest{}) - if err != nil { - return 0, 0, err - } - - if spec, ok := volumeTypes.Volumes[volumeType]; ok && spec.Constraints != nil { - return int64(spec.Constraints.Min), int64(spec.Constraints.Max), nil - } - - return 0, 0, fmt.Errorf("volume type %s not found", volumeType) -} - -// GetVolumeByName is a helper to find a volume by it's name, type and given size -func (s *Scaleway) GetVolumeByName(name string, size int64, volumeType instance.VolumeVolumeType) (*instance.Volume, error) { - volumesResp, err := s.ListVolumes(&instance.ListVolumesRequest{ - Name: &name, - VolumeType: &volumeType, - }, scw.WithAllPages()) - if err != nil { - return nil, err - } - volumes := volumesResp.Volumes - if len(volumes) != 0 { - if len(volumes) > 1 { - return nil, ErrMultipleVolumes - } - volume := volumes[0] - if uint64(volume.Size) != uint64(size) { - return nil, ErrDifferentSize - } - return volume, nil - } - return nil, ErrVolumeNotFound -} - -// GetSnapshotByName is a helper to find a snapshot by it's name and it's source volume ID and zone -func (s *Scaleway) GetSnapshotByName(name string, sourceVolumeID string, sourceVolumeZone scw.Zone) (*instance.Snapshot, error) { - snapshots, err := s.ListSnapshots(&instance.ListSnapshotsRequest{ - Name: &name, - Zone: sourceVolumeZone, - }, scw.WithAllPages()) - if err != nil { - return nil, err - } - - for _, snapshot := range snapshots.Snapshots { - if snapshot.Name == name { // fuzzy search on the API - if snapshot.BaseVolume == nil || snapshot.BaseVolume.ID == sourceVolumeID { - return snapshot, nil - } - return nil, ErrSnapshotSameName - } - } - return nil, ErrSnapshotNotFound -} diff --git a/scaleway/utils.go b/scaleway/utils.go deleted file mode 100644 index a8618aa..0000000 --- a/scaleway/utils.go +++ /dev/null @@ -1,18 +0,0 @@ -package scaleway - -import "github.com/scaleway/scaleway-sdk-go/api/instance/v1" - -// ExpandSnapshotID concatenates the zone with the snapshot ID -func ExpandSnapshotID(snapshot *instance.Snapshot) string { - return snapshot.Zone.String() + "/" + snapshot.ID -} - -// ExpandVolumeID concatenates the zone with the volume ID -func ExpandVolumeID(volume *instance.Volume) string { - return volume.Zone.String() + "/" + volume.ID -} - -// ExpandServerID concatenates the zone with the server ID -func ExpandServerID(server *instance.Server) string { - return server.Zone.String() + "/" + server.ID -} diff --git a/test/e2e/README.md b/test/e2e/README.md new file mode 100644 index 0000000..5579138 --- /dev/null +++ b/test/e2e/README.md @@ -0,0 +1,59 @@ +# Kubernetes e2e tests + +This document describes how to run Kubernetes [external CSI tests](https://github.com/kubernetes/kubernetes/tree/master/test/e2e/storage/external). + +> **Warning** +> By running this test suite, you will be billed for resources created during the +> tests such as Instances, Block Volumes and Snapshots. There are no guarantees that +> these resources will be deleted automatically after the test suite completes +> (especially if it fails). + +## Requirements + +- A Kubernetes (v1.20+) cluster running with the `scaleway-csi` already installed. +- Clone this repository locally. + +## Running locally + +These tests can be run [manually](#manually) or using the [convenience script](#using-the-convenience-script). + +> **Note** +> Tests that simulate a situation where kubelet goes down will fail when run on +> a Kapsule cluster, due to the way the product is configured. This is not related +> to the CSI implementation. + +### Manually + +1. Set the `K8S_VERSION` environment variable to the version of your Kubernetes cluster: + + ```console + export K8S_VERSION=$(kubectl version --output json | jq -r '.serverVersion.gitVersion') + ``` + +2. Download the Kubernetes `e2e.test` binary that matches the version of your cluster: + + ```console + curl --location https://dl.k8s.io/${K8S_VERSION}/kubernetes-test-linux-amd64.tar.gz | \ + tar --strip-components=3 -zxf - kubernetes/test/bin/e2e.test + ``` + +3. Assuming an SSH key is present at `$HOME/.ssh/id_rsa`, some `[Disruptive]` tests + will attempt to run SSH commands on the nodes of your cluster using this key: + + - Set the `KUBE_SSH_KEY_PATH` environment variable if you need to use another key. + Set this to a fake path to skip these tests altogether. + - Set the `KUBE_SSH_USER` environment variable if you want to specify the SSH user. + +4. Run the tests: + + ```console + ./e2e.test -ginkgo.focus='External.Storage' -ginkgo.skip='Ephemeral-volume' -ginkgo.timeout=6h -storage.testdriver=test-driver.yaml + ``` + +### Using the convenience script + +1. Review the script and run it: + + ```console + KUBECONFIG=/path/to/kubeconfig ./e2e.sh + ``` diff --git a/test/e2e/e2e.sh b/test/e2e/e2e.sh new file mode 100755 index 0000000..3236001 --- /dev/null +++ b/test/e2e/e2e.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -e + +cd "$(dirname "$0")" # Automatically cd to the directory that contains this script. + +K8S_VERSION=$(kubectl version --output json | jq -r '.serverVersion.gitVersion') +export K8S_VERSION +export KUBE_SSH_USER="${KUBE_SSH_USER:=ubuntu}" + +if [[ "${K8S_VERSION}" == "null" ]]; then + echo "ERROR: Unable to get k8s version, make sure you set the KUBECONFIG environment variable." + exit 1 +fi + +curl --location https://dl.k8s.io/"${K8S_VERSION}"/kubernetes-test-linux-amd64.tar.gz | \ + tar --strip-components=3 -zxf - kubernetes/test/bin/e2e.test + +./e2e.test -ginkgo.focus='External.Storage' -ginkgo.skip='Ephemeral-volume' -ginkgo.timeout=6h -storage.testdriver=test-driver.yaml diff --git a/test/e2e/test-driver.yaml b/test/e2e/test-driver.yaml new file mode 100644 index 0000000..f83f2df --- /dev/null +++ b/test/e2e/test-driver.yaml @@ -0,0 +1,25 @@ +StorageClass: + FromName: true +SnapshotClass: + FromName: true +DriverInfo: + Name: csi.scaleway.com + Capabilities: + block: true + controllerExpansion: true + nodeExpansion: true + exec: true + multipods: true + persistence: true + snapshotDataSource: true + topology: true + onlineExpansion: true + singleNodeVolume: true + SupportedFsType: + ext3: + ext4: {} + xfs: {} + TopologyKeys: + - topology.csi.scaleway.com/zone +InlineVolumes: +- Attributes: {} diff --git a/test/sanity/README.md b/test/sanity/README.md new file mode 100644 index 0000000..f4ca700 --- /dev/null +++ b/test/sanity/README.md @@ -0,0 +1,55 @@ +# Sanity test + +The sanity test suite is a wrapper for running the [Sanity Test Command Line Program](https://github.com/kubernetes-csi/csi-test/tree/master/cmd/csi-sanity) +against the `scaleway-csi`. + +It builds the current project, runs it on a Scaleway instance and verifies that +the [sanity](https://github.com/kubernetes-csi/csi-test/tree/master/cmd/csi-sanity) +test suite succeeds. + +## Requirements + +- [Go >=1.20](https://go.dev/) +- [A Scaleway API key](https://www.scaleway.com/en/docs/identity-and-access-management/iam/how-to/create-api-keys/) + +## Running the test + +> **Warning** +> By running this test suite, you will be billed for resources created during the +> tests such as Instances, Block Volumes and Snapshots. There are no guarantees that +> these resources will be deleted automatically after the test suite completes +> (especially if it fails). + +Set the following environment variables: + +```console +export SCW_DEFAULT_ZONE=fr-par-1 +export SCW_DEFAULT_PROJECT_ID=11111111-1111-1111-1111-111111111111 +export SCW_ACCESS_KEY=SCW123456789ABCDE +export SCW_SECRET_KEY=11111111-1111-1111-1111-111111111111 +``` + +Run the test suite: + +```console +$ go test -v -timeout 10m github.com/scaleway/scaleway-csi/test/sanity +=== RUN TestSanity +Running Suite: Sanity Suite - /home/user/dev/scaleway-csi/test/sanity +=============================================================================== +Random Seed: 1693337733 + +Will run 1 of 1 specs +• + +Ran 1 of 1 Specs in 146.095 seconds +SUCCESS! -- 1 Passed | 0 Failed | 0 Pending | 0 Skipped +--- PASS: TestSanity (146.10s) +PASS +ok github.com/scaleway/scaleway-csi/test/sanity 146.104s +``` + +(Optional): Run the test suite with ginkgo: + +```console +ginkgo -v test/sanity/ +``` diff --git a/test/sanity/sanity_suite_test.go b/test/sanity/sanity_suite_test.go new file mode 100644 index 0000000..3b9a4c4 --- /dev/null +++ b/test/sanity/sanity_suite_test.go @@ -0,0 +1,91 @@ +package sanity_test + +import ( + "crypto/ed25519" + "crypto/rand" + "crypto/x509" + "encoding/pem" + "fmt" + "os" + "strings" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/scaleway/scaleway-sdk-go/api/instance/v1" + "github.com/scaleway/scaleway-sdk-go/api/marketplace/v1" + "github.com/scaleway/scaleway-sdk-go/scw" + + "golang.org/x/crypto/ssh" +) + +func TestSanity(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Sanity Suite") +} + +var ( + // sshPublicKey (authorized key). + sshPublicKey string + // ssh signer. + sshSigner ssh.Signer + + instanceAPI *instance.API + marketplaceAPI *marketplace.API +) + +var _ = BeforeSuite(func() { + // Create SSH keypair to access the instances that will be created. + signer, authorization, err := createSSHKeypair() + Expect(err).NotTo(HaveOccurred()) + + sshPublicKey = authorization + sshSigner = signer + + // Make sure necessary env vars are present. + Expect(os.Getenv(scw.ScwDefaultZoneEnv)).ToNot(BeEmpty(), "Please set SCW_DEFAULT_ZONE") + Expect(os.Getenv(scw.ScwDefaultProjectIDEnv)).ToNot(BeEmpty(), "Please set SCW_DEFAULT_PROJECT_ID") + Expect(os.Getenv(scw.ScwAccessKeyEnv)).ToNot(BeEmpty(), "Please set SCW_ACCESS_KEY") + Expect(os.Getenv(scw.ScwSecretKeyEnv)).ToNot(BeEmpty(), "Please set SCW_SECRET_KEY") + + // Create SCW client. + scwClient, err := scw.NewClient(scw.WithEnv()) + Expect(err).NotTo(HaveOccurred()) + + // Create APIs. + instanceAPI = instance.NewAPI(scwClient) + marketplaceAPI = marketplace.NewAPI(scwClient) +}) + +// createSSHKeypair returns an SSH signer for use with the SSH client and the +// corresponding authorized key. +func createSSHKeypair() (signer ssh.Signer, authorization string, err error) { + pub, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, "", fmt.Errorf("failed to generate ed25519 key: %w", err) + } + + sshPub, err := ssh.NewPublicKey(pub) + if err != nil { + return nil, "", fmt.Errorf("failed to create SSH public key: %w", err) + } + + authorization = strings.TrimRight(string(ssh.MarshalAuthorizedKey(sshPub)), "\n") + + b, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return nil, "", fmt.Errorf("failed to marshal private key: %w", err) + } + + signer, err = ssh.ParsePrivateKey(pem.EncodeToMemory( + &pem.Block{ + Type: "PRIVATE KEY", + Bytes: b, + }, + )) + if err != nil { + return nil, "", fmt.Errorf("failed to parse SSH private key: %w", err) + } + + return +} diff --git a/test/sanity/sanity_test.go b/test/sanity/sanity_test.go new file mode 100644 index 0000000..74afd7c --- /dev/null +++ b/test/sanity/sanity_test.go @@ -0,0 +1,325 @@ +package sanity_test + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "context" + "errors" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/pkg/sftp" + "github.com/scaleway/scaleway-sdk-go/api/instance/v1" + "github.com/scaleway/scaleway-sdk-go/api/marketplace/v1" + "github.com/scaleway/scaleway-sdk-go/scw" + "golang.org/x/crypto/ssh" +) + +const ( + instanceCommercialType = "PLAY2-NANO" + remoteCSIPath = "/usr/local/bin/scaleway-csi" + remoteSanityPath = "/usr/local/bin/csi-sanity" + remoteBinaryPerm = 0700 +) + +var _ = Describe("Sanity", func() { + It("should run sanity test successfully", func(ctx SpecContext) { + By("Creating instance") + imageID, err := marketplaceAPI.GetLocalImageIDByLabel(&marketplace.GetLocalImageIDByLabelRequest{ + ImageLabel: "ubuntu_jammy", + CommercialType: instanceCommercialType, + }, scw.WithContext(ctx)) + Expect(err).NotTo(HaveOccurred()) + + server, err := instanceAPI.CreateServer(&instance.CreateServerRequest{ + Name: "csi-sanity", + DynamicIPRequired: scw.BoolPtr(true), + Image: imageID, + CommercialType: instanceCommercialType, + RoutedIPEnabled: scw.BoolPtr(true), + Tags: []string{"AUTHORIZED_KEY=" + strings.ReplaceAll(sshPublicKey, " ", "_")}, + }, scw.WithContext(ctx)) + Expect(err).NotTo(HaveOccurred()) + + DeferCleanup(func(ctx SpecContext) { + _, err = instanceAPI.ServerAction(&instance.ServerActionRequest{ + ServerID: server.Server.ID, + Action: instance.ServerActionTerminate, + }, scw.WithContext(ctx)) + Expect(err).NotTo(HaveOccurred()) + }) + + // Poweron server and wait for it to start. + _, err = instanceAPI.ServerAction(&instance.ServerActionRequest{ + ServerID: server.Server.ID, + Action: instance.ServerActionPoweron, + }, scw.WithContext(ctx)) + Expect(err).NotTo(HaveOccurred()) + server.Server, err = instanceAPI.WaitForServer(&instance.WaitForServerRequest{ + ServerID: server.Server.ID, + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure instance is running and has an IP. + Expect(server.Server.State).To(Equal(instance.ServerStateRunning)) + Expect(server.Server.PublicIP).ToNot(BeNil()) + + By("Connecting to the instance") + client := &sshClient{ + Address: server.Server.PublicIP.Address.String(), + Signer: sshSigner, + } + Eventually(client.Open).WithContext(ctx).Should(Succeed()) + defer client.Close() + + By("Building csi driver") + outDriver := filepath.Join(os.TempDir(), fmt.Sprintf("csi-%d", GinkgoRandomSeed())) + Expect(goBuild(outDriver, "../../cmd/scaleway-csi/main.go")).Should(Succeed()) + DeferCleanup(func() { + Expect(os.Remove(outDriver)).Should(Succeed()) + }) + + By("Downloading csi-test repository and building csi-sanity binary") + // Download csi-test repository as tgz. + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://github.com/kubernetes-csi/csi-test/archive/refs/heads/master.tar.gz", nil) + Expect(err).NotTo(HaveOccurred()) + resp, err := http.DefaultClient.Do(req) + Expect(err).NotTo(HaveOccurred()) + defer resp.Body.Close() + + Expect(resp.StatusCode).To(Equal(http.StatusOK)) + + // Create temporary dir and extract repo in this dir. + tempCSISanityDir, err := os.MkdirTemp(os.TempDir(), "") + Expect(err).NotTo(HaveOccurred()) + DeferCleanup(func() { + Expect(os.RemoveAll(tempCSISanityDir)).Should(Succeed()) + }) + + Expect(untargz(resp.Body, tempCSISanityDir)).Should(Succeed()) + + // Build csi-sanity binary. + outSanity := filepath.Join(tempCSISanityDir, "csi-sanity") + Expect(goBuild(outSanity, filepath.Join(tempCSISanityDir, "csi-test-master/cmd/csi-sanity/main.go"))).Should(Succeed()) + + By("Uploading binaries to instance") + Expect(client.UploadFile(outDriver, remoteCSIPath, remoteBinaryPerm)).Should(Succeed()) + Expect(client.UploadFile(outSanity, remoteSanityPath, remoteBinaryPerm)).Should(Succeed()) + + By("Running scaleway-csi on instance") + driverEnv := map[string]string{ + scw.ScwDefaultZoneEnv: os.Getenv(scw.ScwDefaultZoneEnv), + scw.ScwDefaultProjectIDEnv: os.Getenv(scw.ScwDefaultProjectIDEnv), + scw.ScwAccessKeyEnv: os.Getenv(scw.ScwAccessKeyEnv), + scw.ScwSecretKeyEnv: os.Getenv(scw.ScwSecretKeyEnv), + } + + if apiURL, ok := os.LookupEnv(scw.ScwAPIURLEnv); ok { + driverEnv[scw.ScwAPIURLEnv] = apiURL + } + + csiResult, err := client.RunCmd(ctx, remoteCSIPath, driverEnv) + Expect(err).NotTo(HaveOccurred()) + + By("Running csi-sanity on instance") + sanityResult, err := client.RunCmd(ctx, fmt.Sprintf("%s --csi.endpoint=unix:/tmp/csi.sock --ginkgo.no-color", remoteSanityPath), nil) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for results") + + select { + case csi := <-csiResult: + // We should never receive something unless the driver has crashed. + Fail(fmt.Sprintf("CSI driver has crashed: %v", csi)) + case sanity := <-sanityResult: + if sanity.Err != nil { + Fail(fmt.Sprintf("Sanity test failed: %v", sanity)) + } + + GinkgoLogr.Info("Sanity test ran successfully", "output", sanity.Output) + } + }) +}) + +// sshClient is an SSH and SFTP client. +type sshClient struct { + Address string + Signer ssh.Signer + + client *ssh.Client + sftp *sftp.Client +} + +// Open opens the SSH and SFTP connection. +func (s *sshClient) Open() (err error) { + s.client, err = ssh.Dial("tcp", fmt.Sprintf("%s:22", s.Address), &ssh.ClientConfig{ + User: "root", + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(s.Signer), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec + }) + if err != nil { + return fmt.Errorf("failed to open ssh connection: %w", err) + } + + s.sftp, err = sftp.NewClient(s.client) + if err != nil { + return fmt.Errorf("failed to create sftp client: %w", err) + } + + return nil +} + +// Close closes the SSH and SFTP connection. +func (s *sshClient) Close() error { + return errors.Join(s.sftp.Close(), s.client.Close()) +} + +// sshResult contains the result of the execution of an SSH command. +type sshResult struct { + Err error + Output string +} + +// RunCmd runs the specified command with the provided env variables. +func (s *sshClient) RunCmd(ctx context.Context, cmd string, env map[string]string) (<-chan sshResult, error) { + session, err := s.client.NewSession() + if err != nil { + return nil, fmt.Errorf("failed to create SSH session: %w", err) + } + + var envString = strings.Builder{} + + for k, v := range env { + if envString.Len() > 0 { + envString.WriteString(" ") + } + + envString.WriteString(fmt.Sprintf("%s=%s", k, v)) + } + + result := make(chan sshResult) + resultInternal := make(chan sshResult, 1) + + var b bytes.Buffer + session.Stdout = &b + session.Stderr = &b + go func() { + err := session.Run(fmt.Sprintf("%s %s", envString.String(), cmd)) + resultInternal <- sshResult{Err: err, Output: b.String()} + close(resultInternal) + }() + + go func() { + defer session.Close() + defer close(result) + select { + case <-ctx.Done(): + result <- sshResult{Err: ctx.Err()} + case r := <-resultInternal: + result <- r + } + }() + + return result, nil +} + +// UploadFile uploads a local file to the server. +func (s *sshClient) UploadFile(local, remote string, mode os.FileMode) error { + lf, err := os.Open(local) + if err != nil { + return fmt.Errorf("failed to open local file: %w", err) + } + defer lf.Close() + + rf, err := s.sftp.Create(remote) + if err != nil { + return fmt.Errorf("failed to create remote file: %w", err) + } + defer rf.Close() + + if _, err := io.Copy(rf, lf); err != nil { + return fmt.Errorf("failed to upload file: %w", err) + } + + if err := rf.Chmod(mode); err != nil { + return fmt.Errorf("failed to chmod remote file: %w", err) + } + + return nil +} + +// sanitize archive file pathing from "G305: Zip Slip vulnerability" +func sanitizeArchivePath(d, t string) (v string, err error) { + v = filepath.Join(d, t) + if strings.HasPrefix(v, filepath.Clean(d)) { + return v, nil + } + + return "", fmt.Errorf("%s: %s", "content filepath is tainted", t) +} + +// untargz extracts a targz from a reader to a target directory. +func untargz(r io.Reader, target string) error { + archive, err := gzip.NewReader(r) + if err != nil { + return fmt.Errorf("failed to create gzip reader: %w", err) + } + defer archive.Close() + tarReader := tar.NewReader(archive) + + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } else if err != nil { + return fmt.Errorf("failed to read next entry: %w", err) + } + + path, err := sanitizeArchivePath(target, header.Name) + if err != nil { + return err + } + + info := header.FileInfo() + if info.IsDir() { + if err = os.MkdirAll(path, info.Mode()); err != nil { + return fmt.Errorf("failed to create dir: %w", err) + } + continue + } + + file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode()) + if err != nil { + return fmt.Errorf("failed to create file: %w", err) + } + defer file.Close() + + _, err = io.Copy(file, tarReader) //nolint:gosec + if err != nil && !errors.Is(err, io.EOF) { + return fmt.Errorf("failed to extract file: %w", err) + } + } + return nil +} + +// goBuild builds a go main file. +func goBuild(outPath, mainPath string) error { + cmd := exec.Command("go", "build", "-o", outPath, mainPath) + cmd.Env = append(cmd.Env, os.Environ()...) + cmd.Env = append(cmd.Env, "GOOS=linux", "GOARCH=amd64") + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to run go build command: %w", err) + } + + return nil +}