├── .trivyignore ├── test ├── bdd-test │ ├── c.out │ ├── run.sh │ └── bdd_main_test.go ├── scale-test │ ├── 100volumes │ │ ├── values.yaml │ │ └── Chart.yaml │ ├── 10volumes │ │ ├── values.yaml │ │ ├── Chart.yaml │ │ └── templates │ │ │ └── test.yaml │ ├── 20volumes │ │ ├── values.yaml │ │ ├── Chart.yaml │ │ └── templates │ │ │ └── test.yaml │ ├── 2volumes │ │ ├── values.yaml │ │ ├── Chart.yaml │ │ └── templates │ │ │ └── test.yaml │ ├── 30volumes │ │ ├── values.yaml │ │ └── Chart.yaml │ ├── 50volumes │ │ ├── values.yaml │ │ └── Chart.yaml │ ├── serviceAccount.yaml │ ├── README.md │ ├── rescaletest.sh │ └── scaletest.sh ├── sanity │ ├── params.yaml │ ├── run.sh │ ├── start_driver.sh │ ├── secrets.yaml │ └── README.md ├── integration-test │ ├── run.sh │ ├── integration_main_test.go │ └── features │ │ └── integration.feature └── sample.yaml ├── core ├── .gitignore ├── semver.tpl ├── core.go └── semver │ ├── semver_test.go │ └── semver.go ├── dell-csi-helm-installer ├── .gitignore ├── verify-csi-unity.sh ├── csi-uninstall.sh ├── common.sh └── README.md ├── .gitignore ├── .github ├── workflows │ ├── license-checker.yaml │ ├── common-workflows.yaml │ ├── update-libraries.yaml │ ├── go-version.yaml │ ├── update-libraries-to-commits.yaml │ ├── image-version-update.yaml │ └── create-tag-release.yaml ├── CODEOWNERS └── pull_request_template.md ├── hooks └── pre-commit ├── samples ├── secret │ ├── emptysecret.yaml │ └── secret.yaml ├── volumesnapshotclass │ └── snapclass-v1.yaml └── storageclass │ ├── unity-plain.yaml │ ├── unity-iscsi.yaml │ ├── unity-fc.yaml │ └── unity-nfs.yaml ├── scripts ├── run.sh └── check.sh ├── docker.mk ├── common └── constants.go ├── env.sh ├── provider ├── provider.go └── provider_test.go ├── Dockerfile ├── service ├── envvars.go ├── identity.go ├── main_test.go ├── identity_test.go └── logging │ ├── logging.go │ └── logging_test.go ├── k8sutils ├── k8sutils.go └── k8sutils_test.go ├── README.md ├── Makefile ├── main.go ├── go.mod └── main_test.go /.trivyignore: -------------------------------------------------------------------------------- 1 | CVE-2019-1010022 -------------------------------------------------------------------------------- /test/bdd-test/c.out: -------------------------------------------------------------------------------- 1 | mode: set 2 | -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | core_generated.go 2 | -------------------------------------------------------------------------------- /dell-csi-helm-installer/.gitignore: -------------------------------------------------------------------------------- 1 | images.manifest 2 | images.tar 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | builds/ 3 | .idea 4 | .idea/ 5 | .vscode/ 6 | *.exe 7 | vendor/ 8 | bin/ 9 | semver.mk 10 | go.sum 11 | ./csi-unity 12 | csi-unity.exe 13 | helm/myvalues.yaml 14 | test.properties 15 | -------------------------------------------------------------------------------- /core/semver.tpl: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import "time" 4 | 5 | func init() { 6 | SemVer = "{{.SemVer}}" 7 | CommitSha7 = "{{.Sha7}}" 8 | CommitSha32 = "{{.Sha32}}" 9 | CommitTime = time.Unix({{.Epoch}}, 0) 10 | } 11 | -------------------------------------------------------------------------------- /.github/workflows/license-checker.yaml: -------------------------------------------------------------------------------- 1 | name: Weekly License Header Check 2 | 3 | on: 4 | workflow_dispatch: 5 | jobs: 6 | license-check: 7 | name: Run License Header Checker 8 | uses: dell/common-github-actions/.github/workflows/license-checker.yaml@main 9 | secrets: inherit 10 | -------------------------------------------------------------------------------- /.github/workflows/common-workflows.yaml: -------------------------------------------------------------------------------- 1 | name: Common Workflows 2 | on: # yamllint disable-line rule:truthy 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: ["**"] 7 | 8 | jobs: 9 | 10 | # golang static analysis checks 11 | go-static-analysis: 12 | uses: dell/common-github-actions/.github/workflows/go-static-analysis.yaml@main 13 | name: Golang Validation 14 | 15 | common: 16 | name: Quality Checks 17 | uses: dell/common-github-actions/.github/workflows/go-common.yml@main 18 | -------------------------------------------------------------------------------- /hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # git gofmt pre-commit hook 4 | # 5 | # This script does not handle file names that contain spaces. 6 | gofiles=$(git diff --cached --name-only --diff-filter=ACM | grep '\.go$') 7 | [ -z "$gofiles" ] && exit 0 8 | 9 | unformatted=$(gofmt -l $gofiles) 10 | [ -z "$unformatted" ] && exit 0 11 | 12 | # Some files are not gofmt'd. Print message and fail. 13 | 14 | echo >&2 "Go files must be formatted with gofmt. Please run:" 15 | for fn in $unformatted; do 16 | echo >&2 " gofmt -w $PWD/$fn" 17 | done 18 | 19 | exit 1 20 | 21 | -------------------------------------------------------------------------------- /test/scale-test/100volumes/values.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | StorageClassName: unity-iscsi 15 | replicas: 3 16 | -------------------------------------------------------------------------------- /test/scale-test/10volumes/values.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | StorageClassName: unity-iscsi 15 | replicas: 3 16 | -------------------------------------------------------------------------------- /test/scale-test/20volumes/values.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | StorageClassName: unity-iscsi 15 | replicas: 3 16 | -------------------------------------------------------------------------------- /test/scale-test/2volumes/values.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | StorageClassName: unity-iscsi 15 | replicas: 3 16 | -------------------------------------------------------------------------------- /test/scale-test/30volumes/values.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | StorageClassName: unity-iscsi 15 | replicas: 3 16 | -------------------------------------------------------------------------------- /test/scale-test/50volumes/values.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | StorageClassName: unity-iscsi 15 | replicas: 3 16 | -------------------------------------------------------------------------------- /test/scale-test/serviceAccount.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | apiVersion: v1 15 | kind: ServiceAccount 16 | metadata: 17 | name: unitytest 18 | -------------------------------------------------------------------------------- /test/sanity/params.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | arrayId: "" # update arrayId here 15 | storagePool: "pool_1" # update storagePool here 16 | -------------------------------------------------------------------------------- /.github/workflows/update-libraries.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Dell Inc., or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Reusable workflow to perform updates of Dell client libraries 10 | name: Dell Libraries Release Update 11 | on: # yamllint disable-line rule:truthy 12 | workflow_dispatch: 13 | repository_dispatch: 14 | types: [latest-released-libraries] 15 | 16 | jobs: 17 | package-update: 18 | uses: dell/common-github-actions/.github/workflows/update-libraries.yml@main 19 | name: Dell Libraries Update 20 | secrets: inherit 21 | -------------------------------------------------------------------------------- /.github/workflows/go-version.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 Dell Inc., or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Reusable workflow to perform go version update on Golang based projects 10 | name: Go Version Update 11 | 12 | on: # yamllint disable-line rule:truthy 13 | workflow_dispatch: 14 | repository_dispatch: 15 | types: [go-update-workflow] 16 | 17 | jobs: 18 | # go version update 19 | go-version-update: 20 | uses: dell/common-github-actions/.github/workflows/go-version-workflow.yaml@main 21 | name: Go Version Update 22 | secrets: inherit 23 | -------------------------------------------------------------------------------- /.github/workflows/update-libraries-to-commits.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Dell Inc., or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Reusable workflow to perform updates of Dell client libraries to latest commits 10 | name: Dell Libraries Commit Update 11 | on: # yamllint disable-line rule:truthy 12 | workflow_dispatch: 13 | repository_dispatch: 14 | types: [latest-commits-libraries] 15 | 16 | jobs: 17 | package-update: 18 | uses: dell/common-github-actions/.github/workflows/update-libraries-to-commits.yml@main 19 | name: Dell Libraries Update 20 | secrets: inherit 21 | -------------------------------------------------------------------------------- /samples/secret/emptysecret.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | apiVersion: v1 15 | kind: Secret 16 | metadata: 17 | name: unity-certs-0 18 | namespace: unity 19 | type: Opaque 20 | data: 21 | cert-0: "" 22 | -------------------------------------------------------------------------------- /test/scale-test/10volumes/Chart.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | name: 10volumes 15 | version: 1.0.0 16 | appVersion: 1.0.0 17 | description: | 18 | Tests CSI Unity deployments. 19 | keywords: 20 | - csi-unity 21 | - storage 22 | engine: gotpl 23 | -------------------------------------------------------------------------------- /test/scale-test/2volumes/Chart.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | name: 2volumes 15 | version: 1.0.0 16 | appVersion: 1.0.0 17 | description: | 18 | Tests CSI Unity deployments. 19 | keywords: 20 | - csi-unity 21 | - storage 22 | engine: gotpl 23 | -------------------------------------------------------------------------------- /test/scale-test/30volumes/Chart.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | name: 30volumes 15 | version: 1.0.0 16 | appVersion: 1.0.0 17 | description: | 18 | Tests CSI Unity deployments. 19 | keywords: 20 | - csi-unity 21 | - storage 22 | engine: gotpl 23 | -------------------------------------------------------------------------------- /test/scale-test/50volumes/Chart.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | name: 50volumes 15 | version: 1.0.0 16 | appVersion: 1.0.0 17 | description: | 18 | Tests CSI Unity deployments. 19 | keywords: 20 | - csi-unity 21 | - storage 22 | engine: gotpl 23 | -------------------------------------------------------------------------------- /test/scale-test/100volumes/Chart.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | name: 100volumes 15 | version: 1.0.0 16 | appVersion: 1.0.0 17 | description: | 18 | Tests CSI Unity deployments. 19 | keywords: 20 | - csi-unity 21 | - storage 22 | engine: gotpl 23 | -------------------------------------------------------------------------------- /test/scale-test/20volumes/Chart.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | name: 20volumes 15 | version: 1.0.0 16 | appVersion: 1.0.0 17 | description: | 18 | Tests CSI Unity XT deployments. 19 | keywords: 20 | - csi-unity 21 | - storage 22 | engine: gotpl 23 | -------------------------------------------------------------------------------- /test/sanity/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright © 2019 Dell Inc. or its subsidiaries. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | rm -rf /tmp/csi-mount 16 | 17 | csi-sanity --ginkgo.v \ 18 | --csi.endpoint=unix_sock \ 19 | --csi.secrets=secrets.yaml \ 20 | --csi.testvolumeparameters=params.yaml \ 21 | --ginkgo.skip "GetCapacity|ListSnapshots|create a volume with already existing name and different capacity" \ 22 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # CODEOWNERS 2 | # 3 | # Documentation for this file can be found at: 4 | # https://help.github.com/en/articles/about-code-owners 5 | # These are the default owners for the code and will 6 | # be requested for review when someone opens a pull request. 7 | # Order is alphabetical for easier maintenance. 8 | # 9 | # Aaron Tye (atye) 10 | # Aly Nathoo (anathoodell) 11 | # Christian Coffield (ChristianAtDell) 12 | # Keerthi Bandapati (bandak2) 13 | # Deepak Ghivari (Deepak-Ghivari) 14 | # Evgeny Uglov (EvgenyUglov) 15 | # Fernando Alfaro Campos (falfaroc) 16 | # Francis Nijay (francis-nijay) 17 | # Karthik K (karthikk92) 18 | # Spandita Panigrahi (panigs7) 19 | # Rajendra Indukuri (rajendraindukuri) 20 | # Santhosh Lakshmanan (santhoshatdell) 21 | # Shayna Finocchiaro (shaynafinocchiaro) 22 | 23 | # for all files: 24 | * @atye @ChristianAtDell @bandak2 @Deepak-Ghivari @EvgenyUglov @falfaroc @francis-nijay @karthikk92 @panigs7 @rajendraindukuri @santhoshatdell @shaynafinocchiaro @anathoodell 25 | -------------------------------------------------------------------------------- /.github/workflows/image-version-update.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Dell Inc., or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | # Reusable workflow to perform image version update on Golang based projects 10 | name: Image Version Update 11 | 12 | on: # yamllint disable-line rule:truthy 13 | workflow_dispatch: 14 | inputs: 15 | version: 16 | description: "Version to release (major, minor, patch) Ex: minor" 17 | required: true 18 | repository_dispatch: 19 | types: [image-update-workflow] 20 | 21 | jobs: 22 | # image version update 23 | image-version-update: 24 | uses: dell/common-github-actions/.github/workflows/image-version-workflow.yaml@main 25 | with: 26 | version: "${{ github.event.inputs.version || 'minor' }}" 27 | secrets: inherit 28 | -------------------------------------------------------------------------------- /test/integration-test/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright © 2019 Dell Inc. or its subsidiaries. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # This will run coverage analysis using the integration testing. 16 | # The env.sh must point to a valid Unity Array# on this system. 17 | 18 | rm -f /root/go/bin/csi.sock 19 | source ../../env.sh 20 | echo $SDC_GUID 21 | go test -v -coverprofile=c.out -timeout 30m -coverpkg=github.com/dell/csi-unity/service *test.go & 22 | wait 23 | go tool cover -html=c.out 24 | 25 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Description 2 | A few sentences describing the overall goals of the pull request's commits. 3 | 4 | # GitHub Issues 5 | List the GitHub issues impacted by this PR: 6 | 7 | | GitHub Issue # | 8 | | -------------- | 9 | | | 10 | 11 | # Checklist: 12 | 13 | - [ ] I have performed a self-review of my own code to ensure there are no formatting, vetting, linting, or security issues 14 | - [ ] I have verified that new and existing unit tests pass locally with my changes 15 | - [ ] I have not allowed coverage numbers to degenerate 16 | - [ ] I have maintained at least 90% code coverage 17 | - [ ] I have commented my code, particularly in hard-to-understand areas 18 | - [ ] I have made corresponding changes to the documentation 19 | - [ ] I have added tests that prove my fix is effective or that my feature works 20 | - [ ] Backward compatibility is not broken 21 | 22 | # How Has This Been Tested? 23 | Please describe the tests that you ran to verify your changes. Please also list any relevant details for your test configuration 24 | 25 | - [ ] Test A 26 | - [ ] Test B 27 | -------------------------------------------------------------------------------- /.github/workflows/create-tag-release.yaml: -------------------------------------------------------------------------------- 1 | name: Create Tag and Release 2 | # Invocable as a reusable workflow 3 | # Can be manually triggered 4 | on: # yamllint disable-line rule:truthy 5 | workflow_call: 6 | workflow_dispatch: 7 | inputs: 8 | option: 9 | description: "Select type of release. If first release, use major and it will release v1.0.0." 10 | required: true 11 | type: choice 12 | default: "minor" 13 | options: 14 | - major 15 | - minor 16 | - patch 17 | - version 18 | version: 19 | description: "Specific semver version to release. Only used when 'version' is the selected option. Example: v2.1.x." 20 | required: false 21 | type: string 22 | repository_dispatch: 23 | types: [auto-release-workflow] 24 | 25 | jobs: 26 | csm-release: 27 | name: Create Tag and Release 28 | uses: dell/common-github-actions/.github/workflows/create-tag-release.yaml@main 29 | with: 30 | option: ${{ inputs.option || 'minor' }} 31 | version: ${{ inputs.version || '' }} 32 | secrets: inherit 33 | -------------------------------------------------------------------------------- /scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Copyright © 2019 Dell Inc. or its subsidiaries. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | echo "Endpoint ${CSI_ENDPOINT}" 16 | if [[ -S ${CSI_ENDPOINT} ]]; then 17 | rm -rf ${CSI_ENDPOINT} 18 | echo "Removed endpoint $CSI_ENDPOINT" 19 | ls -l ${CSI_ENDPOINT} 20 | elif [[ ${CSI_ENDPOINT} == unix://* ]]; then 21 | ENDPOINT=${CSI_ENDPOINT/unix:\/\//} 22 | ls -l ${ENDPOINT} 23 | if [[ -S ${ENDPOINT} ]]; then 24 | rm -rf ${ENDPOINT} 25 | echo "Removed endpoint $ENDPOINT" 26 | fi 27 | fi 28 | exec /csi-unity "$@" 29 | -------------------------------------------------------------------------------- /test/bdd-test/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright © 2019 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # 14 | # This will run coverage analysis using the integration testing. 15 | # The env.sh must point to a valid Unity XT Array# on this system. 16 | 17 | 18 | source ../../env.sh 19 | mkdir $(dirname "${CSI_ENDPOINT}") || rm -f ${CSI_ENDPOINT} 20 | echo $SDC_GUID 21 | go test -v -coverprofile=c.out -timeout 60m -coverpkg=github.com/dell/csi-unity/service *test.go & 22 | # go test -v --godog.tags=wip -coverprofile=c.out -timeout 60m -coverpkg=github.com/dell/csi-unity/service *test.go & 23 | wait 24 | go tool cover -html=c.out -------------------------------------------------------------------------------- /test/sanity/start_driver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright © 2019 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # 14 | # This will run coverage analysis using the integration testing. 15 | # The env.sh must point to a valid Unisphere deployment and the iscsi packages must be installed 16 | # on this system. This will make real calls to Unisphere 17 | 18 | rm -f unix_sock 19 | . ../../env.sh 20 | echo ENDPOINT $CSI_ENDPOINT 21 | echo "Starting the csi-unity driver. You should wait until the node setup is complete before running tests." 22 | ../../csi-unity --driver-name=$DRIVER_NAME --driver-config=$DRIVER_CONFIG --driver-secret=$DRIVER_SECRET 23 | -------------------------------------------------------------------------------- /core/core.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | //go:generate go run semver/semver.go -f semver.tpl -o core_generated.go 15 | 16 | package core 17 | 18 | import "time" 19 | 20 | var ( 21 | // SemVer is the semantic version. 22 | SemVer = "unknown" 23 | 24 | // CommitSha7 is the short version of the commit hash from which 25 | // this program was built. 26 | CommitSha7 string 27 | 28 | // CommitSha32 is the long version of the commit hash from which 29 | // this program was built. 30 | CommitSha32 string 31 | 32 | // CommitTime is the commit timestamp of the commit from which 33 | // this program was built. 34 | CommitTime time.Time 35 | ) 36 | -------------------------------------------------------------------------------- /samples/volumesnapshotclass/snapclass-v1.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | apiVersion: snapshot.storage.k8s.io/v1 15 | kind: VolumeSnapshotClass 16 | metadata: 17 | name: unity-snapclass 18 | driver: csi-unity.dellemc.com 19 | # Configure what happens to a VolumeSnapshotContent when the VolumeSnapshot object 20 | # it is bound to is to be deleted 21 | # Allowed values: 22 | # Delete: the underlying storage snapshot will be deleted along with the VolumeSnapshotContent object. 23 | # Retain: both the underlying snapshot and VolumeSnapshotContent remain. 24 | # Default value: None 25 | # Optional: false 26 | # Examples: Delete 27 | deletionPolicy: Delete 28 | -------------------------------------------------------------------------------- /docker.mk: -------------------------------------------------------------------------------- 1 | # Includes the following generated file to get semantic version information 2 | include semver.mk 3 | ifdef NOTES 4 | RELNOTE="-$(NOTES)" 5 | else 6 | RELNOTE= 7 | endif 8 | 9 | # local build, use user and timestamp it 10 | NAME:=csi-unity 11 | DOCKER_IMAGE_NAME ?= ${NAME} 12 | VERSION:=$(shell date +%Y%m%d%H%M%S) 13 | BIN_DIR:=bin 14 | BIN_NAME:=${NAME} 15 | DOCKER_REPO ?= dellemc 16 | DOCKER_NAMESPACE ?= csi-unity 17 | 18 | .PHONY: docker-build 19 | docker-build: 20 | $(eval include csm-common.mk) 21 | echo ${VERSION} ${GITLAB_CI} ${CI_COMMIT_TAG} ${CI_COMMIT_SHA} 22 | rm -f core/core_generated.go 23 | cd core && go generate 24 | go run core/semver/semver.go -f mk >semver.mk 25 | mkdir -p ${BIN_DIR} 26 | GOOS=linux CGO_ENABLED=0 GOARCH=amd64 go build -ldflags '-extldflags "-static"' -o ${BIN_DIR}/${BIN_NAME} 27 | docker build --pull -t ${DOCKER_REPO}/${DOCKER_NAMESPACE}/${DOCKER_IMAGE_NAME}:${IMAGETAG} --build-arg GOPROXY=$(GOPROXY) --build-arg BASEIMAGE=$(CSM_BASEIMAGE) --build-arg GOIMAGE=$(DEFAULT_GOIMAGE) . 28 | 29 | .PHONY: docker-push 30 | docker-push: docker-build 31 | docker push ${DOCKER_REPO}/${DOCKER_NAMESPACE}/${DOCKER_IMAGE_NAME}:${IMAGETAG} 32 | 33 | version: 34 | @echo "MAJOR $(MAJOR) MINOR $(MINOR) PATCH $(PATCH) BUILD ${BUILD} TYPE ${TYPE} RELNOTE $(RELNOTE) SEMVER $(SEMVER)" 35 | @echo "Target Version: $(VERSION)" 36 | 37 | -------------------------------------------------------------------------------- /common/constants.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package constants 16 | 17 | const ( 18 | 19 | // ParamCSILogLevel csi driver log level 20 | ParamCSILogLevel = "CSI_LOG_LEVEL" 21 | 22 | // ParamAllowRWOMultiPodAccess to enable multi pod access for RWO volume 23 | ParamAllowRWOMultiPodAccess = "ALLOW_RWO_MULTIPOD_ACCESS" 24 | 25 | // ParamMaxUnityVolumesPerNode defines maximum number of unity volumes per node 26 | ParamMaxUnityVolumesPerNode = "MAX_UNITY_VOLUMES_PER_NODE" 27 | 28 | // ParamSyncNodeInfoTimeInterval defines time interval to sync node info 29 | ParamSyncNodeInfoTimeInterval = "SYNC_NODE_INFO_TIME_INTERVAL" 30 | 31 | // ParamTenantName defines tenant name to be set while adding host entry 32 | ParamTenantName = "TENANT_NAME" 33 | ) 34 | -------------------------------------------------------------------------------- /test/sanity/secrets.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | CreateVolumeSecret: 15 | storagePool: pool_1 16 | secretKey: secretval1 17 | ApplicationPrefix: sanity 18 | ListVolumesSecret: 19 | storagePool: pool_1 20 | ValidateVolumeCapabilitiesSecret: 21 | storagePool: pool_1 22 | CreateSnapshotSecret: 23 | storagePool: pool_1 24 | # ListSnapshotSecret: 25 | # storagePool: pool_1 26 | DeleteSnapshotSecret: 27 | storagePool: pool_1 28 | DeleteVolumeSecret: 29 | secretKey: secretval2 30 | ControllerPublishVolumeSecret: 31 | secretKey: secretval3 32 | # storagepool: pool_1 33 | ControllerUnpublishVolumeSecret: 34 | secretKey: secretval4 35 | # storagepool: pool_1 36 | NodeStageVolumeSecret: 37 | secretKey: secretval5 38 | NodePublishVolumeSecret: 39 | secretKey: secretval6 40 | ControllerValidateVolumeCapabilitiesSecret: 41 | secretKey: secretval7 42 | storagePool: pool_1 43 | GetCapacitySecret: 44 | storagepool: pool_1 45 | -------------------------------------------------------------------------------- /env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright © 2019 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | export X_CSI_UNITY_NODENAME= 15 | export X_CSI_UNITY_LONGNODENAME= 16 | export X_CSI_STAGING_TARGET_PATH= 17 | export X_CSI_EPHEMERAL_STAGING_PATH= 18 | export X_CSI_PUBLISH_TARGET_PATH= 19 | export CSI_ENDPOINT= 20 | export X_CSI_DEBUG= 21 | export arrayId= 22 | export STORAGE_POOL= 23 | export NAS_SERVER= 24 | export X_CSI_REQ_LOGGING= 25 | export X_CSI_REP_LOGGING= 26 | export GOUNITY_DEBUG= 27 | export DRIVER_NAME= 28 | export DRIVER_SECRET= 29 | export DRIVER_CONFIG= 30 | export X_CSI_UNITY_AUTOPROBE= 31 | export X_CSI_UNITY_ALLOW_MULTI_POD_ACCESS= 32 | export X_CSI_HEALTH_MONITOR_ENABLED= 33 | export CSI_LOG_LEVEL= 34 | export ALLOW_RWO_MULTIPOD_ACCESS= 35 | export MAX_UNITY_VOLUMES_PER_NODE= 36 | export SYNC_NODE_INFO_TIME_INTERVAL= 37 | export TENANT_NAME= 38 | # if user has not provided any allowed networks, set it to empty else provide comma separated allowed networks 39 | export X_CSI_ALLOWED_NETWORKS="" 40 | -------------------------------------------------------------------------------- /provider/provider.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package provider 16 | 17 | import ( 18 | "github.com/dell/csi-unity/service" 19 | "github.com/dell/gocsi" 20 | ) 21 | 22 | // New returns a new CSI Storage Plug-in Provider. 23 | func New() gocsi.StoragePluginProvider { 24 | svc := service.New() 25 | return &gocsi.StoragePlugin{ 26 | Controller: svc, 27 | Identity: svc, 28 | Node: svc, 29 | BeforeServe: svc.BeforeServe, 30 | RegisterAdditionalServers: svc.RegisterAdditionalServers, 31 | 32 | EnvVars: []string{ 33 | // Enable request validation 34 | gocsi.EnvVarSpecReqValidation + "=true", 35 | 36 | // Enable serial volume access 37 | gocsi.EnvVarSerialVolAccess + "=true", 38 | 39 | // Treat the following fields as required: 40 | // * ControllerPublishVolumeRequest.NodeId 41 | // * GetNodeIDResponse.NodeId 42 | // gocsi.EnvVarRequireNodeID + "=true", 43 | 44 | // Treat the following fields as required: 45 | // * ControllerPublishVolumeResponse.PublishInfo 46 | // * NodePublishVolumeRequest.PublishInfo 47 | // gocsi.EnvVarRequirePubVolInfo + "=false", 48 | }, 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | ARG GOIMAGE 15 | ARG BASEIMAGE 16 | ARG GOPROXY 17 | 18 | # Stage to build the driver 19 | FROM $GOIMAGE as builder 20 | RUN mkdir -p /go/src 21 | COPY ./ /go/src/csi-unity 22 | 23 | WORKDIR /go/src/csi-unity 24 | RUN mkdir -p bin 25 | RUN go generate 26 | RUN GOOS=linux CGO_ENABLED=0 GOARCH=amd64 go build -ldflags '-extldflags "-static"' -o bin/csi-unity 27 | # Print the version 28 | RUN go run core/semver/semver.go -f mk 29 | 30 | 31 | # Dockerfile to build Unity CSI Driver 32 | # Fetching the base ubi micro image with the require packges committed using buildah 33 | FROM $BASEIMAGE as driver 34 | 35 | COPY --from=builder /go/src/csi-unity/bin/csi-unity / 36 | COPY scripts/run.sh / 37 | RUN chmod 777 /run.sh 38 | ENTRYPOINT ["/run.sh"] 39 | 40 | # final stage 41 | FROM driver as final 42 | 43 | LABEL vendor="Dell Technologies" \ 44 | maintainer="Dell Technologies" \ 45 | name="csi-unity" \ 46 | summary="CSI Driver for Dell Unity XT" \ 47 | description="CSI Driver for provisioning persistent storage from Dell Unity XT" \ 48 | release="1.15.0" \ 49 | version="2.15.0" \ 50 | license="Apache-2.0" 51 | COPY licenses /licenses 52 | -------------------------------------------------------------------------------- /test/sanity/README.md: -------------------------------------------------------------------------------- 1 | 14 | # Kubernetes Sanity Script Test 15 | 16 | This test runs the Kubernetes sanity test at https://github.com/kubernetes-csi/csi-test. 17 | The test last qualified was v2.8.0. 18 | 19 | To run the test, follow these steps: 20 | 21 | 1. "go get github.com/kubernetes-csi/csi-test" 22 | 2. Build and install the executable, csi-sanity, in a directory in your $PATH. 23 | 3. Make sure your env.sh is up to date so the CSI driver can be run. 24 | 4. Edit the secrets.yaml to have the correct SYMID, ServiceLevel, SRP, and ApplicationPrefix. 25 | 5. Use the start_driver.sh to start the driver. 26 | 6. Wait until the driver has fully come up and completed node setup. If you remain attached the logs will print on the screen. 27 | 7. Use the script run.sh to start csi-sanity (best if you do this in a separate window.) 28 | 29 | ## Excluded Tests 30 | 31 | The following tests were excluded for the reasons specified: 32 | 1. GetCapacity -- the test does not support supplying the Parameters fields that are required (for things like SYMID). 33 | 2. An idempotent volume test that attempts to create a volume with a different size as the existing volume. It appears to have a problem; 34 | the new size is over the maximum capability, and we detect that error first and disqualify the request, which I think is valid. 35 | -------------------------------------------------------------------------------- /samples/secret/secret.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | storageArrayList: 15 | # Array ID of Unity XT 16 | # Default value: None 17 | # Examples: "ABC00000000001" 18 | - arrayId: "ABC00000000001" 19 | # username for connecting to Unity XT Unisphere REST API server 20 | # Default value: None 21 | # Examples: "user1" 22 | username: "user" 23 | # password for connecting to Unity XT Unisphere REST API server 24 | # Default value: None 25 | # Examples: "Password" 26 | password: "password" 27 | # HTTPS endpoint of the Unity XT Unisphere REST API server 28 | # Default value: None 29 | # Examples: "https://10.0.0.0" 30 | endpoint: "https://1.2.3.4/" 31 | # indicates if client side validation of server's certificate can be skipped 32 | # Default value: None 33 | # Examples: false, true 34 | skipCertificateValidation: true 35 | # default array (would be used by storage classes without arrayId parameter) 36 | # Default value: None 37 | # Examples: false, true 38 | isDefault: true 39 | 40 | # # To add more Unity XT arrays, uncomment the following lines and provide the required values 41 | # - arrayId: "ABC00000000002" 42 | # username: "user" 43 | # password: "password" 44 | # endpoint: "https://1.2.3.5/" 45 | # skipCertificateValidation: true 46 | # isDefault: false 47 | -------------------------------------------------------------------------------- /provider/provider_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2025 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package provider 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/dell/csi-unity/service" 21 | "github.com/dell/gocsi" 22 | "github.com/stretchr/testify/assert" 23 | "github.com/stretchr/testify/require" 24 | ) 25 | 26 | func TestNew(t *testing.T) { 27 | pluginProvider := New() 28 | require.NotNil(t, pluginProvider, "StoragePluginProvider should not be nil") 29 | 30 | plugin, ok := pluginProvider.(*gocsi.StoragePlugin) 31 | require.True(t, ok, "Returned provider should be of type *gocsi.StoragePlugin") 32 | require.NotNil(t, plugin, "*gocsi.StoragePlugin instance should not be nil") 33 | 34 | svc := service.New() 35 | 36 | assert.Equal(t, svc, plugin.Controller, "Controller should be equal to the service") 37 | assert.Equal(t, svc, plugin.Identity, "Identity should be equal to the service") 38 | assert.Equal(t, svc, plugin.Node, "Node should be equal to the service") 39 | assert.NotNil(t, plugin.BeforeServe, "BeforeServe should not be nil") 40 | assert.NotNil(t, plugin.RegisterAdditionalServers, "RegisterAdditionalServers should not be nil") 41 | 42 | expectedEnvVars := []string{ 43 | gocsi.EnvVarSpecReqValidation + "=true", 44 | gocsi.EnvVarSerialVolAccess + "=true", 45 | } 46 | assert.ElementsMatch(t, expectedEnvVars, plugin.EnvVars, "Environment variables should match the expected values") 47 | } 48 | -------------------------------------------------------------------------------- /test/scale-test/2volumes/templates/test.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | kind: StatefulSet 15 | apiVersion: apps/v1 16 | metadata: 17 | name: unitytest 18 | namespace: test 19 | spec: 20 | serviceName: unitytest-2vol 21 | replicas: 22 | '[object Object]': null 23 | selector: 24 | matchLabels: 25 | app: unitytest 26 | template: 27 | metadata: 28 | labels: 29 | app: unitytest 30 | spec: 31 | serviceAccount: unitytest 32 | hostNetwork: true 33 | containers: 34 | - name: test 35 | image: 'quay.io/centos/centos:latest' 36 | command: 37 | - /bin/sleep 38 | - '3600' 39 | volumeMounts: 40 | - mountPath: /data0 41 | name: unityvolx0 42 | - mountPath: /data1 43 | name: unityvolx1 44 | volumeClaimTemplates: 45 | - metadata: 46 | name: unityvolx0 47 | spec: 48 | accessModes: 49 | - ReadWriteOnce 50 | storageClassName: 51 | '[object Object]': null 52 | resources: 53 | requests: 54 | storage: 5Gi 55 | - metadata: 56 | name: unityvolx1 57 | spec: 58 | accessModes: 59 | - ReadWriteOnce 60 | storageClassName: 61 | '[object Object]': null 62 | resources: 63 | requests: 64 | storage: 3Gi 65 | -------------------------------------------------------------------------------- /scripts/check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright © 2021 Dell Inc. or its subsidiaries. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | if [ -f "../vendor" ]; then 16 | # Tell the applicable Go tools to use the vendor directory, if it exists. 17 | MOD_FLAGS="-mod=vendor" 18 | fi 19 | FMT_TMPFILE=/tmp/check_fmt 20 | FMT_COUNT_TMPFILE=${FMT_TMPFILE}.count 21 | 22 | fmt_count() { 23 | if [ ! -f $FMT_COUNT_TMPFILE ]; then 24 | echo "0" 25 | fi 26 | 27 | head -1 $FMT_COUNT_TMPFILE 28 | } 29 | 30 | fmt() { 31 | gofmt -d ./service ./common/ ./core/ ./k8sutils/ ./provider/ | tee $FMT_TMPFILE 32 | cat $FMT_TMPFILE | wc -l > $FMT_COUNT_TMPFILE 33 | if [ ! `cat $FMT_COUNT_TMPFILE` -eq "0" ]; then 34 | echo Found `cat $FMT_COUNT_TMPFILE` formatting issue\(s\). 35 | return 1 36 | fi 37 | } 38 | 39 | echo === Checking format... 40 | fmt 41 | FMT_RETURN_CODE=$? 42 | echo === Finished 43 | 44 | echo === Vetting... 45 | go vet ${MOD_FLAGS} ./service/... ./common/... 46 | VET_RETURN_CODE=$? 47 | echo === Finished 48 | 49 | echo === Linting... 50 | (command -v golint >/dev/null 2>&1 \ 51 | || GO111MODULE=on go install golang.org/x/lint/golint@latest) \ 52 | && golint --set_exit_status ./service/... ./common/... 53 | LINT_RETURN_CODE=$? 54 | echo === Finished 55 | 56 | # Report output. 57 | fail_checks=0 58 | [ "${FMT_RETURN_CODE}" != "0" ] && echo "Formatting checks failed!" && fail_checks=1 59 | [ "${VET_RETURN_CODE}" != "0" ] && echo "Vetting checks failed!" && fail_checks=1 60 | [ "${LINT_RETURN_CODE}" != "0" ] && echo "Linting checks failed!" && fail_checks=1 61 | 62 | exit ${fail_checks} -------------------------------------------------------------------------------- /test/scale-test/README.md: -------------------------------------------------------------------------------- 1 | 14 | # Helm charts and scripts for scalability tests 15 | 16 | ## Helm charts 17 | | Name | Usage | 18 | |------------|-------| 19 | | 2volumes | Creates 2 volumes per pod/replica 20 | | 10volumes | Creates 10 volumes per pod/replica 21 | | 20volumes | Creates 20 volumes per pod/replica 22 | | 30volumes | Creates 30 volumes per pod/replica 23 | | 50volumes | Creates 50 volumes per pod/replica 24 | | 100volumes | Creates 100 volumes per pod/replica 25 | 26 | 27 | ## Scripts 28 | | Name | Usage | 29 | |----------------|-------| 30 | | scaletest.sh | Script to start the scalability tests 31 | | rescaletest.sh | Script to rescale pods to a different number of replicas 32 | 33 | 34 | ## Scale test prerequisites 35 | * Works only on Helm v3 36 | * Make sure the driver is installed and running fine in the k8s cluster 37 | * The namespace that is to be used for the scale test should be pre-created in the k8s cluster and make sure no objects are present under the namespace. 38 | * The right storage class to be used for the test should be pre-created and specified as StorageClassName in values.yaml under the test folder that is to be run. 39 | 40 | 41 | ## scaletest.sh 42 | The test is run using the script file scaletest.sh in the directory test/scale-tests. The script takes the below arguments. 43 | -r : number of replicas for the statefulset 44 | -t : the test name to be run [default: 2volumes] 45 | -n : namespace to be used to run the test [default: test] 46 | 47 | Examples: 48 | 49 | To scale the statefulset to 3 resplicas and then re-scale it back to 0 replicas: 50 | 51 | > ./scaletest.sh -r 3 -t 10volumes -n test 52 | 53 | To rescale the statefulset to 2 replicas: 54 | 55 | > sh rescaletest.sh -r 0 -t 10volumes -n test 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /dell-csi-helm-installer/verify-csi-unity.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (c) 2020 Dell Inc., or its subsidiaries. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | # verify-csi-unity method 12 | function verify-csi-unity() { 13 | verify_k8s_versions "1.32" "1.34" 14 | verify_openshift_versions "4.18" "4.19" 15 | verify_namespace "${NS}" 16 | verify_required_secrets "${RELEASE}-creds" 17 | verify_optional_secrets "${RELEASE}-certs" 18 | snapshot_value=$(is_snapshot_enabled) 19 | if [ "$snapshot_value" == "true" ]; then 20 | verify_alpha_snap_resources 21 | fi 22 | verify_unity_protocol_installation 23 | if [ "$snapshot_value" == "true" ]; then 24 | verify_snap_requirements 25 | fi 26 | verify_helm_3 27 | verify_helm_values_version "${DRIVER_VERSION}" 28 | } 29 | 30 | function verify_unity_protocol_installation() { 31 | if [ ${NODE_VERIFY} -eq 0 ]; then 32 | return 33 | fi 34 | 35 | log smart_step "Verifying sshpass installation.." 36 | SSHPASS=$(which sshpass) 37 | if [ -z "$SSHPASS" ]; then 38 | found_warning "sshpass is not installed. It is mandatory to have ssh pass software for multi node kubernetes setup." 39 | fi 40 | 41 | 42 | log smart_step "Verifying iSCSI installation" "$1" 43 | 44 | error=0 45 | for node in $MINION_NODES; do 46 | # check if the iSCSI client is installed 47 | echo 48 | echo -n "Enter the ${NODEUSER} password of ${node}: " 49 | read -s nodepassword 50 | echo 51 | echo "$nodepassword" > protocheckfile 52 | chmod 0400 protocheckfile 53 | unset nodepassword 54 | run_command sshpass -f protocheckfile ssh -o StrictHostKeyChecking=no ${NODEUSER}@"${node}" "cat /etc/iscsi/initiatorname.iscsi" > /dev/null 2>&1 55 | rv=$? 56 | if [ $rv -ne 0 ]; then 57 | error=1 58 | found_warning "iSCSI client is either not found on node: $node or not able to verify" 59 | fi 60 | run_command sshpass -f protocheckfile ssh -o StrictHostKeyChecking=no ${NODEUSER}@"${node}" "pgrep iscsid" > /dev/null 2>&1 61 | rv1=$? 62 | if [ $rv1 -ne 0 ]; then 63 | error=1 64 | found_warning "iscsid service is either not running on node: $node or not able to verify" 65 | fi 66 | rm -f protocheckfile 67 | done 68 | check_error error 69 | } 70 | -------------------------------------------------------------------------------- /samples/storageclass/unity-plain.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | # This is a sample manifest to create plain storageclass without any features like topology, mountOptions etc. 15 | 16 | # Change all instances of to the array serial ID of the unisphere instance used 17 | 18 | apiVersion: storage.k8s.io/v1 19 | kind: StorageClass 20 | metadata: 21 | name: unity--iscsi 22 | provisioner: csi-unity.dellemc.com 23 | reclaimPolicy: Delete 24 | allowVolumeExpansion: true 25 | # volumeBindingMode- controls when volume binding and dynamic provisioning should occur. 26 | # Allowed values: 27 | # Immediate- indicates that volume binding and dynamic provisioning 28 | # occurs once the PersistentVolumeClaim is created 29 | volumeBindingMode: Immediate 30 | parameters: 31 | protocol: iSCSI 32 | # arrayId - Serial Id of the array that will be used for provisioning. 33 | # Allowed values: String 34 | # Default value: None 35 | # Optional: false 36 | # Examples: "APM000000001", "APM000000002" 37 | arrayId: 38 | # storagePool - Define Storage pool CLI Id of the storage array. 39 | # Allowed values: String 40 | # Default value: None 41 | # Optional: false 42 | # Examples: pool_0 43 | storagePool: 44 | thinProvisioned: "true" 45 | isDataReductionEnabled: "true" 46 | # tieringPolicy - Define Tiering policy that is to be used for provisioning 47 | # Allowed values: integer 48 | # Default value: None 49 | # Optional: false 50 | # Examples: 0 51 | tieringPolicy: 52 | # hostIOLimitName - Insert Host IO Limit Name that is to be used for provisioning here. 53 | # Allowed values: String 54 | # Default value: None 55 | # Optional: false 56 | # Examples: "Autotyre" 57 | hostIOLimitName: 58 | # csi.storage.k8s.io/fstype - Set the filesystem type to format the new volume 59 | # Default value: ext4 60 | # Accepted values: 61 | # "ext4" 62 | # "xfs" 63 | csi.storage.k8s.io/fstype: ext4 64 | -------------------------------------------------------------------------------- /test/scale-test/rescaletest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright © 2020 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # 14 | # rescaletest 15 | # This script will rescale helm deployments created as part of the scaletest 16 | # Finally delete all the pvc that were created 17 | 18 | 19 | TEST="2volumes" 20 | NAMESPACE="test" 21 | REPLICAS=-1 22 | 23 | # Usage information 24 | function usage { 25 | echo 26 | echo "`basename ${0}`" 27 | echo " -n namespace - Namespace in which to place the test. Default is: ${NAMESPACE}" 28 | echo " -t test - Test to run. Default is: ${TEST}. The value must point to a Helm Chart" 29 | echo " -r replicas - Number of replicas to create" 30 | echo 31 | exit 1 32 | } 33 | 34 | # Parse the options passed on the command line 35 | while getopts "n:r:t:" opt; do 36 | case $opt in 37 | t) 38 | TEST="${OPTARG}" 39 | ;; 40 | n) 41 | NAMESPACE="${OPTARG}" 42 | ;; 43 | r) 44 | REPLICAS="${OPTARG}" 45 | ;; 46 | \?) 47 | echo "Invalid option: -$OPTARG" >&2 48 | usage 49 | ;; 50 | :) 51 | echo "Option -$OPTARG requires an argument." >&2 52 | usage 53 | ;; 54 | esac 55 | done 56 | 57 | if [ ${REPLICAS} -eq -1 ]; then 58 | echo "No value for number of replicas provided"; 59 | usage 60 | fi 61 | 62 | TARGET=$(expr $REPLICAS \* 1) 63 | echo "Targeting replicas: $REPLICAS" 64 | echo "Targeting pods: $TARGET" 65 | 66 | helm upgrade scalevoltest --install --set "name=scalevoltest,namespace=test,replicas=$REPLICAS,storageClass=unity" --namespace "${NAMESPACE}" "${TEST}" 67 | 68 | waitOnRunning() { 69 | if [ "$1" = "" ]; 70 | then echo "arg: target" ; 71 | exit 2; 72 | fi 73 | WAITINGFOR=$1 74 | 75 | RUNNING=$(kubectl get pods -n "${NAMESPACE}" | grep "Running" | wc -l) 76 | while [ $RUNNING -ne $WAITINGFOR ]; 77 | do 78 | sleep 15 79 | RUNNING=$(kubectl get pods -n "${NAMESPACE}" | grep "Running" | wc -l) 80 | CREATING=$(kubectl get pods -n "${NAMESPACE}" | grep "ContainerCreating" | wc -l) 81 | TERMINATING=$(kubectl get pods -n "${NAMESPACE}" | grep "Terminating" | wc -l) 82 | PVCS=$(kubectl get pvc -n "${NAMESPACE}" | wc -l) 83 | date 84 | date >>log.output 85 | echo running $RUNNING creating $CREATING terminating $TERMINATING pvcs $PVCS 86 | echo running $RUNNING creating $CREATING terminating $TERMINATING pvcs $PVCS >>log.output 87 | done 88 | } 89 | 90 | waitOnRunning $TARGET 91 | 92 | echo "Re scale to $REPLICAS completed successfully" 93 | -------------------------------------------------------------------------------- /service/envvars.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package service 16 | 17 | const ( 18 | // EnvEndpoint is the name of the environment variable used to set the 19 | // HTTP endpoint of the Unity XT Gateway 20 | EnvEndpoint = "X_CSI_UNITY_ENDPOINT" 21 | 22 | // EnvNodeName is the name of the enviroment variable used to set the 23 | // hostname where the node service is running 24 | EnvNodeName = "X_CSI_UNITY_NODENAME" 25 | 26 | // EnvAutoProbe is the name of the environment variable used to specify 27 | // that the controller service should automatically probe itself if it 28 | // receives incoming requests before having been probed, in direct 29 | // violation of the CSI spec 30 | EnvAutoProbe = "X_CSI_UNITY_AUTOPROBE" 31 | 32 | // EnvPvtMountDir is required to Node Unstage volume where the volume has been mounted 33 | // as a global mount via CSI-Unity v1.0 or v1.1 34 | EnvPvtMountDir = "X_CSI_PRIVATE_MOUNT_DIR" 35 | 36 | // EnvEphemeralStagingPath - Ephemeral staging path 37 | EnvEphemeralStagingPath = "X_CSI_EPHEMERAL_STAGING_PATH" 38 | 39 | // EnvISCSIChroot is the path to which the driver will chroot before 40 | // running any iscsi commands. This value should only be set when instructed 41 | // by technical support. 42 | EnvISCSIChroot = "X_CSI_ISCSI_CHROOT" 43 | 44 | // EnvKubeConfigPath indicates kubernetes configuration that has to be used by CSI Driver 45 | EnvKubeConfigPath = "KUBECONFIG" 46 | 47 | // SyncNodeInfoTimeInterval - Time interval to add node info to array. Default 60 minutes. 48 | // X_CSI_UNITY_SYNC_NODEINFO_INTERVAL has been deprecated and will be removes in a future release 49 | SyncNodeInfoTimeInterval = "X_CSI_UNITY_SYNC_NODEINFO_INTERVAL" 50 | 51 | // EnvAllowRWOMultiPodAccess - Environment variable to configure sharing of a single volume across multiple pods within the same node 52 | // Multi-node access is still not allowed for ReadWriteOnce Mount volumes. 53 | // Enabling this option techincally violates the CSI 1.3 spec in the NodePublishVolume stating the required error returns. 54 | // X_CSI_UNITY_ALLOW_MULTI_POD_ACCESS has been deprecated and will be removes in a future release 55 | EnvAllowRWOMultiPodAccess = "X_CSI_UNITY_ALLOW_MULTI_POD_ACCESS" 56 | 57 | // EnvIsVolumeHealthMonitorEnabled - Environment variable to enable/disable health monitor of CSI volumes 58 | EnvIsVolumeHealthMonitorEnabled = "X_CSI_HEALTH_MONITOR_ENABLED" 59 | 60 | // EnvAllowedNetworks indicates list of networks on which NFS traffic is allowed 61 | EnvAllowedNetworks = "X_CSI_ALLOWED_NETWORKS" 62 | ) 63 | -------------------------------------------------------------------------------- /service/identity.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package service 16 | 17 | import ( 18 | "strings" 19 | 20 | "github.com/container-storage-interface/spec/lib/go/csi" 21 | "github.com/dell/csi-unity/core" 22 | "golang.org/x/net/context" 23 | ) 24 | 25 | func (s *service) Probe( 26 | ctx context.Context, 27 | req *csi.ProbeRequest) ( 28 | *csi.ProbeResponse, error, 29 | ) { 30 | ctx, log, _ := GetRunidLog(ctx) 31 | log.Infof("Executing Probe with args: %+v", *req) 32 | if strings.EqualFold(s.mode, "controller") { 33 | if err := s.controllerProbe(ctx, ""); err != nil { 34 | log.Error("Identity probe failed:", err) 35 | return nil, err 36 | } 37 | } 38 | if strings.EqualFold(s.mode, "node") { 39 | if err := s.nodeProbe(ctx, ""); err != nil { 40 | log.Error("Identity probe failed:", err) 41 | return nil, err 42 | } 43 | } 44 | log.Info("Identity probe success") 45 | return &csi.ProbeResponse{}, nil 46 | } 47 | 48 | func (s *service) GetPluginInfo( 49 | _ context.Context, 50 | _ *csi.GetPluginInfoRequest) ( 51 | *csi.GetPluginInfoResponse, error, 52 | ) { 53 | return &csi.GetPluginInfoResponse{ 54 | Name: Name, 55 | VendorVersion: core.SemVer, 56 | Manifest: Manifest, 57 | }, nil 58 | } 59 | 60 | func (s *service) GetPluginCapabilities( 61 | ctx context.Context, 62 | req *csi.GetPluginCapabilitiesRequest) ( 63 | *csi.GetPluginCapabilitiesResponse, error, 64 | ) { 65 | ctx, log, _ := GetRunidLog(ctx) 66 | log.Infof("Executing GetPluginCapabilities with args: %+v", *req) 67 | return &csi.GetPluginCapabilitiesResponse{ 68 | Capabilities: []*csi.PluginCapability{ 69 | { 70 | Type: &csi.PluginCapability_Service_{ 71 | Service: &csi.PluginCapability_Service{ 72 | Type: csi.PluginCapability_Service_CONTROLLER_SERVICE, 73 | }, 74 | }, 75 | }, 76 | { 77 | Type: &csi.PluginCapability_VolumeExpansion_{ 78 | VolumeExpansion: &csi.PluginCapability_VolumeExpansion{ 79 | Type: csi.PluginCapability_VolumeExpansion_ONLINE, 80 | }, 81 | }, 82 | }, 83 | { 84 | Type: &csi.PluginCapability_VolumeExpansion_{ 85 | VolumeExpansion: &csi.PluginCapability_VolumeExpansion{ 86 | Type: csi.PluginCapability_VolumeExpansion_OFFLINE, 87 | }, 88 | }, 89 | }, 90 | { 91 | Type: &csi.PluginCapability_Service_{ 92 | Service: &csi.PluginCapability_Service{ 93 | Type: csi.PluginCapability_Service_VOLUME_ACCESSIBILITY_CONSTRAINTS, 94 | }, 95 | }, 96 | }, 97 | }, 98 | }, nil 99 | } 100 | -------------------------------------------------------------------------------- /service/main_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package service 16 | 17 | import ( 18 | "bufio" 19 | "context" 20 | "encoding/json" 21 | "errors" 22 | "fmt" 23 | "io" 24 | "os" 25 | "strings" 26 | "testing" 27 | 28 | "github.com/dell/csi-unity/service/logging" 29 | "github.com/sirupsen/logrus" 30 | ) 31 | 32 | type testConfig struct { 33 | unityConfig string 34 | service *service 35 | ctx context.Context 36 | defaultArray string 37 | } 38 | 39 | var testConf *testConfig 40 | 41 | func TestMain(m *testing.M) { 42 | fmt.Println("------------In TestMain--------------") 43 | os.Setenv("GOUNITY_DEBUG", "true") 44 | os.Setenv("X_CSI_DEBUG", "true") 45 | 46 | // for this tutorial, we will hard code it to config.txt 47 | // testProp, err := readTestProperties("../test.properties") 48 | // if err != nil { 49 | // panic("The system cannot find the file specified") 50 | // } 51 | 52 | // if err != nil { 53 | // fmt.Println(err) 54 | // } 55 | testConf = &testConfig{} 56 | 57 | // testConf.unityConfig = testProp["DRIVER_CONFIG"] 58 | testConf.service = new(service) 59 | DriverConfig = testConf.unityConfig 60 | // os.Setenv("X_CSI_UNITY_NODENAME", testProp["X_CSI_UNITY_NODENAME"]) 61 | testConf.service.BeforeServe(context.Background(), nil, nil) 62 | fmt.Println() 63 | 64 | entry := logrus.WithField(logging.RUNID, "test-1") 65 | testConf.ctx = context.WithValue(context.Background(), logging.UnityLogger, entry) 66 | 67 | for _, v := range testConf.service.getStorageArrayList() { 68 | if v.IsDefaultArray { 69 | testConf.defaultArray = v.ArrayID 70 | break 71 | } 72 | } 73 | code := m.Run() 74 | fmt.Println("------------End of TestMain--------------") 75 | os.Exit(code) 76 | } 77 | 78 | func readTestProperties(filename string) (map[string]string, error) { 79 | // init with some bogus data 80 | configPropertiesMap := map[string]string{} 81 | if len(filename) == 0 { 82 | return nil, errors.New("Error reading properties file " + filename) 83 | } 84 | file, err := os.Open(filename) 85 | if err != nil { 86 | return nil, err 87 | } 88 | defer file.Close() 89 | 90 | reader := bufio.NewReader(file) 91 | 92 | for { 93 | line, err := reader.ReadString('\n') 94 | 95 | // check if the line has = sign 96 | // and process the line. Ignore the rest. 97 | if equal := strings.Index(line, "="); equal >= 0 { 98 | if key := strings.TrimSpace(line[:equal]); len(key) > 0 { 99 | value := "" 100 | if len(line) > equal { 101 | value = strings.TrimSpace(line[equal+1:]) 102 | } 103 | // assign the config map 104 | configPropertiesMap[key] = value 105 | } 106 | } 107 | if err == io.EOF { 108 | break 109 | } 110 | if err != nil { 111 | return nil, err 112 | } 113 | } 114 | return configPropertiesMap, nil 115 | } 116 | 117 | func prettyPrintJSON(obj interface{}) string { 118 | data, _ := json.Marshal(obj) 119 | return string(data) 120 | } 121 | -------------------------------------------------------------------------------- /k8sutils/k8sutils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package k8sutils 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "os" 21 | "time" 22 | 23 | "github.com/kubernetes-csi/csi-lib-utils/leaderelection" 24 | log "github.com/sirupsen/logrus" 25 | "k8s.io/client-go/kubernetes" 26 | "k8s.io/client-go/rest" 27 | "k8s.io/client-go/tools/clientcmd" 28 | ) 29 | 30 | var ( 31 | buildConfigFromFlags = clientcmd.BuildConfigFromFlags 32 | newForConfig = kubernetes.NewForConfig 33 | inClusterConfig = rest.InClusterConfig 34 | ) 35 | 36 | // leaderElection interface 37 | type leaderElection interface { 38 | Run() error 39 | WithNamespace(namespace string) 40 | } 41 | 42 | // ExitFunc is a variable that holds the os.Exit function 43 | var ExitFunc = os.Exit 44 | 45 | // RunLeaderElection runs the leader election and handles errors 46 | func RunLeaderElection(le leaderElection) { 47 | if err := le.Run(); err != nil { 48 | _, _ = fmt.Fprintf(os.Stderr, "failed to initialize leader election: %v", err) 49 | ExitFunc(1) 50 | } 51 | } 52 | 53 | // CreateKubeClientSet - Returns kubeclient set 54 | func CreateKubeClientSet(kubeconfig string) (*kubernetes.Clientset, error) { 55 | var clientset *kubernetes.Clientset 56 | if kubeconfig != "" { 57 | // use the current context in kubeconfig 58 | config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) 59 | if err != nil { 60 | return nil, err 61 | } 62 | // create the clientset 63 | clientset, err = kubernetes.NewForConfig(config) 64 | if err != nil { 65 | return nil, err 66 | } 67 | } else { 68 | config, err := rest.InClusterConfig() 69 | if err != nil { 70 | return nil, err 71 | } 72 | // creates the clientset 73 | clientset, err = kubernetes.NewForConfig(config) 74 | if err != nil { 75 | return nil, err 76 | } 77 | } 78 | return clientset, nil 79 | } 80 | 81 | // LeaderElection - Initialize leader selection 82 | func LeaderElection(clientset *kubernetes.Clientset, lockName string, namespace string, 83 | leaderElectionRenewDeadline, leaderElectionLeaseDuration, leaderElectionRetryPeriod time.Duration, runFunc func(ctx context.Context), 84 | ) { 85 | log.Info("Starting leader election setup...") 86 | 87 | le := leaderelection.NewLeaderElection(clientset, lockName, runFunc) 88 | log.Infof("Leader election created with lock name: %s", lockName) 89 | 90 | le.WithNamespace(namespace) 91 | log.Infof("Namespace set to: %s", namespace) 92 | 93 | le.WithLeaseDuration(leaderElectionLeaseDuration) 94 | log.Infof("Lease duration set to: %v", leaderElectionLeaseDuration) 95 | 96 | le.WithRenewDeadline(leaderElectionRenewDeadline) 97 | log.Infof("Renew deadline set to: %v", leaderElectionRenewDeadline) 98 | 99 | le.WithRetryPeriod(leaderElectionRetryPeriod) 100 | log.Infof("Retry period set to: %v", leaderElectionRetryPeriod) 101 | 102 | log.Info("Running leader election...") 103 | RunLeaderElection(le) 104 | log.Info("Leader election completed.") 105 | } 106 | -------------------------------------------------------------------------------- /test/sample.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | # This test creates 2 different PersistentVolumeClaims 15 | # These PVCs use ext4 and xfs default storage classes 16 | # PVs are mounted to pod from StatefulSet 17 | # 18 | # To test the driver just run from root directory of repository 19 | # > kubectl create -f ./tests/sample/ 20 | # 21 | # You can find all resources in unity namespace. 22 | # Check if pod is created and Ready and Running by running 23 | # > kubectl get all -n unity 24 | # (if it's in CrashLoopback state then driver installation wasn't successful, check logs of node and controller) 25 | # After that can go into created container and check if everything is mounted correctly. 26 | # 27 | # After that you can uninstall the testing PVCs and StatefulSet 28 | # > kubectl delete -f ./tests/simple/ 29 | # 30 | apiVersion: v1 31 | kind: Namespace 32 | metadata: 33 | name: unity 34 | --- 35 | kind: PersistentVolumeClaim 36 | apiVersion: v1 37 | metadata: 38 | name: pvol0 39 | namespace: unity 40 | spec: 41 | accessModes: 42 | - ReadWriteOnce 43 | volumeMode: Filesystem 44 | resources: 45 | requests: 46 | storage: 8Gi 47 | storageClassName: unity 48 | --- 49 | kind: PersistentVolumeClaim 50 | apiVersion: v1 51 | metadata: 52 | name: pvol1 53 | namespace: unity 54 | spec: 55 | accessModes: 56 | - ReadWriteOnce 57 | volumeMode: Filesystem 58 | resources: 59 | requests: 60 | storage: 12Gi 61 | storageClassName: unity-iscsi 62 | --- 63 | kind: PersistentVolumeClaim 64 | apiVersion: v1 65 | metadata: 66 | name: pvol2 67 | namespace: unity 68 | spec: 69 | accessModes: 70 | - ReadWriteMany 71 | volumeMode: Filesystem 72 | resources: 73 | requests: 74 | storage: 10Gi 75 | storageClassName: unity-nfs 76 | --- 77 | apiVersion: v1 78 | kind: ServiceAccount 79 | metadata: 80 | name: unitytest 81 | namespace: unity 82 | --- 83 | kind: StatefulSet 84 | apiVersion: apps/v1 85 | metadata: 86 | name: unitytest 87 | namespace: unity 88 | spec: 89 | serviceName: unitytest 90 | selector: 91 | matchLabels: 92 | app: unitytest 93 | template: 94 | metadata: 95 | labels: 96 | app: unitytest 97 | spec: 98 | serviceAccountName: unitytest 99 | hostNetwork: true 100 | containers: 101 | - name: test 102 | image: quay.io/centos/centos:latest 103 | command: [ "/bin/sleep", "3600" ] 104 | volumeMounts: 105 | - mountPath: "/data0" 106 | name: pvol0 107 | - mountPath: "/data1" 108 | name: pvol1 109 | - mountPath: "/data2" 110 | name: pvol2 111 | volumes: 112 | - name: pvol0 113 | persistentVolumeClaim: 114 | claimName: pvol0 115 | - name: pvol1 116 | persistentVolumeClaim: 117 | claimName: pvol1 118 | - name: pvol2 119 | persistentVolumeClaim: 120 | claimName: pvol2 121 | -------------------------------------------------------------------------------- /dell-csi-helm-installer/csi-uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (c) 2021 Dell Inc., or its subsidiaries. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 12 | PROG="${0}" 13 | DRIVER="csi-unity" 14 | 15 | # export the name of the debug log, so child processes will see it 16 | export DEBUGLOG="${SCRIPTDIR}/uninstall-debug.log" 17 | 18 | declare -a VALIDDRIVERS 19 | 20 | source "$SCRIPTDIR"/common.sh 21 | 22 | if [ -f "${DEBUGLOG}" ]; then 23 | rm -f "${DEBUGLOG}" 24 | fi 25 | 26 | # 27 | # usage will print command execution help and then exit 28 | function usage() { 29 | decho "Help for $PROG" 30 | decho 31 | decho "Usage: $PROG options..." 32 | decho "Options:" 33 | decho " Required" 34 | decho " --namespace[=] Kubernetes namespace to uninstall the CSI driver from" 35 | 36 | decho " Optional" 37 | decho " --release[=] Name to register with helm, default value will match the driver name" 38 | decho " -h Help" 39 | decho 40 | 41 | exit 0 42 | } 43 | 44 | 45 | 46 | # 47 | # validate_params will validate the parameters passed in 48 | function validate_params() { 49 | # make sure the driver was specified 50 | if [ -z "${DRIVER}" ]; then 51 | decho "No driver specified" 52 | exit 1 53 | fi 54 | # the namespace is required 55 | if [ -z "${NAMESPACE}" ]; then 56 | decho "No namespace specified" 57 | usage 58 | exit 1 59 | fi 60 | } 61 | 62 | 63 | # check_for_driver will see if the driver is installed within the namespace provided 64 | function check_for_driver() { 65 | NUM=$(run_command helm list --namespace "${NAMESPACE}" | grep "^${RELEASE}\b" | wc -l) 66 | if [ "${NUM}" == "0" ]; then 67 | log uninstall_error "The CSI Driver is not installed." 68 | exit 1 69 | fi 70 | } 71 | 72 | DRIVERDIR="${SCRIPTDIR}/../helm-charts/charts" 73 | 74 | while getopts ":h-:" optchar; do 75 | case "${optchar}" in 76 | -) 77 | case "${OPTARG}" in 78 | # NAMESPACE 79 | namespace) 80 | NAMESPACE="${!OPTIND}" 81 | OPTIND=$((OPTIND + 1)) 82 | ;; 83 | namespace=*) 84 | NAMESPACE=${OPTARG#*=} 85 | ;; 86 | # RELEASE 87 | release) 88 | RELEASE="${!OPTIND}" 89 | OPTIND=$((OPTIND + 1)) 90 | ;; 91 | release=*) 92 | RELEASE=${OPTARG#*=} 93 | ;; 94 | *) 95 | decho "Unknown option --${OPTARG}" 96 | decho "For help, run $PROG -h" 97 | exit 1 98 | ;; 99 | esac 100 | ;; 101 | h) 102 | usage 103 | ;; 104 | *) 105 | decho "Unknown option -${OPTARG}" 106 | decho "For help, run $PROG -h" 107 | exit 1 108 | ;; 109 | esac 110 | done 111 | 112 | # by default the NAME of the helm release of the driver is the same as the driver name 113 | RELEASE=$(get_release_name "${DRIVER}") 114 | 115 | # validate the parameters passed in 116 | validate_params 117 | 118 | check_for_driver 119 | run_command helm delete -n "${NAMESPACE}" "${RELEASE}" 120 | if [ $? -ne 0 ]; then 121 | decho "Removal of the CSI Driver was unsuccessful" 122 | exit 1 123 | fi 124 | 125 | decho "Removal of the CSI Driver is in progress." 126 | decho "It may take a few minutes for all pods to terminate." 127 | 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 14 | 15 | # CSI Driver for Dell Unity XT 16 | 17 | [![Go Report Card](https://goreportcard.com/badge/github.com/dell/csi-unity?style=flat-square)](https://goreportcard.com/report/github.com/dell/csi-unity) 18 | [![License](https://img.shields.io/github/license/dell/csi-unity?style=flat-square&color=blue&label=License)](https://github.com/dell/csi-unity/blob/main/LICENSE) 19 | [![Docker](https://img.shields.io/docker/pulls/dellemc/csi-unity.svg?logo=docker&style=flat-square&label=Pulls)](https://hub.docker.com/r/dellemc/csi-unity) 20 | [![Last Release](https://img.shields.io/github/v/release/dell/csi-unity?label=Latest&style=flat-square&logo=go)](https://github.com/dell/csi-unity/releases) 21 | 22 | **Repository for CSI Driver for Dell Unity XT** 23 | 24 | ## Description 25 | CSI Driver for Unity XT is part of the [CSM (Container Storage Modules)](https://github.com/dell/csm) open-source suite of Kubernetes storage enablers for Dell products. CSI Driver for Unity XT is a Container Storage Interface (CSI) driver that provides support for provisioning persistent storage using Dell Unity XT storage array. 26 | 27 | This project may be compiled as a stand-alone binary using Golang that, when run, provides a valid CSI endpoint. It also can be used as a precompiled container image. 28 | 29 | ## Table of Contents 30 | 31 | * [Code of Conduct](https://github.com/dell/csm/blob/main/docs/CODE_OF_CONDUCT.md) 32 | * [Maintainer Guide](https://github.com/dell/csm/blob/main/docs/MAINTAINER_GUIDE.md) 33 | * [Committer Guide](https://github.com/dell/csm/blob/main/docs/COMMITTER_GUIDE.md) 34 | * [Contributing Guide](https://github.com/dell/csm/blob/main/docs/CONTRIBUTING.md) 35 | * [List of Adopters](https://github.com/dell/csm/blob/main/docs/ADOPTERS.md) 36 | * [Support](#support) 37 | * [Security](https://github.com/dell/csm/blob/main/docs/SECURITY.md) 38 | * [Building](#building) 39 | * [Runtime Dependecies](#runtime-dependencies) 40 | * [Documentation](#documentation) 41 | 42 | ## Support 43 | For any issues, questions or feedback, please contact [Dell support](https://www.dell.com/support/incidents-online/en-us/contactus/product/container-storage-modules). 44 | 45 | ## Building 46 | This project is a Go module (see golang.org Module information for explanation). 47 | The dependencies for this project are in the go.mod file. 48 | 49 | To build the source, execute `make go-build`. 50 | 51 | To run unit tests, execute `make unit-test`. 52 | 53 | To build a podman based image, execute `make podman-build`. 54 | 55 | You can run an integration test on a Linux system by populating the env files at `test/integration/` with values for your Dell Unity XT systems and then run `make integration-test`. 56 | 57 | 58 | ## Runtime Dependencies 59 | Both the Controller and the Node portions of the driver can only be run on nodes which have network connectivity to “`Unisphere for Unity XT`” (which is used by the driver). 60 | 61 | ## Documentation 62 | For more detailed information on the driver, please refer to [Container Storage Modules documentation](https://dell.github.io/csm-docs/). 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME:=csi-unity 2 | 3 | .PHONY: all 4 | all: go-build 5 | 6 | ifneq (on,$(GO111MODULE)) 7 | export GO111MODULE := on 8 | endif 9 | 10 | IMAGE_NAME=csi-unity 11 | IMAGE_REGISTRY=dellemc 12 | DEFAULT_IMAGE_TAG=$(shell date +%Y%m%d%H%M%S) 13 | ifeq ($(IMAGETAG),) 14 | export IMAGETAG="$(DEFAULT_IMAGE_TAG)" 15 | endif 16 | 17 | .PHONY: go-vendor 18 | go-vendor: 19 | go mod vendor 20 | 21 | .PHONY: go-build 22 | go-build: clean 23 | git config core.hooksPath hooks 24 | rm -f core/core_generated.go 25 | cd core && go generate 26 | go build . 27 | 28 | UNIT_TESTED_PACKAGES := \ 29 | github.com/dell/csi-unity \ 30 | github.com/dell/csi-unity/k8sutils \ 31 | github.com/dell/csi-unity/provider \ 32 | github.com/dell/csi-unity/service \ 33 | github.com/dell/csi-unity/service/csiutils \ 34 | github.com/dell/csi-unity/service/logging 35 | 36 | unit-test: 37 | go clean -cache 38 | @for pkg in $(UNIT_TESTED_PACKAGES); do \ 39 | echo "****** go test -v -short -race -count=1 -cover -coverprofile cover.out $$pkg ******"; \ 40 | go test -v -short -race -count=1 -cover -coverprofile cover.out $$pkg; \ 41 | done 42 | 43 | # Integration tests using Godog. Populate env.sh with the hardware parameters 44 | integration-test: 45 | ( cd test/integration-test; sh run.sh ) 46 | 47 | # BDD tests using Godog. Populate env.sh with the hardware parameters 48 | bdd-test: 49 | ( cd test/bdd-test; sh run.sh ) 50 | 51 | .PHONY: download-csm-common 52 | download-csm-common: 53 | curl -O -L https://raw.githubusercontent.com/dell/csm/main/config/csm-common.mk 54 | 55 | # 56 | # Docker-related tasks 57 | # 58 | # Generates the docker container (but does not push) 59 | podman-build: download-csm-common go-build 60 | $(eval include csm-common.mk) 61 | podman build --pull -t $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGETAG) --build-arg GOIMAGE=$(DEFAULT_GOIMAGE) --build-arg BASEIMAGE=$(CSM_BASEIMAGE) --build-arg GOPROXY=$(GOPROXY) . --format=docker 62 | 63 | podman-build-no-cache: download-csm-common go-build 64 | $(eval include csm-common.mk) 65 | podman build --pull --no-cache -t $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGETAG) --build-arg GOIMAGE=$(DEFAULT_GOIMAGE) --build-arg BASEIMAGE=$(CSM_BASEIMAGE) --build-arg GOPROXY=$(GOPROXY) . --format=docker 66 | 67 | podman-push: 68 | podman push $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGETAG) 69 | 70 | # 71 | # Docker-related tasks 72 | # 73 | # Generates the docker container (but does not push) 74 | docker-build: download-csm-common 75 | make -f docker.mk docker-build 76 | 77 | docker-push: 78 | make -f docker.mk docker-push 79 | 80 | version: 81 | go generate 82 | go run core/semver/semver.go -f mk >semver.mk 83 | make -f docker.mk version 84 | 85 | .PHONY: clean 86 | clean: 87 | rm -f core/core_generated.go 88 | go clean 89 | 90 | # 91 | # Tests-related tasks 92 | .PHONY: integ-test 93 | integ-test: go-build 94 | go test -v ./test/... 95 | 96 | check: 97 | sh scripts/check.sh 98 | 99 | .PHONY: actions action-help 100 | actions: ## Run all GitHub Action checks that run on a pull request creation 101 | @echo "Running all GitHub Action checks for pull request events..." 102 | @act -l | grep -v ^Stage | grep pull_request | grep -v image_security_scan | awk '{print $$2}' | while read WF; do \ 103 | echo "Running workflow: $${WF}"; \ 104 | act pull_request --no-cache-server --platform ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-latest --job "$${WF}"; \ 105 | done 106 | 107 | action-help: ## Echo instructions to run one specific workflow locally 108 | @echo "GitHub Workflows can be run locally with the following command:" 109 | @echo "act pull_request --no-cache-server --platform ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-latest --job " 110 | @echo "" 111 | @echo "Where '' is a Job ID returned by the command:" 112 | @echo "act -l" 113 | @echo "" 114 | @echo "NOTE: if act is not installed, it can be downloaded from https://github.com/nektos/act" 115 | -------------------------------------------------------------------------------- /test/scale-test/scaletest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright © 2020 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # 14 | # scaletest 15 | # This script will kick off a test designed to stress the limits of the driver as well as the array 16 | # It will install a user supplied helm chart with a user supplied number of replicas. 17 | # Each replica will contain a number of volumes 18 | # The test will continue to run until all replicas have been started, volumes created, and mapped 19 | # Then the replicas will be scaled down to 0 and then all the created pvc will be flushed out. 20 | 21 | TEST="2volumes" 22 | NAMESPACE="test" 23 | REPLICAS=-1 24 | 25 | # Usage information 26 | function usage { 27 | echo 28 | echo "`basename ${0}`" 29 | echo " -n namespace - Namespace in which to place the test. Default is: ${NAMESPACE}" 30 | echo " -t test - Test to run. Default is: ${TEST}. The value must point to a Helm Chart" 31 | echo " -r replicas - Number of replicas to create" 32 | echo " -p replicas - Protocol" 33 | echo 34 | exit 1 35 | } 36 | 37 | # Parse the options passed on the command line 38 | while getopts "n:r:t:" opt; do 39 | case $opt in 40 | t) 41 | TEST="${OPTARG}" 42 | ;; 43 | n) 44 | NAMESPACE="${OPTARG}" 45 | ;; 46 | r) 47 | REPLICAS="${OPTARG}" 48 | ;; 49 | \?) 50 | echo "Invalid option: -$OPTARG" >&2 51 | usage 52 | ;; 53 | :) 54 | echo "Option -$OPTARG requires an argument." >&2 55 | usage 56 | ;; 57 | esac 58 | done 59 | 60 | if [ ${REPLICAS} -eq -1 ]; then 61 | echo "No value for number of replicas provided"; 62 | usage 63 | fi 64 | 65 | validateServiceAccount() { 66 | # validate that the service account exists 67 | ACCOUNTS=$(kubectl describe serviceaccount -n "${NAMESPACE}" "unitytest") 68 | if [ $? -ne 0 ]; then 69 | echo "Creating Service Account" 70 | kubectl create -n ${NAMESPACE} -f serviceAccount.yaml 71 | fi 72 | } 73 | 74 | validateServiceAccount 75 | 76 | TARGET=$(expr $REPLICAS \* 1) 77 | echo "Targeting replicas: $REPLICAS" 78 | echo "Targeting pods: $TARGET" 79 | 80 | helm install scalevoltest --set "name=scalevoltest,replicas=$REPLICAS,storageClass=unity,namespace=${NAMESPACE}" -n ${NAMESPACE} ./${TEST} 81 | 82 | waitOnRunning() { 83 | if [ "$1" = "" ]; 84 | then echo "arg: target" ; 85 | exit 2; 86 | fi 87 | WAITINGFOR=$1 88 | 89 | RUNNING=$(kubectl get pods -n ${NAMESPACE} | grep "Running" | wc -l) 90 | 91 | while [ $RUNNING -ne $WAITINGFOR ]; 92 | do 93 | sleep 5 94 | RUNNING=$(kubectl get pods -n ${NAMESPACE} | grep "Running" | wc -l) 95 | CREATING=$(kubectl get pods -n ${NAMESPACE} | grep "ContainerCreating" | wc -l) 96 | PVCS=$(kubectl get pvc -n ${NAMESPACE} | wc -l) 97 | date 98 | date >>log.output 99 | echo running $RUNNING creating $CREATING pvcs $PVCS 100 | echo running $RUNNING creating $CREATING pvcs $PVCS >>log.output 101 | done 102 | } 103 | 104 | SECONDS=0 105 | waitOnRunning $TARGET 106 | END=$SECONDS 107 | echo "Time taken to create $REPLICAS replicas with $TEST: $(( (${END} / 60) % 60 ))m $(( ${END} % 60 ))s" 108 | sleep 30 109 | 110 | SECONDS=0 111 | # rescale the environment back to 0 replicas 112 | sh rescaletest.sh -n "${NAMESPACE}" -r 0 -t "${TEST}" 113 | END=$SECONDS 114 | echo "Time taken to rescale from $REPLICAS to 0 replicas on test $TEST: $(( (${END} / 60) % 60 ))m $(( ${END} % 60 ))s" 115 | 116 | echo "Deleting hem installation under namespace: ${NAMESPACE}" 117 | helm delete -n ${NAMESPACE} scalevoltest 118 | 119 | echo "Deleting all pvcs under namespace: ${NAMESPACE}" 120 | kubectl delete pvc --all -n ${NAMESPACE} 121 | -------------------------------------------------------------------------------- /test/integration-test/integration_main_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package integration_test 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "fmt" 21 | "os" 22 | "strconv" 23 | "testing" 24 | "time" 25 | 26 | "github.com/container-storage-interface/spec/lib/go/csi" 27 | "github.com/cucumber/godog" 28 | "github.com/dell/csi-unity/provider" 29 | "github.com/dell/csi-unity/service" 30 | csiutils "github.com/dell/gocsi/utils/csi" 31 | "google.golang.org/grpc" 32 | ) 33 | 34 | var grpcClient *grpc.ClientConn 35 | 36 | // To parse the secret json file 37 | type StorageArrayList struct { 38 | StorageArrayList []StorageArrayConfig `json:"storageArrayList"` 39 | } 40 | 41 | type StorageArrayConfig struct { 42 | ArrayID string `json:"arrayId"` 43 | } 44 | 45 | func TestMain(m *testing.M) { 46 | var stop func() 47 | os.Setenv("X_CSI_MODE", "") 48 | 49 | file, err := os.ReadFile(os.Getenv("DRIVER_CONFIG")) 50 | if err != nil { 51 | panic("Driver Config missing") 52 | } 53 | arrayIDList := StorageArrayList{} 54 | _ = json.Unmarshal([]byte(file), &arrayIDList) 55 | if len(arrayIDList.StorageArrayList) == 0 { 56 | panic("Array Info not provided") 57 | } 58 | for i := 0; i < len(arrayIDList.StorageArrayList); i++ { 59 | arrayIdvar := "Array" + strconv.Itoa(i+1) + "-Id" 60 | os.Setenv(arrayIdvar, arrayIDList.StorageArrayList[i].ArrayID) 61 | } 62 | 63 | ctx := context.Background() 64 | fmt.Printf("calling startServer") 65 | grpcClient, stop = startServer(ctx) 66 | fmt.Printf("back from startServer") 67 | time.Sleep(5 * time.Second) 68 | opt := godog.Options{ 69 | Format: "pretty", 70 | Paths: []string{"features"}, 71 | } 72 | exitVal := godog.TestSuite{ 73 | Name: "godog", 74 | ScenarioInitializer: FeatureContext, 75 | Options: &opt, 76 | }.Run() 77 | if st := m.Run(); st > exitVal { 78 | exitVal = st 79 | } 80 | stop() 81 | os.Exit(exitVal) 82 | } 83 | 84 | func TestIdentityGetPluginInfo(t *testing.T) { 85 | ctx := context.Background() 86 | fmt.Printf("testing GetPluginInfo\n") 87 | client := csi.NewIdentityClient(grpcClient) 88 | info, err := client.GetPluginInfo(ctx, &csi.GetPluginInfoRequest{}) 89 | if err != nil { 90 | fmt.Printf("GetPluginInfo %s:\n", err.Error()) 91 | t.Error("GetPluginInfo failed") 92 | } else { 93 | fmt.Printf("testing GetPluginInfo passed: %s\n", info.GetName()) 94 | } 95 | } 96 | 97 | func startServer(ctx context.Context) (*grpc.ClientConn, func()) { 98 | // Create a new SP instance and serve it with a piped connection. 99 | sp := provider.New() 100 | lis, err := csiutils.GetCSIEndpointListener() 101 | if err != nil { 102 | fmt.Printf("couldn't open listener: %s\n", err.Error()) 103 | return nil, nil 104 | } 105 | service.Name = os.Getenv("DRIVER_NAME") 106 | service.DriverConfig = os.Getenv("DRIVER_CONFIG") 107 | fmt.Printf("lis: %v\n", lis) 108 | go func() { 109 | fmt.Printf("starting server\n") 110 | if err := sp.Serve(ctx, lis); err != nil { 111 | fmt.Printf("http: Server closed. Error: %v", err) 112 | } 113 | }() 114 | network, addr, err := csiutils.GetCSIEndpoint() 115 | if err != nil { 116 | return nil, nil 117 | } 118 | fmt.Printf("network %v addr %v\n", network, addr) 119 | 120 | clientOpts := []grpc.DialOption{ 121 | grpc.WithInsecure(), 122 | } 123 | 124 | // Create a client for the piped connection. 125 | fmt.Printf("calling gprc.DialContext, ctx %v, addr %s, clientOpts %v\n", ctx, addr, clientOpts) 126 | client, err := grpc.DialContext(ctx, "unix:"+addr, clientOpts...) 127 | if err != nil { 128 | fmt.Printf("DialContext returned error: %s", err.Error()) 129 | } 130 | fmt.Printf("grpc.DialContext returned ok\n") 131 | 132 | return client, func() { 133 | client.Close() 134 | sp.GracefulStop(ctx) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /test/bdd-test/bdd_main_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package bdd_test 16 | 17 | import ( 18 | "context" 19 | "flag" 20 | "fmt" 21 | "os" 22 | "strconv" 23 | "testing" 24 | "time" 25 | 26 | "gopkg.in/yaml.v2" 27 | 28 | "github.com/container-storage-interface/spec/lib/go/csi" 29 | "github.com/cucumber/godog" 30 | "github.com/cucumber/godog/colors" 31 | "github.com/dell/csi-unity/provider" 32 | "github.com/dell/csi-unity/service" 33 | csiutils "github.com/dell/gocsi/utils/csi" 34 | "google.golang.org/grpc" 35 | ) 36 | 37 | var ( 38 | grpcClient *grpc.ClientConn 39 | stop func() 40 | opt = godog.Options{Output: colors.Colored(os.Stdout)} 41 | ) 42 | 43 | func init() { 44 | godog.BindFlags("godog.", flag.CommandLine, &opt) 45 | } 46 | 47 | // To parse the secret json file 48 | type StorageArrayList struct { 49 | StorageArrayList []StorageArrayConfig `yaml:"storageArrayList"` 50 | } 51 | 52 | type StorageArrayConfig struct { 53 | ArrayID string `yaml:"arrayId"` 54 | } 55 | 56 | func TestMain(m *testing.M) { 57 | os.Setenv("X_CSI_MODE", "") 58 | 59 | file, err := os.ReadFile(os.Getenv("DRIVER_SECRET")) 60 | if err != nil { 61 | panic("Driver Config missing") 62 | } 63 | arrayIDList := StorageArrayList{} 64 | _ = yaml.Unmarshal([]byte(file), &arrayIDList) 65 | if len(arrayIDList.StorageArrayList) == 0 { 66 | panic("Array Info not provided") 67 | } 68 | for i := 0; i < len(arrayIDList.StorageArrayList); i++ { 69 | arrayIdvar := "Array" + strconv.Itoa(i+1) + "-Id" 70 | os.Setenv(arrayIdvar, arrayIDList.StorageArrayList[i].ArrayID) 71 | } 72 | 73 | ctx := context.Background() 74 | fmt.Printf("calling startServer") 75 | grpcClient, stop = startServer(ctx) 76 | fmt.Printf("back from startServer") 77 | time.Sleep(5 * time.Second) 78 | 79 | flag.Parse() 80 | opt.Format = "pretty" 81 | opt.Paths = []string{"features"} 82 | exitVal := godog.TestSuite{ 83 | Name: "godog", 84 | ScenarioInitializer: FeatureContext, 85 | Options: &opt, 86 | }.Run() 87 | if st := m.Run(); st > exitVal { 88 | exitVal = st 89 | } 90 | stop() 91 | os.Exit(exitVal) 92 | } 93 | 94 | func TestIdentityGetPluginInfo(t *testing.T) { 95 | ctx := context.Background() 96 | fmt.Printf("testing GetPluginInfo\n") 97 | client := csi.NewIdentityClient(grpcClient) 98 | info, err := client.GetPluginInfo(ctx, &csi.GetPluginInfoRequest{}) 99 | if err != nil { 100 | fmt.Printf("GetPluginInfo %s:\n", err.Error()) 101 | t.Error("GetPluginInfo failed") 102 | } else { 103 | fmt.Printf("testing GetPluginInfo passed: %s\n", info.GetName()) 104 | } 105 | } 106 | 107 | func startServer(ctx context.Context) (*grpc.ClientConn, func()) { 108 | // Create a new SP instance and serve it with a piped connection. 109 | sp := provider.New() 110 | lis, err := csiutils.GetCSIEndpointListener() 111 | if err != nil { 112 | fmt.Printf("couldn't open listener: %s\n", err.Error()) 113 | return nil, nil 114 | } 115 | service.Name = os.Getenv("DRIVER_NAME") 116 | service.DriverConfig = os.Getenv("DRIVER_CONFIG") 117 | service.DriverSecret = os.Getenv("DRIVER_SECRET") 118 | fmt.Printf("lis: %v\n", lis) 119 | go func() { 120 | fmt.Printf("starting server\n") 121 | if err := sp.Serve(ctx, lis); err != nil { 122 | fmt.Printf("http: Server closed. Error: %v", err) 123 | } 124 | }() 125 | network, addr, err := csiutils.GetCSIEndpoint() 126 | if err != nil { 127 | return nil, nil 128 | } 129 | fmt.Printf("network %v addr %v\n", network, addr) 130 | 131 | clientOpts := []grpc.DialOption{ 132 | grpc.WithInsecure(), 133 | } 134 | 135 | // Create a client for the piped connection. 136 | fmt.Printf("calling gprc.DialContext, ctx %v, addr %s, clientOpts %v\n", ctx, addr, clientOpts) 137 | client, err := grpc.DialContext(ctx, "unix:"+addr, clientOpts...) 138 | if err != nil { 139 | fmt.Printf("DialContext returned error: %s", err.Error()) 140 | } 141 | fmt.Printf("grpc.DialContext returned ok\n") 142 | 143 | return client, func() { 144 | client.Close() 145 | sp.GracefulStop(ctx) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /test/scale-test/10volumes/templates/test.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | kind: StatefulSet 15 | apiVersion: apps/v1 16 | metadata: 17 | name: unitytest 18 | namespace: test 19 | spec: 20 | serviceName: unitytest-10vol 21 | replicas: 22 | '[object Object]': null 23 | selector: 24 | matchLabels: 25 | app: unitytest 26 | template: 27 | metadata: 28 | labels: 29 | app: unitytest 30 | spec: 31 | serviceAccount: unitytest 32 | hostNetwork: true 33 | containers: 34 | - name: test 35 | image: 'quay.io/centos/centos:latest' 36 | command: 37 | - /bin/sleep 38 | - '3600' 39 | volumeMounts: 40 | - mountPath: /data0 41 | name: unityvolx0 42 | - mountPath: /data1 43 | name: unityvolx1 44 | - mountPath: /data2 45 | name: unityvolx2 46 | - mountPath: /data3 47 | name: unityvolx3 48 | - mountPath: /data4 49 | name: unityvolx4 50 | - mountPath: /data5 51 | name: unityvolx5 52 | - mountPath: /data6 53 | name: unityvolx6 54 | - mountPath: /data7 55 | name: unityvolx7 56 | - mountPath: /data8 57 | name: unityvolx8 58 | - mountPath: /data9 59 | name: unityvolx9 60 | volumeClaimTemplates: 61 | - metadata: 62 | name: unityvolx0 63 | spec: 64 | accessModes: 65 | - ReadWriteOnce 66 | storageClassName: 67 | '[object Object]': null 68 | resources: 69 | requests: 70 | storage: 8Gi 71 | - metadata: 72 | name: unityvolx1 73 | spec: 74 | accessModes: 75 | - ReadWriteOnce 76 | storageClassName: 77 | '[object Object]': null 78 | resources: 79 | requests: 80 | storage: 8Gi 81 | - metadata: 82 | name: unityvolx2 83 | spec: 84 | accessModes: 85 | - ReadWriteOnce 86 | storageClassName: 87 | '[object Object]': null 88 | resources: 89 | requests: 90 | storage: 8Gi 91 | - metadata: 92 | name: unityvolx3 93 | spec: 94 | accessModes: 95 | - ReadWriteOnce 96 | storageClassName: 97 | '[object Object]': null 98 | resources: 99 | requests: 100 | storage: 8Gi 101 | - metadata: 102 | name: unityvolx4 103 | spec: 104 | accessModes: 105 | - ReadWriteOnce 106 | storageClassName: 107 | '[object Object]': null 108 | resources: 109 | requests: 110 | storage: 8Gi 111 | - metadata: 112 | name: unityvolx5 113 | spec: 114 | accessModes: 115 | - ReadWriteOnce 116 | storageClassName: 117 | '[object Object]': null 118 | resources: 119 | requests: 120 | storage: 8Gi 121 | - metadata: 122 | name: unityvolx6 123 | spec: 124 | accessModes: 125 | - ReadWriteOnce 126 | storageClassName: 127 | '[object Object]': null 128 | resources: 129 | requests: 130 | storage: 8Gi 131 | - metadata: 132 | name: unityvolx7 133 | spec: 134 | accessModes: 135 | - ReadWriteOnce 136 | storageClassName: 137 | '[object Object]': null 138 | resources: 139 | requests: 140 | storage: 8Gi 141 | - metadata: 142 | name: unityvolx8 143 | spec: 144 | accessModes: 145 | - ReadWriteOnce 146 | storageClassName: 147 | '[object Object]': null 148 | resources: 149 | requests: 150 | storage: 8Gi 151 | - metadata: 152 | name: unityvolx9 153 | spec: 154 | accessModes: 155 | - ReadWriteOnce 156 | storageClassName: 157 | '[object Object]': null 158 | resources: 159 | requests: 160 | storage: 8Gi 161 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package main 16 | 17 | import ( 18 | "context" 19 | "flag" 20 | "fmt" 21 | "os" 22 | "strings" 23 | "time" 24 | 25 | "github.com/dell/gocsi" 26 | "k8s.io/client-go/kubernetes" 27 | 28 | "github.com/dell/csi-unity/k8sutils" 29 | "github.com/dell/csi-unity/provider" 30 | "github.com/dell/csi-unity/service" 31 | ) 32 | 33 | type leaderElection interface { 34 | Run() error 35 | WithNamespace(namespace string) 36 | } 37 | 38 | var exitFunc = os.Exit 39 | 40 | func validateArgs(driverConfigParamsfile *string, driverName *string, driverSecret *string) { 41 | if *driverConfigParamsfile == "" { 42 | fmt.Fprintf(os.Stderr, "driver-config argument is mandatory") 43 | exitFunc(1) 44 | } 45 | if *driverName == "" { 46 | fmt.Fprintf(os.Stderr, "driver-name argument is mandatory") 47 | exitFunc(1) 48 | } 49 | if *driverSecret == "" { 50 | fmt.Fprintf(os.Stderr, "driver-secret argument is mandatory") 51 | exitFunc(3) 52 | } 53 | } 54 | 55 | func checkLeaderElectionError(err error) { 56 | if err != nil { 57 | fmt.Fprintf(os.Stderr, "failed to initialize leader election: %v", err) 58 | exitFunc(1) 59 | } 60 | } 61 | 62 | func main() { 63 | mainR(gocsi.Run, func(kubeconfig string) (kubernetes.Interface, error) { 64 | return k8sutils.CreateKubeClientSet(kubeconfig) 65 | }, func(clientset kubernetes.Interface, lockName, namespace string, renewDeadline, leaseDuration, retryPeriod time.Duration, run func(ctx context.Context)) { 66 | k8sutils.LeaderElection(clientset.(*kubernetes.Clientset), lockName, namespace, renewDeadline, leaseDuration, retryPeriod, run) 67 | }) 68 | } 69 | 70 | // main is ignored when this package is built as a go plug-in. 71 | func mainR(runFunc func(ctx context.Context, name, desc string, usage string, sp gocsi.StoragePluginProvider), createKubeClientSet func(kubeconfig string) (kubernetes.Interface, error), leaderElection func(clientset kubernetes.Interface, lockName, namespace string, renewDeadline, leaseDuration, retryPeriod time.Duration, run func(ctx context.Context))) { 72 | driverName := flag.String("driver-name", "", "driver name") 73 | driverSecret := flag.String("driver-secret", "", "driver secret yaml file") 74 | driverConfig := flag.String("driver-config", "", "driver config yaml file") 75 | enableLeaderElection := flag.Bool("leader-election", false, "boolean to enable leader election") 76 | leaderElectionNamespace := flag.String("leader-election-namespace", "", "namespace where leader election lease will be created") 77 | leaderElectionLeaseDuration := flag.Duration("leader-election-lease-duration", 15*time.Second, "Duration, in seconds, that non-leader candidates will wait to force acquire leadership") 78 | leaderElectionRenewDeadline := flag.Duration("leader-election-renew-deadline", 10*time.Second, "Duration, in seconds, that the acting leader will retry refreshing leadership before giving up.") 79 | leaderElectionRetryPeriod := flag.Duration("leader-election-retry-period", 5*time.Second, "Duration, in seconds, the LeaderElector clients should wait between tries of actions") 80 | flag.Parse() 81 | 82 | service.Name = *driverName 83 | service.DriverSecret = *driverSecret 84 | service.DriverConfig = *driverConfig 85 | // validate driver name, driver secret and driver config arguments 86 | validateArgs(driverConfig, driverName, driverSecret) 87 | 88 | // Always set X_CSI_DEBUG to false irrespective of what user has specified 89 | _ = os.Setenv(gocsi.EnvVarDebug, "false") 90 | // We always want to enable Request and Response logging (no reason for users to control this) 91 | _ = os.Setenv(gocsi.EnvVarReqLogging, "true") 92 | _ = os.Setenv(gocsi.EnvVarRepLogging, "true") 93 | 94 | kubeconfig := flag.String("kubeconfig", "", "absolute path to the kubeconfig file") 95 | flag.Parse() 96 | run := func(ctx context.Context) { 97 | runFunc( 98 | ctx, 99 | "csi-unity.dellemc.com", 100 | "An Unity Container Storage Interface (CSI) Plugin", 101 | usage, 102 | provider.New()) 103 | } 104 | if *enableLeaderElection == false { 105 | run(context.TODO()) 106 | } else { 107 | driverName := strings.Replace("csi-unity.dellemc.com", ".", "-", -1) 108 | lockName := fmt.Sprintf("driver-%s", driverName) 109 | k8sclientset, err := createKubeClientSet(*kubeconfig) 110 | checkLeaderElectionError(err) 111 | // Attempt to become leader and start the driver 112 | 113 | leaderElection(k8sclientset, lockName, *leaderElectionNamespace, 114 | *leaderElectionRenewDeadline, *leaderElectionLeaseDuration, *leaderElectionRetryPeriod, run) 115 | } 116 | } 117 | 118 | const usage = ` 119 | X_CSI_UNITY_NODENAME 120 | Specifies the name of the node where the Node plugin is running. 121 | The default value is empty. 122 | ` 123 | -------------------------------------------------------------------------------- /dell-csi-helm-installer/common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (c) 2021 Dell Inc., or its subsidiaries. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | DRIVERDIR="${SCRIPTDIR}/../helm" 12 | 13 | RED='\033[0;31m' 14 | GREEN='\033[0;32m' 15 | YELLOW='\033[1;33m' 16 | DARK_GRAY='\033[1;30m' 17 | NC='\033[0m' # No Color 18 | 19 | function decho() { 20 | if [ -n "${DEBUGLOG}" ]; then 21 | echo "$@" | tee -a "${DEBUGLOG}" 22 | fi 23 | } 24 | 25 | function debuglog_only() { 26 | if [ -n "${DEBUGLOG}" ]; then 27 | echo "$@" >> "${DEBUGLOG}" 28 | fi 29 | } 30 | 31 | function log() { 32 | case $1 in 33 | separator) 34 | decho "------------------------------------------------------" 35 | ;; 36 | error) 37 | decho 38 | log separator 39 | printf "${RED}Error: $2\n" 40 | printf "${RED}Installation cannot continue${NC}\n" 41 | debuglog_only "Error: $2" 42 | debuglog_only "Installation cannot continue" 43 | exit 1 44 | ;; 45 | uninstall_error) 46 | log separator 47 | printf "${RED}Error: $2\n" 48 | printf "${RED}Uninstallation cannot continue${NC}\n" 49 | debuglog_only "Error: $2" 50 | debuglog_only "Uninstallation cannot continue" 51 | exit 1 52 | ;; 53 | step) 54 | printf "|\n|- %-65s" "$2" 55 | debuglog_only "${2}" 56 | ;; 57 | small_step) 58 | printf "%-61s" "$2" 59 | debuglog_only "${2}" 60 | ;; 61 | section) 62 | log separator 63 | printf "> %s\n" "$2" 64 | debuglog_only "${2}" 65 | log separator 66 | ;; 67 | smart_step) 68 | if [[ $3 == "small" ]]; then 69 | log small_step "$2" 70 | else 71 | log step "$2" 72 | fi 73 | ;; 74 | arrow) 75 | printf " %s\n %s" "|" "|--> " 76 | ;; 77 | step_success) 78 | printf "${GREEN}Success${NC}\n" 79 | ;; 80 | step_failure) 81 | printf "${RED}Failed${NC}\n" 82 | ;; 83 | step_warning) 84 | printf "${YELLOW}Warning${NC}\n" 85 | ;; 86 | info) 87 | printf "${DARK_GRAY}%s${NC}\n" "$2" 88 | ;; 89 | passed) 90 | printf "${GREEN}Success${NC}\n" 91 | ;; 92 | warnings) 93 | printf "${YELLOW}Warnings:${NC}\n" 94 | ;; 95 | errors) 96 | printf "${RED}Errors:${NC}\n" 97 | ;; 98 | *) 99 | echo -n "Unknown" 100 | ;; 101 | esac 102 | } 103 | 104 | function check_error() { 105 | if [[ $1 -ne 0 ]]; then 106 | log step_failure 107 | else 108 | log step_success 109 | fi 110 | } 111 | 112 | # 113 | # get_release will determine the helm release name to use 114 | # If ${RELEASE} is set, use that 115 | # Otherwise, use the driver name minus any "csi-" prefix 116 | # argument 1: Driver name 117 | function get_release_name() { 118 | local D="${1}" 119 | if [ ! -z "${RELEASE}" ]; then 120 | decho "${RELEASE}" 121 | return 122 | fi 123 | 124 | local PREFIX="csi-" 125 | R=${D#"$PREFIX"} 126 | decho "${R}" 127 | } 128 | 129 | function run_command() { 130 | local RC=0 131 | if [ -n "${DEBUGLOG}" ]; then 132 | local ME=$(basename "${0}") 133 | echo "---------------" >> "${DEBUGLOG}" 134 | echo "${ME}:${BASH_LINENO[0]} - Running command: $@" >> "${DEBUGLOG}" 135 | debuglog_only "Results:" 136 | eval "$@" | tee -a "${DEBUGLOG}" 137 | RC=${PIPESTATUS[0]} 138 | echo "---------------" >> "${DEBUGLOG}" 139 | else 140 | eval "$@" 141 | RC=$? 142 | fi 143 | return $RC 144 | } 145 | 146 | # dump out information about a helm chart to the debug file 147 | # takes a few arguments 148 | # $1 the namespace 149 | # $2 the release 150 | function debuglog_helm_status() { 151 | local NS="${1}" 152 | local RLS="${2}" 153 | 154 | debuglog_only "Getting information about Helm release: ${RLS}" 155 | debuglog_only "****************" 156 | debuglog_only "Helm Status:" 157 | helm status "${RLS}" -n "${NS}" >> "${DEBUGLOG}" 158 | debuglog_only "****************" 159 | debuglog_only "Manifest" 160 | helm get manifest "${RLS}" -n "${NS}" >> "${DEBUGLOG}" 161 | debuglog_only "****************" 162 | debuglog_only "Status of resources" 163 | helm get manifest "${RLS}" -n "${NS}" | kubectl get -f - >> "${DEBUGLOG}" 164 | 165 | } 166 | 167 | # determines if the current KUBECONFIG is pointing to an OpenShift cluster 168 | # echos "true" or "false" 169 | function isOpenShift() { 170 | # check if the securitycontextconstraints.security.openshift.io crd exists 171 | run_command kubectl get crd | grep securitycontextconstraints.security.openshift.io --quiet >/dev/null 2>&1 172 | local O=$? 173 | if [[ ${O} == 0 ]]; then 174 | # this is openshift 175 | echo "true" 176 | else 177 | echo "false" 178 | fi 179 | } 180 | 181 | # determines the version of OpenShift 182 | # echos version, or empty string if not OpenShift 183 | function OpenShiftVersion() { 184 | # check if this is OpenShift 185 | local O=$(isOpenShift) 186 | if [ "${O}" == "false" ]; then 187 | # this is not openshift 188 | echo "" 189 | else 190 | local V=$(run_command kubectl get clusterversions -o jsonpath="{.items[*].status.desired.version}") 191 | local MAJOR=$(echo "${V}" | awk -F '.' '{print $1}') 192 | local MINOR=$(echo "${V}" | awk -F '.' '{print $2}') 193 | echo "${MAJOR}.${MINOR}" 194 | fi 195 | } 196 | 197 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dell/csi-unity 2 | 3 | go 1.25 4 | 5 | require ( 6 | bou.ke/monkey v1.0.2 7 | github.com/container-storage-interface/spec v1.6.0 8 | github.com/cucumber/godog v0.15.0 9 | github.com/dell/dell-csi-extensions/podmon v1.9.0 10 | github.com/dell/gobrick v1.15.0 11 | github.com/dell/gocsi v1.15.0 12 | github.com/dell/gofsutil v1.20.0 13 | github.com/dell/goiscsi v1.13.0 14 | github.com/dell/gounity v1.22.0 15 | github.com/fsnotify/fsnotify v1.9.0 16 | github.com/kubernetes-csi/csi-lib-utils v0.7.0 17 | github.com/sirupsen/logrus v1.9.3 18 | github.com/spf13/viper v1.20.0 19 | github.com/stretchr/testify v1.11.0 20 | golang.org/x/net v0.43.0 21 | google.golang.org/grpc v1.75.0 22 | google.golang.org/protobuf v1.36.6 23 | gopkg.in/yaml.v2 v2.4.0 24 | gopkg.in/yaml.v3 v3.0.1 25 | k8s.io/apimachinery v0.22.2 26 | k8s.io/client-go v0.22.2 27 | ) 28 | 29 | require ( 30 | github.com/akutz/gosync v0.1.0 // indirect 31 | github.com/coreos/go-semver v0.3.1 // indirect 32 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect 33 | github.com/cucumber/gherkin/go/v26 v26.2.0 // indirect 34 | github.com/cucumber/messages/go/v21 v21.0.1 // indirect 35 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 36 | github.com/dell/gonvme v1.12.0 // indirect 37 | github.com/evanphx/json-patch v4.9.0+incompatible // indirect 38 | github.com/go-logr/logr v1.4.3 // indirect 39 | github.com/go-viper/mapstructure/v2 v2.4.0 // indirect 40 | github.com/gofrs/uuid v4.4.0+incompatible // indirect 41 | github.com/gogo/protobuf v1.3.2 // indirect 42 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 43 | github.com/golang/mock v1.6.0 // indirect 44 | github.com/golang/protobuf v1.5.4 // indirect 45 | github.com/google/gofuzz v1.2.0 // indirect 46 | github.com/googleapis/gnostic v0.4.1 // indirect 47 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect 48 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect 49 | github.com/hashicorp/go-memdb v1.3.4 // indirect 50 | github.com/hashicorp/golang-lru v1.0.2 // indirect 51 | github.com/imdario/mergo v0.3.5 // indirect 52 | github.com/json-iterator/go v1.1.12 // indirect 53 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 54 | github.com/modern-go/reflect2 v1.0.2 // indirect 55 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 56 | github.com/pkg/errors v0.9.1 // indirect 57 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 58 | github.com/sagikazarmark/locafero v0.7.0 // indirect 59 | github.com/sourcegraph/conc v0.3.0 // indirect 60 | github.com/spf13/afero v1.12.0 // indirect 61 | github.com/spf13/cast v1.7.1 // indirect 62 | github.com/spf13/pflag v1.0.9 // indirect 63 | github.com/stretchr/objx v0.5.2 // indirect 64 | github.com/subosito/gotenv v1.6.0 // indirect 65 | go.etcd.io/etcd/api/v3 v3.6.1 // indirect 66 | go.etcd.io/etcd/client/pkg/v3 v3.6.1 // indirect 67 | go.etcd.io/etcd/client/v3 v3.6.1 // indirect 68 | go.uber.org/multierr v1.11.0 // indirect 69 | go.uber.org/zap v1.27.0 // indirect 70 | golang.org/x/crypto v0.41.0 // indirect 71 | golang.org/x/oauth2 v0.30.0 // indirect 72 | golang.org/x/sync v0.17.0 // indirect 73 | golang.org/x/sys v0.36.0 // indirect 74 | golang.org/x/term v0.34.0 // indirect 75 | golang.org/x/text v0.28.0 // indirect 76 | golang.org/x/time v0.9.0 // indirect 77 | google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect 78 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect 79 | gopkg.in/inf.v0 v0.9.1 // indirect 80 | k8s.io/api v0.22.2 // indirect 81 | k8s.io/klog v1.0.0 // indirect 82 | k8s.io/klog/v2 v2.130.1 // indirect 83 | k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 // indirect 84 | k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a // indirect 85 | sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect 86 | sigs.k8s.io/yaml v1.4.0 // indirect 87 | ) 88 | 89 | replace ( 90 | k8s.io/api => k8s.io/api v0.20.2 91 | k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.20.2 92 | k8s.io/apimachinery => k8s.io/apimachinery v0.20.2 93 | k8s.io/apiserver => k8s.io/apiserver v0.20.2 94 | k8s.io/cli-runtime => k8s.io/cli-runtime v0.20.2 95 | k8s.io/client-go => k8s.io/client-go v0.20.2 96 | k8s.io/cloud-provider => k8s.io/cloud-provider v0.20.2 97 | k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.20.2 98 | k8s.io/code-generator => k8s.io/code-generator v0.20.2 99 | k8s.io/component-base => k8s.io/component-base v0.20.2 100 | k8s.io/component-helpers => k8s.io/component-helpers v0.22.2 101 | k8s.io/controller-manager => k8s.io/controller-manager v0.20.2 102 | k8s.io/cri-api => k8s.io/cri-api v0.20.2 103 | k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.20.2 104 | k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.20.2 105 | k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.20.2 106 | k8s.io/kube-proxy => k8s.io/kube-proxy v0.20.2 107 | k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.20.2 108 | k8s.io/kubectl => k8s.io/kubectl v0.20.2 109 | k8s.io/kubelet => k8s.io/kubelet v0.20.2 110 | k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.20.2 111 | k8s.io/metrics => k8s.io/metrics v0.20.2 112 | k8s.io/mount-utils => k8s.io/mount-utils v0.20.2 113 | k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.20.2 114 | k8s.io/scheduler => k8s.io/schduler v0.20.2 115 | ) 116 | -------------------------------------------------------------------------------- /service/identity_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2025 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package service 17 | 18 | import ( 19 | "context" 20 | "errors" 21 | "sync" 22 | "testing" 23 | 24 | "github.com/container-storage-interface/spec/lib/go/csi" 25 | "github.com/dell/csi-unity/core" 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | type probeService interface { 30 | controllerProbe(ctx context.Context, arrayID string) error 31 | nodeProbe(ctx context.Context, arrayID string) error 32 | } 33 | 34 | type mockProbeService struct { 35 | mockControllerProbe func(ctx context.Context, arrayID string) error 36 | mockNodeProbe func(ctx context.Context, arrayID string) error 37 | } 38 | 39 | func (m *mockProbeService) controllerProbe(ctx context.Context, arrayID string) error { 40 | if m.mockControllerProbe != nil { 41 | return m.mockControllerProbe(ctx, arrayID) 42 | } 43 | return nil 44 | } 45 | 46 | func (m *mockProbeService) nodeProbe(ctx context.Context, arrayID string) error { 47 | if m.mockNodeProbe != nil { 48 | return m.mockNodeProbe(ctx, arrayID) 49 | } 50 | return nil 51 | } 52 | 53 | func TestService_Probe(t *testing.T) { 54 | tests := []struct { 55 | name string 56 | mode string 57 | probeMock *mockProbeService 58 | expectErr bool 59 | }{ 60 | { 61 | name: "Controller Mode - Success", 62 | mode: "controller", 63 | probeMock: &mockProbeService{ 64 | mockControllerProbe: func(_ context.Context, _ string) error { return nil }, 65 | }, 66 | expectErr: false, 67 | }, 68 | { 69 | name: "Controller Mode - Failure", 70 | mode: "controller", 71 | probeMock: &mockProbeService{ 72 | mockControllerProbe: func(_ context.Context, _ string) error { return errors.New("controller probe failed") }, 73 | }, 74 | expectErr: true, 75 | }, 76 | { 77 | name: "Node Mode - Success", 78 | mode: "node", 79 | probeMock: &mockProbeService{ 80 | mockNodeProbe: func(_ context.Context, _ string) error { return nil }, 81 | }, 82 | expectErr: false, 83 | }, 84 | { 85 | name: "Node Mode - Failure", 86 | mode: "node", 87 | probeMock: &mockProbeService{ 88 | mockNodeProbe: func(_ context.Context, _ string) error { return errors.New("node probe failed") }, 89 | }, 90 | expectErr: true, 91 | }, 92 | } 93 | 94 | for _, tt := range tests { 95 | t.Run(tt.name, func(t *testing.T) { 96 | var err error 97 | if tt.mode == "controller" { 98 | err = tt.probeMock.controllerProbe(context.TODO(), "") 99 | } else { 100 | err = tt.probeMock.nodeProbe(context.TODO(), "") 101 | } 102 | if tt.expectErr { 103 | assert.Error(t, err) 104 | } else { 105 | assert.NoError(t, err) 106 | } 107 | }) 108 | } 109 | } 110 | 111 | func TestService_GetPluginInfo(t *testing.T) { 112 | s := &service{} 113 | 114 | resp, err := s.GetPluginInfo(context.TODO(), &csi.GetPluginInfoRequest{}) 115 | assert.NoError(t, err) 116 | assert.NotNil(t, resp) 117 | assert.Equal(t, Name, resp.Name) 118 | assert.Equal(t, core.SemVer, resp.VendorVersion) 119 | assert.Equal(t, Manifest, resp.Manifest) 120 | } 121 | 122 | func TestService_GetPluginCapabilities(t *testing.T) { 123 | s := &service{} 124 | resp, err := s.GetPluginCapabilities(context.TODO(), &csi.GetPluginCapabilitiesRequest{}) 125 | 126 | assert.NoError(t, err) 127 | assert.NotNil(t, resp) 128 | assert.Len(t, resp.Capabilities, 4) 129 | 130 | expectedCapabilities := []*csi.PluginCapability{ 131 | { 132 | Type: &csi.PluginCapability_Service_{ 133 | Service: &csi.PluginCapability_Service{ 134 | Type: csi.PluginCapability_Service_CONTROLLER_SERVICE, 135 | }, 136 | }, 137 | }, 138 | { 139 | Type: &csi.PluginCapability_VolumeExpansion_{ 140 | VolumeExpansion: &csi.PluginCapability_VolumeExpansion{ 141 | Type: csi.PluginCapability_VolumeExpansion_ONLINE, 142 | }, 143 | }, 144 | }, 145 | { 146 | Type: &csi.PluginCapability_VolumeExpansion_{ 147 | VolumeExpansion: &csi.PluginCapability_VolumeExpansion{ 148 | Type: csi.PluginCapability_VolumeExpansion_OFFLINE, 149 | }, 150 | }, 151 | }, 152 | { 153 | Type: &csi.PluginCapability_Service_{ 154 | Service: &csi.PluginCapability_Service{ 155 | Type: csi.PluginCapability_Service_VOLUME_ACCESSIBILITY_CONSTRAINTS, 156 | }, 157 | }, 158 | }, 159 | } 160 | 161 | for i, expected := range expectedCapabilities { 162 | assert.Equal(t, expected.Type, resp.Capabilities[i].Type) 163 | } 164 | } 165 | 166 | func TestRunProbe(t *testing.T) { 167 | svc := &service{ 168 | arrays: &sync.Map{}, 169 | mode: "controller", 170 | } 171 | ctx := context.Background() 172 | req := &csi.ProbeRequest{} 173 | _, err := svc.Probe(ctx, req) 174 | assert.Error(t, err) 175 | 176 | svc.mode = "node" 177 | _, err = svc.Probe(ctx, req) 178 | assert.Error(t, err) 179 | 180 | svc.mode = "" 181 | _, err = svc.Probe(ctx, req) 182 | assert.NoError(t, err) 183 | } 184 | -------------------------------------------------------------------------------- /samples/storageclass/unity-iscsi.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | # This is a sample manifest for utilizing the topology feature and mount options. 15 | # PVCs created using this storage class will be scheduled 16 | # only on the nodes with iSCSI connectivity to the array 17 | 18 | # Change all instances of to the array serial ID of the unisphere instance used 19 | 20 | # Provide mount options through "mountOptions" attribute 21 | # to create PVCs with mount options. 22 | 23 | apiVersion: storage.k8s.io/v1 24 | kind: StorageClass 25 | metadata: 26 | name: unity--iscsi 27 | # If using custom driver name, change the following to point to the custom name 28 | # Default value: None 29 | # Optional: false 30 | # Examples: "csi-driver-unity", "csi-unity.dellemc.com" 31 | provisioner: csi-unity.dellemc.com 32 | # reclaimPolicy - Configure what happens to a Persistent Volume when the PVC 33 | # it is bound to is to be deleted 34 | # Allowed values: 35 | # Delete: the underlying persistent volume will be deleted along with the persistent volume claim. 36 | # Retain: the underlying persistent volume remain. 37 | # Optional: false 38 | # Default value: None 39 | reclaimPolicy: Delete 40 | # allowVolumeExpansion - Attribute to allow volume expansion 41 | # Allowed values: 42 | # "true" - Volume can be resized 43 | # "false" - Volume cannot be resized 44 | # Default value: "true" 45 | # Optional: true 46 | allowVolumeExpansion: true 47 | # volumeBindingMode- controls when volume binding and dynamic provisioning should occur. 48 | # Allowed values: 49 | # Immediate- indicates that volume binding and dynamic provisioning 50 | # occurs once the PersistentVolumeClaim is created 51 | # WaitForFirstConsumer- will delay the binding and provisioning of a PersistentVolume 52 | # until a Pod using the PersistentVolumeClaim is created 53 | # Default value: "Immediate" 54 | # Optional: true 55 | volumeBindingMode: WaitForFirstConsumer 56 | parameters: 57 | # protocol - Defines"FC" or "FIBRE" for fibrechannel, "ISCSI" for iSCSI, or "" for autoselection. 58 | # Allowed values: 59 | # "FC" - Fiber Channel protocol 60 | # "FIBER" - Fiber Channel protocol 61 | # "ISCSI" - iSCSI protocol 62 | # "" - Automatic selection of transport protocol 63 | # Default value: "" 64 | # Optional: false 65 | protocol: iSCSI 66 | # arrayId - Serial Id of the array that will be used for provisioning. 67 | # Allowed values: String 68 | # Default value: None 69 | # Optional: false 70 | # Examples: "APM000000001", "APM000000002" 71 | arrayId: 72 | # storagePool - Defines storage pool. The value should be picked from the column labeled "CLI ID" of Pools in the Unisphere GUI. 73 | # Allowed values: String 74 | # Default value: None 75 | # Optional: false 76 | # Examples: pool_0 77 | storagePool: 78 | # thinProvisioned- Defines Boolean to choose value of thinProvisioned while creating a new volume 79 | # Allowed values: 80 | # "true" - for thin provision 81 | # "false" - for thick provision 82 | # Default value: false 83 | # Optional: true 84 | thinProvisioned: "true" 85 | # isDataReductionEnabled - Defines Boolean to choose value of is DataReductionEnabled while creating a new volume 86 | # Allowed values: 87 | # "true" - Enables data reduction for all-flash storage pool. 88 | # "false" - Disables data reduction. 89 | # Optional: true 90 | # Default value: false 91 | isDataReductionEnabled: "true" 92 | # TieringPolicy - Tiering policy to be used during provisioning 93 | # Requires FAST VP license. 94 | # Allowed values: String 95 | # Optional: true 96 | # Examples: "0" 97 | # Default value: None 98 | # Accepted values: 99 | # "0" for "Start High Then Auto-Tier" 100 | # "1" for "Auto-Tier" 101 | # "2" for "Highest Available Tier" 102 | # "3" for "Lowest Available Tier" 103 | tieringPolicy: 104 | # hostIOLimitName- Insert Host IO Limit Name that is to be used for provisioning here. 105 | # Allowed values: String 106 | # Default value: None 107 | # Optional: true 108 | # Examples: "Autotier" 109 | hostIOLimitName: 110 | # csi.storage.k8s.io/fstype - Set the filesystem type to format the new volume 111 | # Default value: ext4 112 | # Accepted values: 113 | # "ext4" 114 | # "xfs" 115 | csi.storage.k8s.io/fstype: xfs 116 | # Restrict provisioning to specific topologies 117 | # Allowed values: map of key-value pairs 118 | # Default value: None 119 | # Optional: false 120 | # Examples: "apm0020280XXXX" , "apm0021340XXXX" 121 | # Here first three characters should be in small letters. 122 | allowedTopologies: 123 | - matchLabelExpressions: 124 | - key: csi-unity.dellemc.com/-iscsi 125 | values: 126 | - "true" 127 | # mountOptions - Defines mount input values. 128 | # Default value: [] 129 | # Optional: false 130 | # Examples: 131 | # "hard" - option for mounting with NFS 132 | # "context" - option for mounting with block storage 133 | mountOptions: ["", "", ..., ""] 134 | -------------------------------------------------------------------------------- /samples/storageclass/unity-fc.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | # This is a sample manifest for utilizing the topology feature and mount options. 15 | # PVCs created using this storage class will be scheduled 16 | # only on the nodes with FC connectivity to the array 17 | 18 | # Change all instances of to the array serial ID of the unisphere instance used 19 | 20 | # Provide mount options through "mountOptions" attribute 21 | # to create PVCs with mount options. 22 | 23 | apiVersion: storage.k8s.io/v1 24 | kind: StorageClass 25 | metadata: 26 | name: unity--fc 27 | # If using custom driver name, change the following to point to the custom name 28 | # Default value: csi-unity.dellemc.com 29 | # Optional: false 30 | # Examples: "csi-driver-unity", "csi-unity.dellemc.com" 31 | provisioner: csi-unity.dellemc.com 32 | # reclaimPolicy - Configure what happens to a Persistent Volume when the PVC 33 | # it is bound to is to be deleted 34 | # Allowed values: 35 | # Delete: the underlying persistent volume will be deleted along with the persistent volume claim. 36 | # Retain: the underlying persistent volume remain. 37 | # Optional: false 38 | # Default value: None 39 | reclaimPolicy: Delete 40 | # allowVolumeExpansion - Attribute to allow volume expansion 41 | # Allowed values: 42 | # "true" - Volume can be resized 43 | # "false" - Volume cannot be resized 44 | # Optional: true 45 | # Default value: "true" 46 | allowVolumeExpansion: true 47 | # volumeBindingMode- controls when volume binding and dynamic provisioning should occur. 48 | # Allowed values: 49 | # Immediate- indicates that volume binding and dynamic provisioning 50 | # occurs once the PersistentVolumeClaim is created 51 | # WaitForFirstConsumer- will delay the binding and provisioning of a PersistentVolume 52 | # until a Pod using the PersistentVolumeClaim is created 53 | ## Optional: true 54 | # Default value: "Immediate" 55 | volumeBindingMode: WaitForFirstConsumer 56 | parameters: 57 | # protocol - Defines"FC" or "FIBRE" for fibrechannel, "ISCSI" for iSCSI, or "" for autoselection. 58 | # Allowed values: 59 | # "FC" - Fiber Channel protocol 60 | # "FIBER" - Fiber Channel protocol 61 | # "ISCSI" - iSCSI protocol 62 | # "" - Automatic selection of transport protocol 63 | # Optional: false 64 | # Default value: "" 65 | protocol: FC 66 | # arrayId - Serial Id of the array that will be used for provisioning. 67 | # Allowed values: String 68 | # Default value: None 69 | # Optional: false 70 | # Examples: "APM000000001", "APM000000002" 71 | arrayId: 72 | # storagePool - Defines storage pool. The value should be picked from the column labeled "CLI ID" of Pools in the Unisphere GUI. 73 | # Allowed values: String 74 | # Default value: None 75 | # Optional: false 76 | # Examples: pool_0 77 | storagePool: 78 | # thinProvisioned- Defines Boolean to choose value of thinProvisioned while creating a new volume 79 | # Allowed values: 80 | # "true" - for thin provision 81 | # "false" - for thick provision 82 | # Optional: true 83 | # Default value: false 84 | thinProvisioned: "true" 85 | # isDataReductionEnabled - Defines Boolean to choose value of is DataReductionEnabled while creating a new volume 86 | # Allowed values: 87 | # "true" - Enables data reduction for all-flash storage pool. 88 | # "false" - Disables data reduction. 89 | # Optional: true 90 | # Default value: false 91 | isDataReductionEnabled: "true" 92 | # TieringPolicy - Tiering policy to be used during provisioning 93 | # Requires FAST VP license. 94 | # Allowed values: String 95 | # Optional: true 96 | # Examples: "0" 97 | # Default value: None 98 | # Accepted values: 99 | # "0" for "Start High Then Auto-Tier" 100 | # "1" for "Auto-Tier" 101 | # "2" for "Highest Available Tier" 102 | # "3" for "Lowest Available Tier" 103 | tieringPolicy: 104 | # hostIOLimitName - Insert Host IO Limit Name that is to be used for provisioning here. 105 | # Allowed values: String 106 | # Optional: true 107 | # Default value: None 108 | # Examples: "Autotier" 109 | hostIOLimitName: 110 | # csi.storage.k8s.io/fstype - Set the filesystem type to format the new volume 111 | # Default value: ext4 112 | # Accepted values: 113 | # "ext4" 114 | # "xfs" 115 | csi.storage.k8s.io/fstype: xfs 116 | # Restrict provisioning to specific topologies 117 | # Allowed values: map of key-value pairs 118 | # Optional: false 119 | # Default value: None 120 | # Examples: "apm0020280XXXX" , "apm0021340XXXX" 121 | # Here first three characters should be in small letters. 122 | allowedTopologies: 123 | - matchLabelExpressions: 124 | - key: csi-unity.dellemc.com/-fc 125 | values: 126 | - "true" 127 | # mountOptions - Defines mount input values. 128 | # Default value: [] 129 | # Optional: false 130 | # Examples: 131 | # "hard" - option for mounting with NFS 132 | # "context" - option for mounting with block storage 133 | mountOptions: ["", "", ..., ""] 134 | -------------------------------------------------------------------------------- /samples/storageclass/unity-nfs.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | # This is a sample manifest for utilizing the topology feature and mount options. 15 | # PVCs created using this storage class will be scheduled on any available node 16 | 17 | # Change all instances of to the array serial ID of the unisphere instance used 18 | 19 | # Provide mount options through "mountOptions" attribute 20 | # to create PVCs with mount options. 21 | 22 | apiVersion: storage.k8s.io/v1 23 | kind: StorageClass 24 | metadata: 25 | name: unity--nfs 26 | # If using custom driver name, change the following to point to the custom name 27 | # Default value: csi-unity.dellemc.com 28 | # Examples: "csi-driver-unity", "csi-unity.dellemc.com" 29 | provisioner: csi-unity.dellemc.com 30 | # reclaimPolicy - Configure what happens to a Persistent Volume when the PVC 31 | # it is bound to is to be deleted 32 | # Allowed values: 33 | # Delete: the underlying persistent volume will be deleted along with the persistent volume claim. 34 | # Retain: the underlying persistent volume remain. 35 | # Optional: false 36 | # Default value: None 37 | reclaimPolicy: Delete 38 | # allowVolumeExpansion - Attribute to allow volume expansion 39 | # Allowed values: 40 | # "true" - Volume can be resized 41 | # "false" - Volume cannot be resized 42 | # Default value: "true" 43 | # Optional: true 44 | allowVolumeExpansion: true 45 | # volumeBindingMode- controls when volume binding and dynamic provisioning should occur. 46 | # Allowed values: 47 | # Immediate- indicates that volume binding and dynamic provisioning 48 | # occurs once the PersistentVolumeClaim is created 49 | # WaitForFirstConsumer- will delay the binding and provisioning of a PersistentVolume 50 | # until a Pod using the PersistentVolumeClaim is created 51 | # Default value: "Immediate" 52 | # Optional: true 53 | volumeBindingMode: WaitForFirstConsumer 54 | parameters: 55 | # protocol - Defines"FC" or "FIBRE" for fibrechannel, "ISCSI" for iSCSI, or "" for autoselection. 56 | # Allowed values: 57 | # "FC" - Fiber Channel protocol 58 | # "FIBER" - Fiber Channel protocol 59 | # "ISCSI" - iSCSI protocol 60 | # "" - Automatic selection of transport protocol 61 | # Default value: "" 62 | # Optional: false 63 | protocol: NFS 64 | # arrayId - Serial Id of the array that will be used for provisioning. 65 | # Allowed values: String 66 | # Default value: None 67 | # Examples: "APM000000001", "APM000000002" 68 | arrayId: 69 | # storagePool - Defines storage pool. The value should be picked from the column labeled "CLI ID" of Pools in the Unisphere GUI. 70 | # Allowed values: String 71 | # Default value: None 72 | # Optional: false 73 | # Examples: pool_0 74 | storagePool: 75 | # thinProvisioned- Defines Boolean to choose value of thinProvisioned while creating a new volume 76 | # Allowed values: 77 | # "true" - for thin provision 78 | # "false" - for thick provision 79 | # Default value: false 80 | # Optional: true 81 | thinProvisioned: "true" 82 | # isDataReductionEnabled - Defines Boolean to choose value of is DataReductionEnabled while creating a new volume 83 | # Allowed values: 84 | # "true" - Enables data reduction for all-flash storage pool. 85 | # "false" - Disables data reduction. 86 | # Optional: true 87 | # Default value: false 88 | isDataReductionEnabled: "true" 89 | # TieringPolicy - Tiering policy to be used during provisioning 90 | # Requires FAST VP license. 91 | # Allowed values: String 92 | # Optional: true 93 | # Examples: "0" 94 | # Default value: None 95 | # Accepted values: 96 | # "0" for "Start High Then Auto-Tier" 97 | # "1" for "Auto-Tier" 98 | # "2" for "Highest Available Tier" 99 | # "3" for "Lowest Available Tier" 100 | tieringPolicy: 101 | # nasServer - Defines storage NAS Servers. The value should be picked from the column labeled "CLI ID" of NAS Servers tab in the Unisphere GUI. 102 | # Default value: None 103 | # Optional: false 104 | # Examples : nasserver_0 105 | nasServer: 106 | # hostIoSize - Insert Host IO Size that is to be set for the filesystem. 107 | # Default value: None 108 | # Optional: false 109 | # Examples : 8192 110 | hostIoSize: 111 | # csi.storage.k8s.io/fstype - Set the filesystem type; it is ignored with NFS protocol if different from 'nfs' 112 | # Default value: ext4 113 | # Accepted values: 114 | # "nfs" 115 | csi.storage.k8s.io/fstype: nfs 116 | # Restrict provisioning to specific topologies 117 | # Allowed values: map of key-value pairs 118 | # Optional: false 119 | # Default value: None 120 | # Examples: "apm0020280XXXX" , "apm0021340XXXX" 121 | # Here first three characters should be in small letters. 122 | allowedTopologies: 123 | - matchLabelExpressions: 124 | - key: csi-unity.dellemc.com/-nfs 125 | values: 126 | - "true" 127 | # mountOptions - Defines mount input values. 128 | # Default value: [] 129 | # Optional: false 130 | # Examples: 131 | # "hard" - option for mounting with NFS 132 | # "context" - option for mounting with block storage 133 | mountOptions: ["", "", ..., ""] 134 | -------------------------------------------------------------------------------- /service/logging/logging.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package logging 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "reflect" 21 | "runtime" 22 | "strconv" 23 | "strings" 24 | "sync" 25 | "time" 26 | 27 | "github.com/sirupsen/logrus" 28 | ) 29 | 30 | var ( 31 | singletonLog *logrus.Logger 32 | once sync.Once 33 | ) 34 | 35 | const ( 36 | // Default log format will output [INFO]: 2006-01-02T15:04:05Z07:00 - Log message 37 | defaultLogFormat = "time=\"%time%\" level=%lvl% %arrayid% %runid% msg=\"%msg%\"" 38 | defaultTimestampFormat = time.RFC3339 39 | ) 40 | 41 | // Formatter implements logrus.Formatter interface. 42 | type Formatter struct { 43 | // logrus.TextFormatter 44 | // Timestamp format 45 | TimestampFormat string 46 | // Available standard keys: time, msg, lvl 47 | // Also can include custom fields but limited to strings. 48 | // All of fields need to be wrapped inside %% i.e %time% %msg% 49 | LogFormat string 50 | 51 | CallerPrettyfier func(*runtime.Frame) (function string, file string) 52 | } 53 | 54 | // Format building log message. 55 | func (f *Formatter) Format(entry *logrus.Entry) ([]byte, error) { 56 | output := f.LogFormat 57 | if output == "" { 58 | output = defaultLogFormat 59 | } 60 | 61 | timestampFormat := f.TimestampFormat 62 | if timestampFormat == "" { 63 | timestampFormat = defaultTimestampFormat 64 | } 65 | 66 | output = strings.Replace(output, "%time%", entry.Time.Format(timestampFormat), 1) 67 | output = strings.Replace(output, "%msg%", entry.Message, 1) 68 | level := strings.ToUpper(entry.Level.String()) 69 | output = strings.Replace(output, "%lvl%", strings.ToLower(level), 1) 70 | 71 | fields := entry.Data 72 | x, b := fields[RUNID] 73 | if b { 74 | output = strings.Replace(output, "%runid%", fmt.Sprintf("runid=%v", x), 1) 75 | } else { 76 | output = strings.Replace(output, "%runid%", "", 1) 77 | } 78 | x, b = fields[ARRAYID] 79 | 80 | if b { 81 | output = strings.Replace(output, "%arrayid%", fmt.Sprintf("arrayid=%v", x), 1) 82 | } else { 83 | output = strings.Replace(output, "%arrayid%", "", 1) 84 | } 85 | 86 | for k, val := range entry.Data { 87 | switch v := val.(type) { 88 | case string: 89 | output = strings.Replace(output, "%"+k+"%", v, 1) 90 | case int: 91 | s := strconv.Itoa(v) 92 | output = strings.Replace(output, "%"+k+"%", s, 1) 93 | case bool: 94 | s := strconv.FormatBool(v) 95 | output = strings.Replace(output, "%"+k+"%", s, 1) 96 | } 97 | } 98 | 99 | var funcVal, fileVal string 100 | if entry.HasCaller() { 101 | if f.CallerPrettyfier != nil { 102 | funcVal, fileVal = f.CallerPrettyfier(entry.Caller) 103 | } else { 104 | funcVal = entry.Caller.Function 105 | fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line) 106 | } 107 | 108 | if funcVal != "" { 109 | output = fmt.Sprintf("%s func=\"%s\"", output, funcVal) 110 | } 111 | if fileVal != "" { 112 | output = fmt.Sprintf("%s file=\"%s\"", output, fileVal) 113 | } 114 | } 115 | 116 | output = fmt.Sprintf("%s\n", output) 117 | 118 | return []byte(output), nil 119 | } 120 | 121 | // GetLogger - get logger 122 | func GetLogger() *logrus.Logger { 123 | once.Do(func() { 124 | singletonLog = logrus.New() 125 | fmt.Println("csi-unity logger initiated. This should be called only once.") 126 | 127 | // Setting default level to Info since the driver is yet to read secrect that has the debug level set 128 | singletonLog.Level = logrus.InfoLevel 129 | 130 | singletonLog.SetReportCaller(true) 131 | singletonLog.Formatter = &Formatter{ 132 | CallerPrettyfier: func(f *runtime.Frame) (string, string) { 133 | filename1 := strings.Split(f.File, "dell/csi-unity") 134 | if len(filename1) > 1 { 135 | return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("dell/csi-unity%s:%d", filename1[1], f.Line) 136 | } 137 | filename2 := strings.Split(f.File, "dell/gounity") 138 | if len(filename2) > 1 { 139 | return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("dell/gounity%s:%d", filename2[1], f.Line) 140 | } 141 | return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("%s:%d", f.File, f.Line) 142 | }, 143 | } 144 | }) 145 | 146 | return singletonLog 147 | } 148 | 149 | // ChangeLogLevel - change log level 150 | func ChangeLogLevel(logLevel string) { 151 | switch strings.ToLower(logLevel) { 152 | 153 | case "debug": 154 | singletonLog.Level = logrus.DebugLevel 155 | break 156 | 157 | case "warn", "warning": 158 | singletonLog.Level = logrus.WarnLevel 159 | break 160 | 161 | case "error": 162 | singletonLog.Level = logrus.ErrorLevel 163 | break 164 | 165 | case "info": 166 | // Default level will be Info 167 | fallthrough 168 | 169 | default: 170 | singletonLog.Level = logrus.InfoLevel 171 | } 172 | } 173 | 174 | type unityLog string 175 | 176 | // Constants which can be used across modules 177 | const ( 178 | UnityLogger unityLog = "unitylog" 179 | LogFields unityLog = "fields" 180 | RUNID = "runid" 181 | ARRAYID = "arrayid" 182 | ) 183 | 184 | // GetRunidLogger - Get runid logger 185 | func GetRunidLogger(ctx context.Context) *logrus.Entry { 186 | tempLog := ctx.Value(UnityLogger) 187 | if ctx.Value(UnityLogger) != nil && reflect.TypeOf(tempLog) == reflect.TypeOf(&logrus.Entry{}) { 188 | return ctx.Value(UnityLogger).(*logrus.Entry) 189 | } 190 | return nil 191 | } 192 | 193 | // GetRunidAndLogger - Get runid and logger 194 | func GetRunidAndLogger(ctx context.Context) (string, *logrus.Entry) { 195 | rid := "" 196 | fields, ok := ctx.Value(LogFields).(logrus.Fields) 197 | if ok && fields != nil && reflect.TypeOf(fields) == reflect.TypeOf(logrus.Fields{}) { 198 | if fields[RUNID] != nil { 199 | rid = fields[RUNID].(string) 200 | } 201 | } 202 | 203 | tempLog := ctx.Value(UnityLogger) 204 | if tempLog != nil && reflect.TypeOf(tempLog) == reflect.TypeOf(&logrus.Entry{}) { 205 | // rid = fmt.Sprintf("%s", tempLog.(*logrus.Logger).Data[RUNID]) 206 | return rid, tempLog.(*logrus.Entry) 207 | } 208 | return rid, nil 209 | } 210 | 211 | var GetRunidAndLoggerWrapper = func(ctx context.Context) (string, *logrus.Entry) { 212 | return GetRunidAndLogger(ctx) 213 | } 214 | -------------------------------------------------------------------------------- /k8sutils/k8sutils_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2025 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package k8sutils 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "fmt" 21 | "io/ioutil" 22 | "os" 23 | "testing" 24 | "time" 25 | 26 | "github.com/stretchr/testify/mock" 27 | "github.com/stretchr/testify/require" 28 | "k8s.io/client-go/kubernetes" 29 | "k8s.io/client-go/rest" 30 | ) 31 | 32 | var exitFunc = os.Exit 33 | 34 | type MockLeaderElection struct { 35 | mock.Mock 36 | } 37 | 38 | func (m *MockLeaderElection) Run() error { 39 | args := m.Called() 40 | return args.Error(0) 41 | } 42 | 43 | func (m *MockLeaderElection) WithNamespace(namespace string) { 44 | m.Called(namespace) 45 | } 46 | 47 | // MockExit is a mock implementation of os.Exit 48 | var MockExit = func(code int) { 49 | fmt.Printf("os.Exit(%d) called\n", code) 50 | } 51 | 52 | func TestLeaderElection(t *testing.T) { 53 | clientset := &kubernetes.Clientset{} // Mock or use a fake clientset if needed 54 | runFunc := func(_ context.Context) { 55 | fmt.Println("Running leader function") 56 | } 57 | 58 | mockLE := new(MockLeaderElection) 59 | mockLE.On("Run").Return(nil) // Mocking a successful run 60 | 61 | // Override exitFunc to prevent test from exiting 62 | exitCalled := false 63 | oldExit := exitFunc 64 | defer func() { recover(); exitFunc = oldExit }() 65 | exitFunc = func(_ int) { exitCalled = true; panic("exitFunc called") } 66 | 67 | // Simulate LeaderElection function 68 | func() { 69 | defer func() { 70 | if r := recover(); r != nil { 71 | exitCalled = true 72 | } 73 | }() 74 | LeaderElection(clientset, "test-lock", "test-namespace", time.Second, time.Second*2, time.Second*3, runFunc) 75 | }() 76 | 77 | if !exitCalled { 78 | t.Errorf("exitFunc was called unexpectedly") 79 | } 80 | } 81 | 82 | func TestCreateKubeClientSet(t *testing.T) { 83 | // Test cases 84 | tests := []struct { 85 | name string 86 | kubeconfig string 87 | configErr error 88 | clientErr error 89 | wantErr bool 90 | }{ 91 | { 92 | name: "Valid kubeconfig", 93 | kubeconfig: "valid_kubeconfig", 94 | configErr: nil, 95 | clientErr: nil, 96 | wantErr: false, 97 | }, 98 | { 99 | name: "Invalid kubeconfig", 100 | kubeconfig: "invalid_kubeconfig", 101 | configErr: errors.New("config error"), 102 | clientErr: nil, 103 | wantErr: true, 104 | }, 105 | { 106 | name: "In-cluster config", 107 | kubeconfig: "", 108 | configErr: nil, 109 | clientErr: nil, 110 | wantErr: false, 111 | }, 112 | { 113 | name: "In-cluster config error", 114 | kubeconfig: "", 115 | configErr: errors.New("config error"), 116 | clientErr: nil, 117 | wantErr: true, 118 | }, 119 | { 120 | name: "New for config error", 121 | kubeconfig: "", 122 | configErr: nil, 123 | clientErr: errors.New("client error"), 124 | wantErr: true, 125 | }, 126 | { 127 | name: "New for config error with valid kubeconfig", 128 | kubeconfig: "valid_kubeconfig", 129 | configErr: nil, 130 | clientErr: errors.New("client error"), 131 | wantErr: true, 132 | }, 133 | } 134 | 135 | // Save original functions 136 | origBuildConfigFromFlags := buildConfigFromFlags 137 | origInClusterConfig := inClusterConfig 138 | origNewForConfig := newForConfig 139 | 140 | // Restore original functions after tests 141 | defer func() { 142 | buildConfigFromFlags = origBuildConfigFromFlags 143 | inClusterConfig = origInClusterConfig 144 | newForConfig = origNewForConfig 145 | }() 146 | 147 | for _, tt := range tests { 148 | t.Run(tt.name, func(t *testing.T) { 149 | // Create a temporary kubeconfig file for the valid kubeconfig test case 150 | if tt.name == "Valid kubeconfig" || tt.name == "New for config error with valid kubeconfig" { 151 | tmpFile, err := ioutil.TempFile("", "kubeconfig") 152 | require.NoError(t, err) 153 | defer os.Remove(tmpFile.Name()) 154 | 155 | kubeconfigContent := ` 156 | apiVersion: v1 157 | clusters: 158 | - cluster: 159 | server: https://127.0.0.1:6443 160 | name: test-cluster 161 | contexts: 162 | - context: 163 | cluster: test-cluster 164 | user: test-user 165 | name: test-context 166 | current-context: test-context 167 | kind: Config 168 | preferences: {} 169 | users: 170 | - name: test-user 171 | user: 172 | token: test-token 173 | ` 174 | err = ioutil.WriteFile(tmpFile.Name(), []byte(kubeconfigContent), 0o600) 175 | require.NoError(t, err) 176 | tt.kubeconfig = tmpFile.Name() 177 | } 178 | 179 | // Mock environment variables for in-cluster config 180 | if tt.name == "In-cluster config" || tt.name == "In-cluster config error" { 181 | os.Setenv("KUBERNETES_SERVICE_HOST", "127.0.0.1") 182 | os.Setenv("KUBERNETES_SERVICE_PORT", "443") 183 | defer os.Unsetenv("KUBERNETES_SERVICE_HOST") 184 | defer os.Unsetenv("KUBERNETES_SERVICE_PORT") 185 | } 186 | 187 | // Mock functions 188 | buildConfigFromFlags = func(_, _ string) (*rest.Config, error) { 189 | return &rest.Config{}, tt.configErr 190 | } 191 | inClusterConfig = func() (*rest.Config, error) { 192 | return &rest.Config{}, tt.configErr 193 | } 194 | newForConfig = func(_ *rest.Config) (*kubernetes.Clientset, error) { 195 | if tt.clientErr != nil { 196 | return nil, tt.clientErr 197 | } 198 | return &kubernetes.Clientset{}, nil 199 | } 200 | 201 | clientset, err := CreateKubeClientSet(tt.kubeconfig) 202 | if (err != nil) != tt.wantErr { 203 | return 204 | } 205 | if !tt.wantErr && clientset == nil { 206 | t.Errorf("CreateKubeClientSet() = nil, want non-nil") 207 | } 208 | }) 209 | } 210 | } 211 | 212 | func TestRunLeaderElection(t *testing.T) { 213 | // Save the original ExitFunc 214 | origExitFunc := ExitFunc 215 | // Restore the original ExitFunc after the test 216 | defer func() { ExitFunc = origExitFunc }() 217 | 218 | // Override ExitFunc with the mock implementation 219 | ExitFunc = MockExit 220 | 221 | t.Run("RunLeaderElection_Success", func(t *testing.T) { 222 | mockLE := new(MockLeaderElection) 223 | mockLE.On("Run").Return(nil) 224 | 225 | RunLeaderElection(mockLE) 226 | 227 | mockLE.AssertExpectations(t) 228 | }) 229 | 230 | t.Run("RunLeaderElection_Error", func(t *testing.T) { 231 | mockLE := new(MockLeaderElection) 232 | mockLE.On("Run").Return(errors.New("leader election error")) 233 | 234 | // Capture the output 235 | oldStderr := os.Stderr 236 | r, w, _ := os.Pipe() 237 | os.Stderr = w 238 | 239 | // Run the function 240 | RunLeaderElection(mockLE) 241 | 242 | // Restore stderr 243 | w.Close() 244 | os.Stderr = oldStderr 245 | 246 | // Read the captured output 247 | var buf [1024]byte 248 | n, _ := r.Read(buf[:]) 249 | output := string(buf[:n]) 250 | 251 | require.Contains(t, output, "failed to initialize leader election: leader election error") 252 | mockLE.AssertExpectations(t) 253 | }) 254 | } 255 | -------------------------------------------------------------------------------- /test/scale-test/20volumes/templates/test.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | # See the License for the specific language governing permissions and 11 | # limitations under the License. 12 | # 13 | 14 | kind: StatefulSet 15 | apiVersion: apps/v1 16 | metadata: 17 | name: unitytest 18 | namespace: test 19 | spec: 20 | serviceName: unitytest-20vol 21 | replicas: 22 | '[object Object]': null 23 | selector: 24 | matchLabels: 25 | app: unitytest 26 | template: 27 | metadata: 28 | labels: 29 | app: unitytest 30 | spec: 31 | serviceAccount: unitytest 32 | hostNetwork: true 33 | containers: 34 | - name: test 35 | image: 'quay.io/centos/centos:latest' 36 | command: 37 | - /bin/sleep 38 | - '3600' 39 | volumeMounts: 40 | - mountPath: /data0 41 | name: unityvolx0 42 | - mountPath: /data1 43 | name: unityvolx1 44 | - mountPath: /data2 45 | name: unityvolx2 46 | - mountPath: /data3 47 | name: unityvolx3 48 | - mountPath: /data4 49 | name: unityvolx4 50 | - mountPath: /data5 51 | name: unityvolx5 52 | - mountPath: /data6 53 | name: unityvolx6 54 | - mountPath: /data7 55 | name: unityvolx7 56 | - mountPath: /data8 57 | name: unityvolx8 58 | - mountPath: /data9 59 | name: unityvolx9 60 | - mountPath: /data10 61 | name: unityvolx10 62 | - mountPath: /data11 63 | name: unityvolx11 64 | - mountPath: /data12 65 | name: unityvolx12 66 | - mountPath: /data13 67 | name: unityvolx13 68 | - mountPath: /data14 69 | name: unityvolx14 70 | - mountPath: /data15 71 | name: unityvolx15 72 | - mountPath: /data16 73 | name: unityvolx16 74 | - mountPath: /data17 75 | name: unityvolx17 76 | - mountPath: /data18 77 | name: unityvolx18 78 | - mountPath: /data19 79 | name: unityvolx19 80 | volumeClaimTemplates: 81 | - metadata: 82 | name: unityvolx0 83 | spec: 84 | accessModes: 85 | - ReadWriteOnce 86 | storageClassName: 87 | '[object Object]': null 88 | resources: 89 | requests: 90 | storage: 8Gi 91 | - metadata: 92 | name: unityvolx1 93 | spec: 94 | accessModes: 95 | - ReadWriteOnce 96 | storageClassName: 97 | '[object Object]': null 98 | resources: 99 | requests: 100 | storage: 8Gi 101 | - metadata: 102 | name: unityvolx2 103 | spec: 104 | accessModes: 105 | - ReadWriteOnce 106 | storageClassName: 107 | '[object Object]': null 108 | resources: 109 | requests: 110 | storage: 8Gi 111 | - metadata: 112 | name: unityvolx3 113 | spec: 114 | accessModes: 115 | - ReadWriteOnce 116 | storageClassName: 117 | '[object Object]': null 118 | resources: 119 | requests: 120 | storage: 8Gi 121 | - metadata: 122 | name: unityvolx4 123 | spec: 124 | accessModes: 125 | - ReadWriteOnce 126 | storageClassName: 127 | '[object Object]': null 128 | resources: 129 | requests: 130 | storage: 8Gi 131 | - metadata: 132 | name: unityvolx5 133 | spec: 134 | accessModes: 135 | - ReadWriteOnce 136 | storageClassName: 137 | '[object Object]': null 138 | resources: 139 | requests: 140 | storage: 8Gi 141 | - metadata: 142 | name: unityvolx6 143 | spec: 144 | accessModes: 145 | - ReadWriteOnce 146 | storageClassName: 147 | '[object Object]': null 148 | resources: 149 | requests: 150 | storage: 8Gi 151 | - metadata: 152 | name: unityvolx7 153 | spec: 154 | accessModes: 155 | - ReadWriteOnce 156 | storageClassName: 157 | '[object Object]': null 158 | resources: 159 | requests: 160 | storage: 8Gi 161 | - metadata: 162 | name: unityvolx8 163 | spec: 164 | accessModes: 165 | - ReadWriteOnce 166 | storageClassName: 167 | '[object Object]': null 168 | resources: 169 | requests: 170 | storage: 8Gi 171 | - metadata: 172 | name: unityvolx9 173 | spec: 174 | accessModes: 175 | - ReadWriteOnce 176 | storageClassName: 177 | '[object Object]': null 178 | resources: 179 | requests: 180 | storage: 8Gi 181 | - metadata: 182 | name: unityvolx10 183 | spec: 184 | accessModes: 185 | - ReadWriteOnce 186 | storageClassName: 187 | '[object Object]': null 188 | resources: 189 | requests: 190 | storage: 8Gi 191 | - metadata: 192 | name: unityvolx11 193 | spec: 194 | accessModes: 195 | - ReadWriteOnce 196 | storageClassName: 197 | '[object Object]': null 198 | resources: 199 | requests: 200 | storage: 8Gi 201 | - metadata: 202 | name: unityvolx12 203 | spec: 204 | accessModes: 205 | - ReadWriteOnce 206 | storageClassName: 207 | '[object Object]': null 208 | resources: 209 | requests: 210 | storage: 8Gi 211 | - metadata: 212 | name: unityvolx13 213 | spec: 214 | accessModes: 215 | - ReadWriteOnce 216 | storageClassName: 217 | '[object Object]': null 218 | resources: 219 | requests: 220 | storage: 8Gi 221 | - metadata: 222 | name: unityvolx14 223 | spec: 224 | accessModes: 225 | - ReadWriteOnce 226 | storageClassName: 227 | '[object Object]': null 228 | resources: 229 | requests: 230 | storage: 8Gi 231 | - metadata: 232 | name: unityvolx15 233 | spec: 234 | accessModes: 235 | - ReadWriteOnce 236 | storageClassName: 237 | '[object Object]': null 238 | resources: 239 | requests: 240 | storage: 8Gi 241 | - metadata: 242 | name: unityvolx16 243 | spec: 244 | accessModes: 245 | - ReadWriteOnce 246 | storageClassName: 247 | '[object Object]': null 248 | resources: 249 | requests: 250 | storage: 8Gi 251 | - metadata: 252 | name: unityvolx17 253 | spec: 254 | accessModes: 255 | - ReadWriteOnce 256 | storageClassName: 257 | '[object Object]': null 258 | resources: 259 | requests: 260 | storage: 8Gi 261 | - metadata: 262 | name: unityvolx18 263 | spec: 264 | accessModes: 265 | - ReadWriteOnce 266 | storageClassName: 267 | '[object Object]': null 268 | resources: 269 | requests: 270 | storage: 8Gi 271 | - metadata: 272 | name: unityvolx19 273 | spec: 274 | accessModes: 275 | - ReadWriteOnce 276 | storageClassName: 277 | '[object Object]': null 278 | resources: 279 | requests: 280 | storage: 8Gi 281 | -------------------------------------------------------------------------------- /core/semver/semver_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2025 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package main 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | "os" 22 | "os/exec" 23 | "testing" 24 | 25 | "github.com/stretchr/testify/assert" 26 | ) 27 | 28 | func TestGetStatusError(_ *testing.T) { 29 | exitError := &exec.ExitError{ 30 | ProcessState: &os.ProcessState{}, 31 | } 32 | _, _ = GetStatusError(exitError) 33 | } 34 | 35 | func TestString(t *testing.T) { 36 | s := semver{"", "", "", "", 1, 2, 3, 4, "", "", true, "", "", 64, "", "", "", ""} 37 | assert.NotNil(t, s.String()) 38 | 39 | s = semver{"", "", "", "", 1, 2, 3, 4, "abc", "", true, "", "", 64, "", "", "", ""} 40 | assert.NotNil(t, s.String()) 41 | } 42 | 43 | func TestGetExitError(t *testing.T) { 44 | err := errors.New("error") 45 | _, ok := GetExitError(err) 46 | assert.False(t, ok) 47 | } 48 | 49 | func TestMainFunction(t *testing.T) { 50 | tests := []struct { 51 | name string 52 | format string 53 | outputFile string 54 | expectEmptyFile bool 55 | readFileFunc func(file string) ([]byte, error) 56 | }{ 57 | { 58 | name: "Write mk format to file", 59 | format: "mk", 60 | outputFile: "test_output.mk", 61 | }, 62 | { 63 | name: "Write env format to file", 64 | format: "env", 65 | outputFile: "test_output.env", 66 | }, 67 | { 68 | name: "Write json format to file", 69 | format: "json", 70 | outputFile: "test_output.json", 71 | }, 72 | { 73 | name: "Write ver format to file", 74 | format: "ver", 75 | outputFile: "test_output.ver", 76 | }, 77 | { 78 | name: "Write rpm format to file", 79 | format: "rpm", 80 | outputFile: "test_output.rpm", 81 | }, 82 | { 83 | name: "Write tpl format to file", 84 | format: "../semver.tpl", 85 | outputFile: "test_output.rpm", 86 | }, 87 | { 88 | name: "Write tpl format to file but error reading source file", 89 | format: "../semver.tpl", 90 | outputFile: "test_output.rpm", 91 | readFileFunc: func(_ string) ([]byte, error) { 92 | return nil, errors.New("error reading source file") 93 | }, 94 | expectEmptyFile: true, 95 | }, 96 | { 97 | // go format currently does not print any output, expect an empty file 98 | name: "Write go format to file", 99 | format: "go", 100 | outputFile: "test_output.go", 101 | expectEmptyFile: true, 102 | }, 103 | } 104 | 105 | for _, tt := range tests { 106 | t.Run(tt.name, func(t *testing.T) { 107 | osArgs := os.Args 108 | os.Args = append(os.Args, "-f", tt.format) 109 | os.Args = append(os.Args, "-o", tt.outputFile) 110 | os.Args = append(os.Args, "-x", "true") 111 | 112 | oldReadFile := ReadFile 113 | if tt.readFileFunc != nil { 114 | ReadFile = tt.readFileFunc 115 | } 116 | oldOSExit := OSExit 117 | OSExit = func(_ int) {} 118 | 119 | oldDoExec := doExec 120 | doExec = func(_ string, _ ...string) ([]byte, error) { 121 | return []byte("v2.15.0-77-g38b3a19-dirty"), nil 122 | } 123 | 124 | main() 125 | 126 | // Open the file 127 | file, err := os.Open(tt.outputFile) 128 | if err != nil { 129 | t.Error(err) 130 | } 131 | defer file.Close() 132 | 133 | // Read the file contents 134 | contents, err := io.ReadAll(file) 135 | if err != nil { 136 | t.Error(err) 137 | } 138 | 139 | defer os.Remove(tt.outputFile) 140 | 141 | // make sure file is not empty 142 | if tt.expectEmptyFile { 143 | assert.Equal(t, 0, len(contents)) 144 | } else { 145 | assert.NotEqual(t, 0, len(contents)) 146 | } 147 | os.Args = osArgs 148 | ReadFile = oldReadFile 149 | OSExit = oldOSExit 150 | doExec = oldDoExec 151 | }) 152 | } 153 | } 154 | 155 | func TestChkErr(t *testing.T) { 156 | tests := []struct { 157 | name string 158 | out []byte 159 | err error 160 | wantOut string 161 | wantErr bool 162 | getExitError func(err error) (*exec.ExitError, bool) 163 | getStatusError func(exitError *exec.ExitError) (int, bool) 164 | }{ 165 | { 166 | name: "No error", 167 | out: []byte("output"), 168 | err: nil, 169 | wantOut: "output", 170 | wantErr: false, 171 | getExitError: func(_ error) (*exec.ExitError, bool) { 172 | return nil, true 173 | }, 174 | getStatusError: func(_ *exec.ExitError) (int, bool) { 175 | return 0, true 176 | }, 177 | }, 178 | { 179 | name: "Error with command", 180 | out: []byte("output"), 181 | err: errors.New("error"), 182 | wantOut: "", 183 | wantErr: true, 184 | getExitError: func(_ error) (*exec.ExitError, bool) { 185 | return nil, false 186 | }, 187 | getStatusError: func(_ *exec.ExitError) (int, bool) { 188 | return 1, false 189 | }, 190 | }, 191 | { 192 | name: "Error casting to ExitError", 193 | out: []byte("output"), 194 | err: errors.New("error"), 195 | wantOut: "", 196 | wantErr: true, 197 | getExitError: func(_ error) (*exec.ExitError, bool) { 198 | return nil, true 199 | }, 200 | getStatusError: func(_ *exec.ExitError) (int, bool) { 201 | return 1, false 202 | }, 203 | }, 204 | { 205 | name: "Error getting status from ExitError", 206 | out: []byte("output"), 207 | err: errors.New("error"), 208 | wantOut: "", 209 | wantErr: true, 210 | getExitError: func(_ error) (*exec.ExitError, bool) { 211 | return nil, false 212 | }, 213 | getStatusError: func(_ *exec.ExitError) (int, bool) { 214 | return 0, true 215 | }, 216 | }, 217 | } 218 | 219 | for _, tt := range tests { 220 | t.Run(tt.name, func(t *testing.T) { 221 | GetExitError = tt.getExitError 222 | GetStatusError = tt.getStatusError 223 | OSExit = func(_ int) {} 224 | 225 | gotOut := chkErr(tt.out, tt.err) 226 | if gotOut != tt.wantOut { 227 | t.Errorf("chkErr() gotOut = %v, want %v", gotOut, tt.wantOut) 228 | } 229 | }) 230 | } 231 | } 232 | 233 | func TestFileExists(t *testing.T) { 234 | tests := []struct { 235 | name string 236 | filePath string 237 | want bool 238 | }{ 239 | { 240 | name: "File exists", 241 | filePath: "semver.go", 242 | want: true, 243 | }, 244 | { 245 | name: "File does not exist", 246 | filePath: "non-existent.txt", 247 | want: false, 248 | }, 249 | { 250 | name: "File path is empty", 251 | filePath: "", 252 | want: false, 253 | }, 254 | } 255 | 256 | for _, tt := range tests { 257 | t.Run(tt.name, func(t *testing.T) { 258 | got := fileExists(tt.filePath) 259 | if got != tt.want { 260 | t.Errorf("fileExists(%s) = %v, want %v", tt.filePath, got, tt.want) 261 | } 262 | }) 263 | } 264 | } 265 | 266 | func TestErrorExit(t *testing.T) { 267 | message := "error message" 268 | 269 | if os.Getenv("INVOKE_ERROR_EXIT") == "1" { 270 | errorExit(message) 271 | return 272 | } 273 | // call the test again with INVOKE_ERROR_EXIT=1 so the errorExit function is invoked and we can check the return code 274 | cmd := exec.Command(os.Args[0], "-test.run=TestErrorExit") // #nosec G204 275 | cmd.Env = append(os.Environ(), "INVOKE_ERROR_EXIT=1") 276 | 277 | stderr, err := cmd.StderrPipe() 278 | if err != nil { 279 | fmt.Println("Error creating stderr pipe:", err) 280 | return 281 | } 282 | 283 | if err := cmd.Start(); err != nil { 284 | t.Error(err) 285 | } 286 | 287 | buf := make([]byte, 1024) 288 | n, err := stderr.Read(buf) 289 | if err != nil { 290 | t.Error(err) 291 | } 292 | 293 | err = cmd.Wait() 294 | if e, ok := err.(*exec.ExitError); ok && e.Success() { 295 | t.Error(err) 296 | } 297 | 298 | // check the output is the message we logged in errorExit 299 | assert.Equal(t, message, string(buf[:n])) 300 | } 301 | -------------------------------------------------------------------------------- /service/logging/logging_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package logging 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "runtime" 21 | "strings" 22 | "sync" 23 | "testing" 24 | "time" 25 | 26 | "github.com/sirupsen/logrus" 27 | "github.com/stretchr/testify/assert" 28 | ) 29 | 30 | func TestGetRunidLogger(t *testing.T) { 31 | log := GetLogger() 32 | ctx := context.Background() 33 | entry := log.WithField(RUNID, "1111") 34 | ctx = context.WithValue(ctx, UnityLogger, entry) 35 | 36 | logEntry := GetRunidLogger(ctx) 37 | logEntry.Message = "Hi This is log test1" 38 | message, _ := logEntry.String() 39 | fmt.Println(message) 40 | assert.True(t, strings.Contains(message, `runid=1111 msg="Hi This is log test1"`), "Log message not found") 41 | 42 | entry = logEntry.WithField(ARRAYID, "arr0000") 43 | entry.Message = "Hi this is TestSetArrayIdContext" 44 | message, _ = entry.String() 45 | assert.True(t, strings.Contains(message, `arrayid=arr0000 runid=1111 msg="Hi this is TestSetArrayIdContext"`), "Log message not found") 46 | 47 | // Test case where logger is not present in context 48 | ctx = context.Background() // Reset context 49 | logEntry = GetRunidLogger(ctx) 50 | assert.Nil(t, logEntry, "Log entry should be nil when logger is not present in context") 51 | } 52 | 53 | func TestChangeLogLevel(t *testing.T) { 54 | // Ensure singletonLog is initialized before any tests 55 | GetLogger() 56 | 57 | t.Run("Debug level case", func(t *testing.T) { 58 | ChangeLogLevel("debug") 59 | if singletonLog.Level != logrus.DebugLevel { 60 | t.Errorf("expected DebugLevel, got %v", singletonLog.Level) 61 | } 62 | }) 63 | 64 | t.Run("Warn level case", func(t *testing.T) { 65 | ChangeLogLevel("warn") 66 | if singletonLog.Level != logrus.WarnLevel { 67 | t.Errorf("expected WarnLevel, got %v", singletonLog.Level) 68 | } 69 | }) 70 | 71 | t.Run("Warning level case", func(t *testing.T) { 72 | ChangeLogLevel("warning") 73 | if singletonLog.Level != logrus.WarnLevel { 74 | t.Errorf("expected WarnLevel, got %v", singletonLog.Level) 75 | } 76 | }) 77 | 78 | t.Run("Error level case", func(t *testing.T) { 79 | ChangeLogLevel("error") 80 | if singletonLog.Level != logrus.ErrorLevel { 81 | t.Errorf("expected ErrorLevel, got %v", singletonLog.Level) 82 | } 83 | }) 84 | 85 | t.Run("Info level case", func(t *testing.T) { 86 | ChangeLogLevel("info") 87 | if singletonLog.Level != logrus.InfoLevel { 88 | t.Errorf("expected InfoLevel, got %v", singletonLog.Level) 89 | } 90 | }) 91 | 92 | t.Run("Default level case with unknown input", func(t *testing.T) { 93 | ChangeLogLevel("unknown") 94 | if singletonLog.Level != logrus.InfoLevel { 95 | t.Errorf("expected InfoLevel, got %v", singletonLog.Level) 96 | } 97 | }) 98 | } 99 | 100 | func TestGetRunidAndLogger(t *testing.T) { 101 | log := logrus.New() 102 | entry := log.WithField(RUNID, "1111") 103 | 104 | tests := []struct { 105 | name string 106 | ctx context.Context 107 | expectedRID string 108 | expectedLog *logrus.Entry 109 | }{ 110 | { 111 | name: "Logger and RunID present in context", 112 | ctx: func() context.Context { 113 | ctx := context.Background() 114 | fields := logrus.Fields{RUNID: "1111"} 115 | ctx = context.WithValue(ctx, LogFields, fields) 116 | ctx = context.WithValue(ctx, UnityLogger, entry) 117 | return ctx 118 | }(), 119 | expectedRID: "1111", 120 | expectedLog: entry, 121 | }, 122 | { 123 | name: "Logger present but no RunID in context", 124 | ctx: func() context.Context { 125 | ctx := context.Background() 126 | ctx = context.WithValue(ctx, UnityLogger, entry) 127 | return ctx 128 | }(), 129 | expectedRID: "", 130 | expectedLog: entry, 131 | }, 132 | { 133 | name: "RunID present but no Logger in context", 134 | ctx: func() context.Context { 135 | ctx := context.Background() 136 | fields := logrus.Fields{RUNID: "1111"} 137 | ctx = context.WithValue(ctx, LogFields, fields) 138 | return ctx 139 | }(), 140 | expectedRID: "1111", 141 | expectedLog: nil, 142 | }, 143 | { 144 | name: "Neither Logger nor RunID present in context", 145 | ctx: context.Background(), 146 | expectedRID: "", 147 | expectedLog: nil, 148 | }, 149 | } 150 | 151 | for _, test := range tests { 152 | t.Run(test.name, func(t *testing.T) { 153 | rid, logEntry := GetRunidAndLogger(test.ctx) 154 | assert.Equal(t, test.expectedRID, rid, "RunID should be %v for test %v", test.expectedRID, test.name) 155 | assert.Equal(t, test.expectedLog, logEntry, "Log entry should be %v for test %v", test.expectedLog, test.name) 156 | }) 157 | } 158 | } 159 | 160 | // resetOnce is a helper function to reset the once variable for testing purposes 161 | func resetOnce() { 162 | once = sync.Once{} 163 | } 164 | 165 | func TestGetLogger(t *testing.T) { 166 | // Ensure singletonLog is nil before the test 167 | singletonLog = nil 168 | resetOnce() 169 | 170 | // Call GetLogger and check if it initializes the logger 171 | logger := GetLogger() 172 | assert.NotNil(t, logger, "Logger should not be nil") 173 | assert.Equal(t, logrus.InfoLevel, logger.Level, "Logger level should be Info") 174 | assert.True(t, logger.ReportCaller, "Logger should report caller") 175 | 176 | // Call GetLogger again and check if it returns the same instance 177 | logger2 := GetLogger() 178 | assert.Equal(t, logger, logger2, "GetLogger should return the same instance") 179 | 180 | // Check if the formatter is set correctly 181 | formatter, ok := logger.Formatter.(*Formatter) 182 | assert.True(t, ok, "Logger formatter should be of type *Formatter") 183 | assert.NotNil(t, formatter.CallerPrettyfier, "CallerPrettyfier should not be nil") 184 | 185 | // Test the CallerPrettyfier function 186 | frame := &runtime.Frame{ 187 | Function: "TestFunction", 188 | File: "dell/csi-unity/testfile.go", 189 | Line: 42, 190 | } 191 | function, file := formatter.CallerPrettyfier(frame) 192 | assert.Equal(t, "TestFunction()", function, "Function name should be formatted correctly") 193 | assert.Equal(t, "dell/csi-unity/testfile.go:42", file, "File name should be formatted correctly") 194 | 195 | frame.File = "dell/gounity/testfile.go" 196 | function, file = formatter.CallerPrettyfier(frame) 197 | assert.Equal(t, "TestFunction()", function, "Function name should be formatted correctly") 198 | assert.Equal(t, "dell/gounity/testfile.go:42", file, "File name should be formatted correctly") 199 | 200 | frame.File = "otherpath/testfile.go" 201 | function, file = formatter.CallerPrettyfier(frame) 202 | assert.Equal(t, "TestFunction()", function, "Function name should be formatted correctly") 203 | assert.Equal(t, "otherpath/testfile.go:42", file, "File name should be formatted correctly") 204 | } 205 | 206 | func TestFormatter_Format(t *testing.T) { 207 | formatter := &Formatter{ 208 | LogFormat: "%time% [%lvl%] %msg% %runid% %arrayid% %intField% %boolField%", 209 | TimestampFormat: "2006-01-02 15:04:05", 210 | CallerPrettyfier: func(f *runtime.Frame) (string, string) { 211 | return fmt.Sprintf("%s()", f.Function), fmt.Sprintf("%s:%d", f.File, f.Line) 212 | }, 213 | } 214 | 215 | entry := &logrus.Entry{ 216 | Time: time.Date(2025, time.February, 27, 15, 0, 0, 0, time.UTC), 217 | Level: logrus.InfoLevel, 218 | Message: "Test message", 219 | Data: logrus.Fields{ 220 | RUNID: "1234", 221 | ARRAYID: "5678", 222 | "intField": 42, 223 | "boolField": true, 224 | }, 225 | Caller: &runtime.Frame{ 226 | Function: "main.TestFunction", 227 | File: "main.go", 228 | Line: 42, 229 | }, 230 | } 231 | entry.Logger = logrus.New() 232 | entry.Logger.SetReportCaller(true) 233 | 234 | expectedOutput := "2025-02-27 15:00:00 [info] Test message runid=1234 arrayid=5678 42 true func=\"main.TestFunction()\" file=\"main.go:42\"\n" 235 | output, err := formatter.Format(entry) 236 | assert.NoError(t, err, "Formatter should not return an error") 237 | assert.Equal(t, expectedOutput, string(output), "Formatted output should match expected output") 238 | } 239 | -------------------------------------------------------------------------------- /test/integration-test/features/integration.feature: -------------------------------------------------------------------------------- 1 | Feature: CSI interface 2 | As a consumer of the CSI interface 3 | I want to run a system test 4 | So that I know the service functions correctly. 5 | 6 | Scenario: Controller get capabilities, create, validate capabilities and delete basic volume 7 | Given a CSI service 8 | When I call Controller Get Capabilities 9 | Then there are no errors 10 | And a basic block volume request name "gditest-vol1" arrayId "Array1-Id" protocol "FC" size "5" 11 | When I call CreateVolume 12 | Then there are no errors 13 | When I call validate volume capabilities with protocol "FC" with same access mode 14 | Then there are no errors 15 | And when I call DeleteVolume 16 | Then there are no errors 17 | 18 | Scenario: Create, validate capabilities and delete basic volume 19 | Given a CSI service 20 | And a basic block volume request name "gditest-vol2" arrayId "Array1-Id" protocol "FC" size "5" 21 | When I call CreateVolume 22 | Then there are no errors 23 | When I call validate volume capabilities with protocol "FC" with different access mode 24 | Then the error message should contain "Unsupported capability" 25 | And when I call DeleteVolume 26 | Then there are no errors 27 | 28 | Scenario: Create, expand and delete basic volume 29 | Given a CSI service 30 | And a basic block volume request name "gditest-vol3" arrayId "Array1-Id" protocol "FC" size "2" 31 | When I call CreateVolume 32 | Then there are no errors 33 | When I call Controller Expand Volume "3" 34 | Then there are no errors 35 | And a basic block volume request name "gditest-vol3" arrayId "Array1-Id" protocol "FC" size "3" 36 | When I call CreateVolume 37 | Then there are no errors 38 | And when I call DeleteVolume 39 | Then there are no errors 40 | 41 | Scenario: Controller expand volume with smaller new size 42 | Given a CSI service 43 | And a basic block volume request name "gditest-vol4" arrayId "Array1-Id" protocol "FC" size "3" 44 | When I call CreateVolume 45 | Then there are no errors 46 | When I call Controller Expand Volume "2" 47 | Then the error message should contain "requested new capacity smaller than existing capacity" 48 | And when I call DeleteVolume 49 | Then there are no errors 50 | 51 | Scenario: Idempotent create and delete basic volume 52 | Given a CSI service 53 | And a basic block volume request name "gditest-vol5" arrayId "Array1-Id" protocol "FC" size "5" 54 | When I call CreateVolume 55 | And I call CreateVolume 56 | And when I call DeleteVolume 57 | And when I call DeleteVolume 58 | Then there are no errors 59 | 60 | Scenario: Create a volume from snapshot of thin volume 61 | Given a CSI service 62 | And a basic block volume request name "gditest-vol6" arrayId "Array1-Id" protocol "FC" size "5" 63 | When I call CreateVolume 64 | And there are no errors 65 | Given a create snapshot request "snap_volforsnap" 66 | When I call CreateSnapshot 67 | And there are no errors 68 | Given a basic block volume request with volume content source with name "gditest-vol7" arrayId "Array1-Id" protocol "FC" size "5" 69 | When I call CreateVolume 70 | Then there are no errors 71 | And when I call DeleteVolume 72 | Then there are no errors 73 | Given a delete snapshot request 74 | When I call DeleteSnapshot 75 | Then there are no errors 76 | And When I call DeleteAllCreatedVolumes 77 | Then there are no errors 78 | 79 | Scenario: Create, publish, unpublish, and delete basic volume with idempotency check for publish and unpublish 80 | Given a CSI service 81 | And a basic block volume request name "gditest-vol8" arrayId "Array1-Id" protocol "FC" size "5" 82 | When I call CreateVolume 83 | And there are no errors 84 | And when I call PublishVolume 85 | And there are no errors 86 | And when I call PublishVolume 87 | And there are no errors 88 | And when I call UnpublishVolume 89 | And there are no errors 90 | And when I call UnpublishVolume 91 | And there are no errors 92 | And when I call DeleteVolume 93 | Then there are no errors 94 | 95 | Scenario: Create and delete basic 264000G volume 96 | Given a CSI service 97 | And a basic block volume request name "gditest-vol9" arrayId "Array1-Id" protocol "FC" size "264000" 98 | When I call CreateVolume 99 | Then the error message should contain "The system could not create the LUNs because specified size is too big." 100 | And when I call DeleteVolume 101 | Then there are no errors 102 | 103 | Scenario: Create and delete basic 96G volume 104 | Given a CSI service 105 | And a basic block volume request name "gditest-vol10" arrayId "Array1-Id" protocol "FC" size "96" 106 | When I call CreateVolume 107 | Then there are no errors 108 | And when I call DeleteVolume 109 | Then there are no errors 110 | 111 | Scenario: Create volume, create snapshot, delete snapshot and delete volume 112 | Given a CSI service 113 | And a basic block volume request name "gditest-vol11" arrayId "Array1-Id" protocol "FC" size "5" 114 | When I call CreateVolume 115 | And there are no errors 116 | Given a create snapshot request "snap_integration1" 117 | When I call CreateSnapshot 118 | And there are no errors 119 | Given a delete snapshot request 120 | And I call DeleteSnapshot 121 | And there are no errors 122 | And when I call DeleteVolume 123 | And there are no errors 124 | 125 | Scenario: Create volume, idempotent create snapshot, idempotent delete snapshot delete volume 126 | Given a CSI service 127 | And a basic block volume request name "gditest-vol12" arrayId "Array1-Id" protocol "FC" size "5" 128 | When I call CreateVolume 129 | And there are no errors 130 | Given a create snapshot request "snap_integration1" 131 | When I call CreateSnapshot 132 | And there are no errors 133 | Given a create snapshot request "snap_integration1" 134 | When I call CreateSnapshot 135 | And there are no errors 136 | Given a delete snapshot request 137 | And I call DeleteSnapshot 138 | And there are no errors 139 | Given a delete snapshot request 140 | And I call DeleteSnapshot 141 | And there are no errors 142 | And when I call DeleteVolume 143 | And there are no errors 144 | 145 | Scenario: Node stage, publish, unpublish and unstage volume with idempotency 146 | Given a CSI service 147 | And a basic block volume request name "gditest-vol13" arrayId "Array1-Id" protocol "FC" size "5" 148 | When I call CreateVolume 149 | And there are no errors 150 | And when I call PublishVolume 151 | And there are no errors 152 | And when I call NodeStageVolume fsType "ext4" 153 | And there are no errors 154 | And when I call NodeStageVolume fsType "ext4" 155 | And there are no errors 156 | And when I call NodePublishVolume fsType "ext4" readonly "false" 157 | Then there are no errors 158 | And when I call NodePublishVolume fsType "ext4" readonly "false" 159 | Then there are no errors 160 | And when I call NodeUnPublishVolume 161 | And there are no errors 162 | And when I call NodeUnPublishVolume 163 | Then there are no errors 164 | And when I call NodeUnstageVolume 165 | And there are no errors 166 | And when I call NodeUnstageVolume 167 | And there are no errors 168 | And when I call UnpublishVolume 169 | And there are no errors 170 | And when I call DeleteVolume 171 | Then there are no errors 172 | 173 | Scenario: Node stage, publish, unpublish and unstage volume for iSCSI 174 | Given a CSI service 175 | And a basic block volume request name "gditest-vol14" arrayId "Array1-Id" protocol "iSCSI" size "5" 176 | When I call CreateVolume 177 | And there are no errors 178 | And when I call PublishVolume 179 | And there are no errors 180 | And when I call NodeStageVolume fsType "ext4" 181 | And there are no errors 182 | And when I call NodePublishVolume fsType "ext4" readonly "false" 183 | Then there are no errors 184 | And when I call NodeUnPublishVolume 185 | And there are no errors 186 | And when I call NodeUnstageVolume 187 | And there are no errors 188 | And when I call UnpublishVolume 189 | And there are no errors 190 | And when I call DeleteVolume 191 | Then there are no errors 192 | 193 | Scenario: Node stage, publish, unpublish and unstage volume for NFS 194 | Given a CSI service 195 | And a basic block volume request name "gditest-vol15" arrayId "Array1-Id" protocol "NFS" size "5" 196 | When I call CreateVolume 197 | And there are no errors 198 | And when I call PublishVolume 199 | And there are no errors 200 | And when I call NodeStageVolume fsType "" 201 | And there are no errors 202 | And when I call NodePublishVolume fsType "" readonly "false" 203 | Then there are no errors 204 | And when I call NodeUnPublishVolume 205 | And there are no errors 206 | And when I call NodeUnstageVolume 207 | And there are no errors 208 | And when I call UnpublishVolume 209 | And there are no errors 210 | And when I call DeleteVolume 211 | Then there are no errors -------------------------------------------------------------------------------- /dell-csi-helm-installer/README.md: -------------------------------------------------------------------------------- 1 | 14 | # Helm Installer for Dell CSI Storage Providers 15 | 16 | ## Description 17 | 18 | This directory provides scripts to install, upgrade, uninstall the CSI drivers, and to verify the Kubernetes environment. 19 | These same scripts are present in all Dell Container Storage Interface ([CSI](https://github.com/container-storage-interface/spec)) drivers. This includes the drivers for: 20 | * [PowerFlex](https://github.com/dell/csi-vxflexos) 21 | * [PowerMax](https://github.com/dell/csi-powermax) 22 | * [PowerScale](https://github.com/dell/csi-powerscale) 23 | * [PowerStore](https://github.com/dell/csi-powerstore) 24 | * [Unity XT](https://github.com/dell/csi-unity) 25 | 26 | NOTE: This documentation uses the Unity XT driver as an example. If working with a different driver, substitute the name as appropriate. 27 | 28 | ## Dependencies 29 | 30 | Installing any of the Dell CSI Drivers requires a few utilities to be installed on the system running the installation. 31 | 32 | | Dependency | Usage | 33 | | ------------- | ----- | 34 | | `kubectl` | Kubectl is used to validate that the Kubernetes system meets the requirements of the driver. | 35 | | `helm` | Helm v3 is used as the deployment tool for Charts. See, [Install Helm 3](https://helm.sh/docs/intro/install/) for instructions to install Helm 3. | 36 | | `sshpass` | sshpass is used to check certain pre-requisities in worker nodes (in chosen drivers). | 37 | 38 | 39 | In order to use these tools, a valid `KUBECONFIG` is required. Ensure that either a valid configuration is in the default location or that the `KUBECONFIG` environment variable points to a valid confiugration before using these tools. 40 | 41 | ## Capabilities 42 | 43 | This project provides the following capabilitites, each one is discussed in detail later in this document. 44 | 45 | * Install a driver. When installing a driver, options are provided to specify the target namespace as well as options to control the types of verifications to be performed on the target system. 46 | * Upgrade a driver. Upgrading a driver is an effective way to either deploy a new version of the driver or to modify the parameters used in an initial deployment. 47 | * Uninstall a driver. This removes the driver and any installed storage classes. 48 | * Verify a Kubernetes system for suitability with a driver. These verification steps differ, slightly, from driver to driver but include verifiying version compatibility, namespace availability, existance of required secrets, and validating worker node compatibility with driver protocols such as iSCSI, Fibre Channel, NFS, etc 49 | 50 | 51 | Most of these usages require the creation/specification of a values file. These files specify configuration settings that are passed into the driver and configure it for use. To create one of these files, the following steps should be followed: 52 | 1. Download a template file for the driver to a new location, naming this new file is at the users discretion. The template files are always found at `https://github.com/dell/helm-charts/raw/csi-unity-2.15.0/charts/csi-unity/values.yaml` 53 | 2. Edit the file such that it contains the proper configuration settings for the specific environment. These files are yaml formatted so maintaining the file structure is important. 54 | 55 | For example, to create a values file for the Unity XT driver the following steps can be executed 56 | ``` 57 | # cd to the installation script directory 58 | cd dell-csi-helm-installer 59 | 60 | # download the template file 61 | wget -O my-unity-settings.yaml https://github.com/dell/helm-charts/raw/csi-unity-2.15.0/charts/csi-unity/values.yaml 62 | 63 | # edit the newly created values file 64 | vi my-unity-settings.yaml 65 | ``` 66 | 67 | These values files can then be archived for later reference or for usage when upgrading the driver. 68 | 69 | 70 | ### Install A Driver 71 | 72 | Installing a driver is performed via the `csi-install.sh` script. This script requires a few arguments: the target namespace and the user created values file. By default, this will verify the Kubernetes environment and present a list of warnings and/or errors. Errors must be addressed before installing, warning should be examined for their applicability. For example, in order to install the Unity driver into a namespace called "unity", the following command should be run: 73 | ``` 74 | ./csi-install.sh --namespace unity --values ./my-unity-settings.yaml 75 | ``` 76 | 77 | For usage information: 78 | ``` 79 | [dell-csi-helm-installer]# ./csi-install.sh -h 80 | Help for ./csi-install.sh 81 | 82 | Usage: ./csi-install.sh options... 83 | Options: 84 | Required 85 | --namespace[=] Kubernetes namespace containing the CSI driver 86 | --values[=] Values file, which defines configuration values 87 | Optional 88 | --release[=] Name to register with helm, default value will match the driver name 89 | --upgrade Perform an upgrade of the specified driver, default is false 90 | --node-verify-user[=] Username to SSH to worker nodes as, used to validate node requirements. Default is root 91 | --skip-verify Skip the kubernetes configuration verification to use the CSI driver, default will run verification 92 | --skip-verify-node Skip worker node verification checks 93 | -h Help 94 | ``` 95 | 96 | ### Upgrade A Driver 97 | 98 | Upgrading a driver is very similar to installation. The `csi-install.sh` script is run, with the same required arguments, along with a `--upgrade` argument. For example, to upgrade the previously installed Unity XT driver, the following command can be supplied: 99 | 100 | ``` 101 | ./csi-install.sh --namespace unity --values ./my-unity-settings.yaml --upgrade 102 | ``` 103 | 104 | For usage information: 105 | ``` 106 | [dell-csi-helm-installer]# ./csi-install.sh -h 107 | Help for ./csi-install.sh 108 | 109 | Usage: ./csi-install.sh options... 110 | Options: 111 | Required 112 | --namespace[=] Kubernetes namespace containing the CSI driver 113 | --values[=] Values file, which defines configuration values 114 | Optional 115 | --release[=] Name to register with helm, default value will match the driver name 116 | --upgrade Perform an upgrade of the specified driver, default is false 117 | --node-verify-user[=] Username to SSH to worker nodes as, used to validate node requirements. Default is root 118 | --skip-verify Skip the kubernetes configuration verification to use the CSI driver, default will run verification 119 | --skip-verify-node Skip worker node verification checks 120 | -h Help 121 | ``` 122 | 123 | ### Uninstall A Driver 124 | 125 | To uninstall a driver, the `csi-uninstall.sh` script provides a handy wrapper around the `helm` utility. The only required argument for uninstallation is the namespace name. To uninstall the Unity XT driver: 126 | 127 | ``` 128 | ./csi-uninstall.sh --namespace unity 129 | ``` 130 | 131 | For usage information: 132 | ``` 133 | [dell-csi-helm-installer]# ./csi-uninstall.sh -h 134 | Help for ./csi-uninstall.sh 135 | 136 | Usage: ./csi-uninstall.sh options... 137 | Options: 138 | Required 139 | --namespace[=] Kubernetes namespace to uninstall the CSI driver from 140 | Optional 141 | --release[=] Name to register with helm, default value will match the driver name 142 | -h Help 143 | ``` 144 | 145 | ### Verify A Kubernetes Environment 146 | 147 | The `verify.sh` script is run, automatically, as part of the installation and upgrade procedures and can also be run by itself. This provides a handy means to validate a Kubernetes system without meaning to actually perform the installation. To verify an environment, run `verify.sh` with the namespace name and values file options. 148 | 149 | ``` 150 | ./verify.sh --namespace unity --values ./my-unity-settings.yaml 151 | ``` 152 | 153 | For usage information: 154 | ``` 155 | [dell-csi-helm-installer]# ./verify.sh -h 156 | Help for ./verify.sh 157 | 158 | Usage: ./verify.sh options... 159 | Options: 160 | Required 161 | --namespace[=] Kubernetes namespace to install the CSI driver 162 | --values[=] Values file, which defines configuration values 163 | Optional 164 | --skip-verify-node Skip worker node verification checks 165 | --release[=] Name to register with helm, default value will match the driver name 166 | --node-verify-user[=] Username to SSH to worker nodes as, used to validate node requirements. Default is root 167 | -h Help Help 168 | ``` 169 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2025 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package main 17 | 18 | import ( 19 | "context" 20 | "errors" 21 | "flag" 22 | "fmt" 23 | "os" 24 | "testing" 25 | "time" 26 | 27 | "github.com/dell/gocsi" 28 | "github.com/stretchr/testify/mock" 29 | "github.com/stretchr/testify/require" 30 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 | "k8s.io/client-go/kubernetes" 32 | "k8s.io/client-go/kubernetes/fake" 33 | "k8s.io/client-go/tools/leaderelection" 34 | "k8s.io/client-go/tools/leaderelection/resourcelock" 35 | ) 36 | 37 | type MockGocsi struct { 38 | mock.Mock 39 | } 40 | 41 | func (m *MockGocsi) Run(ctx context.Context, name, desc, usage string, sp gocsi.StoragePluginProvider) { 42 | m.Called(ctx, name, desc, usage, sp) 43 | } 44 | 45 | var osExit = os.Exit 46 | 47 | func expectMockExit(int) { 48 | osExit(0) 49 | } 50 | 51 | func mockExit(int) { 52 | panic("os.Exit called") 53 | } 54 | 55 | func mockCreateKubeClientSet(_ string) (kubernetes.Interface, error) { 56 | return fake.NewSimpleClientset(), nil 57 | } 58 | 59 | func mockLeaderElection(_ kubernetes.Interface, _, _ string, _, _, _ time.Duration, run func(ctx context.Context)) { 60 | // Mock leader election logic 61 | run(context.TODO()) 62 | } 63 | 64 | func TestMainFunctionWithoutLeaderElection(t *testing.T) { 65 | // Save the original command-line arguments and restore them after the test 66 | origArgs := os.Args 67 | // Reset the flag.CommandLine 68 | flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) 69 | 70 | defer func() { os.Args = origArgs }() 71 | 72 | // Mock the gocsi.Run function 73 | mockGocsi := new(MockGocsi) 74 | 75 | // Mock os.Exit 76 | osExit = mockExit 77 | defer func() { osExit = os.Exit }() 78 | 79 | // Test case: Leader election disabled 80 | t.Run("LeaderElectionDisabled", func(t *testing.T) { 81 | // Create a new flag set for this test case 82 | flagSet := flag.NewFlagSet("test", flag.ExitOnError) 83 | os.Args = []string{"cmd", "--leader-election=false", "--driver-config=config.yaml", "--driver-name=csi-unity.dellemc.com", "--driver-secret=secret.yaml"} 84 | flagSet.Bool("leader-election", false, "Enables leader election.") 85 | flagSet.String("driver-config", "config.yaml", "yaml file with driver config params") 86 | flagSet.String("driver-name", "csi-unity.dellemc.com", "drivername") 87 | flagSet.String("driver-secret", "secret.yaml", "driver secret yaml file") 88 | flagSet.Parse(os.Args[1:]) 89 | 90 | mockGocsi.On("Run", mock.Anything, "csi-unity.dellemc.com", "An Unity Container Storage Interface (CSI) Plugin", usage, mock.Anything).Return() 91 | 92 | defer func() { 93 | if r := recover(); r != nil { 94 | t.Errorf("os.Exit was called: %v", r) 95 | } 96 | }() 97 | 98 | mainR(mockGocsi.Run, mockCreateKubeClientSet, mockLeaderElection) 99 | 100 | mockGocsi.AssertCalled(t, "Run", mock.Anything, "csi-unity.dellemc.com", "An Unity Container Storage Interface (CSI) Plugin", usage, mock.Anything) 101 | }) 102 | } 103 | 104 | func TestMainFunctionWithLeaderElection(t *testing.T) { 105 | // Save the original command-line arguments and restore them after the test 106 | origArgs := os.Args 107 | // Reset the flag.CommandLine 108 | flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) 109 | defer func() { os.Args = origArgs }() 110 | 111 | // Mock the gocsi.Run function 112 | mockGocsi := new(MockGocsi) 113 | 114 | // Mock os.Exit 115 | osExit = mockExit 116 | defer func() { osExit = os.Exit }() 117 | 118 | // Set required environment variables for Kubernetes client 119 | os.Setenv("KUBERNETES_SERVICE_HOST", "127.0.0.1") 120 | os.Setenv("KUBERNETES_SERVICE_PORT", "6443") 121 | defer func() { 122 | os.Unsetenv("KUBERNETES_SERVICE_HOST") 123 | os.Unsetenv("KUBERNETES_SERVICE_PORT") 124 | }() 125 | 126 | // reset os.Args before next test 127 | os.Args = origArgs 128 | 129 | // Test case: Leader election enabled 130 | t.Run("LeaderElectionEnabled", func(t *testing.T) { 131 | // Create a new flag set for this test case 132 | flagSet := flag.NewFlagSet("test", flag.ExitOnError) 133 | os.Args = []string{ 134 | "cmd", 135 | "--leader-election=true", 136 | "--leader-election-namespace=default", 137 | "--leader-election-lease-duration=15s", 138 | "--leader-election-renew-deadline=10s", 139 | "--leader-election-retry-period=5s", 140 | "--driver-config=config.yaml", 141 | "--driver-name=csi-unity.dellemc.com", 142 | "--driver-secret=secret.yaml", 143 | } 144 | flagSet.Bool("leader-election", true, "Enables leader election.") 145 | flagSet.String("leader-election-namespace", "default", "The namespace where leader election lease will be created.") 146 | flagSet.Duration("leader-election-lease-duration", 15*time.Second, "Duration, in seconds, that non-leader candidates will wait to force acquire leadership") 147 | flagSet.Duration("leader-election-renew-deadline", 10*time.Second, "Duration, in seconds, that the acting leader will retry refreshing leadership before giving up.") 148 | flagSet.Duration("leader-election-retry-period", 5*time.Second, "Duration, in seconds, the LeaderElector clients should wait between tries of actions") 149 | flagSet.String("driver-config", "config.yaml", "yaml file with driver config params") 150 | flagSet.String("driver-name", "csi-unity.dellemc.com", "driver name") 151 | flagSet.String("driver-secret", "secret.yaml", "driver secret yaml file") 152 | flagSet.Parse(os.Args[1:]) 153 | 154 | // Mock Kubernetes client 155 | client := fake.NewSimpleClientset() 156 | 157 | lock := &resourcelock.LeaseLock{ 158 | LeaseMeta: metav1.ObjectMeta{ 159 | Name: "driver-csi-unity-dellemc-com", 160 | Namespace: "default", 161 | }, 162 | Client: client.CoordinationV1(), 163 | LockConfig: resourcelock.ResourceLockConfig{ 164 | Identity: "test-identity", 165 | }, 166 | } 167 | 168 | leaderElectionConfig := leaderelection.LeaderElectionConfig{ 169 | Lock: lock, 170 | LeaseDuration: 15 * time.Second, 171 | RenewDeadline: 10 * time.Second, 172 | RetryPeriod: 5 * time.Second, 173 | Callbacks: leaderelection.LeaderCallbacks{ 174 | OnStartedLeading: func(_ context.Context) { 175 | // Perform leader-specific tasks here 176 | }, 177 | OnStoppedLeading: func() { 178 | }, 179 | OnNewLeader: func(_ string) { 180 | // Perform leader-specific tasks here 181 | }, 182 | }, 183 | } 184 | 185 | mockLeaderElection := func(_ kubernetes.Interface, lockName, namespace string, renewDeadline, leaseDuration, retryPeriod time.Duration, _ func(ctx context.Context)) { 186 | require.Equal(t, "driver-csi-unity-dellemc-com", lockName) 187 | require.Equal(t, "default", namespace) 188 | require.Equal(t, 10*time.Second, renewDeadline) 189 | require.Equal(t, 15*time.Second, leaseDuration) 190 | require.Equal(t, 5*time.Second, retryPeriod) 191 | go leaderelection.RunOrDie(context.TODO(), leaderElectionConfig) 192 | } 193 | defer func() { 194 | if r := recover(); r != nil { 195 | t.Errorf("os.Exit was called: %v", r) 196 | } 197 | }() 198 | 199 | mainR(mockGocsi.Run, mockCreateKubeClientSet, mockLeaderElection) 200 | }) 201 | } 202 | 203 | var exitCode int 204 | 205 | func fakeExit(code int) { 206 | exitCode = code 207 | } 208 | 209 | func TestCheckLeaderElectionError(_ *testing.T) { 210 | // Mock the function to return an error 211 | err := errors.New("mock error") 212 | 213 | // Capture the output 214 | _, out, _ := os.Pipe() 215 | 216 | // Replace os.Exit with fakeExit 217 | oldExit := exitFunc 218 | exitFunc = fakeExit 219 | defer func() { exitFunc = oldExit }() 220 | 221 | // Call the function that includes the error handling 222 | checkLeaderElectionError(err) 223 | 224 | // Restore the original Stderr 225 | out.Close() 226 | } 227 | 228 | func TestValidateArgs(t *testing.T) { 229 | // Save the original exit function and restore it after the test 230 | origExit := exitFunc 231 | defer func() { exitFunc = origExit }() 232 | 233 | tests := []struct { 234 | name string 235 | driverConfigParamsfile string 236 | driverName string 237 | driverSecret string 238 | expectedExitCode int 239 | }{ 240 | {"MissingDriverConfig", "", "driverName", "driverSecret", 1}, 241 | {"MissingDriverName", "driverConfig", "", "driverSecret", 1}, 242 | {"MissingDriverSecret", "driverConfig", "driverName", "", 3}, 243 | {"AllArgsPresent", "driverConfig", "driverName", "driverSecret", 0}, 244 | } 245 | 246 | for _, tt := range tests { 247 | t.Run(tt.name, func(t *testing.T) { 248 | // Mock the exit function 249 | exitCode := 0 250 | exitFunc = func(code int) { 251 | exitCode = code 252 | panic(fmt.Sprintf("os.Exit called with code %d", code)) 253 | } 254 | 255 | // Convert strings to pointers 256 | driverConfigParamsfile := tt.driverConfigParamsfile 257 | driverName := tt.driverName 258 | driverSecret := tt.driverSecret 259 | 260 | // Run the function and capture the panic 261 | defer func() { 262 | if r := recover(); r != nil { 263 | if exitCode != tt.expectedExitCode { 264 | t.Errorf("expected exit code %d, got %d", tt.expectedExitCode, exitCode) 265 | } 266 | } else if tt.expectedExitCode != 0 { 267 | t.Errorf("expected exit code %d, but no panic occurred", tt.expectedExitCode) 268 | } 269 | }() 270 | 271 | validateArgs(&driverConfigParamsfile, &driverName, &driverSecret) 272 | 273 | if tt.expectedExitCode == 0 && exitCode != 0 { 274 | t.Errorf("expected no exit, but got exit code %d", exitCode) 275 | } 276 | }) 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /core/semver/semver.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019-2025 Dell Inc. or its subsidiaries. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package main 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | "flag" 21 | "fmt" 22 | "io" 23 | "os" 24 | "os/exec" 25 | "path/filepath" 26 | "regexp" 27 | "runtime" 28 | "strconv" 29 | "strings" 30 | "syscall" 31 | "text/template" 32 | "time" 33 | ) 34 | 35 | var ( 36 | format string 37 | output string 38 | export bool 39 | tpl *template.Template 40 | ) 41 | 42 | func init() { 43 | if flag.Lookup("f") == nil { 44 | flag.StringVar( 45 | &format, 46 | "f", 47 | "ver", 48 | "The output format: env, go, json, mk, rpm, ver") 49 | } 50 | if flag.Lookup("o") == nil { 51 | flag.StringVar( 52 | &output, 53 | "o", 54 | "", 55 | "The output file") 56 | } 57 | if flag.Lookup("x") == nil { 58 | flag.BoolVar( 59 | &export, 60 | "x", 61 | false, 62 | "Export env vars. Used with -f env") 63 | } 64 | } 65 | 66 | func initFlags() { 67 | format = flag.Lookup("f").Value.(flag.Getter).Get().(string) 68 | output = flag.Lookup("o").Value.(flag.Getter).Get().(string) 69 | export = flag.Lookup("x").Value.(flag.Getter).Get().(bool) 70 | } 71 | 72 | func main() { 73 | flag.Parse() 74 | initFlags() 75 | 76 | if strings.EqualFold("env", format) { 77 | format = "env" 78 | } else if strings.EqualFold("go", format) { 79 | format = "go" 80 | } else if strings.EqualFold("json", format) { 81 | format = "json" 82 | } else if strings.EqualFold("mk", format) { 83 | format = "mk" 84 | } else if strings.EqualFold("rpm", format) { 85 | format = "rpm" 86 | } else if strings.EqualFold("ver", format) { 87 | format = "ver" 88 | } else { 89 | if fileExists(format) { 90 | buf, err := ReadFile(format) // #nosec G304 91 | if err != nil { 92 | errorExit(fmt.Sprintf("error: read tpl failed: %v\n", err)) 93 | } 94 | format = string(buf) 95 | } 96 | tpl = template.Must(template.New("tpl").Parse(format)) 97 | format = "tpl" 98 | } 99 | 100 | var w io.Writer = os.Stdout 101 | if len(output) > 0 { 102 | fout, err := os.Create(filepath.Clean(output)) 103 | if err != nil { 104 | errorExit(fmt.Sprintf("error: %v\n", err)) 105 | } 106 | w = fout 107 | defer func() { 108 | if err := fout.Close(); err != nil { 109 | panic(err) 110 | } 111 | }() // #nosec G20 112 | } 113 | 114 | gitdesc := chkErr(doExec("git", "describe", "--long", "--dirty")) 115 | rx := regexp.MustCompile( 116 | `^[^\d]*(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z].+?))?(?:-(\d+)-g(.+?)(?:-(dirty))?)?\s*$`) 117 | m := rx.FindStringSubmatch(gitdesc) 118 | if len(m) == 0 { 119 | errorExit(fmt.Sprintf("error: match git describe failed: %s\n", gitdesc)) 120 | } 121 | 122 | goos := os.Getenv("XGOOS") 123 | if goos == "" { 124 | goos = runtime.GOOS 125 | } 126 | goarch := os.Getenv("XGOARCH") 127 | if goarch == "" { 128 | goarch = runtime.GOARCH 129 | } 130 | // get the build number. Jenkins exposes this as an 131 | // env variable called BUILD_NUMBER 132 | buildNumber := os.Getenv("BUILD_NUMBER") 133 | if buildNumber == "" { 134 | buildNumber = m[5] 135 | } 136 | buildType := os.Getenv("BUILD_TYPE") 137 | if buildType == "" { 138 | buildType = "X" 139 | } 140 | ver := &semver{ 141 | GOOS: goos, 142 | GOARCH: goarch, 143 | OS: goosToUname[goos], 144 | Arch: goarchToUname[goarch], 145 | Major: toInt(m[1]), 146 | Minor: toInt(m[2]), 147 | Patch: toInt(m[3]), 148 | Notes: m[4], 149 | Type: buildType, 150 | Build: toInt(buildNumber), 151 | Sha7: m[6], 152 | Sha32: chkErr(doExec("git", "log", "-n1", `--format=%H`)), 153 | Dirty: m[7] != "", 154 | Epoch: toInt64(chkErr(doExec("git", "log", "-n1", `--format=%ct`))), 155 | } 156 | ver.SemVer = ver.String() 157 | ver.SemVerRPM = ver.RPM() 158 | ver.BuildDate = ver.Timestamp().Format("Mon, 02 Jan 2006 15:04:05 MST") 159 | ver.ReleaseDate = ver.Timestamp().Format("06-01-02") 160 | 161 | switch format { 162 | case "env": 163 | for _, v := range ver.EnvVars() { 164 | if export { 165 | fmt.Fprint(w, "export ") 166 | } 167 | fmt.Fprintln(w, v) 168 | } 169 | case "go": 170 | case "json": 171 | enc := json.NewEncoder(w) 172 | enc.SetIndent("", " ") 173 | if err := enc.Encode(ver); err != nil { 174 | errorExit(fmt.Sprintf("error: encode to json failed: %v\n", err)) 175 | } 176 | case "mk": 177 | for _, v := range ver.EnvVars() { 178 | p := strings.SplitN(v, "=", 2) 179 | key := p[0] 180 | fmt.Fprintf(w, "%s ?=", key) 181 | if len(p) == 1 { 182 | fmt.Fprintln(w) 183 | } else { 184 | val := p[1] 185 | if strings.HasPrefix(val, `"`) && 186 | strings.HasSuffix(val, `"`) { 187 | val = val[1 : len(val)-1] 188 | } 189 | val = strings.Replace(val, "$", "$$", -1) 190 | fmt.Fprintf(w, " %s\n", val) 191 | } 192 | } 193 | case "rpm": 194 | fmt.Fprintln(w, ver.RPM()) 195 | case "tpl": 196 | if err := tpl.Execute(w, ver); err != nil { 197 | errorExit(fmt.Sprintf("error: template failed: %v\n", err)) 198 | } 199 | case "ver": 200 | fmt.Fprintln(w, ver.String()) 201 | } 202 | } 203 | 204 | var doExec = func(cmd string, args ...string) ([]byte, error) { 205 | c := exec.Command(cmd, args...) // #nosec G204 206 | c.Stderr = os.Stderr 207 | return c.Output() 208 | } 209 | 210 | func errorExit(message string) { 211 | fmt.Fprintf(os.Stderr, "%s", message) 212 | OSExit(1) 213 | } 214 | 215 | func chkErr(out []byte, err error) string { 216 | if err == nil { 217 | return strings.TrimSpace(string(out)) 218 | } 219 | 220 | e, ok := GetExitError(err) 221 | if !ok { 222 | OSExit(1) 223 | } 224 | 225 | status, ok := GetStatusError(e) 226 | if !ok { 227 | OSExit(1) 228 | } 229 | 230 | OSExit(status) 231 | return "" 232 | } 233 | 234 | type semver struct { 235 | GOOS string `json:"goos"` 236 | GOARCH string `json:"goarch"` 237 | OS string `json:"os"` 238 | Arch string `json:"arch"` 239 | Major int `json:"major"` 240 | Minor int `json:"minor"` 241 | Patch int `json:"patch"` 242 | Build int `json:"build"` 243 | Notes string `json:"notes"` 244 | Type string `json:"type"` 245 | Dirty bool `json:"dirty"` 246 | Sha7 string `json:"sha7"` 247 | Sha32 string `json:"sha32"` 248 | Epoch int64 `json:"epoch"` 249 | SemVer string `json:"semver"` 250 | SemVerRPM string `json:"semverRPM"` 251 | BuildDate string `json:"buildDate"` 252 | ReleaseDate string `json:"releaseDate"` 253 | } 254 | 255 | func (v *semver) String() string { 256 | buf := &bytes.Buffer{} 257 | fmt.Fprintf(buf, "%d.%d.%d", v.Major, v.Minor, v.Patch) 258 | if len(v.Notes) > 0 { 259 | fmt.Fprintf(buf, "-%s", v.Notes) 260 | } 261 | if v.Build > 0 { 262 | fmt.Fprintf(buf, "+%d", v.Build) 263 | } 264 | if v.Dirty { 265 | fmt.Fprint(buf, "+dirty") 266 | } 267 | return buf.String() 268 | } 269 | 270 | func (v *semver) RPM() string { 271 | return strings.Replace(v.String(), "-", "+", -1) 272 | } 273 | 274 | func (v *semver) EnvVars() []string { 275 | return []string{ 276 | fmt.Sprintf("GOOS=%s", v.GOOS), 277 | fmt.Sprintf("GOARCH=%s", v.GOARCH), 278 | fmt.Sprintf("OS=%s", v.OS), 279 | fmt.Sprintf("ARCH=%s", v.Arch), 280 | fmt.Sprintf("MAJOR=%d", v.Major), 281 | fmt.Sprintf("MINOR=%d", v.Minor), 282 | fmt.Sprintf("PATCH=%d", v.Patch), 283 | fmt.Sprintf("BUILD=%3.3d", v.Build), 284 | fmt.Sprintf("NOTES=\"%s\"", v.Notes), 285 | fmt.Sprintf("TYPE=%s", v.Type), 286 | fmt.Sprintf("DIRTY=%v", v.Dirty), 287 | fmt.Sprintf("SHA7=%s", v.Sha7), 288 | fmt.Sprintf("SHA32=%s", v.Sha32), 289 | fmt.Sprintf("EPOCH=%d", v.Epoch), 290 | fmt.Sprintf("SEMVER=\"%s\"", v.SemVer), 291 | fmt.Sprintf("SEMVER_RPM=\"%s\"", v.SemVerRPM), 292 | fmt.Sprintf("BUILD_DATE=\"%s\"", v.BuildDate), 293 | fmt.Sprintf("RELEASE_DATE=\"%s\"", v.ReleaseDate), 294 | } 295 | } 296 | 297 | func (v *semver) Timestamp() time.Time { 298 | return time.Unix(v.Epoch, 0) 299 | } 300 | 301 | func toInt(sz string) int { 302 | i, _ := strconv.Atoi(sz) 303 | return i 304 | } 305 | 306 | func toInt64(sz string) int64 { 307 | i, _ := strconv.Atoi(sz) 308 | return int64(i) 309 | } 310 | 311 | var goosToUname = map[string]string{ 312 | "android": "Android", 313 | "darwin": "Darwin", 314 | "dragonfly": "DragonFly", 315 | "freebsd": "kFreeBSD", 316 | "linux": "Linux", 317 | "nacl": "NaCl", 318 | "netbsd": "NetBSD", 319 | "openbsd": "OpenBSD", 320 | "plan9": "Plan9", 321 | "solaris": "Solaris", 322 | "windows": "Windows", 323 | } 324 | 325 | var goarchToUname = map[string]string{ 326 | "386": "i386", 327 | "amd64": "x86_64", 328 | "amd64p32": "x86_64_P32", 329 | "arm": "ARMv7", 330 | "arm64": "ARMv8", 331 | "mips": "MIPS32", 332 | "mips64": "MIPS64", 333 | "mips64le": "MIPS64LE", 334 | "mipsle": "MIPS32LE", 335 | "ppc64": "PPC64", 336 | "ppc64le": "PPC64LE", 337 | "s390x": "S390X", 338 | } 339 | 340 | func fileExists(filePath string) bool { 341 | if _, err := os.Stat(filePath); !os.IsNotExist(err) { 342 | return true 343 | } 344 | return false 345 | } 346 | 347 | // ReadFile is a wrapper around os.ReadFile 348 | var ReadFile = func(file string) ([]byte, error) { 349 | return os.ReadFile(file) // #nosec G304 350 | } 351 | 352 | // OSExit is a wrapper around os.Exit 353 | var OSExit = func(code int) { 354 | os.Exit(code) 355 | } 356 | 357 | // GetExitError is a wrapper around exec.ExitError 358 | var GetExitError = func(err error) (e *exec.ExitError, ok bool) { 359 | e, ok = err.(*exec.ExitError) 360 | return 361 | } 362 | 363 | // GetStatusError is a wrapper around syscall.WaitStatus 364 | var GetStatusError = func(exitError *exec.ExitError) (status int, ok bool) { 365 | if e, ok := exitError.Sys().(syscall.WaitStatus); ok { 366 | return e.ExitStatus(), true 367 | } 368 | return 1, false 369 | } 370 | --------------------------------------------------------------------------------