diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 63d3559..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,48 +0,0 @@ -# Please see the documentation for all configuration options: -# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -version: 2 -updates: - - package-ecosystem: github-actions - directory: / - labels: - - dependencies - - actions - schedule: - interval: weekly - day: sunday - - package-ecosystem: docker - directory: /cmd/cloudstack-csi-sc-syncer - labels: - - dependencies - - docker - schedule: - interval: weekly - day: sunday - - package-ecosystem: docker - directory: /cmd/cloudstack-csi-driver - labels: - - dependencies - - docker - schedule: - interval: weekly - day: sunday - - package-ecosystem: gomod - directory: / - labels: - - dependencies - - go - schedule: - interval: weekly - day: sunday - ignore: - - dependency-name: "k8s.io/api" - update-types: ["version-update:semver-minor"] - - dependency-name: "k8s.io/apimachinery" - update-types: ["version-update:semver-minor"] - - dependency-name: "k8s.io/client-go" - update-types: ["version-update:semver-minor"] - - dependency-name: "k8s.io/mount-utils" - update-types: ["version-update:semver-minor"] - - dependency-name: "github.com/container-storage-interface/spec" - update-types: ["version-update:semver-minor"] diff --git a/.github/workflows/pr-check.yaml b/.github/workflows/pr-check.yaml deleted file mode 100644 index 62f05f3..0000000 --- a/.github/workflows/pr-check.yaml +++ /dev/null @@ -1,45 +0,0 @@ -name: PR Check - -on: - pull_request: {} - -jobs: - lint: - name: Lint - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v4 - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 - with: - version: v1.53.2 - args: --timeout=5m - - build: - name: Test & Build - runs-on: ubuntu-20.04 - steps: - - name: Setup up Go 1.x - uses: actions/setup-go@v5 - with: - go-version: "^1.15" - - - name: Check out code - uses: actions/checkout@v4 - - - name: Cache - uses: actions/cache@v3 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Run unit tests - run: make test - - - name: Run sanity tests - run: make test-sanity - - - name: Build - run: make diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index e0a3670..0000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,131 +0,0 @@ -name: Release - -on: - push: - branches: - - master - tags: - - v* - -env: - REGISTRY_NAME: ghcr.io/leaseweb - IMAGES: "cloudstack-csi-driver cloudstack-csi-sc-syncer" - -jobs: - push: - name: Push images - runs-on: ubuntu-20.04 - - steps: - - name: Check out code - uses: actions/checkout@v4 - - - name: Cache - uses: actions/cache@v3 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Build container images - run: make container - - - name: Log into registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{github.actor}} - password: ${{secrets.GITHUB_TOKEN}} - - - name: Push master - if: github.ref == 'refs/heads/master' - run: | - for img in $IMAGES; do - docker tag ${img} ${REGISTRY_NAME}/${img}:master - docker push ${REGISTRY_NAME}/${img}:master - done - - - name: Push tagged release - if: startsWith(github.ref, 'refs/tags/v') - run: | - # Strip prefix from version - VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,' | sed -e 's/^v//') - - for img in $IMAGES; do - docker tag ${img} ${REGISTRY_NAME}/${img}:${VERSION} - docker push ${REGISTRY_NAME}/${img}:${VERSION} - done - - - name: Upload cloudstack-csi-sc-syncer artifact - if: startsWith(github.ref, 'refs/tags/v') - uses: actions/upload-artifact@v4 - with: - name: bin - path: bin/cloudstack-csi-sc-syncer - retention-days: 1 - - release: - name: Release - runs-on: ubuntu-20.04 - - # Run only if previous job has succeeded - needs: [push] - - # Create a release only for tags v* - if: startsWith(github.ref, 'refs/tags/v') - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Create manifest - run: | - VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,' | sed -e 's/^v//') - echo "---" >> manifest.yaml - cat deploy/k8s/rbac.yaml >> manifest.yaml - echo "---" >> manifest.yaml - cat deploy/k8s/csidriver.yaml >> manifest.yaml - echo "---" >> manifest.yaml - sed -E "s|image: +cloudstack-csi-driver|image: ${REGISTRY_NAME}/cloudstack-csi-driver:${VERSION}|" deploy/k8s/controller-deployment.yaml >> manifest.yaml - echo "---" >> manifest.yaml - sed -E "s|image: +cloudstack-csi-driver|image: ${REGISTRY_NAME}/cloudstack-csi-driver:${VERSION}|" deploy/k8s/node-daemonset.yaml >> manifest.yaml - - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: ${{ github.ref }} - draft: false - prerelease: false - - - name: Upload Release Asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: manifest.yaml - asset_name: manifest.yaml - asset_content_type: application/x-yaml - - - name: Download cloudstack-csi-sc-syncer artifact - uses: actions/download-artifact@v4 - with: - name: bin - path: bin - - - run: ls -l - - - name: Upload cloudstack-csi-sc-syncer asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: bin/cloudstack-csi-sc-syncer - asset_name: cloudstack-csi-sc-syncer - asset_content_type: application/x-executable diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 295e96b..0000000 --- a/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/bin -/test/e2e/e2e.test -/test/e2e/ginkgo -cloud-config diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index b1b5248..0000000 --- a/.golangci.yml +++ /dev/null @@ -1,34 +0,0 @@ -issues: - exclude-use-default: true - max-issues-per-linter: 50 - max-same-issues: 0 # disable - -linters-settings: - - staticcheck: - go: "1.15" - checks: [ "all" ] - - stylecheck: - go: "1.15" - checks: [ "all" ] - - goimports: - local-prefixes: github.com/apalia/cloudstack-csi-driver - - misspell: - locale: US - -linters: - disable-all: true - enable: - - errcheck - - gosimple - - govet - - ineffassign - - staticcheck - - stylecheck - - goimports - - typecheck - - unused - - misspell diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 261eeb9..0000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/Makefile b/Makefile deleted file mode 100644 index 6ffb13a..0000000 --- a/Makefile +++ /dev/null @@ -1,58 +0,0 @@ -CMDS=cloudstack-csi-driver cloudstack-csi-sc-syncer - -# Revision that gets built into each binary via the main.version -# string. Uses the `git describe` output based on the most recent -# version tag with a short revision suffix or, if nothing has been -# tagged yet, just the revision. -# -# Beware that tags may also be missing in shallow clones as done by -# some CI systems (like TravisCI, which pulls only 50 commits). -REV=$(shell git describe --long --tags --match='v*' --dirty 2>/dev/null || git rev-list -n1 HEAD) - -DOCKER?=docker - -IMPORTPATH_LDFLAGS = -X main.version=$(REV) -LDFLAGS = -s -w -FULL_LDFLAGS = $(LDFLAGS) $(IMPORTPATH_LDFLAGS) - -.PHONY: all -all: build - -.PHONY: build -build: $(CMDS:%=build-%) - -.PHONY: container -container: $(CMDS:%=container-%) - -.PHONY: clean -clean: - rm -rf bin test/e2e/e2e.test test/e2e/ginkgo - -.PHONY: build-% -$(CMDS:%=build-%): build-%: - mkdir -p bin - CGO_ENABLED=0 go build -ldflags '$(FULL_LDFLAGS)' -o "./bin/$*" ./cmd/$* - -.PHONY: container-% -$(CMDS:%=container-%): container-%: build-% - $(DOCKER) build -f ./cmd/$*/Dockerfile -t $*:latest \ - --label org.opencontainers.image.revision=$(REV) . - -.PHONY: test -test: - go test ./... - -.PHONY: test-sanity -test-sanity: - go test --tags=sanity ./test/sanity - -.PHONY: setup-external-e2e -setup-external-e2e: test/e2e/e2e.test test/e2e/ginkgo - -test/e2e/e2e.test test/e2e/ginkgo: - curl --location https://dl.k8s.io/v1.27.5/kubernetes-test-linux-amd64.tar.gz | \ - tar --strip-components=3 -C test/e2e -zxf - kubernetes/test/bin/e2e.test kubernetes/test/bin/ginkgo - -.PHONY: test-e2e -test-e2e: setup-external-e2e - bash ./test/e2e/run.sh diff --git a/README.md b/README.md index abfb2e0..cc4569c 100644 --- a/README.md +++ b/README.md @@ -1,129 +1,23 @@ -# CloudStack CSI Driver +# Leaseweb Cloudstck CSI Hem Charts + [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ![Release Charts](https://github.com/Leaseweb/cloudstack-csi-driver/actions/workflows/charts-release.yaml/badge.svg?branch=add-csi-charts) [![Releases downloads](https://img.shields.io/github/downloads/Leaseweb/cloudstack-csi-driver/total.svg)](https://github.com/Leaseweb/cloudstack-csi-driver/releases) -[![Go Reference](https://pkg.go.dev/badge/github.com/leaseweb/cloudstack-csi-driver.svg)](https://pkg.go.dev/github.com/leaseweb/cloudstack-csi-driver) -[![Go Report Card](https://goreportcard.com/badge/github.com/leaseweb/cloudstack-csi-driver)](https://goreportcard.com/report/github.com/leaseweb/cloudstack-csi-driver) -[![Release](https://github.com/leaseweb/cloudstack-csi-driver/workflows/Release/badge.svg?branch=master)](https://github.com/leaseweb/cloudstack-csi-driver/actions) -This repository provides a [Container Storage Interface (CSI)](https://github.com/container-storage-interface/spec) -plugin for [Apache CloudStack](https://cloudstack.apache.org/). +This functionality is in beta and is subject to change. The code is provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features. -## Usage with Kubernetes +## Usage -### Requirements +[Helm](https://helm.sh) must be installed to use the charts. +Please refer to Helm's [documentation](https://helm.sh/docs/) to get started. -- Minimal Kubernetes version: v1.25 +Once Helm is set up properly, add the repository as follows: -- The Kubernetes cluster must run in CloudStack. Tested only in a KVM zone. - -- A disk offering with custom size must be available, with type "shared". - -- In order to match the Kubernetes node and the CloudStack instance, - they should both have the same name. If not, it is also possible to use - [cloud-init instance metadata](https://cloudinit.readthedocs.io/en/latest/topics/instancedata.html) - to get the instance name: if the node has cloud-init enabled, metadata will - be available in `/run/cloud-init/instance-data.json`; you should then make - sure that `/run/cloud-init/` is mounted from the node. - -- Kubernetes nodes must be in the Root domain, and be created by the CloudStack - account whose credentials are used in [configuration](#configuration). - -### Configuration - -Create the CloudStack configuration file `cloud-config`. - -It should have the following format, defined for the [CloudStack Kubernetes Provider](https://github.com/apache/cloudstack-kubernetes-provider): - -```ini -[Global] -api-url = -api-key = -secret-key = -ssl-no-verify = -``` - -Create a secret named `cloudstack-secret` in namespace `kube-system`: - -``` -kubectl create secret generic \ - --namespace kube-system \ - --from-file ./cloud-config \ - cloudstack-secret -``` - -If you have also deployed the [CloudStack Kubernetes Provider](https://github.com/apache/cloudstack-kubernetes-provider), -you may use the same secret for both tools. - -### Deployment - -``` -kubectl apply -f https://github.com/leaseweb/cloudstack-csi-driver/releases/latest/download/manifest.yaml -``` - -### Creation of Storage classes - -#### Manually - -A storage class can be created manually: see [example](./examples/k8s/0-storageclass.yaml). - -The `provisioner` value must be `csi.cloudstack.apache.org`. - -The `volumeBindingMode` must be `WaitForFirstConsumer`, in order to delay the -binding and provisioning of a PersistentVolume until a Pod using the -PersistentVolumeClaim is created. It enables the provisioning of volumes -in respect to topology constraints (e.g. volume in the right zone). - -The storage class must also have a parameter named -`csi.cloudstack.apache.org/disk-offering-id` whose value is the CloudStack disk -offering ID. - -#### Using cloudstack-csi-sc-syncer - -The tool `cloudstack-csi-sc-syncer` may also be used to synchronize CloudStack -disk offerings to Kubernetes storage classes. - -[More info...](./cmd/cloudstack-csi-sc-syncer/README.md) - -### Usage - -Example: - -``` -kubectl apply -f ./examples/k8s/pvc.yaml -kubectl apply -f ./examples/k8s/pod.yaml -``` - -## Building - -To build the driver binary: - -``` -make build-cloudstack-csi-driver -``` - -To build the container images: - -``` -make container +```console +helm repo add cloudstack-csi https://leaseweb.github.io/cloudstack-csi-driver ``` +## License + +[Apache 2.0 License](https://github.com/Leaseweb/cloudstack-csi-driver/blob/master/LICENSE). -## See also - -- [CloudStack Kubernetes Provider](https://github.com/apache/cloudstack-kubernetes-provider) - Kubernetes Cloud Controller Manager for Apache CloudStack -- [CloudStack documentation on storage](http://docs.cloudstack.apache.org/en/latest/adminguide/storage.html) -- [CSI (Container Storage Interface) specification](https://github.com/container-storage-interface/spec) - ---- - - Copyright 2021 Apalia SAS - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 +## Helm charts build status - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +![Release Charts](https://github.com/Leaseweb/cloudstack-csi-driver/actions/workflows/release-charts.yml/badge.svg?branch=add-csi-charts) \ No newline at end of file diff --git a/_config.yaml b/_config.yaml new file mode 100644 index 0000000..c65fced --- /dev/null +++ b/_config.yaml @@ -0,0 +1,3 @@ +theme: jekyll-theme-cayman +title: 'Helm Charts Repository using GitHub Pages' +subtitle: 'Instructions and working demo of how to publish a Helm charts repository using GitHub pages' \ No newline at end of file diff --git a/cmd/cloudstack-csi-driver/Dockerfile b/cmd/cloudstack-csi-driver/Dockerfile deleted file mode 100644 index 5d10a2b..0000000 --- a/cmd/cloudstack-csi-driver/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM alpine:3.18 - -LABEL \ - org.opencontainers.image.description="CloudStack CSI driver" \ - org.opencontainers.image.source="https://github.com/leaseweb/cloudstack-csi-driver/" - -RUN apk add --no-cache \ - ca-certificates \ - # Provides mkfs.ext2, mkfs.ext3, mkfs.ext4 (used by k8s.io/mount-utils) - e2fsprogs \ - e2fsprogs-extra \ - # Provides mkfs.xfs - xfsprogs \ - # Provides blkid, also used by k8s.io/mount-utils - blkid - -COPY ./bin/cloudstack-csi-driver /cloudstack-csi-driver -ENTRYPOINT ["/cloudstack-csi-driver"] \ No newline at end of file diff --git a/cmd/cloudstack-csi-driver/main.go b/cmd/cloudstack-csi-driver/main.go deleted file mode 100644 index 61fdf1c..0000000 --- a/cmd/cloudstack-csi-driver/main.go +++ /dev/null @@ -1,85 +0,0 @@ -// cloudstack-csi-driver binary. -// -// To get usage information: -// -// cloudstack-csi-driver -h -package main - -import ( - "flag" - "fmt" - "os" - "path" - - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - - "github.com/leaseweb/cloudstack-csi-driver/pkg/cloud" - "github.com/leaseweb/cloudstack-csi-driver/pkg/driver" -) - -var ( - endpoint = flag.String("endpoint", "unix:///tmp/csi.sock", "CSI endpoint") - cloudstackconfig = flag.String("cloudstackconfig", "./cloud-config", "CloudStack configuration file") - nodeName = flag.String("nodeName", "", "Node name") - debug = flag.Bool("debug", false, "Enable debug logging") - showVersion = flag.Bool("version", false, "Show version") - - // Version is set by the build process - version = "" - isDevEnv = false -) - -func main() { - flag.Parse() - - if *showVersion { - baseName := path.Base(os.Args[0]) - fmt.Println(baseName, version) - return - } - - if version == "" { - isDevEnv = true - } - - run() - os.Exit(0) -} - -func run() { - // Setup logging - var logConfig zap.Config - if isDevEnv { - logConfig = zap.NewDevelopmentConfig() - } else { - logConfig = zap.NewProductionConfig() - } - if *debug { - logConfig.Level.SetLevel(zapcore.DebugLevel) - } - logger, _ := logConfig.Build() - defer func() { _ = logger.Sync() }() - undo := zap.ReplaceGlobals(logger) - defer undo() - - // Setup cloud connector - config, err := cloud.ReadConfig(*cloudstackconfig) - if err != nil { - logger.Sugar().Errorw("Cannot read CloudStack configuration", "error", err) - os.Exit(1) - } - logger.Sugar().Debugf("Successfully read CloudStack configuration %v", *cloudstackconfig) - csConnector := cloud.New(config) - - d, err := driver.New(*endpoint, csConnector, nil, *nodeName, version, logger) - if err != nil { - logger.Sugar().Errorw("Failed to initialize driver", "error", err) - os.Exit(1) - } - - if err = d.Run(); err != nil { - logger.Sugar().Errorw("Server error", "error", err) - os.Exit(1) - } -} diff --git a/cmd/cloudstack-csi-sc-syncer/Dockerfile b/cmd/cloudstack-csi-sc-syncer/Dockerfile deleted file mode 100644 index 39ff16f..0000000 --- a/cmd/cloudstack-csi-sc-syncer/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM alpine:3.18 - -LABEL \ - org.opencontainers.image.description="CloudStack disk offering to Kubernetes storage class syncer" \ - org.opencontainers.image.source="https://github.com/leaseweb/cloudstack-csi-driver/" - -RUN apk add --no-cache ca-certificates - -COPY ./bin/cloudstack-csi-sc-syncer /cloudstack-csi-sc-syncer -ENTRYPOINT ["/cloudstack-csi-sc-syncer"] \ No newline at end of file diff --git a/cmd/cloudstack-csi-sc-syncer/README.md b/cmd/cloudstack-csi-sc-syncer/README.md deleted file mode 100644 index 8dab035..0000000 --- a/cmd/cloudstack-csi-sc-syncer/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# cloudstack-csi-sc-syncer - -`cloudstack-csi-sc-syncer` connects to CloudStack (using the same CloudStack -configuration file as `cloudstack-csi-driver`), lists all disk offerings -suitable for usage in Kubernetes (currently: checks they have a custom size), -and creates corresponding Storage Classes in Kubernetes if needed. - -It also adds a label to the Storage Classes it creates. - -If option `-delete=true` is passed, it may also delete Kubernetes Storage -Classes, when they have its label and their corresponding CloudStack disk -offering has been deleted. - -## Usage - -You may use it locally or as a Kubernetes Job. - -### Locally - -You must have a CloudStack configuration file and a Kubernetes `kubeconfig` -file. - -1. Download `cloudstack-csi-sc-syncer` from [latest release](https://github.com/leaseweb/cloudstack-csi-driver/releases/latest/); - -1. Set the execution permission: - - ``` - chmod +x ./cloudstack-csi-sc-syncer - ``` - -1. Then simply execute the tool: - - ``` - ./cloudstack-csi-sc-syncer - ``` - -Run `./cloudstack-csi-sc-syncer -h` to get the complete list of options and their default values. - -### As a Kubernetes Job - -You may run `cloudstack-csi-sc-syncer` as a Kubernetes Job. In that case, it -re-uses the CloudStack configuration file in Secret `cloudstack-secret`, and use -in-cluster Kubernetes authentification, using a ServiceAccount. - -```sh -export version=... - -kubectl apply -f - < diff --git a/examples/k8s/pod-block.yaml b/examples/k8s/pod-block.yaml deleted file mode 100644 index 5016e66..0000000 --- a/examples/k8s/pod-block.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: example-pod-block -spec: - containers: - - name: example - image: ubuntu - volumeDevices: - - devicePath: "/dev/example-block" - name: example-volume - stdin: true - stdinOnce: true - tty: true - volumes: - - name: example-volume - persistentVolumeClaim: - claimName: example-pvc-block diff --git a/examples/k8s/pod.yaml b/examples/k8s/pod.yaml deleted file mode 100644 index afcc288..0000000 --- a/examples/k8s/pod.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: example-pod -spec: - containers: - - name: example - image: busybox - volumeMounts: - - mountPath: "/data" - name: example-volume - stdin: true - stdinOnce: true - tty: true - volumes: - - name: example-volume - persistentVolumeClaim: - claimName: example-pvc diff --git a/examples/k8s/pvc-block.yaml b/examples/k8s/pvc-block.yaml deleted file mode 100644 index b689a86..0000000 --- a/examples/k8s/pvc-block.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: example-pvc-block -spec: - storageClassName: cloudstack-custom - volumeMode: Block - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi diff --git a/examples/k8s/pvc.yaml b/examples/k8s/pvc.yaml deleted file mode 100644 index a050773..0000000 --- a/examples/k8s/pvc.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: example-pvc -spec: - storageClassName: cloudstack-custom - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi diff --git a/go.mod b/go.mod deleted file mode 100644 index 4bd0612..0000000 --- a/go.mod +++ /dev/null @@ -1,69 +0,0 @@ -module github.com/leaseweb/cloudstack-csi-driver - -go 1.21 - -require ( - github.com/apache/cloudstack-go/v2 v2.15.0 - github.com/container-storage-interface/spec v1.9.0 - github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 - github.com/hashicorp/go-uuid v1.0.3 - github.com/kubernetes-csi/csi-lib-utils v0.17.0 - github.com/kubernetes-csi/csi-test/v5 v5.2.0 - go.uber.org/zap v1.26.0 - golang.org/x/text v0.14.0 - google.golang.org/grpc v1.60.1 - gopkg.in/gcfg.v1 v1.2.3 - k8s.io/api v0.29.0 - k8s.io/apimachinery v0.29.0 - k8s.io/client-go v0.29.0 - k8s.io/mount-utils v0.29.0 - k8s.io/utils v0.0.0-20240102154912-e7106e64919e -) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/go-logr/logr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.3 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/mock v1.6.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect - github.com/google/uuid v1.4.0 // indirect - github.com/imdario/mergo v0.3.6 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/moby/sys/mountinfo v0.6.2 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/onsi/ginkgo/v2 v2.13.1 // indirect - github.com/onsi/gomega v1.30.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - go.uber.org/multierr v1.10.0 // indirect - golang.org/x/net v0.18.0 // indirect - golang.org/x/oauth2 v0.13.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.14.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect - google.golang.org/protobuf v1.31.0 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/warnings.v0 v0.1.2 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/klog/v2 v2.110.1 // indirect - k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 6f30a9c..0000000 --- a/go.sum +++ /dev/null @@ -1,284 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/apache/cloudstack-go/v2 v2.15.0 h1:oojn1qx0+wBwrFSSmA2rL8XjWd4BXqwYo0RVCrAXoHk= -github.com/apache/cloudstack-go/v2 v2.15.0/go.mod h1:Mc+tXpujtslBuZFk5atoGT2LanVxOrXS2GGgidAoz1A= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -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/container-storage-interface/spec v1.9.0 h1:zKtX4STsq31Knz3gciCYCi1SXtO2HJDecIjDVboYavY= -github.com/container-storage-interface/spec v1.9.0/go.mod h1:ZfDu+3ZRyeVqxZM0Ds19MVLkN2d1XJ5MAfi1L3VjlT0= -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/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -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 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -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.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/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -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.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -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-lib-utils v0.17.0 h1:xEpJ3WYgMyyYF6fvcKHh4cDRtknuTkBS9rG8bYoLTCU= -github.com/kubernetes-csi/csi-lib-utils v0.17.0/go.mod h1:2Ba5/aQgUjbpqyC2uCcFwMF3rnPVs5jhZXm8jAzcT9Q= -github.com/kubernetes-csi/csi-test/v5 v5.2.0 h1:Z+sdARWC6VrONrxB24clCLCmnqCnZF7dzXtzx8eM35o= -github.com/kubernetes-csi/csi-test/v5 v5.2.0/go.mod h1:o/c5w+NU3RUNE+DbVRhEUTmkQVBGk+tFOB2yPXT8teo= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU= -github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= -github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= -github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -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/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/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -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.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= -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/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -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.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -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-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/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= -golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= -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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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/sync v0.0.0-20220722155255-886fb9371eb4/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-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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/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-20210330210617-4fbd30eecc44/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-20211025201205-69cdffdb9359/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -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.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -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-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -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/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -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-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= -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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= -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.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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/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.0-20210107192922-496545a6307b/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/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= -k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= -k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= -k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= -k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= -k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= -k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= -k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= -k8s.io/mount-utils v0.29.0 h1:KcUE0bFHONQC10V3SuLWQ6+l8nmJggw9lKLpDftIshI= -k8s.io/mount-utils v0.29.0/go.mod h1:N3lDK/G1B8R/IkAt4NhHyqB07OqEr7P763z3TNge94U= -k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= -k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/pkg/cloud/cloud.go b/pkg/cloud/cloud.go deleted file mode 100644 index df51335..0000000 --- a/pkg/cloud/cloud.go +++ /dev/null @@ -1,64 +0,0 @@ -// Package cloud contains CloudStack related -// functions. -package cloud - -import ( - "context" - "errors" - - "github.com/apache/cloudstack-go/v2/cloudstack" -) - -// Interface is the CloudStack client interface. -type Interface interface { - GetNodeInfo(ctx context.Context, vmName string) (*VM, error) - GetVMByID(ctx context.Context, vmID string) (*VM, error) - - ListZonesID(ctx context.Context) ([]string, error) - - GetVolumeByID(ctx context.Context, volumeID string) (*Volume, error) - GetVolumeByName(ctx context.Context, name string) (*Volume, error) - CreateVolume(ctx context.Context, diskOfferingID, zoneID, name string, sizeInGB int64) (string, error) - DeleteVolume(ctx context.Context, id string) error - AttachVolume(ctx context.Context, volumeID, vmID string) (string, error) - DetachVolume(ctx context.Context, volumeID string) error - ExpandVolume(ctx context.Context, volumeID string, newSizeInGB int64) error -} - -// Volume represents a CloudStack volume. -type Volume struct { - ID string - Name string - - // Size in Bytes - Size int64 - - DiskOfferingID string - ZoneID string - - VirtualMachineID string - DeviceID string -} - -// VM represents a CloudStack Virtual Machine. -type VM struct { - ID string - ZoneID string -} - -// Specific errors -var ( - ErrNotFound = errors.New("not found") - ErrTooManyResults = errors.New("too many results") -) - -// client is the implementation of Interface. -type client struct { - *cloudstack.CloudStackClient -} - -// New creates a new cloud connector, given its configuration. -func New(config *Config) Interface { - csClient := cloudstack.NewAsyncClient(config.APIURL, config.APIKey, config.SecretKey, config.VerifySSL) - return &client{csClient} -} diff --git a/pkg/cloud/config.go b/pkg/cloud/config.go deleted file mode 100644 index 691be99..0000000 --- a/pkg/cloud/config.go +++ /dev/null @@ -1,46 +0,0 @@ -package cloud - -import ( - "fmt" - - "gopkg.in/gcfg.v1" -) - -// Config holds CloudStack connection configuration. -type Config struct { - APIURL string - APIKey string - SecretKey string - VerifySSL bool -} - -// csConfig wraps the config for the CloudStack cloud provider. -// It is taken from https://github.com/apache/cloudstack-kubernetes-provider -// in order to have the same config in cloudstack-kubernetes-provider -// and in this cloudstack-csi-driver -type csConfig struct { - Global struct { - APIURL string `gcfg:"api-url"` - APIKey string `gcfg:"api-key"` - SecretKey string `gcfg:"secret-key"` - SSLNoVerify bool `gcfg:"ssl-no-verify"` - ProjectID string `gcfg:"project-id"` - Zone string `gcfg:"zone"` - } -} - -// ReadConfig reads a config file with a format defined by CloudStack -// Cloud Controller Manager, and returns a CloudStackConfig. -func ReadConfig(configFilePath string) (*Config, error) { - cfg := &csConfig{} - if err := gcfg.ReadFileInto(cfg, configFilePath); err != nil { - return nil, fmt.Errorf("could not parse CloudStack config: %w", err) - } - - return &Config{ - APIURL: cfg.Global.APIURL, - APIKey: cfg.Global.APIKey, - SecretKey: cfg.Global.SecretKey, - VerifySSL: cfg.Global.SSLNoVerify, - }, nil -} diff --git a/pkg/cloud/fake/fake.go b/pkg/cloud/fake/fake.go deleted file mode 100644 index dc6cc51..0000000 --- a/pkg/cloud/fake/fake.go +++ /dev/null @@ -1,120 +0,0 @@ -// Package fake provides a fake implementation of the cloud -// connector interface, to be used in tests. -package fake - -import ( - "context" - - "github.com/hashicorp/go-uuid" - - "github.com/leaseweb/cloudstack-csi-driver/pkg/cloud" - "github.com/leaseweb/cloudstack-csi-driver/pkg/util" -) - -const zoneID = "a1887604-237c-4212-a9cd-94620b7880fa" - -type fakeConnector struct { - node *cloud.VM - volumesByID map[string]cloud.Volume - volumesByName map[string]cloud.Volume -} - -// New returns a new fake implementation of the -// CloudStack connector. -func New() cloud.Interface { - volume := cloud.Volume{ - ID: "ace9f28b-3081-40c1-8353-4cc3e3014072", - Name: "vol-1", - Size: 10, - DiskOfferingID: "9743fd77-0f5d-4ef9-b2f8-f194235c769c", - ZoneID: zoneID, - VirtualMachineID: "", - DeviceID: "", - } - node := &cloud.VM{ - ID: "0d7107a3-94d2-44e7-89b8-8930881309a5", - ZoneID: zoneID, - } - return &fakeConnector{ - node: node, - volumesByID: map[string]cloud.Volume{volume.ID: volume}, - volumesByName: map[string]cloud.Volume{volume.Name: volume}, - } -} - -func (f *fakeConnector) GetVMByID(ctx context.Context, vmID string) (*cloud.VM, error) { - if vmID == f.node.ID { - return f.node, nil - } - return nil, cloud.ErrNotFound -} - -func (f *fakeConnector) GetNodeInfo(ctx context.Context, vmName string) (*cloud.VM, error) { - return f.node, nil -} - -func (f *fakeConnector) ListZonesID(ctx context.Context) ([]string, error) { - return []string{zoneID}, nil -} - -func (f *fakeConnector) GetVolumeByID(ctx context.Context, volumeID string) (*cloud.Volume, error) { - vol, ok := f.volumesByID[volumeID] - if ok { - return &vol, nil - } - return nil, cloud.ErrNotFound -} - -func (f *fakeConnector) GetVolumeByName(ctx context.Context, name string) (*cloud.Volume, error) { - vol, ok := f.volumesByName[name] - if ok { - return &vol, nil - } - return nil, cloud.ErrNotFound -} - -func (f *fakeConnector) CreateVolume(ctx context.Context, diskOfferingID, zoneID, name string, sizeInGB int64) (string, error) { - id, _ := uuid.GenerateUUID() - vol := cloud.Volume{ - ID: id, - Name: name, - Size: util.GigaBytesToBytes(sizeInGB), - DiskOfferingID: diskOfferingID, - ZoneID: zoneID, - } - f.volumesByID[vol.ID] = vol - f.volumesByName[vol.Name] = vol - return vol.ID, nil -} - -func (f *fakeConnector) DeleteVolume(ctx context.Context, id string) error { - if vol, ok := f.volumesByID[id]; ok { - name := vol.Name - delete(f.volumesByName, name) - } - delete(f.volumesByID, id) - return nil -} - -func (f *fakeConnector) AttachVolume(ctx context.Context, volumeID, vmID string) (string, error) { - return "1", nil -} - -func (f *fakeConnector) DetachVolume(ctx context.Context, volumeID string) error { - return nil -} -func (f *fakeConnector) ExpandVolume(ctx context.Context, volumeID string, newSizeInGB int64) error { - if vol, ok := f.volumesByID[volumeID]; ok { - newSizeInBytes := newSizeInGB * 1024 * 1024 * 1024 - if newSizeInBytes > vol.Size { - vol.Size = newSizeInBytes - f.volumesByID[volumeID] = vol - f.volumesByName[vol.Name] = vol - return nil - } else { - return nil - } - } else { - return nil - } -} diff --git a/pkg/cloud/metadata.go b/pkg/cloud/metadata.go deleted file mode 100644 index b2b5638..0000000 --- a/pkg/cloud/metadata.go +++ /dev/null @@ -1,83 +0,0 @@ -package cloud - -import ( - "context" - "encoding/json" - "fmt" - "io/ioutil" - "os" - "strings" - - "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" -) - -const ( - cloudInitInstanceFilePath = "/run/cloud-init/instance-data.json" - cloudStackCloudName = "cloudstack" -) - -func (c *client) metadataInstanceID(ctx context.Context) string { - slog := ctxzap.Extract(ctx).Sugar() - - // Try a NODE_ID environment variable - if envNodeID := os.Getenv("NODE_ID"); envNodeID != "" { - slog.Debugf("Found CloudStack VM ID from environment variable NODE_ID: %s", envNodeID) - return envNodeID - } - - // Try cloud-init - slog.Debug("Try with cloud-init") - if _, err := os.Stat(cloudInitInstanceFilePath); err == nil { - slog.Debugf("File %s exists", cloudInitInstanceFilePath) - ciData, err := c.readCloudInit(ctx, cloudInitInstanceFilePath) - if err != nil { - slog.Errorf("Cannot read cloud-init instance data: %v", err) - } else { - if ciData.V1.InstanceID != "" { - slog.Debugf("Found CloudStack VM ID from cloud-init: %s", ciData.V1.InstanceID) - return ciData.V1.InstanceID - } - } - slog.Error("cloud-init instance ID is not provided") - } else if os.IsNotExist(err) { - slog.Debugf("File %s does not exist", cloudInitInstanceFilePath) - } else { - slog.Errorf("Cannot read %s: %v", cloudInitInstanceFilePath, err) - } - - slog.Debug("CloudStack VM ID not found in meta-data.") - return "" -} - -type cloudInitInstanceData struct { - V1 cloudInitV1 `json:"v1"` -} - -type cloudInitV1 struct { - CloudName string `json:"cloud_name"` - InstanceID string `json:"instance_id"` - Zone string `json:"availability_zone"` -} - -func (c *client) readCloudInit(ctx context.Context, instanceFilePath string) (*cloudInitInstanceData, error) { - slog := ctxzap.Extract(ctx).Sugar() - - b, err := ioutil.ReadFile(instanceFilePath) - if err != nil { - slog.Errorf("Cannot read %s", instanceFilePath) - return nil, err - } - - var data cloudInitInstanceData - if err := json.Unmarshal(b, &data); err != nil { - slog.Errorf("Cannot parse JSON file %s", instanceFilePath) - return nil, err - } - - if strings.ToLower(data.V1.CloudName) != cloudStackCloudName { - slog.Errorf("Cloud-Init cloud name is %s, only %s is supported", data.V1.CloudName, cloudStackCloudName) - return nil, fmt.Errorf("Cloud-Init cloud name is %s, only %s is supported", data.V1.CloudName, cloudStackCloudName) - } - - return &data, nil -} diff --git a/pkg/cloud/node.go b/pkg/cloud/node.go deleted file mode 100644 index 29c11bf..0000000 --- a/pkg/cloud/node.go +++ /dev/null @@ -1,17 +0,0 @@ -package cloud - -import "context" - -func (c *client) GetNodeInfo(ctx context.Context, vmName string) (*VM, error) { - // First, try to read the instance ID from meta-data (cloud-init) - if id := c.metadataInstanceID(ctx); id != "" { - // Instance ID found using metadata - - // Use CloudStack API to get VM info - return c.GetVMByID(ctx, id) - } - - // VM ID was not found using metadata. - // Use VM name instead - return c.getVMByName(ctx, vmName) -} diff --git a/pkg/cloud/vms.go b/pkg/cloud/vms.go deleted file mode 100644 index 29124c3..0000000 --- a/pkg/cloud/vms.go +++ /dev/null @@ -1,53 +0,0 @@ -package cloud - -import ( - "context" - - "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" -) - -func (c *client) GetVMByID(ctx context.Context, vmID string) (*VM, error) { - p := c.VirtualMachine.NewListVirtualMachinesParams() - p.SetId(vmID) - ctxzap.Extract(ctx).Sugar().Infow("CloudStack API call", "command", "ListVirtualMachines", "params", map[string]string{ - "id": vmID, - }) - l, err := c.VirtualMachine.ListVirtualMachines(p) - if err != nil { - return nil, err - } - if l.Count == 0 { - return nil, ErrNotFound - } - if l.Count > 1 { - return nil, ErrTooManyResults - } - vm := l.VirtualMachines[0] - return &VM{ - ID: vm.Id, - ZoneID: vm.Zoneid, - }, nil -} - -func (c *client) getVMByName(ctx context.Context, name string) (*VM, error) { - p := c.VirtualMachine.NewListVirtualMachinesParams() - p.SetName(name) - ctxzap.Extract(ctx).Sugar().Infow("CloudStack API call", "command", "ListVirtualMachines", "params", map[string]string{ - "name": name, - }) - l, err := c.VirtualMachine.ListVirtualMachines(p) - if err != nil { - return nil, err - } - if l.Count == 0 { - return nil, ErrNotFound - } - if l.Count > 1 { - return nil, ErrTooManyResults - } - vm := l.VirtualMachines[0] - return &VM{ - ID: vm.Id, - ZoneID: vm.Zoneid, - }, nil -} diff --git a/pkg/cloud/volumes.go b/pkg/cloud/volumes.go deleted file mode 100644 index 4dd3766..0000000 --- a/pkg/cloud/volumes.go +++ /dev/null @@ -1,155 +0,0 @@ -package cloud - -import ( - "context" - "fmt" - "strconv" - "strings" - - "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" - "github.com/leaseweb/cloudstack-csi-driver/pkg/util" -) - -func (c *client) GetVolumeByID(ctx context.Context, volumeID string) (*Volume, error) { - p := c.Volume.NewListVolumesParams() - p.SetId(volumeID) - ctxzap.Extract(ctx).Sugar().Infow("CloudStack API call", "command", "ListVolumes", "params", map[string]string{ - "id": volumeID, - }) - l, err := c.Volume.ListVolumes(p) - if err != nil { - return nil, err - } - if l.Count == 0 { - return nil, ErrNotFound - } - if l.Count > 1 { - return nil, ErrTooManyResults - } - vol := l.Volumes[0] - v := Volume{ - ID: vol.Id, - Name: vol.Name, - Size: vol.Size, - DiskOfferingID: vol.Diskofferingid, - ZoneID: vol.Zoneid, - VirtualMachineID: vol.Virtualmachineid, - DeviceID: strconv.FormatInt(vol.Deviceid, 10), - } - return &v, nil -} - -func (c *client) GetVolumeByName(ctx context.Context, name string) (*Volume, error) { - p := c.Volume.NewListVolumesParams() - p.SetName(name) - ctxzap.Extract(ctx).Sugar().Infow("CloudStack API call", "command", "ListVolumes", "params", map[string]string{ - "name": name, - }) - l, err := c.Volume.ListVolumes(p) - if err != nil { - return nil, err - } - if l.Count == 0 { - return nil, ErrNotFound - } - if l.Count > 1 { - return nil, ErrTooManyResults - } - vol := l.Volumes[0] - v := Volume{ - ID: vol.Id, - Name: vol.Name, - Size: vol.Size, - DiskOfferingID: vol.Diskofferingid, - ZoneID: vol.Zoneid, - VirtualMachineID: vol.Virtualmachineid, - DeviceID: strconv.FormatInt(vol.Deviceid, 10), - } - return &v, nil -} - -func (c *client) CreateVolume(ctx context.Context, diskOfferingID, zoneID, name string, sizeInGB int64) (string, error) { - p := c.Volume.NewCreateVolumeParams() - p.SetDiskofferingid(diskOfferingID) - p.SetZoneid(zoneID) - p.SetName(name) - p.SetSize(sizeInGB) - ctxzap.Extract(ctx).Sugar().Infow("CloudStack API call", "command", "CreateVolume", "params", map[string]string{ - "diskofferingid": diskOfferingID, - "zoneid": zoneID, - "name": name, - "size": strconv.FormatInt(sizeInGB, 10), - }) - vol, err := c.Volume.CreateVolume(p) - if err != nil { - return "", err - } - return vol.Id, nil -} - -func (c *client) DeleteVolume(ctx context.Context, id string) error { - p := c.Volume.NewDeleteVolumeParams(id) - ctxzap.Extract(ctx).Sugar().Infow("CloudStack API call", "command", "DeleteVolume", "params", map[string]string{ - "id": id, - }) - _, err := c.Volume.DeleteVolume(p) - if err != nil && strings.Contains(err.Error(), "4350") { - // CloudStack error InvalidParameterValueException - return ErrNotFound - } - return err -} - -func (c *client) AttachVolume(ctx context.Context, volumeID, vmID string) (string, error) { - p := c.Volume.NewAttachVolumeParams(volumeID, vmID) - ctxzap.Extract(ctx).Sugar().Infow("CloudStack API call", "command", "AttachVolume", "params", map[string]string{ - "id": volumeID, - "virtualmachineid": vmID, - }) - r, err := c.Volume.AttachVolume(p) - if err != nil { - return "", err - } - return strconv.FormatInt(r.Deviceid, 10), nil -} - -func (c *client) DetachVolume(ctx context.Context, volumeID string) error { - p := c.Volume.NewDetachVolumeParams() - p.SetId(volumeID) - ctxzap.Extract(ctx).Sugar().Infow("CloudStack API call", "command", "DetachVolume", "params", map[string]string{ - "id": volumeID, - }) - _, err := c.Volume.DetachVolume(p) - return err -} - -// ExpandVolume expands the volume to new size -func (c *client) ExpandVolume(ctx context.Context, volumeID string, newSizeInGB int64) error { - volume, _, err := c.Volume.GetVolumeByID(volumeID) - if err != nil { - return fmt.Errorf("failed to retrieve volume '%s': %v", volumeID, err) - } - if volume.State != "Allocated" && volume.State != "Ready" { - return fmt.Errorf("volume '%s' is not in 'Allocated' or 'Ready' state to get resized", volumeID) - } - currentSize := volume.Size - currentSizeInGB := util.RoundUpBytesToGB(currentSize) - volumeName := volume.Name - p := c.Volume.NewResizeVolumeParams(volumeID) - p.SetId(volumeID) - p.SetSize(newSizeInGB) - ctxzap.Extract(ctx).Sugar().Infow("CloudStack API call", "command", "ExpandVolume", "params", map[string]string{ - "name": volumeName, - "volumeid": volumeID, - "current_size": strconv.FormatInt(currentSizeInGB, 10), - "requested_size": strconv.FormatInt(newSizeInGB, 10), - }) - // Execute the API call to resize the volume - expandedVol, err := c.Volume.ResizeVolume(p) - if err != nil { - // Handle the error accordingly - return fmt.Errorf("failed to expand volume '%s': %v", volumeID, err) - } - fmt.Printf("Volume %s resied to %d successfully", expandedVol.Id, expandedVol.Size) - return nil -} diff --git a/pkg/cloud/zones.go b/pkg/cloud/zones.go deleted file mode 100644 index 7eee620..0000000 --- a/pkg/cloud/zones.go +++ /dev/null @@ -1,24 +0,0 @@ -package cloud - -import ( - "context" - - "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" -) - -func (c *client) ListZonesID(ctx context.Context) ([]string, error) { - result := []string{} - p := c.Zone.NewListZonesParams() - p.SetAvailable(true) - ctxzap.Extract(ctx).Sugar().Infow("CloudStack API call", "command", "ListZones", "params", map[string]string{ - "available": "true", - }) - r, err := c.Zone.ListZones(p) - if err != nil { - return result, err - } - for _, zone := range r.Zones { - result = append(result, zone.Id) - } - return result, nil -} diff --git a/pkg/driver/constants.go b/pkg/driver/constants.go deleted file mode 100644 index 2f548e5..0000000 --- a/pkg/driver/constants.go +++ /dev/null @@ -1,17 +0,0 @@ -package driver - -// DriverName is the name of the CSI plugin -const DriverName = "csi.cloudstack.apache.org" - -// Topology keys -const ( - ZoneKey = "topology." + DriverName + "/zone" - HostKey = "topology." + DriverName + "/host" -) - -// Volume parameters keys -const ( - DiskOfferingKey = DriverName + "/disk-offering-id" -) - -const deviceIDContextKey = "deviceID" diff --git a/pkg/driver/controller.go b/pkg/driver/controller.go deleted file mode 100644 index 56df0b2..0000000 --- a/pkg/driver/controller.go +++ /dev/null @@ -1,507 +0,0 @@ -package driver - -import ( - "context" - "fmt" - "math/rand" - - "github.com/container-storage-interface/spec/lib/go/csi" - "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" - "github.com/kubernetes-csi/csi-lib-utils/protosanitizer" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/leaseweb/cloudstack-csi-driver/pkg/cloud" - "github.com/leaseweb/cloudstack-csi-driver/pkg/util" -) - -// onlyVolumeCapAccessMode is the only volume capability access -// mode possible for CloudStack: SINGLE_NODE_WRITER, since a -// CloudStack volume can only be attached to a single node at -// any given time. -var onlyVolumeCapAccessMode = csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, -} - -type controllerServer struct { - csi.UnimplementedControllerServer - connector cloud.Interface - volumeLocks *util.VolumeLocks -} - -// NewControllerServer creates a new Controller gRPC server. -func NewControllerServer(connector cloud.Interface) csi.ControllerServer { - return &controllerServer{ - connector: connector, - volumeLocks: util.NewVolumeLocks(), - } -} - -func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { - - // Check arguments - - if req.GetName() == "" { - return nil, status.Error(codes.InvalidArgument, "Volume name missing in request") - } - name := req.GetName() - - volCaps := req.GetVolumeCapabilities() - if len(volCaps) == 0 { - return nil, status.Error(codes.InvalidArgument, "Volume capabilities missing in request") - } - if !isValidVolumeCapabilities(volCaps) { - return nil, status.Error(codes.InvalidArgument, "Volume capabilities not supported. Only SINGLE_NODE_WRITER supported.") - } - - if req.GetParameters() == nil { - return nil, status.Error(codes.InvalidArgument, "Volume parameters missing in request") - } - diskOfferingID := req.GetParameters()[DiskOfferingKey] - if diskOfferingID == "" { - return nil, status.Errorf(codes.InvalidArgument, "Missing parameter %v", DiskOfferingKey) - } - - if acquired := cs.volumeLocks.TryAcquire(name); !acquired { - ctxzap.Extract(ctx).Sugar().Errorf(util.VolumeOperationAlreadyExistsFmt, name) - return nil, status.Errorf(codes.Aborted, util.VolumeOperationAlreadyExistsFmt, name) - } - defer cs.volumeLocks.Release(name) - - // Check if a volume with that name already exists - if vol, err := cs.connector.GetVolumeByName(ctx, name); err == cloud.ErrNotFound { - // The volume does not exist - } else if err != nil { - // Error with CloudStack - return nil, status.Errorf(codes.Internal, "CloudStack error: %v", err) - } else { - // The volume exists. Check if it suits the request. - if ok, message := checkVolumeSuitable(vol, diskOfferingID, req.GetCapacityRange(), req.GetAccessibilityRequirements()); !ok { - return nil, status.Errorf(codes.AlreadyExists, "Volume %v already exists but does not satisfy request: %s", name, message) - } - // Existing volume is ok - return &csi.CreateVolumeResponse{ - Volume: &csi.Volume{ - VolumeId: vol.ID, - CapacityBytes: vol.Size, - VolumeContext: req.GetParameters(), - // ContentSource: req.GetVolumeContentSource(), TODO: snapshot support - AccessibleTopology: []*csi.Topology{ - Topology{ZoneID: vol.ZoneID}.ToCSI(), - }, - }, - }, nil - } - - // We have to create the volume - - // Determine volume size using requested capacity range - sizeInGB, err := determineSize(req) - if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) - } - - // Determine zone using topology constraints - var zoneID string - topologyRequirement := req.GetAccessibilityRequirements() - if topologyRequirement == nil || topologyRequirement.GetRequisite() == nil { - // No topology requirement. Use random zone - zones, err := cs.connector.ListZonesID(ctx) - if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) - } - n := len(zones) - if n == 0 { - return nil, status.Error(codes.Internal, "No zone available") - } - zoneID = zones[rand.Intn(n)] - } else { - reqTopology := topologyRequirement.GetRequisite() - if len(reqTopology) > 1 { - return nil, status.Error(codes.InvalidArgument, "Too many topology requirements") - } - t, err := NewTopology(reqTopology[0]) - if err != nil { - return nil, status.Error(codes.InvalidArgument, "Cannot parse topology requirements") - } - zoneID = t.ZoneID - } - - ctxzap.Extract(ctx).Sugar().Infow("Creating new volume", - "name", name, - "size", sizeInGB, - "offering", diskOfferingID, - "zone", zoneID, - ) - - volID, err := cs.connector.CreateVolume(ctx, diskOfferingID, zoneID, name, sizeInGB) - if err != nil { - return nil, status.Errorf(codes.Internal, "Cannot create volume %s: %v", name, err.Error()) - } - - return &csi.CreateVolumeResponse{ - Volume: &csi.Volume{ - VolumeId: volID, - CapacityBytes: util.GigaBytesToBytes(sizeInGB), - VolumeContext: req.GetParameters(), - // ContentSource: req.GetVolumeContentSource(), TODO: snapshot support - AccessibleTopology: []*csi.Topology{ - Topology{ZoneID: zoneID}.ToCSI(), - }, - }, - }, nil -} - -func checkVolumeSuitable(vol *cloud.Volume, - diskOfferingID string, capRange *csi.CapacityRange, topologyRequirement *csi.TopologyRequirement) (bool, string) { - - if vol.DiskOfferingID != diskOfferingID { - return false, fmt.Sprintf("Disk offering %s; requested disk offering %s", vol.DiskOfferingID, diskOfferingID) - } - - if capRange != nil { - if capRange.GetLimitBytes() > 0 && vol.Size > capRange.GetLimitBytes() { - return false, fmt.Sprintf("Disk size %v bytes > requested limit size %v bytes", vol.Size, capRange.GetLimitBytes()) - } - if capRange.GetRequiredBytes() > 0 && vol.Size < capRange.GetRequiredBytes() { - return false, fmt.Sprintf("Disk size %v bytes < requested required size %v bytes", vol.Size, capRange.GetRequiredBytes()) - } - } - - if topologyRequirement != nil && topologyRequirement.GetRequisite() != nil { - reqTopology := topologyRequirement.GetRequisite() - if len(reqTopology) > 1 { - return false, "Too many topology requirements" - } - t, err := NewTopology(reqTopology[0]) - if err != nil { - return false, "Cannot parse topology requirements" - } - if t.ZoneID != vol.ZoneID { - return false, fmt.Sprintf("Volume in zone %s, requested zone is %s", vol.ZoneID, t.ZoneID) - } - } - - return true, "" -} - -func determineSize(req *csi.CreateVolumeRequest) (int64, error) { - var sizeInGB int64 - - if req.GetCapacityRange() != nil { - capRange := req.GetCapacityRange() - - required := capRange.GetRequiredBytes() - sizeInGB = util.RoundUpBytesToGB(required) - if sizeInGB == 0 { - sizeInGB = 1 - } - - if limit := capRange.GetLimitBytes(); limit > 0 { - if util.GigaBytesToBytes(sizeInGB) > limit { - return 0, fmt.Errorf("after round-up, volume size %v GB exceeds the limit specified of %v bytes", sizeInGB, limit) - } - } - } - - if sizeInGB == 0 { - sizeInGB = 1 - } - return sizeInGB, nil -} - -func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) { - if req.GetVolumeId() == "" { - return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request") - } - - volumeID := req.GetVolumeId() - - if acquired := cs.volumeLocks.TryAcquire(volumeID); !acquired { - ctxzap.Extract(ctx).Sugar().Errorf(util.VolumeOperationAlreadyExistsFmt, volumeID) - return nil, status.Errorf(codes.Aborted, util.VolumeOperationAlreadyExistsFmt, volumeID) - } - defer cs.volumeLocks.Release(volumeID) - - ctxzap.Extract(ctx).Sugar().Infow("Deleting volume", - "volumeID", volumeID, - ) - - err := cs.connector.DeleteVolume(ctx, volumeID) - if err != nil && err != cloud.ErrNotFound { - return nil, status.Errorf(codes.Internal, "Cannot delete volume %s: %s", volumeID, err.Error()) - } - return &csi.DeleteVolumeResponse{}, nil -} - -func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { - // Check arguments - - if req.GetVolumeId() == "" { - return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request") - } - volumeID := req.GetVolumeId() - - if req.GetNodeId() == "" { - return nil, status.Error(codes.InvalidArgument, "Node ID missing in request") - } - nodeID := req.GetNodeId() - - if req.GetReadonly() { - return nil, status.Error(codes.InvalidArgument, "Readonly not possible") - } - - if req.GetVolumeCapability() == nil { - return nil, status.Error(codes.InvalidArgument, "Volume capability missing in request") - } - if req.GetVolumeCapability().AccessMode.Mode != onlyVolumeCapAccessMode.GetMode() { - return nil, status.Error(codes.InvalidArgument, "Access mode not accepted") - } - - ctxzap.Extract(ctx).Sugar().Infow("Initiating attaching volume", - "volumeID", volumeID, - "nodeID", nodeID, - ) - - // Check volume - vol, err := cs.connector.GetVolumeByID(ctx, volumeID) - if err == cloud.ErrNotFound { - return nil, status.Errorf(codes.NotFound, "Volume %v not found", volumeID) - } else if err != nil { - // Error with CloudStack - return nil, status.Errorf(codes.Internal, "Error %v", err) - } - - if vol.VirtualMachineID != "" && vol.VirtualMachineID != nodeID { - ctxzap.Extract(ctx).Sugar().Errorw("Volume already attached to another node", - "volumeID", volumeID, - "nodeID", nodeID, - "attached nodeID", vol.VirtualMachineID, - ) - return nil, status.Error(codes.AlreadyExists, "Volume already assigned to another node") - } - - if _, err := cs.connector.GetVMByID(ctx, nodeID); err == cloud.ErrNotFound { - return nil, status.Errorf(codes.NotFound, "VM %v not found", nodeID) - } else if err != nil { - // Error with CloudStack - return nil, status.Errorf(codes.Internal, "Error %v", err) - } - - if vol.VirtualMachineID == nodeID { - // volume already attached - ctxzap.Extract(ctx).Sugar().Infow("Volume already attached to node", - "volumeID", volumeID, - "nodeID", nodeID, - "deviceID", vol.DeviceID, - ) - publishContext := map[string]string{ - deviceIDContextKey: vol.DeviceID, - } - return &csi.ControllerPublishVolumeResponse{PublishContext: publishContext}, nil - } - - ctxzap.Extract(ctx).Sugar().Infow("Attaching volume to node", - "volumeID", volumeID, - "nodeID", nodeID, - ) - - deviceID, err := cs.connector.AttachVolume(ctx, volumeID, nodeID) - if err != nil { - return nil, status.Errorf(codes.Internal, "Cannot attach volume %s: %s", volumeID, err.Error()) - } - - ctxzap.Extract(ctx).Sugar().Infow("Attached volume to node successfully", - "volumeID", volumeID, - "nodeID", nodeID, - ) - - publishContext := map[string]string{ - deviceIDContextKey: deviceID, - } - return &csi.ControllerPublishVolumeResponse{PublishContext: publishContext}, nil -} - -func (cs *controllerServer) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) { - // Check arguments - - if req.GetVolumeId() == "" { - return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request") - } - volumeID := req.GetVolumeId() - nodeID := req.GetNodeId() - - // Check volume - if vol, err := cs.connector.GetVolumeByID(ctx, volumeID); err == cloud.ErrNotFound { - // Volume does not exist in CloudStack. We can safely assume this volume is no longer attached - // The spec requires us to return OK here - return &csi.ControllerUnpublishVolumeResponse{}, nil - } else if err != nil { - // Error with CloudStack - return nil, status.Errorf(codes.Internal, "Error %v", err) - } else if nodeID != "" && vol.VirtualMachineID != nodeID { - // Volume is present but not attached to this particular nodeID - return &csi.ControllerUnpublishVolumeResponse{}, nil - } - - // Check VM existence - if _, err := cs.connector.GetVMByID(ctx, nodeID); err == cloud.ErrNotFound { - // volumes cannot be attached to deleted VMs - ctxzap.Extract(ctx).Sugar().Warnw("VM not found, marking ControllerUnpublishVolume successful", - "volumeID", volumeID, - "nodeID", nodeID, - ) - return &csi.ControllerUnpublishVolumeResponse{}, nil - } else if err != nil { - // Error with CloudStack - return nil, status.Errorf(codes.Internal, "Error %v", err) - } - - ctxzap.Extract(ctx).Sugar().Infow("Detaching volume from node", - "volumeID", volumeID, - "nodeID", nodeID, - ) - - err := cs.connector.DetachVolume(ctx, volumeID) - if err != nil { - return nil, status.Errorf(codes.Internal, "Cannot detach volume %s: %s", volumeID, err.Error()) - } - - ctxzap.Extract(ctx).Sugar().Infow("Detached volume from node successfully", - "volumeID", volumeID, - "nodeID", nodeID, - ) - - return &csi.ControllerUnpublishVolumeResponse{}, nil -} - -func (cs *controllerServer) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) { - volumeID := req.GetVolumeId() - if len(volumeID) == 0 { - return nil, status.Error(codes.InvalidArgument, "Volume ID not provided") - } - - volCaps := req.GetVolumeCapabilities() - if len(volCaps) == 0 { - return nil, status.Error(codes.InvalidArgument, "Volume capabilities not provided") - } - - if _, err := cs.connector.GetVolumeByID(ctx, volumeID); err == cloud.ErrNotFound { - return nil, status.Errorf(codes.NotFound, "Volume %v not found", volumeID) - } else if err != nil { - // Error with CloudStack - return nil, status.Errorf(codes.Internal, "Error %v", err) - } - - if !isValidVolumeCapabilities(volCaps) { - return &csi.ValidateVolumeCapabilitiesResponse{Message: "Requested VolumeCapabilities are invalid"}, nil - } - - return &csi.ValidateVolumeCapabilitiesResponse{ - Confirmed: &csi.ValidateVolumeCapabilitiesResponse_Confirmed{ - VolumeContext: req.GetVolumeContext(), - VolumeCapabilities: volCaps, - Parameters: req.GetParameters(), - }}, nil -} - -func isValidVolumeCapabilities(volCaps []*csi.VolumeCapability) bool { - for _, c := range volCaps { - if c.GetAccessMode() != nil && c.GetAccessMode().GetMode() != onlyVolumeCapAccessMode.GetMode() { - return false - } - } - return true -} - -func (cs *controllerServer) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) { - expandVolumeLock := util.NewOperationLock(ctx) - logger := ctxzap.Extract(ctx).Sugar() - logger.Infow("Expand Volume: called with args", "args", protosanitizer.StripSecrets(*req)) - - volumeID := req.GetVolumeId() - if len(volumeID) == 0 { - return nil, status.Error(codes.InvalidArgument, "Volume ID not provided") - } - err := expandVolumeLock.GetExpandLock(volumeID) - if err != nil { - - logger.Errorf(util.VolumeOperationAlreadyExistsFmt, volumeID) - return nil, status.Errorf(codes.Aborted, util.VolumeOperationAlreadyExistsFmt, volumeID) - } - defer expandVolumeLock.ReleaseExpandLock(volumeID) - - cap := req.GetCapacityRange() - if cap == nil { - return nil, status.Error(codes.InvalidArgument, "Capacity range not provided") - } - - volSizeBytes := cap.GetRequiredBytes() - volSizeGB := util.RoundUpBytesToGB(volSizeBytes) - maxVolSize := cap.GetLimitBytes() - - if maxVolSize > 0 && maxVolSize < util.GigaBytesToBytes(volSizeGB) { - return nil, status.Error(codes.OutOfRange, "Volume size exceeds the limit specified") - } - - volume, err := cs.connector.GetVolumeByID(ctx, volumeID) - if err != nil { - if err == cloud.ErrNotFound { - return nil, status.Errorf(codes.NotFound, "Volume %v not found", volumeID) - } - return nil, status.Error(codes.Internal, fmt.Sprintf("GetVolume failed with error %v", err)) - } - - if volume.Size >= util.GigaBytesToBytes(volSizeGB) { - // A volume was already resized - logger.Infof("Volume %q has been already expanded to %d. requested %d", volumeID, volume.Size, volSizeGB) - - return &csi.ControllerExpandVolumeResponse{ - CapacityBytes: volume.Size, - NodeExpansionRequired: true, - }, nil - - } - err = cs.connector.ExpandVolume(ctx, volumeID, volSizeGB) - if err != nil { - return nil, status.Errorf(codes.Internal, "Could not resize volume %q to size %v: %v", volumeID, volSizeGB, err) - } - - logger.Infow("ControllerExpandVolume resized", - "requested_volume_ID", volumeID, - "new_size", volSizeGB, - ) - - return &csi.ControllerExpandVolumeResponse{ - CapacityBytes: util.GigaBytesToBytes(volSizeGB), - NodeExpansionRequired: true, - }, nil -} - -func (cs *controllerServer) ControllerGetCapabilities(ctx context.Context, req *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) { - return &csi.ControllerGetCapabilitiesResponse{ - Capabilities: []*csi.ControllerServiceCapability{ - { - Type: &csi.ControllerServiceCapability_Rpc{ - Rpc: &csi.ControllerServiceCapability_RPC{ - Type: csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, - }, - }, - }, - { - Type: &csi.ControllerServiceCapability_Rpc{ - Rpc: &csi.ControllerServiceCapability_RPC{ - Type: csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME, - }, - }, - }, - { - Type: &csi.ControllerServiceCapability_Rpc{ - Rpc: &csi.ControllerServiceCapability_RPC{ - Type: csi.ControllerServiceCapability_RPC_EXPAND_VOLUME, - }, - }, - }, - }, - }, nil -} diff --git a/pkg/driver/controller_test.go b/pkg/driver/controller_test.go deleted file mode 100644 index bf9f3d1..0000000 --- a/pkg/driver/controller_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package driver - -import ( - "testing" - - "github.com/container-storage-interface/spec/lib/go/csi" -) - -func TestDetermineSize(t *testing.T) { - cases := []struct { - name string - capacityRange *csi.CapacityRange - expectedSize int64 - expectError bool - }{ - {"no range", nil, 1, false}, - {"only limit", &csi.CapacityRange{LimitBytes: 100 * 1024 * 1024 * 1024}, 1, false}, - {"only limit (too small)", &csi.CapacityRange{LimitBytes: 1024 * 1024}, 0, true}, - {"only required", &csi.CapacityRange{RequiredBytes: 50 * 1024 * 1024 * 1024}, 50, false}, - {"required and limit", &csi.CapacityRange{RequiredBytes: 25 * 1024 * 1024 * 1024, LimitBytes: 100 * 1024 * 1024 * 1024}, 25, false}, - {"required = limit", &csi.CapacityRange{RequiredBytes: 30 * 1024 * 1024 * 1024, LimitBytes: 30 * 1024 * 1024 * 1024}, 30, false}, - {"required = limit (not GB int)", &csi.CapacityRange{RequiredBytes: 3_000_000_000, LimitBytes: 3_000_000_000}, 0, true}, - {"no int GB int possible", &csi.CapacityRange{RequiredBytes: 4_000_000_000, LimitBytes: 1_000_001_000}, 0, true}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - req := &csi.CreateVolumeRequest{ - CapacityRange: c.capacityRange, - } - size, err := determineSize(req) - if err != nil && !c.expectError { - t.Errorf("Unexepcted error: %v", err.Error()) - } - if err == nil && c.expectError { - t.Error("Expected an error") - } - if size != c.expectedSize { - t.Errorf("Expected size %v, got %v", c.expectedSize, size) - } - }) - } -} diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go deleted file mode 100644 index 85b49a0..0000000 --- a/pkg/driver/driver.go +++ /dev/null @@ -1,47 +0,0 @@ -// Package driver provides the implementation of the CSI plugin. -// -// It contains the gRPC server implementation of CSI specification. -package driver - -import ( - "go.uber.org/zap" - - "github.com/leaseweb/cloudstack-csi-driver/pkg/cloud" - "github.com/leaseweb/cloudstack-csi-driver/pkg/mount" -) - -// Interface is the CloudStack CSI driver interface. -type Interface interface { - // Run the CSI driver gRPC server - Run() error -} - -type cloudstackDriver struct { - endpoint string - nodeName string - version string - - connector cloud.Interface - mounter mount.Interface - logger *zap.Logger -} - -// New instantiates a new CloudStack CSI driver -func New(endpoint string, csConnector cloud.Interface, mounter mount.Interface, nodeName string, version string, logger *zap.Logger) (Interface, error) { - return &cloudstackDriver{ - endpoint: endpoint, - nodeName: nodeName, - version: version, - connector: csConnector, - mounter: mounter, - logger: logger, - }, nil -} - -func (cs *cloudstackDriver) Run() error { - ids := NewIdentityServer(cs.version) - ctrls := NewControllerServer(cs.connector) - ns := NewNodeServer(cs.connector, cs.mounter, cs.nodeName) - - return cs.serve(ids, ctrls, ns) -} diff --git a/pkg/driver/identity.go b/pkg/driver/identity.go deleted file mode 100644 index 52186f1..0000000 --- a/pkg/driver/identity.go +++ /dev/null @@ -1,57 +0,0 @@ -package driver - -import ( - "context" - - "github.com/container-storage-interface/spec/lib/go/csi" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -type identityServer struct { - csi.UnimplementedIdentityServer - version string -} - -// NewIdentityServer creates a new Identity gRPC server. -func NewIdentityServer(version string) csi.IdentityServer { - return &identityServer{ - version: version, - } -} - -func (ids *identityServer) GetPluginInfo(ctx context.Context, req *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) { - if ids.version == "" { - return nil, status.Error(codes.Unavailable, "Driver is missing version") - } - - return &csi.GetPluginInfoResponse{ - Name: DriverName, - VendorVersion: ids.version, - }, nil -} - -func (ids *identityServer) Probe(ctx context.Context, req *csi.ProbeRequest) (*csi.ProbeResponse, error) { - return &csi.ProbeResponse{}, nil -} - -func (ids *identityServer) GetPluginCapabilities(ctx context.Context, req *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) { - return &csi.GetPluginCapabilitiesResponse{ - Capabilities: []*csi.PluginCapability{ - { - Type: &csi.PluginCapability_Service_{ - Service: &csi.PluginCapability_Service{ - Type: csi.PluginCapability_Service_CONTROLLER_SERVICE, - }, - }, - }, - { - Type: &csi.PluginCapability_Service_{ - Service: &csi.PluginCapability_Service{ - Type: csi.PluginCapability_Service_VOLUME_ACCESSIBILITY_CONSTRAINTS, - }, - }, - }, - }, - }, nil -} diff --git a/pkg/driver/node.go b/pkg/driver/node.go deleted file mode 100644 index 3ed2ee6..0000000 --- a/pkg/driver/node.go +++ /dev/null @@ -1,453 +0,0 @@ -package driver - -import ( - "context" - "fmt" - "os" - "path/filepath" - - "github.com/container-storage-interface/spec/lib/go/csi" - "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/leaseweb/cloudstack-csi-driver/pkg/cloud" - "github.com/leaseweb/cloudstack-csi-driver/pkg/mount" - "github.com/leaseweb/cloudstack-csi-driver/pkg/util" -) - -const ( - // default file system type to be used when it is not provided - defaultFsType = "ext4" -) - -type nodeServer struct { - csi.UnimplementedNodeServer - connector cloud.Interface - mounter mount.Interface - nodeName string - volumeLocks *util.VolumeLocks -} - -// NewNodeServer creates a new Node gRPC server. -func NewNodeServer(connector cloud.Interface, mounter mount.Interface, nodeName string) csi.NodeServer { - if mounter == nil { - mounter = mount.New() - } - return &nodeServer{ - connector: connector, - mounter: mounter, - nodeName: nodeName, - volumeLocks: util.NewVolumeLocks(), - } -} - -func (ns *nodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { - - // Check parameters - - volumeID := req.GetVolumeId() - if volumeID == "" { - return nil, status.Error(codes.InvalidArgument, "Volume ID not provided") - } - - target := req.GetStagingTargetPath() - if target == "" { - return nil, status.Error(codes.InvalidArgument, "Staging target not provided") - } - - volCap := req.GetVolumeCapability() - if volCap == nil { - return nil, status.Error(codes.InvalidArgument, "Volume capability not provided") - } - if !isValidVolumeCapabilities([]*csi.VolumeCapability{volCap}) { - return nil, status.Error(codes.InvalidArgument, "Volume capability not supported") - } - - if acquired := ns.volumeLocks.TryAcquire(volumeID); !acquired { - ctxzap.Extract(ctx).Sugar().Errorf(util.VolumeOperationAlreadyExistsFmt, volumeID) - return nil, status.Errorf(codes.Aborted, util.VolumeOperationAlreadyExistsFmt, volumeID) - } - defer ns.volumeLocks.Release(volumeID) - - // Now, find the device path - - deviceID := req.PublishContext[deviceIDContextKey] - - devicePath, err := ns.mounter.GetDevicePath(ctx, volumeID) - if err != nil { - return nil, status.Errorf(codes.Internal, "Cannot find device path for volume %s: %s", volumeID, err.Error()) - } - - ctxzap.Extract(ctx).Sugar().Infow("Device found", - "devicePath", devicePath, - "deviceID", deviceID, - ) - - // If the access type is block, do nothing for stage - if blk := volCap.GetBlock(); blk != nil { - return &csi.NodeStageVolumeResponse{}, nil - } - - // The access type should now be "Mount". - // We have to format the partition. - - mnt := volCap.GetMount() - if mnt == nil { - return nil, status.Error(codes.InvalidArgument, "Neither block nor mount volume capability") - } - - // Verify whether mounted - notMnt, err := ns.mounter.IsLikelyNotMountPoint(target) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - - fsType := mnt.GetFsType() - if fsType == "" { - fsType = defaultFsType - } - - var mountOptions []string - for _, f := range mnt.GetMountFlags() { - if !hasMountOption(mountOptions, f) { - mountOptions = append(mountOptions, f) - } - } - - // Volume Mount - if notMnt { - ctxzap.Extract(ctx).Sugar().Infow("NodeStageVolume: formatting and mounting", - "devicePath", devicePath, - "target", target, - "fsType", fsType, - "options", mountOptions, - ) - err = ns.mounter.FormatAndMount(devicePath, target, fsType, mountOptions) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - } - return &csi.NodeStageVolumeResponse{}, nil -} - -// hasMountOption returns a boolean indicating whether the given -// slice already contains a mount option. This is used to prevent -// passing duplicate option to the mount command. -func hasMountOption(options []string, opt string) bool { - for _, o := range options { - if o == opt { - return true - } - } - return false -} - -func (ns *nodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) { - // Check parameters - - volumeID := req.GetVolumeId() - if volumeID == "" { - return nil, status.Error(codes.InvalidArgument, "Volume ID not provided") - } - - target := req.GetStagingTargetPath() - if target == "" { - return nil, status.Error(codes.InvalidArgument, "Staging target not provided") - } - - if acquired := ns.volumeLocks.TryAcquire(volumeID); !acquired { - ctxzap.Extract(ctx).Sugar().Errorf(util.VolumeOperationAlreadyExistsFmt, volumeID) - return nil, status.Errorf(codes.Aborted, util.VolumeOperationAlreadyExistsFmt, volumeID) - } - defer ns.volumeLocks.Release(volumeID) - - notMnt, err := ns.mounter.IsLikelyNotMountPoint(target) - - if err != nil { - if os.IsNotExist(err) { - return nil, status.Error(codes.NotFound, "Target path not found") - } - return nil, status.Error(codes.Internal, err.Error()) - } - if notMnt { - return &csi.NodeUnstageVolumeResponse{}, nil - } - - ctxzap.Extract(ctx).Sugar().Infow("NodeUnstageVolume: unmounting", - "target", target, - ) - - err = ns.mounter.CleanupMountPoint(target, true) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to unmount target %q: %v", target, err) - } - - ctxzap.Extract(ctx).Sugar().Infow("NodeUnstageVolume: unmount succesfull", - "target", target, - ) - - return &csi.NodeUnstageVolumeResponse{}, nil -} - -func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { - // Check arguments - if req.GetVolumeCapability() == nil { - return nil, status.Error(codes.InvalidArgument, "Volume capability missing in request") - } - if req.GetVolumeId() == "" { - return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request") - } - volumeID := req.GetVolumeId() - - if req.GetTargetPath() == "" { - return nil, status.Error(codes.InvalidArgument, "Target path missing in request") - } - targetPath := req.GetTargetPath() - - if req.GetVolumeCapability().GetBlock() != nil && - req.GetVolumeCapability().GetMount() != nil { - return nil, status.Error(codes.InvalidArgument, "Cannot have both block and mount access type") - } - if req.GetStagingTargetPath() == "" { - return nil, status.Error(codes.InvalidArgument, "Staging target path missing in request") - } - - readOnly := req.GetReadonly() - options := []string{"bind"} - if readOnly { - options = append(options, "ro") - } - - deviceID := "" - if req.GetPublishContext() != nil { - deviceID = req.GetPublishContext()[deviceIDContextKey] - } - - if acquired := ns.volumeLocks.TryAcquire(volumeID); !acquired { - ctxzap.Extract(ctx).Sugar().Errorf(util.VolumeOperationAlreadyExistsFmt, volumeID) - return nil, status.Errorf(codes.Aborted, util.VolumeOperationAlreadyExistsFmt, volumeID) - } - defer ns.volumeLocks.Release(volumeID) - - if req.GetVolumeCapability().GetMount() != nil { - source := req.GetStagingTargetPath() - - notMnt, err := ns.mounter.IsLikelyNotMountPoint(targetPath) - if err != nil { - if os.IsNotExist(err) { - if err := ns.mounter.MakeDir(targetPath); err != nil { - return nil, status.Errorf(codes.Internal, "Could not create dir %q: %v", targetPath, err) - } - } else { - return nil, status.Error(codes.Internal, err.Error()) - } - } - if !notMnt { - ctxzap.Extract(ctx).Sugar().Infow("NodePublishVolume: volume is already mounted", - "source", source, - "targetPath", targetPath, - ) - return &csi.NodePublishVolumeResponse{}, nil - } - - fsType := req.GetVolumeCapability().GetMount().GetFsType() - - mountFlags := req.GetVolumeCapability().GetMount().GetMountFlags() - - ctxzap.Extract(ctx).Sugar().Infow("NodePublishVolume: mounting source", - "source", source, - "targetPath", targetPath, - "fsType", fsType, - "deviceID", deviceID, - "readOnly", readOnly, - "volumeID", volumeID, - "mountFlags", mountFlags, - ) - - if err := ns.mounter.Mount(source, targetPath, fsType, options); err != nil { - return nil, status.Errorf(codes.Internal, "failed to mount %s at %s: %s", source, targetPath, err.Error()) - } - } - - if req.GetVolumeCapability().GetBlock() != nil { - volumeID := req.GetVolumeId() - - devicePath, err := ns.mounter.GetDevicePath(ctx, volumeID) - if err != nil { - return nil, status.Errorf(codes.Internal, "Cannot find device path for volume %s: %s", volumeID, err.Error()) - } - - globalMountPath := filepath.Dir(targetPath) - exists, err := ns.mounter.ExistsPath(globalMountPath) - if err != nil { - return nil, status.Errorf(codes.Internal, "Could not check if path exists %q: %v", globalMountPath, err) - } - if !exists { - if err = ns.mounter.MakeDir(globalMountPath); err != nil { - return nil, status.Errorf(codes.Internal, "Could not create dir %q: %v", globalMountPath, err) - } - } - - err = ns.mounter.MakeFile(targetPath) - if err != nil { - if removeErr := os.Remove(targetPath); removeErr != nil { - return nil, status.Errorf(codes.Internal, "Could not remove mount target %q: %v", targetPath, removeErr) - } - return nil, status.Errorf(codes.Internal, "Could not create file %q: %v", targetPath, err) - } - - ctxzap.Extract(ctx).Sugar().Infow("NodePublishVolume: mounting device", - "devicePath", devicePath, - "targetPath", targetPath, - "deviceID", deviceID, - "readOnly", readOnly, - "volumeID", volumeID, - ) - - if err := ns.mounter.Mount(devicePath, targetPath, "", options); err != nil { - return nil, status.Errorf(codes.Internal, "failed to mount %s at %s: %s", devicePath, targetPath, err.Error()) - } - } - - return &csi.NodePublishVolumeResponse{}, nil -} - -func (ns *nodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) { - volumeID := req.GetVolumeId() - if volumeID == "" { - return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request") - } - targetPath := req.GetTargetPath() - if targetPath == "" { - return nil, status.Error(codes.InvalidArgument, "Target path missing in request") - } - - if acquired := ns.volumeLocks.TryAcquire(volumeID); !acquired { - ctxzap.Extract(ctx).Sugar().Errorf(util.VolumeOperationAlreadyExistsFmt, volumeID) - return nil, status.Errorf(codes.Aborted, util.VolumeOperationAlreadyExistsFmt, volumeID) - } - defer ns.volumeLocks.Release(volumeID) - - if _, err := ns.connector.GetVolumeByID(ctx, volumeID); err == cloud.ErrNotFound { - return nil, status.Errorf(codes.NotFound, "Volume %v not found", volumeID) - } else if err != nil { - // Error with CloudStack - return nil, status.Errorf(codes.Internal, "Error %v", err) - } - - ctxzap.Extract(ctx).Sugar().Infow("NodeUnpublishVolume: unmounting volume", - "targetPath", targetPath, - "volumeID", volumeID, - ) - - err := ns.mounter.CleanupMountPoint(targetPath, true) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to unmount target %q: %v", targetPath, err) - } - - ctxzap.Extract(ctx).Sugar().Infow("NodeUnpublishVolume: unmounting successful", - "targetPath", targetPath, - "volumeID", volumeID, - ) - - return &csi.NodeUnpublishVolumeResponse{}, nil -} - -func (ns *nodeServer) NodeGetInfo(ctx context.Context, req *csi.NodeGetInfoRequest) (*csi.NodeGetInfoResponse, error) { - if ns.nodeName == "" { - return nil, status.Error(codes.Internal, "Missing node name") - } - - vm, err := ns.connector.GetNodeInfo(ctx, ns.nodeName) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - if vm.ID == "" { - return nil, status.Error(codes.Internal, "Node with no ID") - } - if vm.ZoneID == "" { - return nil, status.Error(codes.Internal, "Node zone ID not found") - } - - topology := Topology{ZoneID: vm.ZoneID} - return &csi.NodeGetInfoResponse{ - NodeId: vm.ID, - AccessibleTopology: topology.ToCSI(), - }, nil -} - -func (ns *nodeServer) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolumeRequest) (*csi.NodeExpandVolumeResponse, error) { - - volumeID := req.GetVolumeId() - if len(volumeID) == 0 { - return nil, status.Error(codes.InvalidArgument, "Volume ID not provided") - } - volumePath := req.GetVolumePath() - if len(volumePath) == 0 { - return nil, status.Error(codes.InvalidArgument, "Volume path not provided") - } - - ctxzap.Extract(ctx).Sugar().Infow("Node expand volume called", - "volume_id", volumeID, - "volume_path", volumePath, - "method", "node_expand_volume", - ) - volCap := req.GetVolumeCapability() - if volCap != nil { - switch volCap.GetAccessType().(type) { - case *csi.VolumeCapability_Block: - ctxzap.Extract(ctx).Sugar().Info("filesystem expansion is skipped for block volumes") - return &csi.NodeExpandVolumeResponse{}, nil - } - } - - _, err := ns.connector.GetVolumeByID(ctx, volumeID) - if err != nil { - if err == cloud.ErrNotFound { - return nil, status.Error(codes.NotFound, fmt.Sprintf("Volume with ID %s not found", volumeID)) - } - return nil, status.Error(codes.Internal, fmt.Sprintf("NodeExpandVolume failed with error %v", err)) - } - - _, err = ns.mounter.GetMountRefs(volumePath) - if err != nil { - return nil, status.Error(codes.Internal, fmt.Sprintf("Failed to find mount file system %s: %v", volumePath, err)) - } - - devicePath, err := ns.mounter.GetDevicePath(ctx, volumeID) - if devicePath == "" { - return nil, status.Error(codes.Internal, fmt.Sprintf("Unable to find Device path for volume %s: %v", volumeID, err)) - } - - ctxzap.Extract(ctx).Sugar().Infow("Device found", - "devicePath", devicePath, - ) - - r := ns.mounter.NewResizeFs(mount.New()) - if _, err := r.Resize(devicePath, volumePath); err != nil { - return nil, status.Errorf(codes.Internal, "Could not resize volume %q: %v", volumeID, err) - } - return &csi.NodeExpandVolumeResponse{}, nil -} - -func (ns *nodeServer) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) { - return &csi.NodeGetCapabilitiesResponse{ - Capabilities: []*csi.NodeServiceCapability{ - { - Type: &csi.NodeServiceCapability_Rpc{ - Rpc: &csi.NodeServiceCapability_RPC{ - Type: csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME, - }, - }, - }, - { - Type: &csi.NodeServiceCapability_Rpc{ - Rpc: &csi.NodeServiceCapability_RPC{ - Type: csi.NodeServiceCapability_RPC_EXPAND_VOLUME, - }, - }, - }, - }, - }, nil -} diff --git a/pkg/driver/server.go b/pkg/driver/server.go deleted file mode 100644 index adb74cd..0000000 --- a/pkg/driver/server.go +++ /dev/null @@ -1,69 +0,0 @@ -package driver - -import ( - "context" - "fmt" - "net" - "os" - "strings" - - "github.com/container-storage-interface/spec/lib/go/csi" - grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap" - "google.golang.org/grpc" -) - -func (cs *cloudstackDriver) serve(ids csi.IdentityServer, ctrls csi.ControllerServer, ns csi.NodeServer) error { - proto, addr, err := parseEndpoint(cs.endpoint) - if err != nil { - return err - } - - if proto == "unix" { - if !strings.HasPrefix(addr, "/") { - addr = "/" + addr - } - if err := os.Remove(addr); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("failed to remove %s, error: %s", addr, err.Error()) - } - } - - listener, err := net.Listen(proto, addr) - if err != nil { - return fmt.Errorf("failed to listen: %w", err) - } - - // Log every request and payloads (request + response) - opts := []grpc.ServerOption{ - grpc.ChainUnaryInterceptor( - grpc_zap.UnaryServerInterceptor(cs.logger), - grpc_zap.PayloadUnaryServerInterceptor(cs.logger, func(context.Context, string, interface{}) bool { return true }), - ), - } - // Make sure that log statements internal to gRPC library are logged using the zapLogger as well. - grpc_zap.ReplaceGrpcLoggerV2(cs.logger) - - server := grpc.NewServer(opts...) - - if ids != nil { - csi.RegisterIdentityServer(server, ids) - } - if ctrls != nil { - csi.RegisterControllerServer(server, ctrls) - } - if ns != nil { - csi.RegisterNodeServer(server, ns) - } - - cs.logger.Sugar().Infow("Listening for connections", "address", listener.Addr()) - return server.Serve(listener) -} - -func parseEndpoint(ep string) (string, string, error) { - if strings.HasPrefix(strings.ToLower(ep), "unix://") || strings.HasPrefix(strings.ToLower(ep), "tcp://") { - s := strings.SplitN(ep, "://", 2) - if s[1] != "" { - return s[0], s[1], nil - } - } - return "", "", fmt.Errorf("invalid endpoint: %v", ep) -} diff --git a/pkg/driver/topology.go b/pkg/driver/topology.go deleted file mode 100644 index 27246f6..0000000 --- a/pkg/driver/topology.go +++ /dev/null @@ -1,40 +0,0 @@ -package driver - -import ( - "errors" - - "github.com/container-storage-interface/spec/lib/go/csi" -) - -// Topology represents CloudStack storage topology. -type Topology struct { - ZoneID string - HostID string -} - -// NewTopology converts a *csi.Topology to Topology. -func NewTopology(t *csi.Topology) (Topology, error) { - segments := t.GetSegments() - if segments == nil { - return Topology{}, errors.New("nil segment in topology") - } - - zoneID, ok := segments[ZoneKey] - if !ok { - return Topology{}, errors.New("no zone in topology") - } - hostID := segments[HostKey] - return Topology{zoneID, hostID}, nil -} - -// ToCSI converts a Topology to a *csi.Topology. -func (t Topology) ToCSI() *csi.Topology { - segments := make(map[string]string) - segments[ZoneKey] = t.ZoneID - if t.HostID != "" { - segments[ZoneKey] = t.ZoneID - } - return &csi.Topology{ - Segments: segments, - } -} diff --git a/pkg/mount/fake.go b/pkg/mount/fake.go deleted file mode 100644 index 3ffff46..0000000 --- a/pkg/mount/fake.go +++ /dev/null @@ -1,61 +0,0 @@ -package mount - -import ( - "context" - "os" - - "k8s.io/mount-utils" - utilsexec "k8s.io/utils/exec" - exec "k8s.io/utils/exec/testing" -) - -type fakeMounter struct { - mount.SafeFormatAndMount - utilsexec.Interface -} - -// NewFake creates an fake implementation of the -// mount.Interface, to be used in tests. -func NewFake() Interface { - return &fakeMounter{ - mount.SafeFormatAndMount{ - Interface: mount.NewFakeMounter([]mount.MountPoint{}), - Exec: &exec.FakeExec{DisableScripts: true}, - }, - utilsexec.New(), - } -} - -func (m *fakeMounter) CleanupMountPoint(path string, extensiveCheck bool) error { - return mount.CleanupMountPoint(path, m, extensiveCheck) -} - -func (m *fakeMounter) GetDevicePath(ctx context.Context, volumeID string) (string, error) { - return "/dev/sdb", nil -} - -func (m *fakeMounter) GetDeviceName(mountPath string) (string, int, error) { - return mount.GetDeviceNameFromMount(m, mountPath) -} - -func (*fakeMounter) ExistsPath(filename string) (bool, error) { - return true, nil -} - -func (*fakeMounter) MakeDir(pathname string) error { - err := os.MkdirAll(pathname, os.FileMode(0755)) - if err != nil { - if !os.IsExist(err) { - return err - } - } - return nil -} - -func (*fakeMounter) MakeFile(pathname string) error { - return nil -} - -func (*fakeMounter) NewResizeFs(exec utilsexec.Interface) *mount.ResizeFs { - return mount.NewResizeFs(New()) -} diff --git a/pkg/mount/mount.go b/pkg/mount/mount.go deleted file mode 100644 index a8c50f6..0000000 --- a/pkg/mount/mount.go +++ /dev/null @@ -1,182 +0,0 @@ -// Package mount provides utilities to detect, -// format and mount storage devices. -package mount - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - "time" - - "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/mount-utils" - "k8s.io/utils/exec" -) - -const ( - diskIDPath = "/dev/disk/by-id" -) - -// Interface defines the set of methods to allow for -// mount operations on a system. -type Interface interface { - mount.Interface - exec.Interface - - FormatAndMount(source string, target string, fstype string, options []string) error - - CleanupMountPoint(path string, extensiveCheck bool) error - GetDevicePath(ctx context.Context, volumeID string) (string, error) - GetDeviceName(mountPath string) (string, int, error) - ExistsPath(filename string) (bool, error) - MakeDir(pathname string) error - MakeFile(pathname string) error - NewResizeFs(exec exec.Interface) *mount.ResizeFs -} - -type mounter struct { - mount.SafeFormatAndMount - exec.Interface -} - -// New creates an implementation of the mount.Interface. -func New() Interface { - return &mounter{ - mount.SafeFormatAndMount{ - Interface: mount.New(""), - Exec: exec.New(), - }, - exec.New(), - } -} - -func (m *mounter) CleanupMountPoint(path string, extensiveCheck bool) error { - return mount.CleanupMountPoint(path, m, extensiveCheck) -} - -func (m *mounter) GetDevicePath(ctx context.Context, volumeID string) (string, error) { - backoff := wait.Backoff{ - Duration: 1 * time.Second, - Factor: 1.1, - Steps: 15, - } - - var devicePath string - err := wait.ExponentialBackoffWithContext(ctx, backoff, func(context.Context) (bool, error) { - path, err := m.getDevicePathBySerialID(volumeID) - if err != nil { - return false, err - } - if path != "" { - devicePath = path - return true, nil - } - m.probeVolume(ctx) - return false, nil - }) - - if wait.Interrupted(err) { - return "", fmt.Errorf("failed to find device for the volumeID: %q within the alloted time", volumeID) - } else if devicePath == "" { - return "", fmt.Errorf("device path was empty for volumeID: %q", volumeID) - } - return devicePath, nil -} - -func (m *mounter) getDevicePathBySerialID(volumeID string) (string, error) { - sourcePathPrefixes := []string{"virtio-", "scsi-", "scsi-0QEMU_QEMU_HARDDISK_"} - serial := diskUUIDToSerial(volumeID) - for _, prefix := range sourcePathPrefixes { - source := filepath.Join(diskIDPath, prefix+serial) - _, err := os.Stat(source) - if err == nil { - return source, nil - } - if !os.IsNotExist(err) { - return "", err - } - } - return "", nil -} - -func (m *mounter) probeVolume(ctx context.Context) { - log := ctxzap.Extract(ctx).Sugar() - log.Debug("Scaning SCSI host...") - - scsiPath := "/sys/class/scsi_host/" - if dirs, err := ioutil.ReadDir(scsiPath); err == nil { - for _, f := range dirs { - name := scsiPath + f.Name() + "/scan" - data := []byte("- - -") - if err = ioutil.WriteFile(name, data, 0666); err != nil { - log.Warnf("Failed to rescan scsi host %s", name) - } - } - } else { - log.Warnf("Failed to read %s, err %v", scsiPath, err) - } - - args := []string{"trigger"} - cmd := m.Exec.Command("udevadm", args...) - _, err := cmd.CombinedOutput() - if err != nil { - log.Warnf("Error running udevadm trigger %v\n", err) - } -} - -func (m *mounter) GetDeviceName(mountPath string) (string, int, error) { - return mount.GetDeviceNameFromMount(m, mountPath) -} - -// diskUUIDToSerial reproduces CloudStack function diskUuidToSerial -// from https://github.com/apache/cloudstack/blob/0f3f2a0937/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java#L3000 -// -// This is what CloudStack do *with KVM hypervisor* to translate -// a CloudStack volume UUID to libvirt disk serial. -func diskUUIDToSerial(uuid string) string { - uuidWithoutHyphen := strings.ReplaceAll(uuid, "-", "") - if len(uuidWithoutHyphen) < 20 { - return uuidWithoutHyphen - } - return uuidWithoutHyphen[:20] -} - -func (*mounter) ExistsPath(filename string) (bool, error) { - if _, err := os.Stat(filename); os.IsNotExist(err) { - return false, nil - } else if err != nil { - return false, err - } - return true, nil -} - -func (*mounter) MakeDir(pathname string) error { - err := os.MkdirAll(pathname, os.FileMode(0755)) - if err != nil { - if !os.IsExist(err) { - return err - } - } - return nil -} - -func (*mounter) MakeFile(pathname string) error { - f, err := os.OpenFile(pathname, os.O_CREATE, os.FileMode(0644)) - if err != nil { - if !os.IsExist(err) { - return err - } - } - if err = f.Close(); err != nil { - return err - } - return nil -} - -func (*mounter) NewResizeFs(exec exec.Interface) *mount.ResizeFs { - return mount.NewResizeFs(New()) -} diff --git a/pkg/syncer/error.go b/pkg/syncer/error.go deleted file mode 100644 index 1c23c37..0000000 --- a/pkg/syncer/error.go +++ /dev/null @@ -1,13 +0,0 @@ -package syncer - -import "fmt" - -type combinedError []error - -func (errs combinedError) Error() string { - err := "Collected errors:\n" - for i, e := range errs { - err += fmt.Sprintf("\tError %d: %s\n", i, e.Error()) - } - return err -} diff --git a/pkg/syncer/name.go b/pkg/syncer/name.go deleted file mode 100644 index b82dcd0..0000000 --- a/pkg/syncer/name.go +++ /dev/null @@ -1,50 +0,0 @@ -package syncer - -import ( - "fmt" - "regexp" - "strings" - "unicode" - - "golang.org/x/text/runes" - "golang.org/x/text/transform" - "golang.org/x/text/unicode/norm" -) - -func createStorageClassName(origName string) (string, error) { - // Remove accents / diacritics - nonSpacingMarks := runes.In(unicode.Mn) - t := transform.Chain(norm.NFD, runes.Remove(nonSpacingMarks), norm.NFC) - name, _, err := transform.String(t, origName) - if err != nil { - return "", err - } - - // Replace non alphanumeric characters (except .) by a space - nonAlpha := regexp.MustCompile("[^a-zA-Z0-9.]+") - name = nonAlpha.ReplaceAllString(name, " ") - - // Use lowercase - name = strings.ToLower(name) - - // Trim whitespaces - name = strings.TrimSpace(name) - - // Replace whitespaces by a single dash - name = regexp.MustCompile(`\s+`).ReplaceAllString(name, "-") - - // Truncate - if len(name) > 253 { - name = name[:253] - } - - // Remove trailing and leading "." and "-" - name = strings.TrimFunc(name, func(r rune) bool { return r == '.' || r == '-' }) - - // Return an error if the resulting name is empty - if len(name) == 0 { - return "", fmt.Errorf("%s transformed to an empty name", origName) - } - - return name, nil -} diff --git a/pkg/syncer/name_test.go b/pkg/syncer/name_test.go deleted file mode 100644 index 89e623b..0000000 --- a/pkg/syncer/name_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package syncer - -import ( - "testing" -) - -func TestCreateStorageClassName(t *testing.T) { - cases := []struct { - OrigName string - ExpectedName string - ShouldErr bool - }{ - {OrigName: "cloudstack-gold", ExpectedName: "cloudstack-gold"}, - {OrigName: "cloudstack-Silver", ExpectedName: "cloudstack-silver"}, - {OrigName: "cloudstack-copper-1.2", ExpectedName: "cloudstack-copper-1.2"}, - {OrigName: "cloudstack-Custom Storage 1.2 - experimental", ExpectedName: "cloudstack-custom-storage-1.2-experimental"}, - {OrigName: "étendu", ExpectedName: "etendu"}, - {OrigName: "stockage NFS", ExpectedName: "stockage-nfs"}, - {OrigName: "Disque 123", ExpectedName: "disque-123"}, - {OrigName: "123", ExpectedName: "123"}, - {OrigName: "Platinium +", ExpectedName: "platinium"}, - {OrigName: " Platinium Plus ", ExpectedName: "platinium-plus"}, - {OrigName: "cloudstack-Ruthénium", ExpectedName: "cloudstack-ruthenium"}, - {OrigName: "--- gold ---", ExpectedName: "gold"}, - {OrigName: ".silver.", ExpectedName: "silver"}, - {OrigName: "Don't use me!", ExpectedName: "don-t-use-me"}, - {OrigName: "cloudstack-東京", ExpectedName: "cloudstack"}, - {OrigName: "こんにちは世界", ShouldErr: true}, - {OrigName: "", ShouldErr: true}, - } - - for _, c := range cases { - t.Run(c.OrigName, func(t *testing.T) { - name, err := createStorageClassName(c.OrigName) - if err != nil && !c.ShouldErr { - t.Error(err) - } else if err == nil && c.ShouldErr { - t.Error("Expected a non-nil error; error was nil") - } else if err == nil && name != c.ExpectedName { - t.Errorf("Expected name %s; got %s", c.ExpectedName, name) - } - }) - } -} diff --git a/pkg/syncer/run.go b/pkg/syncer/run.go deleted file mode 100644 index 43c7751..0000000 --- a/pkg/syncer/run.go +++ /dev/null @@ -1,217 +0,0 @@ -package syncer - -import ( - "context" - "errors" - "fmt" - "log" - - "github.com/apache/cloudstack-go/v2/cloudstack" - corev1 "k8s.io/api/core/v1" - storagev1 "k8s.io/api/storage/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - - "github.com/leaseweb/cloudstack-csi-driver/pkg/driver" -) - -var ( - volBindingMode = storagev1.VolumeBindingWaitForFirstConsumer - reclaimPolicy = corev1.PersistentVolumeReclaimDelete -) - -func (s syncer) Run(ctx context.Context) error { - oldSc := make([]string, 0) - newSc := make([]string, 0) - errs := make([]error, 0) - - // List existing K8s storage classes - - labelSelector := s.labelsSet.String() - log.Printf("Listing Storage classes with label selector \"%s\"...", labelSelector) - scList, err := s.k8sClient.StorageV1().StorageClasses().List(ctx, metav1.ListOptions{ - LabelSelector: labelSelector, - }) - if err != nil { - return fmt.Errorf("cannot list existing storage classes: %w", err) - } - for _, sc := range scList.Items { - oldSc = append(oldSc, sc.Name) - } - log.Printf("Found %v: %v\n", len(oldSc), oldSc) - - // List CloudStack disk offerings - - log.Println("Listing CloudStack disk offerings...") - p := s.csClient.DiskOffering.NewListDiskOfferingsParams() - diskOfferings, err := s.csClient.DiskOffering.ListDiskOfferings(p) - if err != nil { - return fmt.Errorf("cannot list CloudStack disk offerings: %w", err) - } - - // Iterate over CloudStack disk offerings to synchronize them - - for _, offering := range diskOfferings.DiskOfferings { - name, err := s.syncOffering(ctx, offering) - if err != nil { - err = fmt.Errorf("Error with offering %s: %w", offering.Name, err) - log.Println(err.Error()) - errs = append(errs, err) - } - if name != "" { - newSc = append(newSc, name) - } - } - log.Println("No more CloudStack disk offerings") - - // If enabled, delete unused labeled storage classes - - if s.delete { - del := toDelete(oldSc, newSc) - if len(del) == 0 { - log.Println("No storage class to delete") - } else { - for _, sc := range del { - log.Printf("Deleting storage class %s", sc) - err = s.k8sClient.StorageV1().StorageClasses().Delete(ctx, sc, metav1.DeleteOptions{}) - if err != nil { - err = fmt.Errorf("error deleting storage class %s: %w", sc, err) - log.Println(err.Error()) - errs = append(errs, err) - } - } - } - } - - if s.volumeExpansion { - allowVolumeExpansion := s.volumeExpansion - log.Printf("Setting allowVolumeExpasion to %t...", allowVolumeExpansion) - } - - if len(errs) == 0 { - return nil - } - return combinedError(errs) -} - -func (s syncer) syncOffering(ctx context.Context, offering *cloudstack.DiskOffering) (string, error) { - offeringName := offering.Name - custom := offering.Iscustomized - if !custom { - log.Printf("Disk offering \"%s\" has a fixed size: ignoring\n", offeringName) - return "", nil - } - - log.Printf("Syncing disk offering %s...", offeringName) - name, err := createStorageClassName(s.namePrefix + offeringName) - if err != nil { - log.Printf("Cannot transform name: %s", err.Error()) - name = offering.Id - } - log.Printf("Storage class name: %s", name) - - sc, err := s.k8sClient.StorageV1().StorageClasses().Get(ctx, name, metav1.GetOptions{}) - if err != nil { - if k8serrors.IsNotFound(err) { - - // Storage class does not exist; creating it - - log.Printf("Creating storage class %s", name) - - newSc := &storagev1.StorageClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: s.labelsSet, - }, - Provisioner: driver.DriverName, - VolumeBindingMode: &volBindingMode, - ReclaimPolicy: &reclaimPolicy, - AllowVolumeExpansion: &s.volumeExpansion, - Parameters: map[string]string{ - driver.DiskOfferingKey: offering.Id, - }, - } - _, err = s.k8sClient.StorageV1().StorageClasses().Create(ctx, newSc, metav1.CreateOptions{}) - return name, err - } - return "", err - } - - // Check and update AllowVolumeExpansion if necessary - if sc.AllowVolumeExpansion == nil || *sc.AllowVolumeExpansion != s.volumeExpansion { - log.Printf("Updating AllowVolumeExpansion for storage class %s", sc.Name) - sc.AllowVolumeExpansion = &s.volumeExpansion - - _, err = s.k8sClient.StorageV1().StorageClasses().Update(ctx, sc, metav1.UpdateOptions{}) - if err != nil { - return "", fmt.Errorf("failed to update AllowVolumeExpansion for storage class %s: %w", sc.Name, err) - } - } - - // Storage class already exists - - err = checkStorageClass(sc, offering.Id, s.volumeExpansion) - if err != nil { - // Updates to provisioner, reclaimpolicy, volumeBindingMode and parameters are forbidden - log.Printf("Storage class %s exists but it not compatible.", name) - return name, err - } - - // Update labels if needed - - existingLabels := labels.Set(sc.Labels) - if !s.labelsSet.AsSelector().Matches(existingLabels) { - log.Printf("Storage class %s misses labels %s: updating...", sc.Name, s.labelsSet.String()) - - sc.Labels = labels.Merge(existingLabels, s.labelsSet) - _, err = s.k8sClient.StorageV1().StorageClasses().Update(ctx, sc, metav1.UpdateOptions{}) - return name, err - } - - log.Printf("Storage class %s already ok", sc.Name) - - return name, nil -} - -func checkStorageClass(sc *storagev1.StorageClass, expectedOfferingID string, expectedVolumeExpansion bool) error { - errs := make([]error, 0) - diskOfferingID, ok := sc.Parameters[driver.DiskOfferingKey] - if !ok { - errs = append(errs, fmt.Errorf("missing parameter %s", driver.DiskOfferingKey)) - } else if diskOfferingID != expectedOfferingID { - errs = append(errs, fmt.Errorf("storage class %s has parameter %s=%s, should be %s", sc.Name, driver.DiskOfferingKey, diskOfferingID, expectedOfferingID)) - } - - if sc.ReclaimPolicy == nil || *sc.ReclaimPolicy != reclaimPolicy { - errs = append(errs, errors.New("wrong ReclaimPolicy")) - } - if sc.VolumeBindingMode == nil || *sc.VolumeBindingMode != volBindingMode { - errs = append(errs, errors.New("wrong VolumeBindingMode")) - } - if sc.AllowVolumeExpansion == nil || *sc.AllowVolumeExpansion != expectedVolumeExpansion { - errs = append(errs, fmt.Errorf("wrong AllowVolumeExpansion for storage class %s", sc.Name)) - } - - if len(errs) > 0 { - return combinedError(errs) - } - return nil -} - -func toDelete(oldSc, newSc []string) []string { - del := make([]string, 0) - for _, old := range oldSc { - var found bool - for _, new := range newSc { - if new == old { - found = true - break - } - } - if !found { - del = append(del, old) - } - } - return del -} diff --git a/pkg/syncer/syncer.go b/pkg/syncer/syncer.go deleted file mode 100644 index cf8fa6d..0000000 --- a/pkg/syncer/syncer.go +++ /dev/null @@ -1,108 +0,0 @@ -// Package syncer provides the logic used by command line tool cloudstack-csi-sc-syncer. -// -// It provides functions to synchronize CloudStack disk offerings -// to Kubernetes storage classes. -package syncer - -import ( - "context" - "fmt" - "strings" - - "github.com/apache/cloudstack-go/v2/cloudstack" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - - "github.com/leaseweb/cloudstack-csi-driver/pkg/cloud" -) - -// Config holds the syncer tool configuration. -type Config struct { - Agent string - CloudStackConfig string - KubeConfig string - Label string - NamePrefix string - Delete bool - VolumeExpansion bool -} - -// Syncer has a function Run which synchronizes CloudStack -// disk offerings to Kubernetes Storage classes. -type Syncer interface { - Run(context.Context) error -} - -// syncer is Syncer implementation. -type syncer struct { - k8sClient *kubernetes.Clientset - csClient *cloudstack.CloudStackClient - labelsSet labels.Set - namePrefix string - delete bool - volumeExpansion bool -} - -func createK8sClient(kubeconfig, agent string) (*kubernetes.Clientset, error) { - var config *rest.Config - var err error - if kubeconfig == "-" { - config, err = rest.InClusterConfig() - if err != nil { - return nil, err - } - } else { - config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) - if err != nil { - return nil, err - } - } - config.UserAgent = agent - return kubernetes.NewForConfig(config) -} - -func createCloudStackClient(cloudstackconfig string) (*cloudstack.CloudStackClient, error) { - config, err := cloud.ReadConfig(cloudstackconfig) - if err != nil { - return nil, err - } - client := cloudstack.NewAsyncClient(config.APIURL, config.APIKey, config.SecretKey, config.VerifySSL) - return client, nil -} - -func createLabelsSet(label string) labels.Set { - m := make(map[string]string) - if len(label) > 0 { - parts := strings.SplitN(label, "=", 2) - key := parts[0] - value := "" - if len(parts) > 1 { - value = parts[1] - } - m[key] = value - } - return labels.Set(m) -} - -// New creates a new Syncer instance. -func New(config Config) (Syncer, error) { - k8sClient, err := createK8sClient(config.KubeConfig, config.Agent) - if err != nil { - return nil, fmt.Errorf("cannot create Kubernetes client: %w", err) - } - csClient, err := createCloudStackClient(config.CloudStackConfig) - if err != nil { - return nil, fmt.Errorf("cannot create CloudStack client: %w", err) - } - - return syncer{ - k8sClient: k8sClient, - csClient: csClient, - labelsSet: createLabelsSet(config.Label), - namePrefix: config.NamePrefix, - delete: config.Delete, - volumeExpansion: config.VolumeExpansion, - }, nil -} diff --git a/pkg/util/doc.go b/pkg/util/doc.go deleted file mode 100644 index e1beef7..0000000 --- a/pkg/util/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package util provides shared utility functions. -package util diff --git a/pkg/util/gb.go b/pkg/util/gb.go deleted file mode 100644 index 3454857..0000000 --- a/pkg/util/gb.go +++ /dev/null @@ -1,13 +0,0 @@ -package util - -// RoundUpBytesToGB converts a size given in bytes to GB with -// an upper rounding (it gives the smallest amount in GB -// which is greater than the original amount) -func RoundUpBytesToGB(n int64) int64 { - return (((n+1023)/1024+1023)/1024 + 1023) / 1024 -} - -// GigaBytesToBytes gives an exact conversion from GigaBytes to Bytes -func GigaBytesToBytes(gb int64) int64 { - return gb * 1024 * 1024 * 1024 -} diff --git a/pkg/util/gb_test.go b/pkg/util/gb_test.go deleted file mode 100644 index bde5ad8..0000000 --- a/pkg/util/gb_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package util - -import ( - "strconv" - "testing" -) - -func TestRoundUpBytesToGB(t *testing.T) { - cases := []struct { - b int64 - expectedGb int64 - }{ - {100, 1}, - {3221225472, 3}, - {3000000000, 3}, - {50 * 1024 * 1024 * 1024, 50}, - {50*1024*1024*1024 - 1, 50}, - {50*1024*1024*1024 + 1, 51}, - } - for _, c := range cases { - t.Run(strconv.FormatInt(c.b, 10), func(t *testing.T) { - gb := RoundUpBytesToGB(c.b) - if gb != c.expectedGb { - t.Errorf("%v bytes: expecting %v, got %v", c.b, c.expectedGb, gb) - } - }) - } -} - -func TestGigaBytesToBytes(t *testing.T) { - var gb int64 = 5 - b := GigaBytesToBytes(gb) - var expectedBytes int64 = 5368709120 - if b != expectedBytes { - t.Errorf("Expected %v, got %v", expectedBytes, b) - } - back := RoundUpBytesToGB(b) - if back != gb { - t.Errorf("Expected %v, got %v", gb, back) - } -} diff --git a/pkg/util/idlocker.go b/pkg/util/idlocker.go deleted file mode 100644 index 802b50b..0000000 --- a/pkg/util/idlocker.go +++ /dev/null @@ -1,250 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package util - -import ( - "context" - "fmt" - "sync" - - "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" - "k8s.io/apimachinery/pkg/util/sets" -) - -const ( - // VolumeOperationAlreadyExistsFmt string format to return for concurrent operation. - VolumeOperationAlreadyExistsFmt = "an operation with the given Volume ID %s already exists" - - // SnapshotOperationAlreadyExistsFmt string format to return for concurrent operation. - SnapshotOperationAlreadyExistsFmt = "an operation with the given Snapshot ID %s already exists" -) - -// VolumeLocks implements a map with atomic operations. It stores a set of all volume IDs -// with an ongoing operation. -type VolumeLocks struct { - locks sets.Set[string] - mux sync.Mutex -} - -// NewVolumeLocks returns new VolumeLocks. -func NewVolumeLocks() *VolumeLocks { - return &VolumeLocks{ - locks: sets.New[string](), - } -} - -// TryAcquire tries to acquire the lock for operating on volumeID and returns true if successful. -// If another operation is already using volumeID, returns false. -func (vl *VolumeLocks) TryAcquire(volumeID string) bool { - vl.mux.Lock() - defer vl.mux.Unlock() - if vl.locks.Has(volumeID) { - return false - } - vl.locks.Insert(volumeID) - - return true -} - -// Release deletes the lock on volumeID. -func (vl *VolumeLocks) Release(volumeID string) { - vl.mux.Lock() - defer vl.mux.Unlock() - vl.locks.Delete(volumeID) -} - -type operation string - -const ( - createOp operation = "create" - deleteOp operation = "delete" - cloneOpt operation = "clone" - restoreOp operation = "restore" - expandOp operation = "expand" -) - -// OperationLock implements a map with atomic operations. -type OperationLock struct { - // lock is a map of map, internal key is the list of id and its counters - // and the outer map key is the operation type it will be one of the above - // const - // - // example map[restore][xxx-xxx-xxx-xxx]1 - // map[restore][xxx-xxx-xxx-xxx]2 - // the counter value will be increased for allowed parallel operations and - // it will be decreased when the operation is completed, when the counter - // value goes to zero the `xxx-xxx-xxx` key will be removed from the - // operation map. - locks map[operation]map[string]int - // lock to avoid concurrent operation on map - mux sync.Mutex - // context for logging - ctx context.Context -} - -// NewOperationLock returns new OperationLock. -func NewOperationLock(ctx context.Context) *OperationLock { - lock := make(map[operation]map[string]int) - lock[createOp] = make(map[string]int) - lock[deleteOp] = make(map[string]int) - lock[cloneOpt] = make(map[string]int) - lock[restoreOp] = make(map[string]int) - lock[expandOp] = make(map[string]int) - - return &OperationLock{ - locks: lock, - ctx: ctx, - } -} - -// tryAcquire tries to acquire the lock for operating on volumeID and returns true if successful. -// If another operation is already using volumeID, returns false. -func (ol *OperationLock) tryAcquire(op operation, volumeID string) error { - ol.mux.Lock() - defer ol.mux.Unlock() - switch op { - case createOp: - // snapshot controller make sure the pvc which is the source for the - // snapshot request won't get deleted while snapshot is getting created, - // so we dont need to check for any ongoing delete operation here on the - // volume. - // increment the counter for snapshot create operation - val := ol.locks[createOp][volumeID] - ol.locks[createOp][volumeID] = val + 1 - case cloneOpt: - // During clone operation, controller make sure no pvc deletion happens on the - // referred PVC datasource, so we are safe from source PVC delete. - - // Check any expand operation is going on for given volume ID. - // if yes we need to return an error to avoid issues. - if _, ok := ol.locks[expandOp][volumeID]; ok { - return fmt.Errorf("an Expand operation with given id %s already exists", volumeID) - } - // increment the counter for clone operation - val := ol.locks[cloneOpt][volumeID] - ol.locks[cloneOpt][volumeID] = val + 1 - case deleteOp: - // During delete operation the volume should not be under expand, - // check any expand operation is going on for given volume ID - if _, ok := ol.locks[expandOp][volumeID]; ok { - return fmt.Errorf("an Expand operation with given id %s already exists", volumeID) - } - // check any restore operation is going on for given volume ID - if _, ok := ol.locks[restoreOp][volumeID]; ok { - return fmt.Errorf("a Restore operation with given id %s already exists", volumeID) - } - ol.locks[deleteOp][volumeID] = 1 - case restoreOp: - // During restore operation the volume should not be deleted - // check any delete operation is going on for given volume ID - if _, ok := ol.locks[deleteOp][volumeID]; ok { - return fmt.Errorf("a Delete operation with given id %s already exists", volumeID) - } - // increment the counter for restore operation - val := ol.locks[restoreOp][volumeID] - ol.locks[restoreOp][volumeID] = val + 1 - case expandOp: - // During expand operation the volume should not be deleted or cloned - // and there should not be a create operation also. - // check any delete operation is going on for given volume ID - if _, ok := ol.locks[deleteOp][volumeID]; ok { - return fmt.Errorf("a Delete operation with given id %s already exists", volumeID) - } - // check any clone operation is going on for given volume ID - if _, ok := ol.locks[cloneOpt][volumeID]; ok { - return fmt.Errorf("a Clone operation with given id %s already exists", volumeID) - } - // check any delete operation is going on for given volume ID - if _, ok := ol.locks[createOp][volumeID]; ok { - return fmt.Errorf("a Create operation with given id %s already exists", volumeID) - } - - ol.locks[expandOp][volumeID] = 1 - default: - return fmt.Errorf("%v operation not supported", op) - } - - return nil -} - -// GetSnapshotCreateLock gets the snapshot lock on given volumeID. -func (ol *OperationLock) GetSnapshotCreateLock(volumeID string) error { - return ol.tryAcquire(createOp, volumeID) -} - -// GetCloneLock gets the clone lock on given volumeID. -func (ol *OperationLock) GetCloneLock(volumeID string) error { - return ol.tryAcquire(cloneOpt, volumeID) -} - -// GetDeleteLock gets the delete lock on given volumeID,ensures that there is -// no clone,restore and expand operation on given volumeID. -func (ol *OperationLock) GetDeleteLock(volumeID string) error { - return ol.tryAcquire(deleteOp, volumeID) -} - -// GetRestoreLock gets the restore lock on given volumeID,ensures that there is -// no delete operation on given volumeID. -func (ol *OperationLock) GetRestoreLock(volumeID string) error { - return ol.tryAcquire(restoreOp, volumeID) -} - -// GetExpandLock gets the expand lock on given volumeID,ensures that there is -// no delete and clone operation on given volumeID. -func (ol *OperationLock) GetExpandLock(volumeID string) error { - return ol.tryAcquire(expandOp, volumeID) -} - -// ReleaseSnapshotCreateLock releases the create lock on given volumeID. -func (ol *OperationLock) ReleaseSnapshotCreateLock(volumeID string) { - ol.release(createOp, volumeID) -} - -// ReleaseCloneLock releases the clone lock on given volumeID. -func (ol *OperationLock) ReleaseCloneLock(volumeID string) { - ol.release(cloneOpt, volumeID) -} - -// ReleaseDeleteLock releases the delete lock on given volumeID. -func (ol *OperationLock) ReleaseDeleteLock(volumeID string) { - ol.release(deleteOp, volumeID) -} - -// ReleaseRestoreLock releases the restore lock on given volumeID. -func (ol *OperationLock) ReleaseRestoreLock(volumeID string) { - ol.release(restoreOp, volumeID) -} - -// ReleaseExpandLock releases the expand lock on given volumeID. -func (ol *OperationLock) ReleaseExpandLock(volumeID string) { - ol.release(expandOp, volumeID) -} - -// release deletes the lock on volumeID. -func (ol *OperationLock) release(op operation, volumeID string) { - ol.mux.Lock() - defer ol.mux.Unlock() - switch op { - case cloneOpt, createOp, expandOp, restoreOp, deleteOp: - if val, ok := ol.locks[op][volumeID]; ok { - // decrement the counter for operation - ol.locks[op][volumeID] = val - 1 - if ol.locks[op][volumeID] == 0 { - delete(ol.locks[op], volumeID) - } - } - default: - ctxzap.Extract(ol.ctx).Sugar().Errorf("%v operation not supported", op) - } -} diff --git a/pkg/util/idlocker_test.go b/pkg/util/idlocker_test.go deleted file mode 100644 index f89efc5..0000000 --- a/pkg/util/idlocker_test.go +++ /dev/null @@ -1,122 +0,0 @@ -/* -Copyright 2019 ceph-csi authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package util - -import ( - "context" - "testing" -) - -// very basic tests for the moment. -func TestIDLocker(t *testing.T) { - t.Parallel() - fakeID := "fake-id" - locks := NewVolumeLocks() - // acquire lock for fake-id - ok := locks.TryAcquire(fakeID) - - if !ok { - t.Errorf("TryAcquire failed: want (%v), got (%v)", - true, ok) - } - - // try to acquire lock again for fake-id, as lock is already present - // it should fail - ok = locks.TryAcquire(fakeID) - - if ok { - t.Errorf("TryAcquire failed: want (%v), got (%v)", - false, ok) - } - - // release the lock for fake-id and try to get lock again, it should pass - locks.Release(fakeID) - ok = locks.TryAcquire(fakeID) - - if !ok { - t.Errorf("TryAcquire failed: want (%v), got (%v)", - true, ok) - } -} - -func TestOperationLocks(t *testing.T) { - t.Parallel() - volumeID := "test-vol" - lock := NewOperationLock(context.Background()) - err := lock.GetCloneLock(volumeID) - if err != nil { - t.Errorf("failed to acquire clone lock for %s %s", volumeID, err) - } - - err = lock.GetExpandLock(volumeID) - if err == nil { - t.Errorf("expected to fail for GetExpandLock for %s", volumeID) - } - lock.ReleaseCloneLock(volumeID) - - // Get multiple clone operation - err = lock.GetCloneLock(volumeID) - if err != nil { - t.Errorf("failed to acquire clone lock for %s %s", volumeID, err) - } - err = lock.GetCloneLock(volumeID) - if err != nil { - t.Errorf("failed to acquire clone lock for %s %s", volumeID, err) - } - err = lock.GetCloneLock(volumeID) - if err != nil { - t.Errorf("failed to acquire clone lock for %s %s", volumeID, err) - } - // release all clone locks - lock.ReleaseCloneLock(volumeID) - lock.ReleaseCloneLock(volumeID) - lock.ReleaseCloneLock(volumeID) - - // release extra lock it should not cause any issue as the key is already - // deleted from the map - lock.ReleaseCloneLock(volumeID) - - // get multiple restore lock - err = lock.GetRestoreLock(volumeID) - if err != nil { - t.Errorf("failed to acquire restore lock for %s %s", volumeID, err) - } - err = lock.GetRestoreLock(volumeID) - if err != nil { - t.Errorf("failed to acquire restore lock for %s %s", volumeID, err) - } - err = lock.GetRestoreLock(volumeID) - if err != nil { - t.Errorf("failed to acquire restore lock for %s %s", volumeID, err) - } - // release all restore locks - lock.ReleaseRestoreLock(volumeID) - lock.ReleaseRestoreLock(volumeID) - lock.ReleaseRestoreLock(volumeID) - - err = lock.GetSnapshotCreateLock(volumeID) - if err != nil { - t.Errorf("failed to acquire createSnapshot lock for %s %s", volumeID, err) - } - lock.ReleaseSnapshotCreateLock(volumeID) - - err = lock.GetDeleteLock(volumeID) - if err != nil { - t.Errorf("failed to get GetDeleteLock for %s %v", volumeID, err) - } - lock.ReleaseDeleteLock(volumeID) -} diff --git a/test/e2e/README.md b/test/e2e/README.md deleted file mode 100644 index 5221d2d..0000000 --- a/test/e2e/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# e2e tests - -1. Deploy a Kubernetes cluster on Apache CloudStack; - -1. Deploy the cloudstack-csi-driver; - -1. Set environment variables: - - | Name | Description | Required / Default behaviour | - | ------------------ | -------------------------------------------------------------------------------------------------------- | --------------------------------------------- | - | `DISK_OFFERING_ID` | ID of the CloudStack disk offering to be used in e2e tests. Must accept custom sizes. | **REQUIRED** | - | `KUBECONFIG` | Path to your `kubeconfig` file | Optional - Defaults to `${HOME}/.kube/config` | - | `KUBE_SSH_USER` | Username to use to connect to cluster nodes via SSH | Optional - Defaults to `${USER}`. | - | `KUBE_SSH_KEY` | Path of the SSH key to use to connect to cluster nodes via SSH - may be absolute or relative to `~/.ssh` | Optional - Defaults to `id_rsa` | - | `KUBE_SSH_BASTION` | Address (`host:port`) of a bastion host to use to connect to cluster nodes via SSH | Optional - Direct connection if not set | - -1. From the root of this repository, execute tests with: - - ``` - make test-e2e - ``` diff --git a/test/e2e/run.sh b/test/e2e/run.sh deleted file mode 100755 index e4aa5df..0000000 --- a/test/e2e/run.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -cd "$(dirname "$0")" || exit - -KUBECONFIG=${KUBECONFIG:=${HOME}/.kube/config} - -if [ ! -f "$KUBECONFIG" ]; then - echo "Kubeconfig $KUBECONFIG not found!" - exit 1 -fi - -if [ -z "$DISK_OFFERING_ID" ]; then - echo "Variable DISK_OFFERING_ID not set!" - exit 1 -fi - -# Create storage class "cloudstack-csi-driver-e2e" -scName="cloudstack-csi-driver-e2e" -sed "s//${DISK_OFFERING_ID}/" storageclass.yaml | kubectl apply -f - - -# Run in parallel when possible (exclude [Feature:.*], [Disruptive] and [Serial]): -./ginkgo -p -progress -v \ - -focus='External.Storage.*csi-cloudstack' \ - -skip='\[Feature:|\[Disruptive\]|\[Serial\]' \ - e2e.test -- \ - -storage.testdriver=testdriver.yaml \ - --kubeconfig="$KUBECONFIG" - -# Delete volume populators CRD created by e2e.test in the previous run -# This prevents a test from failing with CRD already exists -kubectl delete crd volumepopulators.populator.storage.k8s.io - -# Then run the remaining tests, sequentially: -./ginkgo -progress -v \ - -focus='External.Storage.*csi-cloudstack.*(\[Feature:|\[Disruptive\]|\[Serial\])' \ - e2e.test -- \ - -storage.testdriver=testdriver.yaml \ - --kubeconfig="$KUBECONFIG" - -# Delete storage class -kubectl delete storageclasses.storage.k8s.io "${scName}" || echo "No storage class named ${scName}" \ No newline at end of file diff --git a/test/e2e/storageclass.yaml b/test/e2e/storageclass.yaml deleted file mode 100644 index 116d77f..0000000 --- a/test/e2e/storageclass.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: storage.k8s.io/v1 -kind: StorageClass -metadata: - name: cloudstack-csi-driver-e2e -provisioner: csi.cloudstack.apache.org -reclaimPolicy: Delete -volumeBindingMode: WaitForFirstConsumer -allowVolumeExpansion: false -parameters: - csi.cloudstack.apache.org/disk-offering-id: diff --git a/test/e2e/testdriver.yaml b/test/e2e/testdriver.yaml deleted file mode 100644 index 701563a..0000000 --- a/test/e2e/testdriver.yaml +++ /dev/null @@ -1,10 +0,0 @@ -StorageClass: - FromExistingClassName: cloudstack-csi-driver-e2e -SnapshotClass: - FromName: true -DriverInfo: - Name: csi-cloudstack - Capabilities: - persistence: true - block: true - exec: true diff --git a/test/sanity/sanity_test.go b/test/sanity/sanity_test.go deleted file mode 100644 index ce15105..0000000 --- a/test/sanity/sanity_test.go +++ /dev/null @@ -1,48 +0,0 @@ -//go:build sanity - -package sanity - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/kubernetes-csi/csi-test/v5/pkg/sanity" - "go.uber.org/zap" - - "github.com/leaseweb/cloudstack-csi-driver/pkg/cloud/fake" - "github.com/leaseweb/cloudstack-csi-driver/pkg/driver" - "github.com/leaseweb/cloudstack-csi-driver/pkg/mount" -) - -func TestSanity(t *testing.T) { - // Setup driver - dir, err := ioutil.TempDir("", "sanity-cloudstack-csi") - if err != nil { - t.Fatalf("error creating directory: %v", err) - } - defer os.RemoveAll(dir) - - targetPath := filepath.Join(dir, "target") - stagingPath := filepath.Join(dir, "staging") - endpoint := "unix://" + filepath.Join(dir, "csi.sock") - - config := sanity.NewTestConfig() - config.TargetPath = targetPath - config.StagingPath = stagingPath - config.Address = endpoint - config.TestVolumeParameters = map[string]string{ - driver.DiskOfferingKey: "9743fd77-0f5d-4ef9-b2f8-f194235c769c", - } - - csiDriver, err := driver.New(endpoint, fake.New(), mount.NewFake(), "node", "v0", zap.NewNop()) - if err != nil { - t.Fatalf("error creating driver: %v", err) - } - go func() { - csiDriver.Run() - }() - - sanity.Test(t, config) -}