diff --git a/Dockerfile b/Dockerfile index f32854e..984f7a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,8 @@ FROM alpine:3 -RUN apk add --no-cache python3 py3-pip && \ - pip3 install --upgrade pip && \ - mkdir /app ADD requirements.txt / -RUN pip3 install -r requirements.txt +RUN apk add --no-cache python3 py3-pip && \ + python3 -m venv /app && \ + /app/bin/pip install -r requirements.txt ADD truenascsp/*.py /app/ WORKDIR /app -ENTRYPOINT [ "gunicorn", "--workers", "3", "--bind", "0.0.0.0:8080", "--timeout", "60", "csp:SERVE" ] +ENTRYPOINT [ "/app/bin/gunicorn", "--workers", "3", "--bind", "0.0.0.0:8080", "--timeout", "180", "csp:SERVE" ] diff --git a/INSTALL.md b/INSTALL.md index cc0e838..c70129d 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -129,3 +129,5 @@ Set `root` to a dataset that will serve as the base dataset where the ZVols will **Important:** Do NOT use underscore "`_`" in your root dataset for now, it will most likely break. Once the `Secret` and `StorageClass` have been created, all functionality is provided by the HPE CSI Driver and is [documented here](https://scod.hpedev.io/csi_driver/using.html). + +**Tip:** If `VolumeSnapshots` are needed, follow the guidance in HPE CSI Driver documentation on how to [enable CSI snapshots](https://scod.hpedev.io/csi_driver/using.html#enabling_csi_snapshots) and [how to use them](https://scod.hpedev.io/csi_driver/using.html#using_csi_snapshots). diff --git a/LICENSE b/LICENSE index 44c1492..439ada2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2020 Hewlett Packard Enterprise Development LP +Copyright 2024 Hewlett Packard Enterprise Development LP Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/Makefile b/Makefile index 9466fda..6c0f00b 100644 --- a/Makefile +++ b/Makefile @@ -23,20 +23,36 @@ run: docker run -d -p8080:8080 --name truenas-csp -e LOG_DEBUG=1 $(REPO_NAME):$(IMAGE_TAG) test: - # Delete host - $(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' -H 'X-Auth-Token: $(password)' \ + + # Delete host 1 + - $(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' -H 'X-Auth-Token: $(password)' \ -H 'X-Array-IP: $(backend)' \ - $(csp)/containers/v1/hosts/41302701-0196-420f-b319-834a79891db0 -f || true - - # Unpublish volume - $(curl) $(curl_args) -XPUT -d @tests/csp/unpublish.yaml -H 'Content-Type: application/json' \ + $(csp)/containers/v1/hosts/41302701-0196-420f-b319-834a79891db0 -f + + # Delete host 2 + - $(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' -H 'X-Auth-Token: $(password)' \ + -H 'X-Array-IP: $(backend)' \ + $(csp)/containers/v1/hosts/41302701-0196-420f-b319-834a79891db1 -f + + # Unpublish volume host 1 + - $(curl) $(curl_args) -XPUT -d @tests/csp/unpublish.yaml -H 'Content-Type: application/json' \ -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ - $(csp)/containers/v1/volumes/tank_my-new-volume16/actions/unpublish -f || true + $(csp)/containers/v1/volumes/tank_my-new-volume16/actions/unpublish -f + + # Unpublish volume host 2 + - $(curl) $(curl_args) -XPUT -d @tests/csp/unpublish-multi.yaml -H 'Content-Type: application/json' \ + -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ + $(csp)/containers/v1/volumes/tank_my-new-volume16/actions/unpublish -f # Delete volume - $(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' -H 'X-Auth-Token: $(password)' \ + - $(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' -H 'X-Auth-Token: $(password)' \ -H 'X-Array-IP: $(backend)' \ - $(csp)/containers/v1/volumes/tank_my-new-volume16 -f || true + $(csp)/containers/v1/volumes/tank_my-new-volume16 -f + + # Delete thick volume + $(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' \ + -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ + $(csp)/containers/v1/volumes/tank_my-new-volume18 -f # "Create" password $(curl) $(curl_args) -XPOST \ @@ -49,16 +65,28 @@ test: # Fail auth $(curl) $(curl_args) -XGET -H 'Content-Type: application/json' $(csp)/containers/v1/tokens/123 -f || true - # Create host + # Create host 1 $(curl) $(curl_args) -XPOST -d @tests/csp/initiator.yaml -H 'Content-Type: application/json' \ -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ $(csp)/containers/v1/hosts -f + # Create host 2 + $(curl) $(curl_args) -XPOST -d @tests/csp/initiator-multi.yaml -H 'Content-Type: application/json' \ + -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ + $(csp)/containers/v1/hosts -f + # Create volume $(curl) $(curl_args) -XPOST -d @tests/csp/volume.yaml -H 'Content-Type: application/json' \ -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ $(csp)/containers/v1/volumes -f + # Create thick volume + $(curl) $(curl_args) -XPOST -d @tests/csp/volume-thick.yaml -H 'Content-Type: application/json' \ + -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ + $(csp)/containers/v1/volumes -f + + sleep 120 + # Get volume $(curl) $(curl_args) -XGET -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ $(csp)/containers/v1/volumes?name=my-new-volume16 -f @@ -76,10 +104,15 @@ test: -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ $(csp)/containers/v1/volumes/tank_my-new-volume16 -f || true - # Publish volume + # Publish volume host 1 $(curl) $(curl_args) -XPUT -d @tests/csp/publish.yaml -H 'Content-Type: application/json' \ -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ $(csp)/containers/v1/volumes/tank_my-new-volume16/actions/publish -f + + # Publish volume host 2 + $(curl) $(curl_args) -XPUT -d @tests/csp/publish-multi.yaml -H 'Content-Type: application/json' \ + -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ + $(csp)/containers/v1/volumes/tank_my-new-volume16/actions/publish -f # Create snapshots $(curl) $(curl_args) -XPOST -d @tests/csp/snapshot1.yaml -H 'Content-Type: application/json' \ @@ -111,17 +144,33 @@ test: $(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' \ -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ $(csp)/containers/v1/snapshots/tank_my-new-volume16@my-first-snapshot -f - # Unpublish volume + + # Unpublish volume host 1 $(curl) $(curl_args) -XPUT -d @tests/csp/unpublish.yaml -H 'Content-Type: application/json' \ -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ $(csp)/containers/v1/volumes/tank_my-new-volume16/actions/unpublish -f + # Unpublish volume host 2 + $(curl) $(curl_args) -XPUT -d @tests/csp/unpublish-multi.yaml -H 'Content-Type: application/json' \ + -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ + $(csp)/containers/v1/volumes/tank_my-new-volume16/actions/unpublish -f + # Delete volume $(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' \ -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ $(csp)/containers/v1/volumes/tank_my-new-volume16 -f - # Delete host + # Delete thick volume + $(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' \ + -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ + $(csp)/containers/v1/volumes/tank_my-new-volume18 -f + + # Delete host 1 $(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' \ -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ $(csp)/containers/v1/hosts/41302701-0196-420f-b319-834a79891db0 -f + + # Delete host 2 + $(curl) $(curl_args) -XDELETE -H 'Content-Type: application/json' \ + -H 'X-Auth-Token: $(password)' -H 'X-Array-IP: $(backend)' \ + $(csp)/containers/v1/hosts/41302701-0196-420f-b319-834a79891db1 -f diff --git a/README.md b/README.md index 9bbca0c..8dd5518 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/truenas-csp)](https://artifacthub.io/packages/search?repo=truenas-csp) # TrueNAS Container Storage Provider The TrueNAS Container Storage Provider (CSP) is an API gateway to provide iSCSI block storage provisioning using the [HPE CSI Driver for Kubernetes](https://github.com/hpe-storage/csi-driver). It allows you to use [TrueNAS](https://www.truenas.com) and [FreeNAS](https://www.freenas.org/) to provide persistent storage using iSCSI to Kubernetes. @@ -29,6 +30,7 @@ Topology is currently not supported by the HPE CSI Driver. Releases will track the upstream versioning of the HPE CSI Driver for Kubernetes and potential bugfixes in the TrueNAS CSP will be pushed to the same image tag matching the HPE CSI Driver version. +* [TrueNAS CSP v2.4.2](https://github.com/hpe-storage/truenas-csp/releases/tag/v2.4.2) for HPE CSI Driver v2.4.2 * [TrueNAS CSP v2.4.0](https://github.com/hpe-storage/truenas-csp/releases/tag/v2.4.0) for HPE CSI Driver v2.4.0 * [TrueNAS CSP v2.3.10](https://github.com/hpe-storage/truenas-csp/releases/tag/v2.3.10) for HPE CSI Driver v2.3.0 * [TrueNAS CSP v2.3.0](https://github.com/hpe-storage/truenas-csp/releases/tag/v2.3.0) for HPE CSI Driver v2.3.0 @@ -70,16 +72,7 @@ make test backend= **Note:** None of the tests are comprehensive nor provide full coverage and should be considered equivalent to "Does the light come on?". -A Kubernetes e2e test `Makefile` is provided in [e2e](e2e) for both RWO and RWX modes. Ensure everything is [installed](INSTALL.md) and a `Secret` named "truenas-secret" exists in the "hpe-storage" `Namespace`. Then run: - -``` -cd e2e -make rwo -make rwx -make clean -``` - -**Note:** FreeNAS, TrueNAS CORE and SCALE should pass all tests when configured properly. +See [e2e/README.md](e2e/README.md) how to configure and run Kubernetes e2e test suite focused the CSI tests for the TrueNAS CSP. # Limitations @@ -90,6 +83,8 @@ These are the known limitations. - **Dataset naming:** The underscore character `_` is used as an internal separator for naming snapshots and datasets. Do NOT use underscores in your pool or dataset names. - **FreeNAS ctl_max_luns:** FreeNAS has an internal limit of 1024 LUNs. That number increments for every new LUN created, even if deleted. The iSCSI Target service won't start and it leads to all sorts of problems. This is the log message on the console: `requested LUN ID 1031 is higher than ctl_max_luns` (this system had two iSCSI Targets). - **FreeNAS iSCSI Target:** On systems with a high degree of churn, especially during e2e testing, the iSCSI Target sometimes croak and needs to be restarted. It's recommended to starve the CSP to ease the API requests against FreeNAS and let failures be handled by CSI driver and Kubernetes (see [Helm chart](https://artifacthub.io/packages/helm/truenas-csp/truenas-csp)). +- **KubeVirt support:** Live migration for KubeVirt is not yet implemented in the CSP. Running KubeVirt without live migration on RWO claims should be fine. This will be implemented in a later released. +- **iSCSI CHAP:** Using CHAP with the HPE CSI Driver will not propagate to the TrueNAS CSP. This will be implemented in a later release of the TrueNAS CSP. # Need help? @@ -125,7 +120,7 @@ FreeNAS(R) is (C) 2011-2023 iXsystems TrueNAS CSP is released under the [MIT License](LICENSE). -(C) Copyright 2023 Hewlett Packard Enterprise Development LP. +(C) Copyright 2024 Hewlett Packard Enterprise Development LP. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/docs/index.md b/docs/index.md index 233214b..4fe7bad 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,3 +1,3 @@ This page intentionally left blank. -(C) Copyright 2023 Hewlett Packard Enterprise Development LP. +(C) Copyright 2024 Hewlett Packard Enterprise Development LP. diff --git a/docs/index.yaml b/docs/index.yaml index 14bc337..88a8c8c 100644 --- a/docs/index.yaml +++ b/docs/index.yaml @@ -1,6 +1,39 @@ apiVersion: v1 entries: truenas-csp: + - annotations: + artifacthub.io/license: MIT + artifacthub.io/links: | + - name: HPE CSI Driver for Kubernetes + url: https://scod.hpedev.io + - name: Install + url: https://github.com/hpe-storage/truenas-csp/blob/master/INSTALL.md + artifacthub.io/prerelease: "false" + apiVersion: v2 + appVersion: 2.4.2 + created: "2024-05-05T17:25:24.566162137Z" + dependencies: + - name: hpe-csi-driver + repository: https://hpe-storage.github.io/co-deployments + version: 2.4.2 + description: TrueNAS Container Storage Provider Helm chart for Kubernetes + digest: a0df2e41a2441fef306c2d8291b44a2b4a05eccd8a0e6d7cc4795972fca4732d + home: https://github.com/hpe-storage/truenas-csp + icon: https://hpe-storage.github.io/truenas-csp/assets/icon.svg + keywords: + - HPE + - Storage + - CSI + maintainers: + - email: michael.mattsson@gmail.com + name: Michael Mattsson + name: truenas-csp + sources: + - https://github.com/hpe-storage/truenas-csp + type: application + urls: + - truenas-csp-1.1.6.tgz + version: 1.1.6 - annotations: artifacthub.io/license: MIT artifacthub.io/links: | @@ -232,4 +265,4 @@ entries: urls: - truenas-csp-1.0.0.tgz version: 1.0.0 -generated: "2023-10-10T00:04:32.481206987Z" +generated: "2024-05-05T17:25:24.564919127Z" diff --git a/docs/truenas-csp-1.1.6.tgz b/docs/truenas-csp-1.1.6.tgz new file mode 100644 index 0000000..36736f8 Binary files /dev/null and b/docs/truenas-csp-1.1.6.tgz differ diff --git a/e2e/.gitignore b/e2e/.gitignore new file mode 100644 index 0000000..4a424df --- /dev/null +++ b/e2e/.gitignore @@ -0,0 +1 @@ +secret.yaml diff --git a/e2e/Dockerfile b/e2e/Dockerfile new file mode 100644 index 0000000..7957f18 --- /dev/null +++ b/e2e/Dockerfile @@ -0,0 +1,14 @@ +FROM almalinux + +# Install stuff +RUN dnf install -y curl jq && \ + curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" && \ + install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl + +# Manipulate stuff +ENV HERE=/cut/csi-e2e +ENV PATH=${PATH}:${HERE} +RUN mkdir -p ${HERE} +ADD cache ${HERE} +ADD runner.sh ${HERE} +WORKDIR ${HERE} diff --git a/e2e/Makefile b/e2e/Makefile deleted file mode 100644 index 3f6dc9d..0000000 --- a/e2e/Makefile +++ /dev/null @@ -1,54 +0,0 @@ -E2E=./e2e.test -TEST_PREFIX=External.Storage.\[Driver:.csi.hpe.com\].* -FOCUS= -GINKGO_EXTRA_ARGS= -SYSTEM=$(shell uname -s | tr A-Z a-z) -ARCH=$(shell uname -m) -ifeq ($(ARCH), x86_64) - ARCH=amd64 -endif - -all: rwo rwx -dl: kcfg -ifeq ("$(wildcard $(E2E))","") - $(shell curl --location https://dl.k8s.io/$(KUBEVER)/kubernetes-test-$(SYSTEM)-$(ARCH).tar.gz | \ - tar --strip-components=3 -zxf - kubernetes/test/bin/e2e.test kubernetes/test/bin/ginkgo) -endif - -kcfg: -ifeq ("$(wildcard $(KUBECONFIG))","") - $(error KUBECONFIG is not set) - exit -endif -ifndef KUBEVER - KUBEVER:=$(shell kubectl --kubeconfig ${KUBECONFIG} version -o json | jq .serverVersion.gitVersion) -endif - -rwo: dl - mkdir -p reports-rwo - $(E2E) $(GINKGO_EXTRA_ARGS) \ - --ginkgo.fail-fast \ - --ginkgo.v \ - --ginkgo.timeout=12h \ - --ginkgo.focus='$(TEST_PREFIX)$(FOCUS)' \ - --ginkgo.skip='\[Disruptive\]|\[Serial\]' \ - --non-blocking-taints=node-role.kubernetes.io/control-plane,node-role.kubernetes.io/etcd,node-role.kubernetes.io/master \ - -storage.testdriver=test-driver-rwo.yaml \ - -report-dir=reports-rwo - -rwx: dl - mkdir -p reports-rwx - $(E2E) $(GINKGO_EXTRA_ARGS) \ - --ginkgo.fail-fast \ - --ginkgo.v \ - --ginkgo.timeout=12h \ - --ginkgo.focus='$(TEST_PREFIX)$(FOCUS)' \ - --ginkgo.skip='\[Feature:|\[Disruptive\]|\[Serial\]' \ - --ginkgo.skip='.phemeral' \ - --non-blocking-taints=node-role.kubernetes.io/control-plane,node-role.kubernetes.io/etcd,node-role.kubernetes.io/master \ - --ginkgo.timeout=8h \ - -storage.testdriver=test-driver-rwx.yaml \ - -report-dir=reports-rwx - -clean: - rm -rf e2e.test ginkgo reports-rwo reports-rwx diff --git a/e2e/README.md b/e2e/README.md new file mode 100644 index 0000000..0871dcc --- /dev/null +++ b/e2e/README.md @@ -0,0 +1,11 @@ +# Synopsis + +Performs CSI e2e tests. + +```text +kubectl apply -f resources.yaml +kubectl apply -k . +kubectl replace --force -f job-rwo.yaml +kubectl replace --force -f job-rwx.yaml +kubectl logs -n csi-e2e job/csi-e2e -f +``` diff --git a/e2e/job-rwo.yaml b/e2e/job-rwo.yaml new file mode 100644 index 0000000..f235f3a --- /dev/null +++ b/e2e/job-rwo.yaml @@ -0,0 +1,47 @@ +--- +kind: Job +apiVersion: batch/v1 +metadata: + name: csi-e2e + namespace: csi-e2e +spec: + backoffLimit: 0 + template: + spec: + restartPolicy: Never + containers: + - name: csi-e2e + image: quay.io/datamattsson/csi-e2e:v2.4.0 + command: + - runner.sh + args: + - --ginkgo.fail-fast + - --ginkgo.v + - --ginkgo.timeout=12h + - --ginkgo.focus=External.Storage.\[Driver:.csi.hpe.com\].* + - --ginkgo.skip=\[Disruptive\]|\[Serial\] + - --non-blocking-taints=node-role.kubernetes.io/control-plane,node-role.kubernetes.io/etcd,node-role.kubernetes.io/master + - -storage.testdriver=test-driver-rwo.yaml + - -report-dir=../report + env: + - name: ARG_DEBUG + value: "" + - name: FORCE_VERSION + value: "" + volumeMounts: + - name: tests + mountPath: /cut/csi-e2e/tests + volumes: + - name: tests + projected: + sources: + - configMap: + name: storage-class-rwx.yaml + - configMap: + name: storage-class-rwo.yaml + - configMap: + name: volume-snapshot-class.yaml + - configMap: + name: test-driver-rwx.yaml + - configMap: + name: test-driver-rwo.yaml diff --git a/e2e/job-rwx-block.yaml b/e2e/job-rwx-block.yaml new file mode 100644 index 0000000..8d9ff7a --- /dev/null +++ b/e2e/job-rwx-block.yaml @@ -0,0 +1,49 @@ +--- +kind: Job +apiVersion: batch/v1 +metadata: + name: csi-e2e + namespace: csi-e2e +spec: + backoffLimit: 0 + template: + spec: + restartPolicy: Never + containers: + - name: csi-e2e + image: quay.io/datamattsson/csi-e2e:v2.4.0 + command: + - runner.sh + args: + - --ginkgo.fail-fast + - --ginkgo.v + - --ginkgo.timeout=12h + - --ginkgo.focus=External.Storage.\[Driver:.csi.hpe.com\].*block.volmode.* + - --non-blocking-taints=node-role.kubernetes.io/control-plane,node-role.kubernetes.io/etcd,node-role.kubernetes.io/master + - -storage.testdriver=test-driver-rwx-block.yaml + - -report-dir=../report + - --ginkgo.skip=\[Feature:|\[Disruptive\]|\[Serial\]|.*two.volumes.with.different.volume.mode.* + env: + - name: ARG_DEBUG + value: "" + - name: FORCE_VERSION + value: "" + volumeMounts: + - name: tests + mountPath: /cut/csi-e2e/tests + volumes: + - name: tests + projected: + sources: + - configMap: + name: storage-class-rwx.yaml + - configMap: + name: storage-class-rwo.yaml + - configMap: + name: volume-snapshot-class.yaml + - configMap: + name: test-driver-rwx.yaml + - configMap: + name: test-driver-rwo.yaml + - configMap: + name: test-driver-rwx-block.yaml diff --git a/e2e/job-rwx.yaml b/e2e/job-rwx.yaml new file mode 100644 index 0000000..67b72c4 --- /dev/null +++ b/e2e/job-rwx.yaml @@ -0,0 +1,48 @@ +--- +kind: Job +apiVersion: batch/v1 +metadata: + name: csi-e2e + namespace: csi-e2e +spec: + backoffLimit: 0 + template: + spec: + restartPolicy: Never + containers: + - name: csi-e2e + image: quay.io/datamattsson/csi-e2e:v2.4.0 + command: + - runner.sh + args: + - --ginkgo.fail-fast + - --ginkgo.v + - --ginkgo.timeout=12h + - --ginkgo.focus=External.Storage.\[Driver:.csi.hpe.com\].* + - --ginkgo.skip=\[Disruptive\]|\[Serial\] + - --ginkgo.skip=.phemeral + - --non-blocking-taints=node-role.kubernetes.io/control-plane,node-role.kubernetes.io/etcd,node-role.kubernetes.io/master + - -storage.testdriver=test-driver-rwx.yaml + - -report-dir=../report + env: + - name: ARG_DEBUG + value: "" + - name: FORCE_VERSION + value: "" + volumeMounts: + - name: tests + mountPath: /cut/csi-e2e/tests + volumes: + - name: tests + projected: + sources: + - configMap: + name: storage-class-rwx.yaml + - configMap: + name: storage-class-rwo.yaml + - configMap: + name: volume-snapshot-class.yaml + - configMap: + name: test-driver-rwx.yaml + - configMap: + name: test-driver-rwo.yaml diff --git a/e2e/kustomization.yaml b/e2e/kustomization.yaml new file mode 100644 index 0000000..17c1940 --- /dev/null +++ b/e2e/kustomization.yaml @@ -0,0 +1,22 @@ +namespace: csi-e2e +configMapGenerator: +- name: storage-class-rwo.yaml + files: + - tests/storage-class-rwo.yaml +- name: storage-class-rwx.yaml + files: + - tests/storage-class-rwx.yaml +- name: volume-snapshot-class.yaml + files: + - tests/volume-snapshot-class.yaml +- name: test-driver-rwo.yaml + files: + - tests/test-driver-rwo.yaml +- name: test-driver-rwx.yaml + files: + - tests/test-driver-rwx.yaml +- name: test-driver-rwx-block.yaml + files: + - tests/test-driver-rwx-block.yaml +generatorOptions: + disableNameSuffixHash: true diff --git a/e2e/resources.yaml b/e2e/resources.yaml new file mode 100644 index 0000000..21e22f4 --- /dev/null +++ b/e2e/resources.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: csi-e2e +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: csi-e2e +subjects: +- kind: ServiceAccount + name: default # name of your service account + namespace: csi-e2e # this is the namespace your service account is in +roleRef: # referring to your ClusterRole + kind: ClusterRole + name: cluster-admin + apiGroup: rbac.authorization.k8s.io diff --git a/e2e/runner.sh b/e2e/runner.sh new file mode 100755 index 0000000..0ccd0b0 --- /dev/null +++ b/e2e/runner.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -xe + +if [[ ${ARG_DEBUG} ]]; then + echo command $@ + exit 0 +fi + +CUT_DIR=/cut/csi-e2e/tests +PATH=${PATH}:${CUT_DIR} +CUT_SYSTEM=$(uname -s | tr A-Z a-z) +CUT_ARCH=$(uname -m) + +if [[ "${FORCE_VERSION}" ]]; then + CUT_VERSION=${FORCE_VERSION} +else + CUT_VERSION=$(kubectl version -o json | jq -rM .serverVersion.gitVersion) +fi + +if [[ "${CUT_ARCH}" == x86_64 ]]; then + CUT_ARCH=amd64 +fi + +if [[ "${CUT_ARCH}" == aarch64 ]]; then + CUT_ARCH=arm64 +fi + +if ! [[ -f "e2e.test" ]] || ! [[ -f "ginkgo" ]]; then + curl --location \ + "https://dl.k8s.io/${CUT_VERSION}/kubernetes-test-${CUT_SYSTEM}-${CUT_ARCH}.tar.gz" | \ + tar --strip-components=3 -zxf - kubernetes/test/bin/e2e.test kubernetes/test/bin/ginkgo +fi + +cd tests +../e2e.test $@ diff --git a/e2e/secret-dist.yaml b/e2e/secret-dist.yaml new file mode 100644 index 0000000..6ebed0d --- /dev/null +++ b/e2e/secret-dist.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: truenas-secret + namespace: hpe-storage +stringData: + serviceName: truenas-csp-svc + servicePort: "8080" + username: hpe-csi (username is a no-op when using API key) + password: API key or root password of TrueNAS/FreeNAS appliance + backend: Management IP address of TrueNAS/FreeNAS appliance diff --git a/e2e/storage-class-rwo.yaml b/e2e/tests/storage-class-rwo.yaml similarity index 95% rename from e2e/storage-class-rwo.yaml rename to e2e/tests/storage-class-rwo.yaml index f392a26..92d028a 100644 --- a/e2e/storage-class-rwo.yaml +++ b/e2e/tests/storage-class-rwo.yaml @@ -15,8 +15,7 @@ parameters: csi.storage.k8s.io/node-stage-secret-namespace: hpe-storage csi.storage.k8s.io/provisioner-secret-name: truenas-secret csi.storage.k8s.io/provisioner-secret-namespace: hpe-storage - description: "Volume created by the HPE CSI Driver for Kubernetes" - accessProtocol: iscsi - root: tank + description: "Volume created by the HPE CSI Driver for Kubernetes from {namespace}" + root: tank/csi-e2e reclaimPolicy: Delete allowVolumeExpansion: true diff --git a/e2e/storage-class-rwx.yaml b/e2e/tests/storage-class-rwx.yaml similarity index 85% rename from e2e/storage-class-rwx.yaml rename to e2e/tests/storage-class-rwx.yaml index 8caa4d5..65c4f29 100644 --- a/e2e/storage-class-rwx.yaml +++ b/e2e/tests/storage-class-rwx.yaml @@ -4,7 +4,7 @@ metadata: name: e2e-standard-rwx provisioner: csi.hpe.com parameters: - csi.storage.k8s.io/fstype: xfs + csi.storage.k8s.io/fstype: ext4 csi.storage.k8s.io/controller-expand-secret-name: truenas-secret csi.storage.k8s.io/controller-expand-secret-namespace: hpe-storage csi.storage.k8s.io/controller-publish-secret-name: truenas-secret @@ -15,8 +15,9 @@ parameters: csi.storage.k8s.io/node-stage-secret-namespace: hpe-storage csi.storage.k8s.io/provisioner-secret-name: truenas-secret csi.storage.k8s.io/provisioner-secret-namespace: hpe-storage - description: "Volume created by the HPE CSI Driver for Kubernetes" - accessProtocol: iscsi + description: "Volume created by the HPE CSI Driver for Kubernetes from {namespace}" nfsResources: "true" - root: tank + nfsNamespace: csi.storage.k8s.io/pvc/namespace + root: tank/csi-e2e reclaimPolicy: Delete +allowVolumeExpansion: false diff --git a/e2e/test-driver-rwo.yaml b/e2e/tests/test-driver-rwo.yaml similarity index 75% rename from e2e/test-driver-rwo.yaml rename to e2e/tests/test-driver-rwo.yaml index ff3f700..a452189 100644 --- a/e2e/test-driver-rwo.yaml +++ b/e2e/tests/test-driver-rwo.yaml @@ -1,11 +1,15 @@ StorageClass: - FromFile: "truenas-csp/e2e/storage-class-rwo.yaml" + FromFile: "csi-e2e/tests/storage-class-rwo.yaml" SnapshotClass: FromFile: "./volume-snapshot-class.yaml" DriverInfo: Name: csi.hpe.com RequiredAccessModes: - ReadWriteOnce + # UNDER TEST + # TopologyKeys: + # - csi.hpe.com/zone + # NumAllowedTopologies: 1 Capabilities: persistence: true block: true @@ -16,7 +20,8 @@ DriverInfo: controllerExpansion: false nodeExpansion: true volumeLimits: false - topology: false + # UNDER TEST + # topology: true singleNodeVolume: false RWX: false pvcDataSource: true diff --git a/e2e/tests/test-driver-rwx-block.yaml b/e2e/tests/test-driver-rwx-block.yaml new file mode 100644 index 0000000..cd5a702 --- /dev/null +++ b/e2e/tests/test-driver-rwx-block.yaml @@ -0,0 +1,32 @@ +StorageClass: + FromFile: "csi-e2e/tests/storage-class-rwo.yaml" +SnapshotClass: + FromFile: "./volume-snapshot-class.yaml" +DriverInfo: + Name: csi.hpe.com + RequiredAccessModes: + - ReadWriteMany + Capabilities: + persistence: true + block: true + fsGroup: true + exec: true + snapshotDataSource: true + multipods: true + controllerExpansion: false + nodeExpansion: true + volumeLimits: false + topology: false + singleNodeVolume: false + RWX: false + pvcDataSource: true + FSResizeFromSourceNotSupported: true + readWriteOncePod: false + SupportedFsType: + ext4: {} + ext3: {} + xfs: {} + btrfs: {} + SupportedSizeRange: + Min: 1Gi + Max: 32Gi diff --git a/e2e/test-driver-rwx.yaml b/e2e/tests/test-driver-rwx.yaml similarity index 75% rename from e2e/test-driver-rwx.yaml rename to e2e/tests/test-driver-rwx.yaml index bbefb35..ce2d22c 100644 --- a/e2e/test-driver-rwx.yaml +++ b/e2e/tests/test-driver-rwx.yaml @@ -1,5 +1,7 @@ StorageClass: - FromFile: "truenas-csp/e2e/storage-class-rwx.yaml" + FromFile: "csi-e2e/tests/storage-class-rwx.yaml" +SnapshotClass: + FromFile: "./volume-snapshot-class.yaml" DriverInfo: Name: csi.hpe.com RequiredAccessModes: @@ -9,7 +11,7 @@ DriverInfo: block: false fsGroup: false exec: true - snapshotDataSource: false + snapshotDataSource: true multipods: true controllerExpansion: false nodeExpansion: false @@ -21,5 +23,5 @@ DriverInfo: FSResizeFromSourceNotSupported: true readWriteOncePod: false SupportedSizeRange: - Min: 1Gi + Min: 4Gi Max: 32Gi diff --git a/e2e/volume-snapshot-class.yaml b/e2e/tests/volume-snapshot-class.yaml similarity index 100% rename from e2e/volume-snapshot-class.yaml rename to e2e/tests/volume-snapshot-class.yaml diff --git a/helm/charts/Makefile b/helm/charts/Makefile index 92127cc..1003989 100755 --- a/helm/charts/Makefile +++ b/helm/charts/Makefile @@ -1,5 +1,6 @@ CHART_TARGET:=../../docs +.PHONY: truenas-csp all: truenas-csp truenas-csp: helm dependency update $@ diff --git a/helm/charts/truenas-csp/Chart.lock b/helm/charts/truenas-csp/Chart.lock index 4a40f83..0b6ce5e 100644 --- a/helm/charts/truenas-csp/Chart.lock +++ b/helm/charts/truenas-csp/Chart.lock @@ -1,6 +1,6 @@ dependencies: - name: hpe-csi-driver repository: https://hpe-storage.github.io/co-deployments - version: 2.4.0 -digest: sha256:bdd21bdd2c3c36bc45fb521b0e762b9272fa86348d914dde185d4b0438b0784a -generated: "2023-10-10T00:04:31.749716697Z" + version: 2.4.2 +digest: sha256:2843d9f603e7c46f931c3cbf033ad80d5df6c1a18b94e90a70237c4e79241c52 +generated: "2024-05-05T17:18:52.756541473Z" diff --git a/helm/charts/truenas-csp/Chart.yaml b/helm/charts/truenas-csp/Chart.yaml index 3c17277..1500a6b 100644 --- a/helm/charts/truenas-csp/Chart.yaml +++ b/helm/charts/truenas-csp/Chart.yaml @@ -12,8 +12,8 @@ annotations: - name: Install url: https://github.com/hpe-storage/truenas-csp/blob/master/INSTALL.md artifacthub.io/prerelease: "false" -version: "1.1.5" -appVersion: "2.4.0" +version: "1.1.6" +appVersion: "2.4.2" maintainers: - name: Michael Mattsson email: michael.mattsson@gmail.com @@ -22,7 +22,7 @@ sources: home: https://github.com/hpe-storage/truenas-csp dependencies: - name: hpe-csi-driver - version: 2.4.0 + version: 2.4.2 repository: "https://hpe-storage.github.io/co-deployments" keywords: - HPE diff --git a/helm/charts/truenas-csp/charts/hpe-csi-driver-2.4.0.tgz b/helm/charts/truenas-csp/charts/hpe-csi-driver-2.4.0.tgz deleted file mode 100644 index 8076dab..0000000 Binary files a/helm/charts/truenas-csp/charts/hpe-csi-driver-2.4.0.tgz and /dev/null differ diff --git a/helm/charts/truenas-csp/charts/hpe-csi-driver-2.4.2.tgz b/helm/charts/truenas-csp/charts/hpe-csi-driver-2.4.2.tgz new file mode 100644 index 0000000..8b31c4b Binary files /dev/null and b/helm/charts/truenas-csp/charts/hpe-csi-driver-2.4.2.tgz differ diff --git a/helm/charts/truenas-csp/values.schema.json b/helm/charts/truenas-csp/values.schema.json index a7eb089..b3f8447 100644 --- a/helm/charts/truenas-csp/values.schema.json +++ b/helm/charts/truenas-csp/values.schema.json @@ -337,11 +337,21 @@ "resources": { "$id": "#/properties/resources", "type": "object", - "title": "The resources schema", - "description": "An explanation about the purpose of this instance.", - "default": {}, - "required": [], - "additionalProperties": false + "title": "resource requests and limits", + "additionalProperties": false, + "required": ["limits", "requests"], + "description": "See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/", + "properties": { + "limits": { + "type": "object", + "title": "resource limits", + "default": {} + }, + "requests": { + "type": "object", + "title": "resource requests", + "default": {} } + } }, "nodeSelector": { "$id": "#/properties/nodeSelector", @@ -392,7 +402,8 @@ "nimble", "primera", "alletra6000", - "alletra9000" + "alletra9000", + "alletraStorageMP" ], "properties": { "nimble": { @@ -422,6 +433,13 @@ "title": "The alletra9000 schema", "description": "An explanation about the purpose of this instance.", "default": false + }, + "alletraStorageMP": { + "$id": "#/properties/hpe-csi-driver/properties/disable/properties/alletraStorageMP", + "type": "boolean", + "title": "The alletraStorageMP schema", + "description": "An explanation about the purpose of this instance.", + "default": false } }, "additionalProperties": false diff --git a/helm/charts/truenas-csp/values.yaml b/helm/charts/truenas-csp/values.yaml index 4b12cf1..7c78396 100644 --- a/helm/charts/truenas-csp/values.yaml +++ b/helm/charts/truenas-csp/values.yaml @@ -11,7 +11,7 @@ image: repository: quay.io/datamattsson/truenas-csp pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. - tag: "v2.4.0" + tag: "v2.4.2" imagePullSecrets: [] nameOverride: "" @@ -59,17 +59,13 @@ ingress: # hosts: # - chart-example.local -resources: {} - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi +resources: + limits: + cpu: 1000m + memory: 1Gi + requests: + cpu: 100m + memory: 256Mi nodeSelector: {} @@ -92,3 +88,4 @@ hpe-csi-driver: primera: true alletra6000: true alletra9000: true + alletraStorageMP: true diff --git a/tests/csp/initiator-multi.yaml b/tests/csp/initiator-multi.yaml new file mode 100644 index 0000000..24db580 --- /dev/null +++ b/tests/csp/initiator-multi.yaml @@ -0,0 +1,13 @@ +{ + "name": "host-02.lab.internet.com", + "uuid": "41302701-0196-420f-b319-834a79891db1", + "iqns": [ + "iqn.2019-07.com.nimblestorage:awe-iqn", + "iqn.2019-06.com.cloudvolumes:awesome-iqn" + ], + "networks": [ + "172.28.12.162/20", + "10.234.63.106/20" + ], + "wwpns": [] +} diff --git a/tests/csp/publish-multi.yaml b/tests/csp/publish-multi.yaml new file mode 100644 index 0000000..d3fdf69 --- /dev/null +++ b/tests/csp/publish-multi.yaml @@ -0,0 +1,4 @@ +{ + "host_uuid": "41302701-0196-420f-b319-834a79891db1", + "access_protocol": "iscsi" +} diff --git a/tests/csp/unpublish-multi.yaml b/tests/csp/unpublish-multi.yaml new file mode 100644 index 0000000..c7b323e --- /dev/null +++ b/tests/csp/unpublish-multi.yaml @@ -0,0 +1,3 @@ +{ + "host_uuid": "41302701-0196-420f-b319-834a79891db1" +} diff --git a/tests/csp/volume-thick.yaml b/tests/csp/volume-thick.yaml new file mode 100644 index 0000000..5dcb8d8 --- /dev/null +++ b/tests/csp/volume-thick.yaml @@ -0,0 +1,15 @@ +{ + "name": "my-new-volume18", + "size": "1073741824", + "description": "my thick volume {pvc}", + "config": { + "csi.storage.k8s.io/pvc/name": "my-thick-volume", + "compression": "GZIP-1", + "deduplication": "OFF", + "sparse": "false", + "volblocksize": "16K", + "sync": "ALWAYS", + "zpool": "tank" + + } +} diff --git a/tests/csp/volume.yaml b/tests/csp/volume.yaml index 8eafa22..594f165 100644 --- a/tests/csp/volume.yaml +++ b/tests/csp/volume.yaml @@ -1,11 +1,12 @@ { "name": "my-new-volume16", "size": "1073741824", - "description": "my first volume", + "description": "my first volume {pvc}", "config": { + "csi.storage.k8s.io/pvc/name": "my-first-volume", "compression": "GZIP-1", "deduplication": "OFF", - "sparse": true, + "sparse": "true", "volblocksize": "16K", "sync": "ALWAYS", "zpool": "tank" diff --git a/truenascsp/backend.py b/truenascsp/backend.py index d0f6313..7281ddc 100755 --- a/truenascsp/backend.py +++ b/truenascsp/backend.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -# (C) Copyright 2020 Hewlett Packard Enterprise Development LP. +# (C) Copyright 2024 Hewlett Packard Enterprise Development LP. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -51,7 +51,7 @@ def __init__(self): self.resp_msg = '100 Continue' self.target_basenames = [ 'iqn.2011-08.org.truenas.ctl', 'iqn.2005-10.org.freenas.ctl' ] self.target_portal = 'hpe-csi' - self.backend_retries = 30 + self.backend_retries = 15 self.backend_delay = 1.5 self.access_name = '{dataset_name}' @@ -63,10 +63,10 @@ def __init__(self): 'deduplication': environ.get('DEFAULT_DEDUPLICATION', 'OFF'), 'compression': environ.get('DEFAULT_COMPRESSION', 'LZ4'), 'sync': environ.get('DEFAULT_SYNC', 'STANDARD'), - 'sparse': environ.get('DEFAULT_SPARSE', 1), + 'sparse': environ.get('DEFAULT_SPARSE', "true"), 'root': environ.get('DEFAULT_ROOT', 'tank'), 'volblocksize': environ.get('DEFAULT_VOLBLOCKSIZE', '8K'), - 'description': environ.get('DEFAULT_DESCRIPTION', 'Dataset created by HPE CSI Driver for Kubernetes') + 'description': environ.get('DEFAULT_DESCRIPTION', 'Dataset created by HPE CSI Driver for Kubernetes as {pv} in {namespace} from {pvc}') } self.dataset_mutables = [ @@ -270,7 +270,9 @@ def fetch(self, resource, **kwargs): else: return results - return None + self.logger.debug('API fetch caught %d items', len(results)) + + return [] def uri_id(self, resource, rid): if resource in ('zfs/snapshot', 'pool/dataset'): @@ -343,16 +345,18 @@ def delete(self, uri, **kwargs): try: self.logger.debug('TrueNAS DELETE request URI: %s', uri) body = kwargs.get('body') if kwargs.get('body') else None + self.logger.debug('Embedding content in DELETE body: %s', body) if type(auth) == HTTPBasicAuth: self.req_backend = requests.delete(self.url_tmpl(uri), - json=body, auth=auth, headers=headers, verify=False) + data=body, auth=auth, headers=headers, verify=False) else: auth.update(headers) self.req_backend = requests.delete(self.url_tmpl(uri), - json=kwargs.get('body'), headers=auth, verify=False) + data=body, headers=auth, verify=False) self.resp_msg = '{code} {reason}'.format( code=str(self.req_backend.status_code), reason=self.req_backend.reason) - self.logger.debug('TrueNAS response: %s', self.req_backend.status_code) + self.logger.debug('TrueNAS response code: %s', self.req_backend.status_code) + self.logger.debug('TrueNAS response msg: %s', self.req_backend.content.decode('utf-8')) self.req_backend.raise_for_status() except Exception: self.csp_error('Backend Request (DELETE) Exception', diff --git a/truenascsp/csp.py b/truenascsp/csp.py index e2db762..6db9e4f 100755 --- a/truenascsp/csp.py +++ b/truenascsp/csp.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -# (C) Copyright 2020 Hewlett Packard Enterprise Development LP. +# (C) Copyright 2024 Hewlett Packard Enterprise Development LP. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/truenascsp/truenascsp.py b/truenascsp/truenascsp.py index e06600d..226cec4 100755 --- a/truenascsp/truenascsp.py +++ b/truenascsp/truenascsp.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -# (C) Copyright 2020 Hewlett Packard Enterprise Development LP. +# (C) Copyright 2024 Hewlett Packard Enterprise Development LP. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -32,7 +32,9 @@ class Unpublish: def on_put(self, req, resp, volume_id): + api = req.context + content = req.media try: dataset_name = api.xslt_volume_id_to_name(volume_id) @@ -42,59 +44,96 @@ def on_put(self, req, resp, volume_id): target = api.fetch('iscsi/target', field='name', value=access_name) + api.logger.debug('Target being unpublished: %s', target) + + # get initiator from uuid + initiator = api.fetch( + 'iscsi/initiator', field='comment', value=content.get('host_uuid'), returnBy=dict) + + api.logger.debug('Initiator requested to be unpublished: %s', initiator) + # FIXME: Only Unpublish the host being requested and # delete target, target/extent and extent if # groups = [] if target: - api.delete( - 'iscsi/target/id/{tid}'.format(tid=str(target.get('id')))) - target_deletion = api.backend_retries + initiators = target.get('groups') - while api.fetch('iscsi/target', field='name', - value=access_name) and target_deletion: - sleep(api.backend_delay) - target_deletion -= 1 - api.delete( - 'iscsi/target/id/{tid}'.format(tid=str(target.get('id')))) - api.logger.debug('Target deletion retried: %s', volume_id) + # Remove ID from groups, if empty, delete the rest. + for initiator_removal in initiators: + if initiator_removal['initiator'] == initiator.get('id'): + initiators.remove(initiator_removal) + break - # get target to extent mapping - mapping = api.fetch('iscsi/targetextent', - field='target', value=str(target.get('id'))) + api.logger.debug('Initiators left intact: %s', initiators) - if mapping: + if not initiators: api.delete( - 'iscsi/targetextent/id/{teid}'.format(teid=str(mapping.get('id')))) + 'iscsi/target/id/{tid}'.format(tid=str(target.get('id')))) - targetextent_deletion = api.backend_retries + target_deletion = api.backend_retries - while api.fetch('iscsi/targetextent', - field='target', value=str(target.get('id'))) and targetextent_deletion: + while api.fetch('iscsi/target', field='name', + value=access_name) and target_deletion: sleep(api.backend_delay) - targetextent_deletion -= 1 + target_deletion -= 1 + api.delete( + 'iscsi/target/id/{tid}'.format(tid=str(target.get('id')))) + api.logger.debug('Target deletion retried: %s', volume_id) + + # Force deletion + if not target_deletion: + api.delete( + 'iscsi/target/id/{tid}'.format(tid=str(target.get('id'))), body='true') + + # get target to extent mapping + mapping = api.fetch('iscsi/targetextent', + field='target', value=str(target.get('id'))) + + if mapping: api.delete( 'iscsi/targetextent/id/{teid}'.format(teid=str(mapping.get('id')))) - api.logger.debug('Target/extent deletion retried: %s', volume_id) - # get extent - extent = api.fetch('iscsi/extent', field='name', - value=access_name) + targetextent_deletion = api.backend_retries - if extent: - api.delete( - 'iscsi/extent/id/{eid}'.format(eid=str(extent.get('id')))) + while api.fetch('iscsi/targetextent', + field='target', value=str(target.get('id'))) and targetextent_deletion: + sleep(api.backend_delay) + targetextent_deletion -= 1 + api.delete( + 'iscsi/targetextent/id/{teid}'.format(teid=str(mapping.get('id')))) + api.logger.debug('Target/extent deletion retried: %s', volume_id) - extent_deletion = api.backend_retries + # Force deletion + if not targetextent_deletion: + api.delete( + 'iscsi/targetextent/id/{teid}'.format(teid=str(mapping.get('id'))), body='true') - while api.fetch('iscsi/extent', field='name', - value=access_name) and extent_deletion: - sleep(api.backend_delay) - extent_deletion -= 1 + # get extent + extent = api.fetch('iscsi/extent', field='name', + value=access_name) + + if extent: api.delete( 'iscsi/extent/id/{eid}'.format(eid=str(extent.get('id')))) - api.logger.debug('Extent deletion retried: %s', volume_id) + + extent_deletion = api.backend_retries + + while api.fetch('iscsi/extent', field='name', + value=access_name) and extent_deletion: + sleep(api.backend_delay) + extent_deletion -= 1 + api.delete( + 'iscsi/extent/id/{eid}'.format(eid=str(extent.get('id')))) + api.logger.debug('Extent deletion retried: %s', volume_id) + + if not extent_deletion: + api.delete( + 'iscsi/extent/id/{eid}'.format(eid=str(extent.get('id'))), body='{"force":true, "remove":true}') + else: + # Replace group list + api.logger.debug('Replacing group list on target: %s', 'placeholder') resp.status = falcon.HTTP_204 api.logger.info('Volume unpublished: %s', volume_id) @@ -177,14 +216,17 @@ def on_put(self, req, resp, volume_id): target_id = api.req_backend.json() api.logger.debug('Target updated: %s', target_id.get('name')) - # extent needs to be part of response to CSI driver - extent = api.fetch('iscsi/extent', field='name', - value=access_name) else: api.post('iscsi/target', req_backend) target = api.req_backend.json() + # extent needs to be part of response to CSI driver + extent = api.fetch('iscsi/extent', field='name', + value=access_name) + + # If extent is empty, try create a new extent + if not extent: # add extent to dataset req_backend = { 'type': 'DISK', @@ -320,7 +362,7 @@ def on_delete(self, req, resp, volume_id): # FIXME: Deal with snapshots api.delete(api.uri_id('pool/dataset', - dataset.get('name')), body={'recursive': True}) + dataset.get('name')), body='{"recursive": true, "force": true}') # things might be pending dataset_deletion = api.backend_retries @@ -330,7 +372,7 @@ def on_delete(self, req, resp, volume_id): dataset_deletion -= 1 sleep(api.backend_delay) api.delete(api.uri_id('pool/dataset', - dataset.get('name')), body={'recursive': True}) + dataset.get('name')), body='{"recursive": true, "force": true}') api.logger.info('Dataset deletion retried: %s', volume_id) resp.status = api.resp_msg @@ -391,11 +433,15 @@ def on_post(self, req, resp): req_backend = { 'type': 'VOLUME', # FIXME - 'comments': content.get('description', api.dataset_defaults.get('description')), + 'comments': content.get('description', api.dataset_defaults.get('description')).format( + pvc=content.get('config').get('csi.storage.k8s.io/pvc/name', 'pvc'), + namespace=content.get('config').get('csi.storage.k8s.io/pvc/namespace', 'namespace'), + pv=content.get('config').get('csi.storage.k8s.io/pv/name', 'pv') + ), 'name': '{root}/{volume_name}'.format(volume_name=content.get('name'), root=root), 'volsize': '{size}'.format(size=int(content.get('size'))), 'volblocksize': content.get('config').get('volblocksize', api.dataset_defaults.get('volblocksize')), - 'sparse': bool(content.get('config').get('sparse', api.dataset_defaults.get('sparse'))), + 'sparse': json.loads(content.get('config').get('sparse', api.dataset_defaults.get('sparse')).lower()), 'deduplication': content.get('config').get('deduplication', api.dataset_defaults.get('deduplication')), 'sync': content.get('config').get('sync', api.dataset_defaults.get('sync')), 'compression': content.get('config').get('compression', api.dataset_defaults.get('compression'))