├── .github ├── CODEOWNERS ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── common-workflows.yaml │ ├── go-version.yaml │ ├── image-version-update.yaml │ ├── release.yaml │ ├── update-libraries-to-commits.yaml │ └── update-libraries.yaml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── core ├── .gitignore ├── core.go ├── semver.tpl └── semver │ ├── semver.go │ └── semver_test.go ├── csi-powermax.sh ├── csireverseproxy ├── .gitignore ├── Dockerfile ├── Makefile ├── certs │ └── .gitkeep ├── deploy │ └── config.yaml ├── docker.mk ├── env.sh ├── go.mod ├── go.sum ├── licenses │ └── LICENSE ├── main.go ├── main_test.go ├── manifests │ └── revproxy.yaml ├── overrides.mk ├── pkg │ ├── cache │ │ ├── cache.go │ │ └── cache_test.go │ ├── common │ │ ├── common.go │ │ ├── common_test.go │ │ ├── constants.go │ │ ├── envoy.go │ │ └── envoy_test.go │ ├── config │ │ ├── config.go │ │ ├── config_test.go │ │ └── mocks │ │ │ └── config-manager.go │ ├── k8smock │ │ ├── k8smock.go │ │ └── k8smock_test.go │ ├── k8sutils │ │ ├── k8sutils.go │ │ └── k8sutils_test.go │ ├── proxy │ │ ├── proxy.go │ │ └── proxy_test.go │ ├── servermock │ │ ├── servermock.go │ │ └── servermock_test.go │ └── utils │ │ ├── locks.go │ │ ├── locks_test.go │ │ ├── utils.go │ │ └── utils_test.go ├── run.sh ├── scripts │ ├── clean.sh │ └── deploy.sh ├── test-config │ ├── config-params.yaml │ ├── config.yaml │ ├── configB.yaml │ ├── configB_single.yaml │ ├── configB_single_noBackup.yaml │ ├── configC_single.yaml │ ├── secret-config.yaml │ └── tmp │ │ └── .gitkeep └── tls │ ├── tls.crt │ └── tls.key ├── dell-csi-helm-installer ├── .gitignore ├── README.md ├── common.sh ├── csi-install.sh ├── csi-offline-bundle.md ├── csi-offline-bundle.sh ├── csi-uninstall.sh ├── verify-csi-powermax.sh └── verify.sh ├── deploy └── config.yaml ├── docker.mk ├── env.sh ├── go.mod ├── go.sum ├── k8smock └── k8sutils_mock.go ├── k8sutils ├── k8sutils.go └── k8sutils_test.go ├── licenses └── LICENSE ├── main.go ├── main_test.go ├── overrides.mk ├── pkg ├── file │ ├── file.go │ └── file_test.go ├── migration │ ├── migration.go │ └── migration_test.go └── symmetrix │ ├── metro.go │ ├── metro_test.go │ ├── mocks │ ├── pmaxclient.go │ └── roundtripper.go │ ├── powermax.go │ └── powermax_test.go ├── plugin.go ├── provider ├── provider.go └── provider_test.go ├── runcontroller.sh ├── samples ├── configmap │ ├── powermax-array-config.yaml │ └── topologyConfig.yaml ├── migrationgroup │ └── migrationgroup.yaml ├── secret │ ├── emptysecret.yaml │ ├── karavi-authorization-config.json │ ├── reveal_secret.sh │ ├── secret.yaml │ └── vcenter-secret.yaml ├── storageclass │ ├── powermax.yaml │ ├── powermax_fc.yaml │ ├── powermax_iscsi.yaml │ ├── powermax_nfs.yaml │ ├── powermax_nvmetcp.yaml │ ├── powermax_srdf.yaml │ ├── powermax_vsphere.yaml │ ├── powermax_xfs.yaml │ └── powermax_zone.yaml └── volumesnapshotclass │ ├── rep_powermax-snapclas.yaml │ └── v1_powermax-snapclass.yaml ├── service ├── Makefile ├── README.md ├── controller.go ├── controller_test.go ├── csi_ctrl_to_node_connectivity.go ├── csi_ctrl_to_node_connectivity_test.go ├── csi_extension_server.go ├── deletion_worker.go ├── deletion_worker_test.go ├── envvars.go ├── features │ ├── controller_publish_unpublish.feature │ ├── csi_extension.feature │ ├── delete_volume.feature │ ├── initiatorname.iscsi │ ├── list_volumes.feature.disabled │ ├── migration.feature │ ├── multiple_iqn.iscsi │ ├── no_iqn.iscsi │ ├── node_publish_unpublish.feature │ ├── replication.feature │ ├── service.feature │ ├── snapshot.feature │ ├── snapshot.feature.disabled │ ├── valid.iscsi │ └── with_comments.iscsi ├── gobrick_mock_test.go ├── gosystemd_mock.go ├── identity.go ├── identity_test.go ├── interfaces.go ├── locks.go ├── migration.go ├── migration_test.go ├── mock-data │ ├── directorIDList.json │ ├── director_template.json │ ├── hostIDList.json │ ├── host_template.json │ ├── initiatorIDList.json │ ├── initiator_template.json │ ├── masking_view_connections_template.json │ ├── masking_view_template.json │ ├── nvme_port_template.json │ ├── portGroupIDList.json │ ├── portIDList.json │ ├── port_group_template.json │ ├── port_template.json │ ├── storageGroupIDList.json │ ├── storageResourcePool.json │ ├── storage_group_snapshot_policy.json │ ├── storage_group_template.json │ ├── storage_pool_template.json │ ├── symmetrix13.json │ ├── symmetrix46.json │ ├── symmetrix47.json │ ├── symmetrixList.json │ ├── volumeList.json │ └── volume_template.json ├── mount.go ├── mount_test.go ├── node.go ├── node_connectivity_checker.go ├── node_connectivity_checker_test.go ├── node_test.go ├── pending.go ├── replication.go ├── replication_test.go ├── service.go ├── service_test.go ├── service_unit_test.go ├── snap.go ├── snap_test.go ├── step_defs_test.go ├── storage_group_svc.go ├── string_slice.go ├── system.go ├── vsphere.go └── vsphere_test.go └── test ├── helm ├── .gitignore ├── 10block │ ├── Chart.yaml │ ├── templates │ │ └── test.yaml │ └── values.yaml ├── 10vols │ ├── Chart.yaml │ ├── templates │ │ ├── pvc.yaml │ │ └── test.yaml │ └── values.yaml ├── 1bigvol │ ├── Chart.yaml │ ├── templates │ │ ├── pvc0.yaml │ │ └── test.yaml │ └── values.yaml ├── 1clonevol │ ├── Chart.yaml │ ├── templates │ │ ├── pvc0.yaml │ │ └── test.yaml │ └── values.yaml ├── 1vol-rep │ ├── Chart.yaml │ ├── templates │ │ ├── pvc0.yaml │ │ └── test.yaml │ └── values.yaml ├── 1vol │ ├── Chart.yaml │ ├── templates │ │ ├── pvc0.yaml │ │ └── test.yaml │ └── values.yaml ├── 2block │ ├── Chart.yaml │ ├── templates │ │ └── test.yaml │ └── values.yaml ├── 2vols+clone │ ├── Chart.yaml │ ├── templates │ │ ├── createFromVolume.yaml │ │ ├── pvc0.yaml │ │ ├── pvc1.yaml │ │ └── test.yaml │ └── values.yaml ├── 2vols+restore │ ├── Chart.yaml │ └── templates │ │ ├── createFromSnap.yaml │ │ ├── pvc0.yaml │ │ ├── pvc1.yaml │ │ └── test.yaml ├── 2vols │ ├── Chart.yaml │ ├── templates │ │ ├── pvc0.yaml │ │ ├── pvc1.yaml │ │ └── test.yaml │ └── values.yaml ├── 7vols │ ├── Chart.yaml │ ├── templates │ │ ├── pvc.yaml │ │ └── test.yaml │ └── values.yaml ├── README.md ├── block │ ├── Chart.yaml │ ├── templates │ │ └── test.yaml │ └── values.yaml ├── deletepvcs.sh ├── get.volume.ids ├── helm.lint.sh ├── logit.sh ├── snap1.yaml ├── snap2.yaml ├── snaprestoretest.sh ├── snaprestorexfstest.sh ├── starttest.sh ├── stoptest.sh ├── volumeclonetest.sh ├── volumeexpansiontest.sh └── xfspre │ ├── Chart.yaml │ ├── templates │ ├── pv.yaml │ ├── pvc4.yaml │ └── test.yaml │ └── values.yaml ├── integration ├── README.md ├── features │ └── integration.feature ├── integration_test.go ├── pool.yml ├── run.sh ├── step_defs_test.go └── validate_http_unauthorized.sh ├── multi-az ├── README.md ├── config ├── run.sh ├── scripts │ ├── log_utils.sh │ ├── modify_zoning_labels.sh │ ├── replace.sh │ ├── waitForPowerMaxPods.sh │ └── workload.sh └── testfiles │ ├── openssl.cnf │ └── templates │ ├── template-powermax-configmap.yaml │ ├── template-powermax-csm.yaml │ ├── template-powermax-secret.yaml │ ├── template-powermax-storageclass-1.yaml │ ├── template-powermax-storageclass-2.yaml │ ├── template-powermax-storageclass-zoneless-arrayid.yaml │ └── template-powermax-storageclass-zoneless.yaml ├── sanity ├── README.md ├── run.sh ├── secrets.yaml └── start_driver.sh └── scale └── README.md /.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 | 6 | # These are the default owners for the code and will 7 | # be requested for review when someone opens a pull request. 8 | # Order is alphabetical for easier maintenance. 9 | # 10 | # Abhilash Muralidhara (abhi16394) 11 | # Adarsh Kumar Yadav (adarsh-dell) 12 | # Akshay Saini (AkshaySainiDell) 13 | # Bahubali Jain (bpjain2004) 14 | # Don Khan (donatwork) 15 | # Jooseppi Luna (jooseppi-luna) 16 | # Trevor Dawe (tdawe) 17 | 18 | # for all files 19 | * @abhi16394 @adarsh-dell @AkshaySainiDell @bpjain2004 @donatwork @jooseppi-luna @tdawe 20 | 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # To get started with Dependabot version updates, you'll need to specify which 3 | # package ecosystems to update and where the package manifests are located. 4 | # Please see the documentation for all configuration options: 5 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 6 | 7 | version: 2 8 | updates: 9 | # Schedule for go module updates 10 | - package-ecosystem: "gomod" 11 | directory: "/" 12 | schedule: 13 | interval: "weekly" 14 | day: "sunday" 15 | time: "18:00" 16 | allow: 17 | # Allow direct updates for packages 18 | - dependency-type: direct 19 | ignore: 20 | - dependency-name: "*" 21 | update-types: 22 | - version-update:semver-patch 23 | # a group of dependencies will be updated together in one pull request 24 | groups: 25 | golang: 26 | # group all semantic versioning levels together in one pull request 27 | update-types: 28 | - major 29 | - minor 30 | patterns: 31 | - "*" 32 | 33 | # github actions 34 | - package-ecosystem: "github-actions" 35 | directory: "/" 36 | schedule: 37 | # Check for updates to GitHub Actions every week 38 | interval: "weekly" 39 | day: "saturday" 40 | groups: 41 | github-actions: 42 | patterns: 43 | - "*" 44 | -------------------------------------------------------------------------------- /.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 | - [ ] Have you run format,vet & lint checks against your submission? 14 | - [ ] Have you made sure that the code compiles? 15 | - [ ] Did you run the unit & integration tests successfully? 16 | - [ ] Have you maintained at least 90% code coverage? 17 | - [ ] Have you commented your code, particularly in hard-to-understand areas 18 | - [ ] Have you done corresponding changes to the documentation 19 | - [ ] Did you run tests in a real Kubernetes cluster? 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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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/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 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release CSI-Powermax 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 version to release' 10 | required: true 11 | type: choice 12 | default: 'minor' 13 | options: 14 | - major 15 | - minor 16 | - patch 17 | - n-1/n-2 patch (Provide input in the below box) 18 | version: 19 | description: "Patch version to release. example: 2.1.x (Use this only if n-1/n-2 patch is selected)" 20 | required: false 21 | type: string 22 | reverseproxyCheckInput: 23 | description: 'Release CSI-ReverseProxy Image' 24 | type: boolean 25 | default: false 26 | required: false 27 | repository_dispatch: 28 | types: [auto-release-workflow] 29 | jobs: 30 | process-inputs: 31 | name: Process Inputs 32 | runs-on: ubuntu-latest 33 | outputs: 34 | processedVersion: ${{ steps.set-version.outputs.versionEnv }} 35 | steps: 36 | - name: Process input 37 | id: set-version 38 | shell: bash 39 | run: | 40 | echo "Triggered by: ${{ github.event_name }}" 41 | if [[ "${{ github.event_name }}" == "repository_dispatch" ]]; then 42 | echo "versionEnv=minor" >> $GITHUB_OUTPUT 43 | exit 0 44 | fi 45 | if [[ "${{ github.event.inputs.version }}" != "" && "${{ github.event.inputs.option }}" == "n-1/n-2 patch (Provide input in the below box)" ]]; then 46 | # if both version and option are provided, then version takes precedence i.e. patch release for n-1/n-2 47 | echo "versionEnv=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT 48 | exit 0 49 | fi 50 | if [[ "${{ github.event.inputs.option }}" != "n-1/n-2 patch (Provide input in the below box)" ]]; then 51 | # if only option is provided, then option takes precedence i.e. minor, major or patch release 52 | echo "versionEnv=${{ github.event.inputs.option }}" >> $GITHUB_OUTPUT 53 | exit 0 54 | fi 55 | # if neither option nor version is provided, then minor release is taken by default (Auto-release) 56 | echo "versionEnv=minor" >> $GITHUB_OUTPUT 57 | csm-release: 58 | needs: [process-inputs] 59 | uses: dell/common-github-actions/.github/workflows/csm-release-driver-module.yaml@main 60 | name: Release CSM Drivers and Modules 61 | with: 62 | version: ${{ needs.process-inputs.outputs.processedVersion }} 63 | images: ${{ github.event.inputs.reverseproxyCheckInput == 'true' && 'csi-powermax,csipowermax-reverseproxy' || 'csi-powermax' }} 64 | secrets: inherit 65 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # test directories and artifacts 2 | pmax/c.out 3 | service/test/ 4 | service/c.out 5 | test/integration/c.linux.out 6 | csm-common.mk 7 | 8 | # binaries and temporary files 9 | csi-powermax 10 | !csi-powermax/ 11 | semver.mk 12 | 13 | # files created by IDEs 14 | .vscode 15 | .idea/ 16 | -------------------------------------------------------------------------------- /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 | ARG GOPROXY 14 | ARG GOIMAGE 15 | ARG BASEIMAGE 16 | ARG DIGEST 17 | 18 | # Stage to build the driver 19 | FROM $GOIMAGE as builder 20 | ARG GOPROXY 21 | RUN mkdir -p /go/src 22 | COPY ./ /go/src/ 23 | WORKDIR /go/src/ 24 | RUN CGO_ENABLED=0 \ 25 | make build 26 | 27 | # Stage to build the driver image 28 | FROM $BASEIMAGE AS final 29 | COPY --from=builder /go/src/csi-powermax . 30 | COPY "csi-powermax.sh" . 31 | ENTRYPOINT ["/csi-powermax.sh"] 32 | RUN chmod +x /csi-powermax.sh 33 | LABEL vendor="Dell Technologies" \ 34 | maintainer="Dell Technologies" \ 35 | name="csi-powermax" \ 36 | summary="CSI Driver for Dell EMC PowerMax" \ 37 | description="CSI Driver for provisioning persistent storage from Dell EMC PowerMax" \ 38 | release="1.14.0" \ 39 | version="2.14.0" \ 40 | license="Apache-2.0" 41 | COPY ./licenses /licenses 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSI Driver for Dell PowerMax 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/dell/csi-powermax?style=flat-square)](https://goreportcard.com/report/github.com/dell/csi-powermax) 4 | [![License](https://img.shields.io/github/license/dell/csi-powermax?style=flat-square&color=blue&label=License)](https://github.com/dell/csi-powermax/blob/main/LICENSE) 5 | [![Docker](https://img.shields.io/docker/pulls/dellemc/csi-powermax.svg?logo=docker&style=flat-square&label=Pulls)](https://hub.docker.com/r/dellemc/csi-powermax) 6 | [![Last Release](https://img.shields.io/github/v/release/dell/csi-powermax?label=Latest&style=flat-square&logo=go)](https://github.com/dell/csi-powermax/releases) 7 | 8 | **Repository for CSI Driver for Dell PowerMax** 9 | 10 | ## Description 11 | CSI Driver for PowerMax is part of the [CSM (Container Storage Modules)](https://github.com/dell/csm) open-source suite of Kubernetes storage enablers for Dell products. The CSI Driver for PowerMax is a Container Storage Interface (CSI) driver that provides support for provisioning persistent storage using Dell PowerMax storage array. 12 | 13 | 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. 14 | 15 | ## Table of Contents 16 | 17 | * [Code of Conduct](https://github.com/dell/csm/blob/main/docs/CODE_OF_CONDUCT.md) 18 | * [Maintainer Guide](https://github.com/dell/csm/blob/main/docs/MAINTAINER_GUIDE.md) 19 | * [Committer Guide](https://github.com/dell/csm/blob/main/docs/COMMITTER_GUIDE.md) 20 | * [Contributing Guide](https://github.com/dell/csm/blob/main/docs/CONTRIBUTING.md) 21 | * [List of Adopters](https://github.com/dell/csm/blob/main/docs/ADOPTERS.md) 22 | * [Support](#support) 23 | * [Security](https://github.com/dell/csm/blob/main/docs/SECURITY.md) 24 | * [Building](#building) 25 | * [Runtime Dependecies](#runtime-dependencies) 26 | * [Documentation](#documentation) 27 | 28 | ## Support 29 | For any issues, questions or feedback, please contact [Dell support](https://www.dell.com/support/incidents-online/en-us/contactus/product/container-storage-modules). 30 | 31 | ## Building 32 | 33 | To build the source, execute `make build`. 34 | 35 | To run unit tests, execute `make unit-test`. 36 | 37 | To build an image, execute `make docker`. 38 | 39 | You can run an integration test on a Linux system by populating the file `env.sh` with values for your Dell PowerMax systems and then run "`make integration-test`". 40 | 41 | ## Runtime Dependencies 42 | For a complete list of dependencies, please visit [Prerequisites](https://dell.github.io/csm-docs/docs/deployment/helm/drivers/installation/powermax/#prerequisites) 43 | 44 | 45 | ## Documentation 46 | For more detailed information on the driver, please refer to [Container Storage Modules documentation](https://dell.github.io/csm-docs/). 47 | -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | core_generated.go 2 | -------------------------------------------------------------------------------- /core/core.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /csi-powermax.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 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 | if [ -z "$CSI_ENDPOINT" ] 15 | then 16 | echo "Warning: CSI_ENDPOINT is not set" 17 | else 18 | socket_file=$CSI_ENDPOINT 19 | if [[ $CSI_ENDPOINT == "unix://"* ]] 20 | then 21 | socket_file=$(echo $CSI_ENDPOINT | sed 's/^.\{7\}//') 22 | fi 23 | [ -e $socket_file ] && rm $socket_file 24 | fi 25 | exec "/csi-powermax" "$@" 26 | -------------------------------------------------------------------------------- /csireverseproxy/.gitignore: -------------------------------------------------------------------------------- 1 | revproxy 2 | revproxy.exe 3 | certs/* 4 | !certs/.gitkeep 5 | c.out 6 | -------------------------------------------------------------------------------- /csireverseproxy/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 | # STEP 1 build executable binary 15 | ############################ 16 | ARG GOIMAGE 17 | ARG BASEIMAGE 18 | ARG GOPROXY 19 | 20 | FROM $GOIMAGE as builder 21 | 22 | # Install git + SSL ca certificates. 23 | # Git is required for fetching the dependencies. 24 | # Ca-certificates is required to call HTTPS endpoints. 25 | RUN apt-get update && apt-get -y install tzdata && update-ca-certificates 26 | 27 | # Create revproxy 28 | ENV USER=revproxy 29 | ENV UID=10001 30 | 31 | # See https://stackoverflow.com/a/55757473/12429735 32 | RUN adduser \ 33 | --disabled-password \ 34 | --gecos "" \ 35 | --home "/nonexistent" \ 36 | --shell "/sbin/nologin" \ 37 | --no-create-home \ 38 | --uid "${UID}" \ 39 | "${USER}" 40 | 41 | RUN mkdir -p /go/src/csireverseproxy 42 | COPY . /go/src/csireverseproxy 43 | 44 | WORKDIR /go/src/csireverseproxy 45 | RUN CGO_ENABLED=0 go build 46 | 47 | 48 | ############################ 49 | # STEP 2 build a small image 50 | ############################ 51 | FROM $BASEIMAGE as final 52 | LABEL vendor="Dell Technologies" \ 53 | maintainer="Dell Technologies" \ 54 | name="csipowermax-reverseproxy" \ 55 | summary="CSI PowerMax Reverse Proxy" \ 56 | description="CSI PowerMax Reverse Proxy which helps manage connections with Unisphere for PowerMax" \ 57 | release="1.13.0" \ 58 | version="2.12.0" \ 59 | license="Apache-2.0" 60 | COPY licenses /licenses 61 | # Import from builder. 62 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 63 | COPY --from=builder /etc/passwd /etc/passwd 64 | COPY --from=builder /etc/group /etc/group 65 | 66 | # Use an unprivileged user. 67 | USER revproxy:revproxy 68 | 69 | COPY --from=builder /go/src/csireverseproxy/csireverseproxy /app/revproxy 70 | 71 | WORKDIR /app 72 | CMD ["/app/revproxy"] 73 | 74 | EXPOSE 2222 75 | -------------------------------------------------------------------------------- /csireverseproxy/Makefile: -------------------------------------------------------------------------------- 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 | include overrides.mk 14 | 15 | run: 16 | @./run.sh 17 | 18 | format: 19 | @gofmt -w -s . 20 | 21 | clean: 22 | rm -f cover.out coverage.txt 23 | go clean 24 | 25 | build: 26 | CGO_ENABLED=0 go build 27 | 28 | docker: 29 | go run ../core/semver/semver.go -f mk >semver.mk 30 | make -f docker.mk docker 31 | 32 | docker-no-cache: 33 | make -f docker.mk docker-no-cache 34 | 35 | docker-push: docker 36 | make -f docker.mk push 37 | 38 | unit-test: 39 | go test -v -coverprofile c1.out ./... 40 | 41 | download-csm-common: 42 | curl -O -L https://raw.githubusercontent.com/dell/csm/main/config/csm-common.mk 43 | -------------------------------------------------------------------------------- /csireverseproxy/certs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dell/csi-powermax/4c9b8f136770d770a09e024468393355d12575d5/csireverseproxy/certs/.gitkeep -------------------------------------------------------------------------------- /csireverseproxy/deploy/config.yaml: -------------------------------------------------------------------------------- 1 | port: 2222 2 | logLevel: debug 3 | logFormat: text 4 | config: 5 | storageArrays: 6 | - storageArrayId: "000000000001" 7 | primaryURL: https://primary-1.unisphe.re:8443 8 | backupURL: https://backup-1.unisphe.re:8443 9 | proxyCredentialSecrets: 10 | - primary-unisphere-secret-1 11 | - backup-unisphere-secret-1 12 | - storageArrayId: "000000000002" 13 | primaryURL: https://primary-2.unisphe.re:8443 14 | backupURL: https://backup-2.unisphe.re:8443 15 | proxyCredentialSecrets: 16 | - primary-unisphere-secret-2 17 | - backup-unisphere-secret-2 18 | managementServers: 19 | - url: https://primary-1.unisphe.re:8443 20 | arrayCredentialSecret: primary-unisphere-secret-1 21 | skipCertificateValidation: true 22 | - url: https://backup-1.unisphe.re:8443 23 | arrayCredentialSecret: backup-unisphere-secret-1 24 | skipCertificateValidation: false 25 | - url: https://primary-2.unisphe.re:8443 26 | arrayCredentialSecret: primary-unisphere-secret-2 27 | skipCertificateValidation: true 28 | - url: https://backup-2.unisphe.re:8443 29 | arrayCredentialSecret: backup-unisphere-secret-2 30 | skipCertificateValidation: false 31 | -------------------------------------------------------------------------------- /csireverseproxy/docker.mk: -------------------------------------------------------------------------------- 1 | # Copyright © 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 | # 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 | # docker makefile, included from Makefile, will build/push images with docker or podman 14 | # 15 | 16 | # Includes the following generated file to get semantic version information 17 | include semver.mk 18 | 19 | ifdef NOTES 20 | RELNOTE="-$(NOTES)" 21 | else 22 | RELNOTE= 23 | endif 24 | 25 | ifeq ($(PROXY_IMAGETAG),) 26 | PROXY_IMAGETAG="v$(MAJOR).$(MINOR).$(PATCH)$(RELNOTE)" 27 | endif 28 | 29 | 30 | docker: download-csm-common 31 | $(eval include csm-common.mk) 32 | @echo "Building: $(REGISTRY)/$(PROXY_IMAGENAME):$(PROXY_IMAGETAG)" 33 | $(BUILDER) build --pull $(NOCACHE) -t "$(REGISTRY)/$(PROXY_IMAGENAME):$(PROXY_IMAGETAG)" --target $(BUILDSTAGE) --build-arg GOPROXY --build-arg BASEIMAGE=$(CSM_BASEIMAGE) --build-arg GOIMAGE=$(DEFAULT_GOIMAGE) . 34 | 35 | docker-no-cache: 36 | @echo "Building with --no-cache ..." 37 | @make docker NOCACHE=--no-cache 38 | 39 | push: 40 | @echo "Pushing: $(REGISTRY)/$(PROXY_IMAGENAME):$(PROXY_IMAGETAG)" 41 | $(BUILDER) push "$(REGISTRY)/$(PROXY_IMAGENAME):$(PROXY_IMAGETAG)" 42 | 43 | download-csm-common: 44 | curl -O -L https://raw.githubusercontent.com/dell/csm/main/config/csm-common.mk -------------------------------------------------------------------------------- /csireverseproxy/env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 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 | # Default value is set to deploy 15 | # This directory contains the configuration for the reverseproxy 16 | # export X_CSI_REVPROXY_CONFIG_DIR="" 17 | 18 | # Default value is set to config 19 | # This is the configuration for the reverseproxy 20 | # export X_CSI_REVPROXY_CONFIG_FILE_NAME="" 21 | 22 | # Default TLS Cert Dir is tls 23 | # This folder is supposed to contain a TLS certificate and key 24 | # which is used by the HTTPS server 25 | # export X_CSI_REVPROXY_TLS_CERT_DIR="" 26 | 27 | # Default value is powermax 28 | # Set this value if you want the proxy to read/monitor secrets 29 | # from a different namespace 30 | # export X_CSI_REVPROXY_WATCH_NAMESPACE = "" 31 | 32 | -------------------------------------------------------------------------------- /csireverseproxy/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dell/csi-powermax/csireverseproxy/v2 2 | 3 | go 1.24 4 | 5 | require ( 6 | github.com/dell/gopowermax/v2 v2.9.0 7 | github.com/fsnotify/fsnotify v1.8.0 8 | github.com/gorilla/mux v1.8.1 9 | github.com/kubernetes-csi/csi-lib-utils v0.20.0 10 | github.com/mitchellh/mapstructure v1.5.0 11 | github.com/pkg/errors v0.9.1 12 | github.com/sirupsen/logrus v1.9.3 13 | github.com/spf13/viper v1.19.0 14 | github.com/stretchr/testify v1.10.0 15 | go.uber.org/mock v0.5.0 16 | gopkg.in/yaml.v2 v2.4.0 17 | k8s.io/api v0.32.1 18 | k8s.io/apimachinery v0.32.1 19 | k8s.io/client-go v0.32.1 20 | ) 21 | 22 | require ( 23 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 24 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 25 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 26 | github.com/go-logr/logr v1.4.2 // indirect 27 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 28 | github.com/go-openapi/jsonreference v0.21.0 // indirect 29 | github.com/go-openapi/swag v0.23.0 // indirect 30 | github.com/gogo/protobuf v1.3.2 // indirect 31 | github.com/golang/protobuf v1.5.4 // indirect 32 | github.com/google/gnostic-models v0.6.9 // indirect 33 | github.com/google/go-cmp v0.6.0 // indirect 34 | github.com/google/gofuzz v1.2.0 // indirect 35 | github.com/google/uuid v1.6.0 // indirect 36 | github.com/hashicorp/hcl v1.0.0 // indirect 37 | github.com/josharian/intern v1.0.0 // indirect 38 | github.com/json-iterator/go v1.1.12 // indirect 39 | github.com/magiconair/properties v1.8.9 // indirect 40 | github.com/mailru/easyjson v0.9.0 // indirect 41 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 42 | github.com/modern-go/reflect2 v1.0.2 // indirect 43 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 44 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 45 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 46 | github.com/sagikazarmark/locafero v0.7.0 // indirect 47 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect 48 | github.com/sourcegraph/conc v0.3.0 // indirect 49 | github.com/spf13/afero v1.12.0 // indirect 50 | github.com/spf13/cast v1.7.1 // indirect 51 | github.com/spf13/pflag v1.0.6 // indirect 52 | github.com/subosito/gotenv v1.6.0 // indirect 53 | github.com/x448/float16 v0.8.4 // indirect 54 | go.uber.org/multierr v1.11.0 // indirect 55 | golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect 56 | golang.org/x/net v0.38.0 // indirect 57 | golang.org/x/oauth2 v0.25.0 // indirect 58 | golang.org/x/sys v0.31.0 // indirect 59 | golang.org/x/term v0.30.0 // indirect 60 | golang.org/x/text v0.23.0 // indirect 61 | golang.org/x/time v0.9.0 // indirect 62 | google.golang.org/protobuf v1.36.4 // indirect 63 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 64 | gopkg.in/inf.v0 v0.9.1 // indirect 65 | gopkg.in/ini.v1 v1.67.0 // indirect 66 | gopkg.in/yaml.v3 v3.0.1 // indirect 67 | k8s.io/klog/v2 v2.130.1 // indirect 68 | k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect 69 | k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect 70 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect 71 | sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect 72 | sigs.k8s.io/yaml v1.4.0 // indirect 73 | ) 74 | -------------------------------------------------------------------------------- /csireverseproxy/manifests/revproxy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: powermax-revproxy 5 | namespace: powermax 6 | --- 7 | kind: Role 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | metadata: 10 | name: powermax-revproxy 11 | namespace: powermax 12 | rules: 13 | - apiGroups: [""] 14 | resources: ["secrets"] 15 | verbs: ["list", "watch", "get"] 16 | --- 17 | kind: RoleBinding 18 | apiVersion: rbac.authorization.k8s.io/v1 19 | metadata: 20 | name: powermax-revproxy 21 | namespace: powermax 22 | subjects: 23 | - kind: ServiceAccount 24 | name: powermax-revproxy 25 | namespace: powermax 26 | roleRef: 27 | kind: Role 28 | name: powermax-revproxy 29 | apiGroup: rbac.authorization.k8s.io 30 | --- 31 | apiVersion: v1 32 | kind: Service 33 | metadata: 34 | name: powermax-revproxy 35 | namespace: powermax 36 | spec: 37 | ports: 38 | - port: 2222 39 | protocol: TCP 40 | targetPort: 2222 41 | selector: 42 | name: powermax-revproxy 43 | type: ClusterIP 44 | --- 45 | apiVersion: apps/v1 46 | kind: Deployment 47 | metadata: 48 | name: powermax-revproxy 49 | namespace: powermax 50 | spec: 51 | replicas: 1 52 | selector: 53 | matchLabels: 54 | name: powermax-revproxy 55 | template: 56 | metadata: 57 | labels: 58 | name: powermax-revproxy 59 | spec: 60 | serviceAccountName: powermax-revproxy 61 | containers: 62 | - name: powermax-revproxy 63 | # Replace this with the built image name 64 | image: quay.io/dell/container-storage-modules/csipowermax-reverseproxy:v2.7.0 65 | imagePullPolicy: Always 66 | env: 67 | - name: X_CSI_REVPROXY_CONFIG_DIR 68 | value: /etc/config/configmap 69 | - name: X_CSI_REVPROXY_CONFIG_FILE_NAME 70 | value: config.yaml 71 | - name: X_CSI_REVPROXY_IN_CLUSTER 72 | value: "true" 73 | - name: X_CSI_REVPROXY_TLS_CERT_DIR 74 | value: /app/tls 75 | # Change this to the namespace where proxy will be installed 76 | - name: X_CSI_REVPROXY_WATCH_NAMESPACE 77 | value: powermax 78 | volumeMounts: 79 | - name: configmap-volume 80 | mountPath: /etc/config/configmap 81 | - name: tls-secret 82 | mountPath: /app/tls 83 | - name: cert-dir 84 | mountPath: /app/certs 85 | volumes: 86 | - name: configmap-volume 87 | configMap: 88 | name: powermax-reverseproxy-config 89 | optional: true 90 | - name: tls-secret 91 | secret: 92 | secretName: csirevproxy-tls-secret 93 | - name: cert-dir 94 | emptyDir: 95 | -------------------------------------------------------------------------------- /csireverseproxy/overrides.mk: -------------------------------------------------------------------------------- 1 | # Copyright © 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 | # 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 | # overrides file 14 | # this file, included from the Makefile, will overlay default values with environment variables 15 | # 16 | 17 | # DEFAULT values 18 | # ubi9/ubi-micro:9.2-13 19 | DEFAULT_GOIMAGE=$(shell sed -En 's/^go (.*)$$/\1/p' go.mod) 20 | DEFAULT_REGISTRY="sample_registry" 21 | DEFAULT_IMAGENAME="csipowermax-reverseproxy" 22 | DEFAULT_BUILDSTAGE="final" 23 | DEFAULT_IMAGETAG="" 24 | 25 | # set the GOIMAGE if needed 26 | ifeq ($(GOIMAGE),) 27 | export GOIMAGE="$(DEFAULT_GOIMAGE)" 28 | endif 29 | 30 | # set the REGISTRY if needed 31 | ifeq ($(REGISTRY),) 32 | export REGISTRY="$(DEFAULT_REGISTRY)" 33 | endif 34 | 35 | # set the PROXY_IMAGENAME if needed 36 | ifeq ($(PROXY_IMAGENAME),) 37 | export PROXY_IMAGENAME="$(DEFAULT_IMAGENAME)" 38 | endif 39 | 40 | #set the PROXY_IMAGETAG if needed 41 | ifneq ($(DEFAULT_IMAGETAG), "") 42 | export PROXY_IMAGETAG="$(DEFAULT_IMAGETAG)" 43 | endif 44 | 45 | # set the BUILDSTAGE if needed 46 | ifeq ($(BUILDSTAGE),) 47 | export BUILDSTAGE="$(DEFAULT_BUILDSTAGE)" 48 | endif 49 | 50 | # figure out if podman or docker should be used (use podman if found) 51 | ifneq (, $(shell which podman 2>/dev/null)) 52 | export BUILDER=podman 53 | else 54 | export BUILDER=docker 55 | endif 56 | 57 | # target to print some help regarding these overrides and how to use them 58 | overrides-help: 59 | @echo 60 | @echo "The following environment variables can be set to control the build" 61 | @echo 62 | @echo "GOIMAGE - The version of Go to build with, default is: $(DEFAULT_GOIMAGE)" 63 | @echo " Current setting is: $(GOIMAGE)" 64 | @echo "REGISTRY - The registry to push images to, default is: $(DEFAULT_REGISTRY)" 65 | @echo " Current setting is: $(REGISTRY)" 66 | @echo "PROXY_IMAGENAME - The image name to be built, defaut is: $(DEFAULT_IMAGENAME)" 67 | @echo " Current setting is: $(PROXY_IMAGENAME)" 68 | @echo "PROXY_IMAGETAG - The image tag to be built, default is an empty string which will determine the tag by examining annotated tags in the repo." 69 | @echo " Current setting is: $(PROXY_IMAGETAG)" 70 | @echo "BUILDSTAGE - The Dockerfile build stage to execute, default is: $(DEFAULT_BUILDSTAGE)" 71 | @echo " Stages can be found by looking at the Dockerfile" 72 | @echo " Current setting is: $(BUILDSTAGE)" 73 | @echo 74 | 75 | 76 | -------------------------------------------------------------------------------- /csireverseproxy/pkg/cache/cache.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 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 cache 16 | 17 | import ( 18 | "sync" 19 | "time" 20 | 21 | log "github.com/sirupsen/logrus" 22 | ) 23 | 24 | // Cache is the interface for a timed key-value store 25 | type Cache interface { 26 | Set(string, interface{}) 27 | Get(string) (interface{}, bool) 28 | Remove(string) 29 | } 30 | 31 | var _ Cache = new(cache) 32 | 33 | // New creates instance of timed key-value store. 34 | func New(name string, ttl time.Duration) Cache { 35 | return &cache{ 36 | name: name, 37 | ttl: ttl, 38 | store: make(map[string]data), 39 | cmu: sync.RWMutex{}, 40 | mu: sync.Mutex{}, 41 | } 42 | } 43 | 44 | type data struct { 45 | value interface{} 46 | cleanupTimer *time.Timer 47 | } 48 | 49 | type cache struct { 50 | name string 51 | ttl time.Duration 52 | store map[string]data 53 | cmu sync.RWMutex 54 | mu sync.Mutex 55 | } 56 | 57 | // Set sets a new value 58 | func (c *cache) Set(key string, value interface{}) { 59 | c.cmu.RLock() 60 | defer c.cmu.RUnlock() 61 | c.mu.Lock() 62 | c.store[key] = data{ 63 | value: value, 64 | cleanupTimer: time.AfterFunc(c.ttl, c.cleanupCallback(key)), 65 | } 66 | c.mu.Unlock() 67 | } 68 | 69 | // Get gets the value from the store 70 | func (c *cache) Get(key string) (interface{}, bool) { 71 | c.cmu.RLock() 72 | defer c.cmu.RUnlock() 73 | c.mu.Lock() 74 | data, ok := c.store[key] 75 | c.mu.Unlock() 76 | if ok { 77 | return data.value, ok 78 | } 79 | return nil, false 80 | } 81 | 82 | // Remove deletes the value from the store 83 | func (c *cache) Remove(key string) { 84 | c.cmu.RLock() 85 | defer c.cmu.RUnlock() 86 | c.mu.Lock() 87 | if data, ok := c.store[key]; ok { 88 | data.cleanupTimer.Stop() 89 | delete(c.store, key) 90 | } 91 | c.mu.Unlock() 92 | } 93 | 94 | func (c *cache) cleanupCallback(key string) func() { 95 | return func() { 96 | c.cmu.Lock() 97 | defer c.cmu.Unlock() 98 | delete(c.store, key) 99 | log.Debugf("Removed %s from store: %s", key, c.name) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /csireverseproxy/pkg/cache/cache_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 cache 16 | 17 | import ( 18 | "testing" 19 | "time" 20 | ) 21 | 22 | func TestCache(t *testing.T) { 23 | // Test case 1: Testing the Set method 24 | testCases := []struct { 25 | key string 26 | value interface{} 27 | }{ 28 | {"key1", "value1"}, 29 | {"key2", "value2"}, 30 | {"key3", "value3"}, 31 | } 32 | 33 | for _, tc := range testCases { 34 | c := New("test", time.Second) 35 | c.Set(tc.key, tc.value) 36 | if v, ok := c.Get(tc.key); !ok || v != tc.value { 37 | t.Errorf("Expected value %v, got %v", tc.value, v) 38 | } 39 | } 40 | 41 | // Test case 2: Testing the Get method 42 | testCases = []struct { 43 | key string 44 | value interface{} 45 | }{ 46 | {"key1", "value1"}, 47 | {"key2", "value2"}, 48 | {"key3", "value3"}, 49 | } 50 | 51 | for _, tc := range testCases { 52 | c := New("test", time.Second) 53 | c.Set(tc.key, tc.value) 54 | if v, ok := c.Get(tc.key); !ok || v != tc.value { 55 | t.Errorf("Expected value %v, got %v", tc.value, v) 56 | } 57 | } 58 | 59 | // Test case 3: Testing the Remove method 60 | testCases = []struct { 61 | key string 62 | value interface{} 63 | }{ 64 | {"key1", "value1"}, 65 | {"key2", "value2"}, 66 | {"key3", "value3"}, 67 | } 68 | 69 | for _, tc := range testCases { 70 | c := New("test", time.Second) 71 | c.Set(tc.key, tc.value) 72 | c.Remove(tc.key) 73 | if v, ok := c.Get(tc.key); ok || v != nil { 74 | t.Errorf("Expected nil, got %v", v) 75 | } 76 | } 77 | } 78 | 79 | func TestCleanupCallback2(t *testing.T) { 80 | tests := []struct { 81 | name string 82 | initialSet bool 83 | cleanupKey string 84 | expectedExists bool 85 | }{ 86 | { 87 | name: "Key does not exist before cleanup", 88 | initialSet: false, 89 | cleanupKey: "testKey", 90 | expectedExists: false, 91 | }, 92 | { 93 | name: "Key exists and is removed", 94 | initialSet: true, 95 | cleanupKey: "testKey", 96 | expectedExists: false, 97 | }, 98 | } 99 | 100 | for _, tt := range tests { 101 | t.Run(tt.name, func(t *testing.T) { 102 | c := &cache{ 103 | name: "test", 104 | store: map[string]data{}, 105 | } 106 | 107 | if tt.initialSet { 108 | c.Set(tt.cleanupKey, "testValue") 109 | } 110 | 111 | cleanup := c.cleanupCallback(tt.cleanupKey) 112 | cleanup() 113 | 114 | c.cmu.Lock() 115 | defer c.cmu.Unlock() 116 | _, exists := c.store[tt.cleanupKey] 117 | if exists != tt.expectedExists { 118 | t.Fatalf("Expected key existence: %v, got: %v", tt.expectedExists, exists) 119 | } 120 | }) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /csireverseproxy/pkg/common/constants.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 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 common 16 | 17 | // Constants for the proxy 18 | const ( 19 | DefaultCertFile = "tls.crt" 20 | DefaultKeyFile = "tls.key" 21 | DefaultTLSCertDirName = "tls" 22 | DefaultCertDirName = "certs" 23 | DefaultConfigFileName = "config" 24 | DefaultConfigDir = "deploy" 25 | DefaultPort = "2222" 26 | TestConfigDir = "test-config" 27 | TempConfigDir = "test-config/tmp" 28 | TestConfigFileName = "config.yaml" 29 | TestSecretFileName = "secret-config.yaml" // #nosec G101 30 | EnvCertDirName = "X_CSI_REVPROXY_CERT_DIR" 31 | EnvTLSCertDirName = "X_CSI_REVPROXY_TLS_CERT_DIR" 32 | EnvWatchNameSpace = "X_CSI_REVPROXY_WATCH_NAMESPACE" 33 | EnvConfigFileName = "X_CSI_REVPROXY_CONFIG_FILE_NAME" 34 | EnvConfigDirName = "X_CSI_REVPROXY_CONFIG_DIR" 35 | EnvInClusterConfig = "X_CSI_REVPROXY_IN_CLUSTER" 36 | EnvIsLeaderElectionEnabled = "X_CSI_REVPROXY_IS_LEADER_ENABLED" 37 | EnvSecretFilePath = "X_CSI_REVPROXY_SECRET_FILEPATH" // #nosec G101 38 | EnvReverseProxyUseSecret = "X_CSI_REVPROXY_USE_SECRET" // #nosec G101 39 | EnvPowermaxConfigPath = "X_CSI_POWERMAX_CONFIG_PATH" 40 | DefaultNameSpace = "powermax" 41 | MaxActiveReadRequests = 5 42 | MaxOutStandingWriteRequests = 50 43 | MaxActiveWriteRequests = 4 44 | MaxOutStandingReadRequests = 50 45 | DefaultSecretPath = "/etc/powermax/config" // #nosec G101 46 | // EnvSidecarProxyPort is the port on which the reverse proxy 47 | // server run, if run as a sidecar container 48 | EnvSidecarProxyPort = "X_CSI_POWERMAX_SIDECAR_PROXY_PORT" 49 | ) 50 | -------------------------------------------------------------------------------- /csireverseproxy/pkg/config/mocks/config-manager.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: config.go 3 | // 4 | // Generated by this command: 5 | // 6 | // mockgen -source=config.go -destination=mocks/config-manager.go -package=mocks 7 | // 8 | 9 | // Package mocks is a generated GoMock package. 10 | package mock_config 11 | 12 | import ( 13 | reflect "reflect" 14 | 15 | gomock "go.uber.org/mock/gomock" 16 | ) 17 | 18 | // MockConfigManager is a mock of ConfigManager interface. 19 | type MockConfigManager struct { 20 | ctrl *gomock.Controller 21 | recorder *MockConfigManagerMockRecorder 22 | isgomock struct{} 23 | } 24 | 25 | // MockConfigManagerMockRecorder is the mock recorder for MockConfigManager. 26 | type MockConfigManagerMockRecorder struct { 27 | mock *MockConfigManager 28 | } 29 | 30 | // NewMockConfigManager creates a new mock instance. 31 | func NewMockConfigManager(ctrl *gomock.Controller) *MockConfigManager { 32 | mock := &MockConfigManager{ctrl: ctrl} 33 | mock.recorder = &MockConfigManagerMockRecorder{mock} 34 | return mock 35 | } 36 | 37 | // EXPECT returns an object that allows the caller to indicate expected use. 38 | func (m *MockConfigManager) EXPECT() *MockConfigManagerMockRecorder { 39 | return m.recorder 40 | } 41 | 42 | // AddConfigPath mocks base method. 43 | func (m *MockConfigManager) AddConfigPath(arg0 string) { 44 | m.ctrl.T.Helper() 45 | m.ctrl.Call(m, "AddConfigPath", arg0) 46 | } 47 | 48 | // AddConfigPath indicates an expected call of AddConfigPath. 49 | func (mr *MockConfigManagerMockRecorder) AddConfigPath(arg0 any) *gomock.Call { 50 | mr.mock.ctrl.T.Helper() 51 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddConfigPath", reflect.TypeOf((*MockConfigManager)(nil).AddConfigPath), arg0) 52 | } 53 | 54 | // GetString mocks base method. 55 | func (m *MockConfigManager) GetString(arg0 string) string { 56 | m.ctrl.T.Helper() 57 | ret := m.ctrl.Call(m, "GetString", arg0) 58 | ret0, _ := ret[0].(string) 59 | return ret0 60 | } 61 | 62 | // GetString indicates an expected call of GetString. 63 | func (mr *MockConfigManagerMockRecorder) GetString(arg0 any) *gomock.Call { 64 | mr.mock.ctrl.T.Helper() 65 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetString", reflect.TypeOf((*MockConfigManager)(nil).GetString), arg0) 66 | } 67 | 68 | // ReadInConfig mocks base method. 69 | func (m *MockConfigManager) ReadInConfig() error { 70 | m.ctrl.T.Helper() 71 | ret := m.ctrl.Call(m, "ReadInConfig") 72 | ret0, _ := ret[0].(error) 73 | return ret0 74 | } 75 | 76 | // ReadInConfig indicates an expected call of ReadInConfig. 77 | func (mr *MockConfigManagerMockRecorder) ReadInConfig() *gomock.Call { 78 | mr.mock.ctrl.T.Helper() 79 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadInConfig", reflect.TypeOf((*MockConfigManager)(nil).ReadInConfig)) 80 | } 81 | 82 | // SetConfigName mocks base method. 83 | func (m *MockConfigManager) SetConfigName(arg0 string) { 84 | m.ctrl.T.Helper() 85 | m.ctrl.Call(m, "SetConfigName", arg0) 86 | } 87 | 88 | // SetConfigName indicates an expected call of SetConfigName. 89 | func (mr *MockConfigManagerMockRecorder) SetConfigName(arg0 any) *gomock.Call { 90 | mr.mock.ctrl.T.Helper() 91 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetConfigName", reflect.TypeOf((*MockConfigManager)(nil).SetConfigName), arg0) 92 | } 93 | 94 | // SetConfigType mocks base method. 95 | func (m *MockConfigManager) SetConfigType(arg0 string) { 96 | m.ctrl.T.Helper() 97 | m.ctrl.Call(m, "SetConfigType", arg0) 98 | } 99 | 100 | // SetConfigType indicates an expected call of SetConfigType. 101 | func (mr *MockConfigManagerMockRecorder) SetConfigType(arg0 any) *gomock.Call { 102 | mr.mock.ctrl.T.Helper() 103 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetConfigType", reflect.TypeOf((*MockConfigManager)(nil).SetConfigType), arg0) 104 | } 105 | -------------------------------------------------------------------------------- /csireverseproxy/pkg/servermock/servermock.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 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 servermock 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/dell/csi-powermax/csireverseproxy/v2/pkg/utils" 21 | 22 | "github.com/gorilla/mux" 23 | ) 24 | 25 | const ( 26 | authenticationEndpoint = "/univmax/authenticate" 27 | timeoutEndpoint = "/univmax/timeout" 28 | defaultEndpoint = "/univmax" 29 | ) 30 | 31 | var mockRouter http.Handler 32 | 33 | // GetHandler returns the http handler 34 | func GetHandler() http.Handler { 35 | handler := http.HandlerFunc( 36 | func(w http.ResponseWriter, r *http.Request) { 37 | if mockRouter != nil { 38 | mockRouter.ServeHTTP(w, r) 39 | } else { 40 | getRouter().ServeHTTP(w, r) 41 | } 42 | }) 43 | return handler 44 | } 45 | 46 | func getRouter() http.Handler { 47 | router := mux.NewRouter() 48 | router.HandleFunc(authenticationEndpoint, handleAuth) 49 | router.HandleFunc(defaultEndpoint, handleDefault) 50 | router.HandleFunc(timeoutEndpoint, handleTimeout) 51 | router.HandleFunc(utils.Prefix+"/version", handleVersion) 52 | router.HandleFunc(utils.Prefix+"/{version}/system/symmetrix", handleSymm) 53 | router.HandleFunc(utils.Prefix+"/common/Iterator/{iterId}/page", handleDefault) 54 | router.HandleFunc(utils.Prefix+"/{version}/replication/capabilities/symmetrix", handleSymmCapabilities) 55 | router.HandleFunc(utils.Prefix+"/{version}/sloprovisioning/symmetrix/{symid}", handleDefault) 56 | router.Path(utils.Prefix + "/{version}/sloprovisioning/symmetrix/{symid}/volume").HandlerFunc(handleVolume) 57 | mockRouter = router 58 | return router 59 | } 60 | 61 | func handleVolume(w http.ResponseWriter, r *http.Request) { 62 | w.WriteHeader(http.StatusOK) 63 | _, _ = w.Write([]byte(`{ "id": "00000000-1111-2abc-def3-44gh55ij66kl_0" }`)) 64 | } 65 | 66 | func handleSymmCapabilities(w http.ResponseWriter, r *http.Request) { 67 | w.WriteHeader(http.StatusOK) 68 | _, _ = w.Write([]byte("{\"symmetrixCapability\":[{\"symmetrixId\":\"000000000000\",\"snapVxCapable\":true,\"rdfCapable\":true,\"virtualWitnessCapable\":false}]}")) 69 | } 70 | 71 | func handleVersion(w http.ResponseWriter, r *http.Request) { 72 | w.WriteHeader(http.StatusOK) 73 | _, _ = w.Write([]byte(`{ "version": "V9.1.0.2" }`)) 74 | } 75 | 76 | func handleSymm(w http.ResponseWriter, r *http.Request) { 77 | w.WriteHeader(http.StatusOK) 78 | data := `{"symmetrixId": [ "000197802104", "000197900046", "000197900047" ]}` 79 | _, _ = w.Write([]byte(data)) 80 | } 81 | 82 | func handleTimeout(w http.ResponseWriter, r *http.Request) { 83 | w.WriteHeader(http.StatusServiceUnavailable) 84 | _, _ = w.Write([]byte(r.Host)) 85 | } 86 | 87 | func handleDefault(w http.ResponseWriter, r *http.Request) { 88 | w.WriteHeader(http.StatusOK) 89 | _, _ = w.Write([]byte(r.Host)) 90 | } 91 | 92 | func handleAuth(w http.ResponseWriter, r *http.Request) { 93 | w.WriteHeader(http.StatusUnauthorized) 94 | _, _ = w.Write([]byte(r.Host)) 95 | } 96 | -------------------------------------------------------------------------------- /csireverseproxy/pkg/servermock/servermock_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 servermock 16 | 17 | import ( 18 | "net/http" 19 | "net/http/httptest" 20 | "testing" 21 | 22 | "github.com/dell/csi-powermax/csireverseproxy/v2/pkg/utils" 23 | ) 24 | 25 | func TestGetHandler(t *testing.T) { 26 | handler := GetHandler() 27 | tests := []struct { 28 | name string 29 | url string 30 | wantStatus int 31 | wantBody string 32 | }{ 33 | {"Version Endpoint", utils.Prefix + "/version", http.StatusOK, `{ "version": "V9.1.0.2" }`}, 34 | {"Symmetrix Endpoint", utils.Prefix + "/v1/system/symmetrix", http.StatusOK, `{"symmetrixId": [ "000197802104", "000197900046", "000197900047" ]}`}, 35 | {"Auth Endpoint", authenticationEndpoint, http.StatusUnauthorized, ""}, 36 | {"Timeout Endpoint", timeoutEndpoint, http.StatusServiceUnavailable, ""}, 37 | } 38 | 39 | for _, tt := range tests { 40 | t.Run(tt.name, func(t *testing.T) { 41 | req := httptest.NewRequest(http.MethodGet, tt.url, nil) 42 | w := httptest.NewRecorder() 43 | handler.ServeHTTP(w, req) 44 | 45 | if w.Code != tt.wantStatus { 46 | t.Errorf("got status %d, want %d", w.Code, tt.wantStatus) 47 | } 48 | 49 | if tt.wantBody != "" && w.Body.String() != tt.wantBody { 50 | t.Errorf("got body %s, want %s", w.Body.String(), tt.wantBody) 51 | } 52 | }) 53 | } 54 | } 55 | 56 | func TestHandleVolume(t *testing.T) { 57 | req, err := http.NewRequest("GET", "/volume", nil) 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | 62 | recorder := httptest.NewRecorder() 63 | handleVolume(recorder, req) 64 | 65 | if status := recorder.Code; status != http.StatusOK { 66 | t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) 67 | } 68 | 69 | expected := `{ "id": "00000000-1111-2abc-def3-44gh55ij66kl_0" }` 70 | if recorder.Body.String() != expected { 71 | t.Errorf("handler returned unexpected body: got %v want %v", recorder.Body.String(), expected) 72 | } 73 | } 74 | 75 | func TestHandleSymmCapabilities2(t *testing.T) { 76 | req, err := http.NewRequest("GET", "/symmCapabilities", nil) 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | 81 | recorder := httptest.NewRecorder() 82 | handleSymmCapabilities(recorder, req) 83 | 84 | if status := recorder.Code; status != http.StatusOK { 85 | t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) 86 | } 87 | 88 | expected := `{"symmetrixCapability":[{"symmetrixId":"000000000000","snapVxCapable":true,"rdfCapable":true,"virtualWitnessCapable":false}]}` 89 | if recorder.Body.String() != expected { 90 | t.Errorf("handler returned unexpected body: got %v want %v", recorder.Body.String(), expected) 91 | } 92 | } 93 | 94 | func TestHandleDefault(t *testing.T) { 95 | req, err := http.NewRequest("GET", "/default", nil) 96 | if err != nil { 97 | t.Fatal(err) 98 | } 99 | 100 | recorder := httptest.NewRecorder() 101 | handleDefault(recorder, req) 102 | 103 | if status := recorder.Code; status != http.StatusOK { 104 | t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) 105 | } 106 | 107 | expected := `` 108 | if recorder.Body.String() != expected { 109 | t.Errorf("handler returned unexpected body: got %v want %v", recorder.Body.String(), expected) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /csireverseproxy/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 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 | source env.sh 15 | go run main.go 16 | 17 | -------------------------------------------------------------------------------- /csireverseproxy/scripts/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 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 | kubectl delete configmap powermax-reverseproxy-config -n powermax 15 | kubectl delete -f manifests/revproxy.yaml 16 | -------------------------------------------------------------------------------- /csireverseproxy/scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 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 | echo "kubectl create configmap revproxy-config --from-file deploy/ -n powermax" 15 | kubectl create configmap powermax-reverseproxy-config --from-file deploy/ -n powermax 16 | echo "kubectl create -f deploy/revproxy.yaml" 17 | kubectl create -f manifests/revproxy.yaml 18 | -------------------------------------------------------------------------------- /csireverseproxy/test-config/config-params.yaml: -------------------------------------------------------------------------------- 1 | csi_powermax_reverse_proxy_port: "2222" 2 | csi_log_level: debug 3 | csi_log_format: json 4 | -------------------------------------------------------------------------------- /csireverseproxy/test-config/config.yaml: -------------------------------------------------------------------------------- 1 | port: 2222 2 | config: 3 | storageArrays: 4 | - storageArrayId: "000000000001" 5 | primaryURL: https://primary-1.unisphe.re:8443 6 | backupURL: https://backup-1.unisphe.re:8443 7 | proxyCredentialSecrets: 8 | - primary-unisphere-secret-1 9 | - backup-unisphere-secret-1 10 | - storageArrayId: "000000000002" 11 | primaryURL: https://primary-2.unisphe.re:8443 12 | backupURL: https://backup-2.unisphe.re:8443 13 | proxyCredentialSecrets: 14 | - primary-unisphere-secret-2 15 | - backup-unisphere-secret-2 16 | managementServers: 17 | - url: https://primary-1.unisphe.re:8443 18 | arrayCredentialSecret: primary-unisphere-secret-1 19 | skipCertificateValidation: true 20 | certSecret: "secret-cert" 21 | - url: https://backup-1.unisphe.re:8443 22 | arrayCredentialSecret: backup-unisphere-secret-1 23 | skipCertificateValidation: false 24 | certSecret: "secret-cert" 25 | - url: https://primary-2.unisphe.re:8443 26 | arrayCredentialSecret: primary-unisphere-secret-2 27 | skipCertificateValidation: true 28 | certSecret: "secret-cert" 29 | - url: https://backup-2.unisphe.re:8443 30 | arrayCredentialSecret: backup-unisphere-secret-2 31 | skipCertificateValidation: false 32 | certSecret: "secret-cert" 33 | -------------------------------------------------------------------------------- /csireverseproxy/test-config/configB.yaml: -------------------------------------------------------------------------------- 1 | port: 2222 2 | config: 3 | storageArrays: 4 | - storageArrayId: "000000000001" 5 | primaryURL: https://primary-3.unisphe.re:8443 6 | backupURL: https://backup-3.unisphe.re:8443 7 | proxyCredentialSecrets: 8 | - primary-unisphere-secret-1 9 | - backup-unisphere-secret-1 10 | - storageArrayId: "000000000002" 11 | primaryURL: https://primary-4.unisphe.re:8443 12 | backupURL: https://backup-5.unisphe.re:8443 13 | proxyCredentialSecrets: 14 | - primary-unisphere-secret-2 15 | - backup-unisphere-secret-2 16 | managementServers: 17 | - url: https://primary-3.unisphe.re:8443 18 | arrayCredentialSecret: primary-unisphere-secret-1 19 | skipCertificateValidation: true 20 | - url: https://backup-3.unisphe.re:8443 21 | arrayCredentialSecret: backup-unisphere-secret-1 22 | skipCertificateValidation: false 23 | certSecret: "secret-cert" 24 | - url: https://primary-4.unisphe.re:8443 25 | arrayCredentialSecret: primary-unisphere-secret-2 26 | skipCertificateValidation: true 27 | - url: https://backup-5.unisphe.re:8443 28 | arrayCredentialSecret: backup-unisphere-secret-2 29 | skipCertificateValidation: false 30 | certSecret: "secret-cert" 31 | -------------------------------------------------------------------------------- /csireverseproxy/test-config/configB_single.yaml: -------------------------------------------------------------------------------- 1 | port: 2222 2 | config: 3 | storageArrays: 4 | - storageArrayId: "000000000001" 5 | primaryURL: https://primary-3.unisphe.re:8443 6 | backupURL: https://backup-3.unisphe.re:8443 7 | proxyCredentialSecrets: 8 | - primary-unisphere-secret-1 9 | - backup-unisphere-secret-1 10 | managementServers: 11 | - url: https://primary-3.unisphe.re:8443 12 | arrayCredentialSecret: primary-unisphere-secret-1 13 | skipCertificateValidation: true 14 | - url: https://backup-3.unisphe.re:8443 15 | arrayCredentialSecret: backup-unisphere-secret-1 16 | skipCertificateValidation: false 17 | certSecret: "secret-cert" 18 | -------------------------------------------------------------------------------- /csireverseproxy/test-config/configB_single_noBackup.yaml: -------------------------------------------------------------------------------- 1 | port: 2222 2 | config: 3 | storageArrays: 4 | - storageArrayId: "000000000001" 5 | primaryURL: https://primary-3.unisphe.re:8443 6 | proxyCredentialSecrets: 7 | - primary-unisphere-secret-1 8 | managementServers: 9 | - url: https://primary-3.unisphe.re:8443 10 | arrayCredentialSecret: primary-unisphere-secret-1 11 | skipCertificateValidation: true 12 | -------------------------------------------------------------------------------- /csireverseproxy/test-config/configC_single.yaml: -------------------------------------------------------------------------------- 1 | port: 2222 2 | config: 3 | storageArrays: 4 | - storageArrayId: "000000000003" 5 | primaryURL: https://primary-1.unisphe.re:8443 6 | proxyCredentialSecrets: 7 | - primary-unisphere-secret-1 8 | managementServers: 9 | - url: https://primary-1.unisphe.re:8443 10 | arrayCredentialSecret: primary-unisphere-secret-1 11 | skipCertificateValidation: true 12 | -------------------------------------------------------------------------------- /csireverseproxy/test-config/secret-config.yaml: -------------------------------------------------------------------------------- 1 | storageArrays: 2 | - storageArrayId: "000000000001" 3 | primaryEndpoint: https://primary-1.unisphe.re:8443 4 | backupEndpoint: https://backup-1.unisphe.re:8443 5 | - storageArrayId: "000000000002" 6 | primaryEndpoint: https://primary-2.unisphe.re:8443 7 | backupEndpoint: https://backup-2.unisphe.re:8443 8 | managementServers: 9 | - endpoint: https://primary-1.unisphe.re:8443 10 | username: admin 11 | password: password 12 | skipCertificateValidation: true 13 | - endpoint: https://backup-1.unisphe.re:8443 14 | username: admin 15 | password: password 16 | skipCertificateValidation: false 17 | - endpoint: https://primary-2.unisphe.re:8443 18 | username: admin 19 | password: password 20 | skipCertificateValidation: true 21 | - endpoint: https://backup-2.unisphe.re:8443 22 | username: admin 23 | password: password 24 | skipCertificateValidation: false 25 | -------------------------------------------------------------------------------- /csireverseproxy/test-config/tmp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dell/csi-powermax/4c9b8f136770d770a09e024468393355d12575d5/csireverseproxy/test-config/tmp/.gitkeep -------------------------------------------------------------------------------- /csireverseproxy/tls/tls.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICIjCCAaigAwIBAgIJAJ5YUqaRcZ98MAoGCCqGSM49BAMCME8xCzAJBgNVBAYT 3 | AlVTMQswCQYDVQQIDAJNQTESMBAGA1UEBwwJSE9QS0lOVE9OMRAwDgYDVQQKDAdE 4 | RUxMRU1DMQ0wCwYDVQQLDARIRVNTMB4XDTIwMDQyNDEwMjU1NloXDTMwMDQyMjEw 5 | MjU1NlowTzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk1BMRIwEAYDVQQHDAlIT1BL 6 | SU5UT04xEDAOBgNVBAoMB0RFTExFTUMxDTALBgNVBAsMBEhFU1MwdjAQBgcqhkjO 7 | PQIBBgUrgQQAIgNiAATuuqEaVBum3c1dT/iZS9ye1KFAT8Url5Tknh1ukl8T2t4n 8 | XJtZ1RC9ToxhjRErMCa1OvTxcTePQKaj9z8sfL941hdaxm8FhgN1vHC53f81ggJH 9 | eBc8qY4ecKDpmiLUKBWjUDBOMB0GA1UdDgQWBBT/evtuekLJNZ9xAkQUMJJVpbvQ 10 | 5TAfBgNVHSMEGDAWgBT/evtuekLJNZ9xAkQUMJJVpbvQ5TAMBgNVHRMEBTADAQH/ 11 | MAoGCCqGSM49BAMCA2gAMGUCMB1CQrLNhL7XtpEC22vG0gpUvUgw71MAFdN+wNMp 12 | j+y7jofFRdMJPmGyHUZddkK8sQIxAKiUBu8IeISBlDha46TsZvrf1jsF68pr6+BP 13 | GHqkoRB/B9FmzMotlUE/oqb2pVMRpw== 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /csireverseproxy/tls/tls.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PARAMETERS----- 2 | BgUrgQQAIg== 3 | -----END EC PARAMETERS----- 4 | -----BEGIN EC PRIVATE KEY----- 5 | MIGkAgEBBDCl+B35DfPTqOQF14qOKKtXVtwVpGLlhDMQ9mx7dcYuqH867aY45zs2 6 | OerCP42cmm2gBwYFK4EEACKhZANiAATuuqEaVBum3c1dT/iZS9ye1KFAT8Url5Tk 7 | nh1ukl8T2t4nXJtZ1RC9ToxhjRErMCa1OvTxcTePQKaj9z8sfL941hdaxm8FhgN1 8 | vHC53f81ggJHeBc8qY4ecKDpmiLUKBU= 9 | -----END EC PRIVATE KEY----- 10 | -------------------------------------------------------------------------------- /dell-csi-helm-installer/.gitignore: -------------------------------------------------------------------------------- 1 | images.manifest 2 | images.tar 3 | -------------------------------------------------------------------------------- /dell-csi-helm-installer/verify-csi-powermax.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright © 2020-2025 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 | 16 | # verify-csi-powermax method 17 | function verify-csi-powermax() { 18 | verify_k8s_versions "1.31" "1.33" 19 | verify_openshift_versions "4.18" "4.19" 20 | verify_helm_values_version "${DRIVER_VERSION}" 21 | verify_namespace "${NS}" 22 | verify_required_secrets "${RELEASE}-creds" 23 | verify_optional_secrets "${RELEASE}-certs" 24 | verify_optional_secrets "csirevproxy-tls-secret" 25 | verify_alpha_snap_resources 26 | verify_snap_requirements 27 | verify_optional_replication_requirements 28 | verify_iscsi_installation 29 | verify_helm_3 30 | verify_authorization_proxy_server 31 | } 32 | 33 | function verify_optional_replication_requirements() { 34 | log step "Verifying Replication requirements" 35 | decho 36 | log arrow 37 | log smart_step "Verifying that Dell CSI Replication CRDs are available" "small" 38 | 39 | error=0 40 | # check for the CRDs. These are required for installation 41 | CRDS=("DellCSIReplicationGroups") 42 | for C in "${CRDS[@]}"; do 43 | # Verify if the CRD is present on the system 44 | run_command kubectl explain ${C} 2> /dev/null | grep "dell" --quiet 45 | if [ $? -ne 0 ]; then 46 | error=1 47 | found_warning "The CRD for ${C} is not installed. This needs to be installed if you are going to enable replication support" 48 | fi 49 | done 50 | check_error error 51 | } 52 | 53 | -------------------------------------------------------------------------------- /deploy/config.yaml: -------------------------------------------------------------------------------- 1 | CSI_LOG_LEVEL: debug 2 | CSI_LOG_FORMAT: text 3 | -------------------------------------------------------------------------------- /docker.mk: -------------------------------------------------------------------------------- 1 | # Copyright © 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 | # 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 | # docker makefile, included from Makefile, will build/push images with docker or podman 14 | # 15 | 16 | # Includes the following generated file to get semantic version information 17 | include semver.mk 18 | 19 | ifdef NOTES 20 | RELNOTE="-$(NOTES)" 21 | else 22 | RELNOTE= 23 | endif 24 | 25 | ifeq ($(IMAGETAG),) 26 | IMAGETAG="v$(MAJOR).$(MINOR).$(PATCH)$(RELNOTE)" 27 | endif 28 | 29 | 30 | docker: download-csm-common 31 | $(eval include csm-common.mk) 32 | @echo "Building: $(REGISTRY)/$(IMAGENAME):$(IMAGETAG)" 33 | $(BUILDER) build --pull $(NOCACHE) -t "$(REGISTRY)/$(IMAGENAME):$(IMAGETAG)" --target $(BUILDSTAGE) --build-arg GOPROXY --build-arg BASEIMAGE=$(CSM_BASEIMAGE) --build-arg GOIMAGE=$(DEFAULT_GOIMAGE) . 34 | 35 | docker-no-cache: 36 | @echo "Building with --no-cache ..." 37 | @make docker NOCACHE=--no-cache 38 | 39 | push: 40 | @echo "Pushing: $(REGISTRY)/$(IMAGENAME):$(IMAGETAG)" 41 | $(BUILDER) push "$(REGISTRY)/$(IMAGENAME):$(IMAGETAG)" 42 | 43 | download-csm-common: 44 | curl -O -L https://raw.githubusercontent.com/dell/csm/main/config/csm-common.mk -------------------------------------------------------------------------------- /env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 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 | export X_CSI_POWERMAX_CONFIG_PATH="deploy/config.yaml" 16 | 17 | # Comma separated list of array managed by the driver 18 | export X_CSI_MANAGED_ARRAYS="000000000001" 19 | 20 | # HTTP endpoint of Unisphere 21 | # set this to "" if using revproxy 22 | export X_CSI_POWERMAX_ENDPOINT="https://0.0.0.1:8443" 23 | 24 | # EnvUser is the name of the environment variable used to set the 25 | # username when authenticating to Unisphere 26 | export X_CSI_POWERMAX_USER="username" 27 | 28 | # EnvPassword is the name of the environment variable used to set the 29 | # user's password when authenticating to Unisphere 30 | export X_CSI_POWERMAX_PASSWORD="password" 31 | 32 | # EnvSkipCertificateValidation is the name of the environment variable used to specify 33 | # that Unisphere's certificate chain and hostname should not 34 | # be verified 35 | export X_CSI_POWERMAX_SKIP_CERTIFICATE_VALIDATION="true" 36 | 37 | # EnvNodeName is the name of the environment variable used to set the 38 | # hostname where the node service is running 39 | export X_CSI_POWERMAX_NODENAME=`hostname` 40 | 41 | # EnvClusterPrefix is the name of the environment variable that is used 42 | # to specify as a prefix to apply to objects created via this K8s cluster 43 | export X_CSI_K8S_CLUSTER_PREFIX="XYZ" 44 | 45 | # EnvAutoProbe is the name of the environment variable used to specify 46 | # that the controller service should automatically probe itself if it 47 | # receives incoming requests before having been probed, indirect 48 | # violation of the CSI spec 49 | export X_CSI_POWERMAX_AUTOPROBE="true" 50 | 51 | # EnvPortGroups is the name of the environment variable that is used 52 | # to specify a list of Port Groups that the driver can choose from 53 | # These Port Groups must exist and be populated 54 | export X_CSI_POWERMAX_PORTGROUPS="iscsi_ports" 55 | 56 | # Enable/Disable CSI request and response logging 57 | # setting them to true sets X_CSI_REQ_ID_INJECTION to true 58 | export X_CSI_REQ_LOGGING="true" 59 | export X_CSI_REP_LOGGING="true" 60 | 61 | # Variables for the integration test code. 62 | export CSI_ENDPOINT=`pwd`/unix_sock 63 | export SYMID="000000000001" 64 | export SRPID="SRP_1" 65 | export SERVICELEVEL="Bronze" 66 | export LOCALRDFGROUP="000" 67 | export REMOTESYMID="000000000001" 68 | export REMOTESRPID="SRP_1" 69 | export REMOTESERVICELEVEL="Gold" 70 | export X_CSI_ENABLE_BLOCK="true" 71 | export REMOTERDFGROUP="000" 72 | export X_CSI_REPLICATION_CONTEXT_PREFIX="powermax" 73 | export X_CSI_REPLICATION_PREFIX="replication.storage.dell.com" 74 | 75 | # EnvPreferredTransportProtocol enables you to be able to force the transport protocol. 76 | # Valid values are "FC" or "ISCSI" or "". Value "" will choose FC if both are available. 77 | # This is mainly for testing. 78 | export X_CSI_TRANSPORT_PROTOCOL="" 79 | # Set this value to a higher number is a proxy is being used 80 | export X_CSI_GRPC_MAX_THREADS="50" 81 | # Set this value to the timeout as: "300ms", "1.5h" or "2h45m". 82 | # Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h" 83 | export X_CSI_UNISPHERE_TIMEOUT="5m" 84 | 85 | # variables for reverseproxy 86 | # service name of the revproxy 87 | export X_CSI_POWERMAX_PROXY_SERVICE_NAME="powermax-revproxy" 88 | # Cluster IP of the revproxy service 89 | export POWERMAX_REVPROXY_SERVICE_HOST="0.0.0.0" 90 | # Port of the revproxy service 91 | export POWERMAX_REVPROXY_SERVICE_PORT="2222" 92 | -------------------------------------------------------------------------------- /k8smock/k8sutils_mock.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 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 k8smock 16 | 17 | import ( 18 | "reflect" 19 | "strings" 20 | 21 | "github.com/golang/mock/gomock" 22 | kubernetes "k8s.io/client-go/kubernetes/fake" 23 | ) 24 | 25 | var mockUtils *MockUtils 26 | 27 | // MockUtils - mock kubernetes utils 28 | type MockUtils struct { 29 | KubernetesClient *kubernetes.Clientset 30 | } 31 | 32 | // Init - initializes the mock k8s utils 33 | func Init() *MockUtils { 34 | if mockUtils != nil { 35 | return mockUtils 36 | } 37 | kubernetesClient := kubernetes.NewSimpleClientset() 38 | mockUtils = &MockUtils{ 39 | KubernetesClient: kubernetesClient, 40 | } 41 | return mockUtils 42 | } 43 | 44 | // GetNodeLabels is mock implementation for GetNodeLabels 45 | func (m *MockUtils) GetNodeLabels(_ string) (map[string]string, error) { 46 | // access the API to fetch node object 47 | return nil, nil 48 | } 49 | 50 | // GetNodeIPs is mock implementation for GetNodeIPs 51 | func (m *MockUtils) GetNodeIPs(nodeID string) string { 52 | nodeElem := strings.Split(nodeID, "-") 53 | if len(nodeElem) < 2 { 54 | return "" 55 | } 56 | return nodeElem[1] 57 | } 58 | 59 | // Added a new mocking capability to help enable mocking this dynamically 60 | 61 | // MockUtilsInterface is a mock of UtilsInterface interface 62 | type MockUtilsInterface struct { 63 | ctrl *gomock.Controller 64 | recorder *MockUtilsInterfaceMockRecorder 65 | } 66 | 67 | // MockUtilsInterfaceMockRecorder is the mock recorder for MockUtilsInterface 68 | type MockUtilsInterfaceMockRecorder struct { 69 | mock *MockUtilsInterface 70 | } 71 | 72 | // NewMockUtilsInterface creates a new mock instance 73 | func NewMockUtilsInterface(ctrl *gomock.Controller) *MockUtilsInterface { 74 | mock := &MockUtilsInterface{ctrl: ctrl} 75 | mock.recorder = &MockUtilsInterfaceMockRecorder{mock} 76 | return mock 77 | } 78 | 79 | // EXPECT returns an object that allows the caller to indicate expected use 80 | func (m *MockUtilsInterface) EXPECT() *MockUtilsInterfaceMockRecorder { 81 | return m.recorder 82 | } 83 | 84 | // GetNodeLabels mocks base method 85 | func (m *MockUtilsInterface) GetNodeLabels(arg0 string) (map[string]string, error) { 86 | m.ctrl.T.Helper() 87 | ret := m.ctrl.Call(m, "GetNodeLabels", arg0) 88 | ret0, _ := ret[0].(map[string]string) 89 | ret1, _ := ret[1].(error) 90 | return ret0, ret1 91 | } 92 | 93 | // GetNodeLabels indicates an expected call of GetNodeLabels 94 | func (mr *MockUtilsInterfaceMockRecorder) GetNodeLabels(arg0 interface{}) *gomock.Call { 95 | mr.mock.ctrl.T.Helper() 96 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodeLabels", reflect.TypeOf((*MockUtilsInterface)(nil).GetNodeLabels), arg0) 97 | } 98 | 99 | // GetNodeIPs mocks base method 100 | func (m *MockUtilsInterface) GetNodeIPs(arg0 string) string { 101 | m.ctrl.T.Helper() 102 | ret := m.ctrl.Call(m, "GetNodeIPs", arg0) 103 | ret0, _ := ret[0].(string) 104 | return ret0 105 | } 106 | 107 | // GetNodeIPs indicates an expected call of GetNodeIPs 108 | func (mr *MockUtilsInterfaceMockRecorder) GetNodeIPs(arg0 interface{}) *gomock.Call { 109 | mr.mock.ctrl.T.Helper() 110 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodeIPs", reflect.TypeOf((*MockUtilsInterface)(nil).GetNodeIPs), arg0) 111 | } 112 | -------------------------------------------------------------------------------- /overrides.mk: -------------------------------------------------------------------------------- 1 | # Copyright © 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 | # 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 | # overrides file 14 | # this file, included from the Makefile, will overlay default values with environment variables 15 | # 16 | 17 | # DEFAULT values 18 | # ubi9/ubi-micro:9.2-13 19 | DEFAULT_GOIMAGE=$(shell sed -En 's/^go (.*)$$/\1/p' go.mod) 20 | DEFAULT_REGISTRY="sample_registry" 21 | DEFAULT_IMAGENAME="csi-powermax" 22 | DEFAULT_BUILDSTAGE="final" 23 | DEFAULT_IMAGETAG="" 24 | 25 | # set the GOIMAGE if needed 26 | ifeq ($(GOIMAGE),) 27 | export GOIMAGE="$(DEFAULT_GOIMAGE)" 28 | endif 29 | 30 | # set the REGISTRY if needed 31 | ifeq ($(REGISTRY),) 32 | export REGISTRY="$(DEFAULT_REGISTRY)" 33 | endif 34 | 35 | # set the IMAGENAME if needed 36 | ifeq ($(IMAGENAME),) 37 | export IMAGENAME="$(DEFAULT_IMAGENAME)" 38 | endif 39 | 40 | #set the IMAGETAG if needed 41 | ifneq ($(DEFAULT_IMAGETAG), "") 42 | export IMAGETAG="$(DEFAULT_IMAGETAG)" 43 | endif 44 | 45 | # set the BUILDSTAGE if needed 46 | ifeq ($(BUILDSTAGE),) 47 | export BUILDSTAGE="$(DEFAULT_BUILDSTAGE)" 48 | endif 49 | 50 | # figure out if podman or docker should be used (use podman if found) 51 | ifneq (, $(shell which podman 2>/dev/null)) 52 | export BUILDER=podman 53 | else 54 | export BUILDER=docker 55 | endif 56 | 57 | # target to print some help regarding these overrides and how to use them 58 | overrides-help: 59 | @echo 60 | @echo "The following environment variables can be set to control the build" 61 | @echo 62 | @echo "GOIMAGE - The version of Go to build with, default is: $(DEFAULT_GOIMAGE)" 63 | @echo " Current setting is: $(GOIMAGE)" 64 | @echo "REGISTRY - The registry to push images to, default is: $(DEFAULT_REGISTRY)" 65 | @echo " Current setting is: $(REGISTRY)" 66 | @echo "IMAGENAME - The image name to be built, defaut is: $(DEFAULT_IMAGENAME)" 67 | @echo " Current setting is: $(IMAGENAME)" 68 | @echo "IMAGETAG - The image tag to be built, default is an empty string which will determine the tag by examining annotated tags in the repo." 69 | @echo " Current setting is: $(IMAGETAG)" 70 | @echo "BUILDSTAGE - The Dockerfile build stage to execute, default is: $(DEFAULT_BUILDSTAGE)" 71 | @echo " Stages can be found by looking at the Dockerfile" 72 | @echo " Current setting is: $(BUILDSTAGE)" 73 | @echo 74 | 75 | 76 | -------------------------------------------------------------------------------- /pkg/symmetrix/metro.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 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 symmetrix 16 | 17 | import ( 18 | "fmt" 19 | "net/http" 20 | "sync" 21 | "sync/atomic" 22 | "time" 23 | 24 | pmax "github.com/dell/gopowermax/v2" 25 | log "github.com/sirupsen/logrus" 26 | ) 27 | 28 | var metroClients sync.Map 29 | 30 | const ( 31 | failoverThereshold int32 = 5 32 | failureTimeThreshold time.Duration = 2 * time.Minute 33 | ) 34 | 35 | // RoundTripperInterface is an interface for http.RoundTripper 36 | // 37 | //go:generate mockgen -destination=mocks/roundtripper.go -package=mocks github.com/dell/csi-powermax/v2/pkg/symmetrix RoundTripperInterface 38 | type RoundTripperInterface interface { 39 | http.RoundTripper 40 | } 41 | 42 | func init() { 43 | metroClients = sync.Map{} 44 | } 45 | 46 | type metroClient struct { 47 | primaryArray string 48 | secondaryArray string 49 | activeArray string 50 | failureCount int32 51 | lastFailure time.Time 52 | mx sync.Mutex 53 | } 54 | 55 | func (m *metroClient) getActiveArray() string { 56 | m.mx.Lock() 57 | defer m.mx.Unlock() 58 | if m.failureCount >= failoverThereshold { 59 | if m.activeArray == m.primaryArray { 60 | m.activeArray = m.secondaryArray 61 | } else { 62 | m.activeArray = m.primaryArray 63 | } 64 | m.failureCount = 0 65 | log.Infof("Failing over to the array: %s", m.activeArray) 66 | } 67 | return m.activeArray 68 | } 69 | 70 | func (m *metroClient) setErrorWatcher(powermaxClient pmax.Pmax) { 71 | client := powermaxClient.GetHTTPClient() 72 | oldTransport := client.Transport 73 | client.Transport = &transport{ 74 | RoundTripper: oldTransport, 75 | healthHandler: m.healthHandler, 76 | } 77 | } 78 | 79 | func (m *metroClient) healthHandler(failureWeight int32) { 80 | m.mx.Lock() 81 | defer m.mx.Unlock() 82 | timeSinceLastFailure := time.Since(m.lastFailure) 83 | m.lastFailure = time.Now() 84 | if timeSinceLastFailure > failureTimeThreshold && m.failureCount != 0 { 85 | log.Infof("Last failure was more than %f minutes ago; reseting the failure count", failureTimeThreshold.Minutes()) 86 | m.failureCount = 1 87 | } else { 88 | atomic.AddInt32(&(m.failureCount), failureWeight) 89 | } 90 | } 91 | 92 | func (m *metroClient) getPowerMaxClient() (pmax.Pmax, error) { 93 | powermax, err := getPowerMax(m.getActiveArray()) 94 | if err != nil { 95 | return nil, err 96 | } 97 | client := powermax.getClient() 98 | m.setErrorWatcher(client) 99 | return client, nil 100 | } 101 | 102 | func (m *metroClient) getIdentifier() string { 103 | return fmt.Sprintf("%s-%s", m.primaryArray, m.secondaryArray) 104 | } 105 | 106 | type transport struct { 107 | http.RoundTripper 108 | healthHandler func(int32) 109 | } 110 | 111 | func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) { 112 | resp, err := t.RoundTripper.RoundTrip(req) 113 | if err != nil { 114 | t.healthHandler(2) 115 | } else if resp.StatusCode == 401 || resp.StatusCode == 403 { 116 | t.healthHandler(99) 117 | } else if int(resp.StatusCode/100) == 5 { 118 | t.healthHandler(1) 119 | } 120 | return resp, err 121 | } 122 | -------------------------------------------------------------------------------- /pkg/symmetrix/mocks/roundtripper.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/dell/csi-powermax/v2/pkg/symmetrix (interfaces: RoundTripperInterface) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | http "net/http" 9 | reflect "reflect" 10 | 11 | gomock "github.com/golang/mock/gomock" 12 | ) 13 | 14 | // MockRoundTripperInterface is a mock of RoundTripperInterface interface. 15 | type MockRoundTripperInterface struct { 16 | ctrl *gomock.Controller 17 | recorder *MockRoundTripperInterfaceMockRecorder 18 | } 19 | 20 | // MockRoundTripperInterfaceMockRecorder is the mock recorder for MockRoundTripperInterface. 21 | type MockRoundTripperInterfaceMockRecorder struct { 22 | mock *MockRoundTripperInterface 23 | } 24 | 25 | // NewMockRoundTripperInterface creates a new mock instance. 26 | func NewMockRoundTripperInterface(ctrl *gomock.Controller) *MockRoundTripperInterface { 27 | mock := &MockRoundTripperInterface{ctrl: ctrl} 28 | mock.recorder = &MockRoundTripperInterfaceMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockRoundTripperInterface) EXPECT() *MockRoundTripperInterfaceMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // RoundTrip mocks base method. 38 | func (m *MockRoundTripperInterface) RoundTrip(arg0 *http.Request) (*http.Response, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "RoundTrip", arg0) 41 | ret0, _ := ret[0].(*http.Response) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // RoundTrip indicates an expected call of RoundTrip. 47 | func (mr *MockRoundTripperInterfaceMockRecorder) RoundTrip(arg0 interface{}) *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RoundTrip", reflect.TypeOf((*MockRoundTripperInterface)(nil).RoundTrip), arg0) 50 | } 51 | -------------------------------------------------------------------------------- /plugin.go: -------------------------------------------------------------------------------- 1 | //go:build linux && plugin 2 | // +build linux,plugin 3 | 4 | /* 5 | Copyright © 2021 Dell Inc. or its subsidiaries. All Rights Reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | //go:generate go generate ./core 18 | 19 | package main 20 | 21 | import "C" 22 | 23 | import ( 24 | "github.com/dell/csi-powermax/v2/provider" 25 | "github.com/dell/csi-powermax/v2/service" 26 | ) 27 | 28 | //////////////////////////////////////////////////////////////////////////////// 29 | // Go Plug-in // 30 | //////////////////////////////////////////////////////////////////////////////// 31 | 32 | // ServiceProviders is an exported symbol that provides a host program 33 | // with a map of the service provider names and constructors. 34 | var ServiceProviders = map[string]func() interface{}{ 35 | service.Name: func() interface{} { return provider.New() }, 36 | } 37 | -------------------------------------------------------------------------------- /provider/provider.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 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 | "os" 19 | "strconv" 20 | "time" 21 | 22 | "github.com/dell/csi-powermax/v2/service" 23 | "github.com/dell/gocsi" 24 | "google.golang.org/grpc" 25 | "google.golang.org/grpc/keepalive" 26 | ) 27 | 28 | // New returns a new Mock Storage Plug-in Provider. 29 | func New() gocsi.StoragePluginProvider { 30 | // Get the MaxConcurrentStreams server option and configure it. 31 | maxConcurrentStreams := uint32(4) 32 | if maxThreads, found := os.LookupEnv(service.EnvGrpcMaxThreads); found { 33 | tempConcurrentStreams, err := strconv.Atoi(maxThreads) 34 | if err == nil { 35 | maxConcurrentStreams = uint32(tempConcurrentStreams) // #nosec:G115 false positive 36 | } 37 | } 38 | maxStreams := grpc.MaxConcurrentStreams(maxConcurrentStreams) 39 | keepaliveEnforcementPolicy := keepalive.EnforcementPolicy{ 40 | MinTime: 10 * time.Second, 41 | } 42 | keepaliveOpt := grpc.KeepaliveEnforcementPolicy(keepaliveEnforcementPolicy) 43 | serverOptions := make([]grpc.ServerOption, 2) 44 | serverOptions[0] = maxStreams 45 | serverOptions[1] = keepaliveOpt 46 | 47 | svc := service.New() 48 | return &gocsi.StoragePlugin{ 49 | Controller: svc, 50 | Identity: svc, 51 | Node: svc, 52 | BeforeServe: svc.BeforeServe, 53 | ServerOpts: serverOptions, 54 | RegisterAdditionalServers: svc.RegisterAdditionalServers, 55 | 56 | EnvVars: []string{ 57 | // Enable request validation 58 | gocsi.EnvVarSpecReqValidation + "=true", 59 | 60 | // Enable serial volume access 61 | gocsi.EnvVarSerialVolAccess + "=true", 62 | }, 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /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 | "os" 19 | "testing" 20 | 21 | "github.com/dell/csi-powermax/v2/service" 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestNew(t *testing.T) { 26 | tests := []struct { 27 | name string 28 | envGrpcMaxThreads string 29 | }{ 30 | { 31 | name: "Default max concurrent streams", 32 | envGrpcMaxThreads: "", 33 | }, 34 | { 35 | name: "Custom max concurrent streams", 36 | envGrpcMaxThreads: "8", 37 | }, 38 | } 39 | 40 | for _, tt := range tests { 41 | t.Run(tt.name, func(t *testing.T) { 42 | // Set the environment variable for the test 43 | if tt.envGrpcMaxThreads != "" { 44 | os.Setenv(service.EnvGrpcMaxThreads, tt.envGrpcMaxThreads) 45 | defer os.Unsetenv(service.EnvGrpcMaxThreads) 46 | } 47 | 48 | // Call the New function and validate it is not nil 49 | p := New() 50 | assert.NotNil(t, p) 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /runcontroller.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 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 | # 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 | mkdir -p /var/run/csi 19 | rm -f /var/run/csi/csi.sock 20 | . env.sh 21 | echo ENDPOINT $X_CSI_POWERMAX_ENDPOINT 22 | export CSI_ENDPOINT=/var/run/csi/csi.sock 23 | export X_CSI_MODE="controller" 24 | go generate 25 | CGO_ENABLED=0 GOOS=linux GO111MODULE=on go build 26 | ./csi-powermax 27 | 28 | -------------------------------------------------------------------------------- /samples/configmap/powermax-array-config.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 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 | # ***DEPRECATION NOTICE*** The powermax-array-configmap won't be 13 | # supported after CSM 1.14 (May 2025) and will be deprecated by 14 | # CSM 1.16 (January 2026). It remains for backward compatibility only. 15 | # These values have been migrated to powermax sample yaml. 16 | # To create this configmap use: kubectl create -f powermax-array-config.yaml 17 | apiVersion: v1 18 | kind: ConfigMap 19 | metadata: 20 | name: powermax-array-config 21 | namespace: powermax 22 | data: 23 | powermax-array-config.yaml: | 24 | # List of comma-separated port groups (ISCSI and NVMe/TCP only). Example: PortGroup1, portGroup2 Required for iSCSI and NVMe/TCP only 25 | X_CSI_POWERMAX_PORTGROUPS: "" 26 | # Choose which transport protocol to use (ISCSI, FC, NVMETCP or auto) defaults to auto if nothing is specified 27 | X_CSI_TRANSPORT_PROTOCOL: "" 28 | # IP address of the Unisphere for PowerMax (Required), Defaults to https://0.0.0.0:8443 29 | X_CSI_POWERMAX_ENDPOINT: "https://10.0.0.0:8443" 30 | # List of comma-separated array ID(s) which will be managed by the driver (Required) 31 | X_CSI_MANAGED_ARRAYS: "000000000000,000000000000," 32 | -------------------------------------------------------------------------------- /samples/configmap/topologyConfig.yaml: -------------------------------------------------------------------------------- 1 | # allowedConnections contains a list of (node, array and protocol) info for user allowed configuration 2 | # For any given storage array ID and protocol on a Node, topology keys will be created for just those pair and 3 | # every other configuration is ignored 4 | # Please refer to the doc website about a detailed explanation of each configuration parameter 5 | # and the various possible inputs 6 | allowedConnections: 7 | # nodeName: Name of the node on which user wants to apply given rules 8 | # Allowed values: 9 | # nodeName - name of a specific node 10 | # * - all the nodes 11 | # Examples: "node1", "*" 12 | - nodeName: "node1" 13 | # rules is a list of 'StorageArrayID:TransportProtocol' pair. ':' is required between both value 14 | # Allowed values: 15 | # StorageArrayID: 16 | # - SymmetrixID : for specific storage array 17 | # - "*" :- for all the arrays connected to the node 18 | # TransportProtocol: 19 | # - FC : Fibre Channel protocol 20 | # - ISCSI : iSCSI protocol 21 | # - "*" - for all the possible Transport Protocol 22 | # Examples: "000000000001:FC", "000000000002:*", "*:FC", "*:*" 23 | rules: 24 | - "000000000001:FC" 25 | - "000000000002:FC" 26 | - nodeName: "*" 27 | rules: 28 | - "000000000002:FC" 29 | # deniedConnections contains a list of (node, array and protocol) info for denied configurations by user 30 | # For any given storage array ID and protocol on a Node, topology keys will be created for every other configuration but 31 | # not these input pairs 32 | deniedConnections: 33 | - nodeName: "node2" 34 | rules: 35 | - "000000000002:*" 36 | - nodeName: "node3" 37 | rules: 38 | - "*:*" 39 | -------------------------------------------------------------------------------- /samples/migrationgroup/migrationgroup.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "replication.storage.dell.com/v1" 2 | kind: DellCSIMigrationGroup 3 | metadata: 4 | # custom name of the migration group 5 | # Default value: pmax-migration 6 | name: pmax-migration 7 | spec: 8 | # driverName: exact name of CSI Powermax driver 9 | driverName: "csi-powermax.dellemc.com" 10 | # sourceID: source ArrayID 11 | sourceID: "000000001234" 12 | # targetID: target ArrayID 13 | targetID: "000000005678" 14 | migrationGroupAttributes: 15 | action: "migrate" 16 | -------------------------------------------------------------------------------- /samples/secret/emptysecret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: powermax-certs 5 | namespace: powermax 6 | type: Opaque 7 | data: 8 | -------------------------------------------------------------------------------- /samples/secret/karavi-authorization-config.json: -------------------------------------------------------------------------------- 1 | [{"username":"","password":"","intendedEndpoint":"https://unisphere-address:8443","endpoint":"https://localhost:9400","systemID":"000000000001","skipCertificateValidation":true,"isDefault":true}] 2 | -------------------------------------------------------------------------------- /samples/secret/reveal_secret.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | username=$(cat secret.yaml | awk '/username:/{ print $2;}' | base64 -d) 3 | echo username: $username 4 | password=$(cat secret.yaml | awk '/password:/{ print $2;}' | base64 -d) 5 | echo password: $password 6 | -------------------------------------------------------------------------------- /samples/secret/secret.yaml: -------------------------------------------------------------------------------- 1 | # Copyright © 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 | # To create this secret use: kubectl create secret generic powermax-creds -n powermax --from-file=config=./secret.yaml 13 | 14 | # List of storage arrays (Required) 15 | # Each storage array must have a primaryEndpoint and backupEndpoint 16 | storageArrays: 17 | - storageArrayId: "000000000001" 18 | primaryEndpoint: https://primary-1.unisphe.re:8443 19 | backupEndpoint: https://backup-1.unisphe.re:8443 20 | # Optional labels allow for restricting access to arrays based on 21 | # one or more user defined topology labels. The following example 22 | # restricts connections for array 000000000002 to only nodes which 23 | # have both labels. The labels are case sensitive. 24 | # labels: 25 | # topology.kubernetes.io/region: region1 26 | # topology.kubernetes.io/zone: zone1 27 | # 28 | # Optional parameters can be specified in the secret so that they can 29 | # be used as defaults when not specified in the storage class. 30 | # Details on the parameters can be found in the sample storage class files. 31 | # All values are treated as strings so numeric and boolean values must be quoted. 32 | # parameters: 33 | # SRP: SRP_1 34 | # ServiceLevel: 35 | # ApplicationPrefix: 36 | # HostLimitName: 37 | # HostIOLimitMBSec: 38 | # HostIOLimitIOSec: 39 | # DynamicDistribution: 40 | - storageArrayId: "000000000002" 41 | primaryEndpoint: https://primary-2.unisphe.re:8443 42 | backupEndpoint: https://backup-2.unisphe.re:8443 43 | 44 | # List of management servers (Required) 45 | # Each management server must have an endpoint, username and password 46 | # SkipCertificateValidation is true by default. Set it to false if you have a self-signed certificate 47 | # certSecret is required if skipCertificateValidation is false 48 | managementServers: 49 | - endpoint: https://primary-1.unisphe.re:8443 50 | username: admin 51 | password: password 52 | skipCertificateValidation: true 53 | - endpoint: https://backup-1.unisphe.re:8443 54 | username: admin 55 | password: password 56 | skipCertificateValidation: true 57 | - endpoint: https://primary-2.unisphe.re:8443 58 | username: admin 59 | password: password 60 | skipCertificateValidation: false 61 | certSecret: "cert-secret" 62 | - endpoint: https://backup-2.unisphe.re:8443 63 | username: admin 64 | password: password 65 | skipCertificateValidation: false 66 | certSecret: "cert-secret" 67 | -------------------------------------------------------------------------------- /samples/secret/vcenter-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: vcenter-creds 5 | namespace: powermax 6 | type: Opaque 7 | data: 8 | # set username to the base64 encoded username of vCenter 9 | username: bm90X3RoZV91c2VybmFtZQ== 10 | # set password to the base64 encoded password of vCenter 11 | password: bm90X3RoZV9wYXNzd29yZA== 12 | -------------------------------------------------------------------------------- /samples/storageclass/powermax.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # This is sample manifest for a Storage Class to use while storage provisioning 3 | # Change all instances of to the Local Symmetrix ID of the PowerMax array 4 | # In case you installed the driver with a custom name, then replace 5 | # all instances of "csi-powermax.dellemc.com" with the custom provisioner name 6 | apiVersion: storage.k8s.io/v1 7 | kind: StorageClass 8 | metadata: 9 | name: powermax 10 | annotations: 11 | # Insert true to make this storage class DEFAULT 12 | # Allowed values: 13 | # "true" - default storage class 14 | # "false" - non-default storage class 15 | # Default value: "false" 16 | storageclass.kubernetes.io/is-default-class: 17 | parameters: 18 | # "csi.storage.k8s.io/fstype" is used to set the filesystem type which will be used to format the new volume 19 | # Allowed values: 20 | # "ext4" - EXT4 File system 21 | # "xfs" - XFS File system 22 | # "nfs" - Network File system 23 | # Optional: true 24 | # Default value: None if defaultFsType is not mentioned in values.yaml 25 | # Else defaultFsType value mentioned in values.yaml 26 | # will be used as default value 27 | csi.storage.k8s.io/fstype: xfs 28 | # Name of SRP on the PowerMax array that should be used for provisioning 29 | # If not provided, the values specified in the secret will be used 30 | # Optional: false 31 | # Examples: "DEFAULT_SRP" , "SRP_1" 32 | SRP: 33 | # ID of the array that is used for provisioning. Must quote SYMID. 34 | # If not provided and availability zones are used then the value specified in the 35 | # secret will be used 36 | # Optional: false 37 | # Example: "000000000001" 38 | SYMID: 39 | # Name of Service Level on PowerMax array that should be used for provisioning 40 | # If not provided, the values specified in the secret will be used 41 | # Optional: true, Default value: Optimized 42 | # Examples: "Diamond" , "Bronze" 43 | ServiceLevel: 44 | # Name of application to be used to group volumes 45 | # If not provided, the values specified in the secret will be used 46 | # This is used in naming storage group 47 | # Optional: true, Default value: None 48 | # Examples: APP, app, sanity, tests 49 | ApplicationPrefix: 50 | # Following params are for HostLimits, set them only if you want to set IOLimits 51 | # If not provided, the values specified in the secret will be used 52 | # HostLimitName uniquely identifies given set of limits on a storage class 53 | # This is used in naming storage group, max of 3 letters 54 | # Optional: true 55 | # Example: "HL1", "HL2" 56 | # HostLimitName: "HL1" 57 | # The MBs per Second Host IO limit for the storage class 58 | # Optional: true, Default: "" 59 | # Examples: "100", "200", NOLIMIT 60 | # HostIOLimitMBSec: "" 61 | # The IOs per Second Host IO limit for the storage class 62 | # Optional: true, Default: "" 63 | # Examples: "100", "200", NOLIMIT 64 | # HostIOLimitIOSec: "" 65 | # distribution of the Host IO limits for the storage class 66 | # Optional: true, Default: "" 67 | # Allowed values: Never","Always" or "OnFailure" only 68 | # DynamicDistribution: "" 69 | # If using custom driver name, change the following to point to the custom name 70 | # Optional: true, Default value: csi-powermax.dellemc.com 71 | # Examples: "csi-driver-powermax", "csi-powermax.dellemc.com" 72 | provisioner: csi-powermax.dellemc.com 73 | # Configure what happens to a Persistent Volume when the PVC 74 | # it is bound to is to be deleted 75 | # Allowed values: 76 | # Delete: the underlying persistent volume will be deleted along with the PVC. 77 | # Retain: the underlying persistent volume remain. 78 | # Optional: true, Default value: None 79 | reclaimPolicy: Delete 80 | -------------------------------------------------------------------------------- /samples/storageclass/powermax_xfs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # This is a sample manifest for a Storage Class for filesystem type xfs 3 | # Change all instances of to the Local Symmetrix ID of the PowerMax array 4 | # In case you installed the driver with a custom name, then replace 5 | # all instances of "csi-powermax.dellemc.com" with the custom provisioner name 6 | apiVersion: storage.k8s.io/v1 7 | kind: StorageClass 8 | metadata: 9 | name: powermax-xfs 10 | parameters: 11 | # "csi.storage.k8s.io/fstype" is used to set the FS type which will be used 12 | # Allowed values: 13 | # "ext4" - EXT4 File system 14 | # "xfs" - XFS File system 15 | # "nfs" - Network File system 16 | # Optional: true 17 | # Default value: None if defaultFsType is not mentioned in values.yaml 18 | # Else defaultFsType value mentioned in values.yaml 19 | # will be used as default value 20 | csi.storage.k8s.io/fstype: xfs 21 | # Name of SRP on the PowerMax array that should be used for provisioning 22 | # If not provided, the value specified in the secret will be used 23 | # Optional: false 24 | # Examples: "DEFAULT_SRP" , "SRP_1" 25 | SRP: 26 | # ID of the array that is used for provisioning. Must quote SYMID. 27 | # If not provided and availability zones are used then the value specified in the 28 | # secret will be used 29 | # Optional: false 30 | # Example: "000000000001" 31 | SYMID: 32 | # Name of Service Level on PowerMax array that should be used for provisioning 33 | # If not provided, the value specified in the secret will be used 34 | # Optional: true, Default value: Optimized 35 | # Examples: "Diamond" , "Bronze" 36 | ServiceLevel: 37 | # Following params are for HostLimits, set them only if you want to set IOLimits 38 | # If not provided, the values specified in the secret will be used 39 | # HostLimitName uniquely identifies given set of limits on a storage class 40 | # This is used in naming storage group, max of 3 letters 41 | # Optional: true 42 | # Example: "HL1", "HL2" 43 | # HostLimitName: "HL1" 44 | # The MBs per Second Host IO limit for the storage class 45 | # Optional: true, Default: "" 46 | # Examples: "100", "200", NOLIMIT 47 | # HostIOLimitMBSec: "" 48 | # The IOs per Second Host IO limit for the storage class 49 | # Optional: true, Default: "" 50 | # Examples: "100", "200", NOLIMIT 51 | # HostIOLimitIOSec: "" 52 | # distribution of the Host IO limits for the storage class 53 | # Optional: true, Default: "" 54 | # Allowed values: Never","Always" or "OnFailure" only 55 | # DynamicDistribution: "" 56 | # If using custom driver name, change the following to point to the custom name 57 | # Optional: true, Default value: csi-powermax.dellemc.com 58 | # Examples: "csi-driver-powermax", "csi-powermax.dellemc.com" 59 | provisioner: csi-powermax.dellemc.com 60 | # Configure what happens to a Persistent Volume when the PVC 61 | # it is bound to is to be deleted 62 | # Allowed values: 63 | # Delete: the underlying persistent volume will be deleted along with the PVC. 64 | # Retain: the underlying persistent volume remain. 65 | # Optional: true, Default value: None 66 | reclaimPolicy: Delete 67 | -------------------------------------------------------------------------------- /samples/storageclass/powermax_zone.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # This minimal YAML represents a StorageClass which can be used with 3 | # PowerMax zones. Any missing parameters such as SYMID can be provided 4 | # in the secret for the array. Consult the CSM Documentation for more 5 | # details. 6 | 7 | apiVersion: storage.k8s.io/v1 8 | kind: StorageClass 9 | metadata: 10 | name: powermax-zone 11 | parameters: 12 | csi.storage.k8s.io/fstype: xfs 13 | provisioner: csi-powermax.dellemc.com 14 | reclaimPolicy: Delete 15 | # volumeBindingMode must be set to WaitForFirstConsumer for 16 | # volumes to be provisioned in the target zone. 17 | volumeBindingMode: WaitForFirstConsumer 18 | allowVolumeExpansion: true 19 | -------------------------------------------------------------------------------- /samples/volumesnapshotclass/rep_powermax-snapclas.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # This sample manifest file should be used to create VolumeSnapshotClass 3 | # if you have v1 VolumeSnapshotClass CRD installed in your cluster 4 | apiVersion: snapshot.storage.k8s.io/v1 5 | kind: VolumeSnapshotClass 6 | metadata: 7 | name: powermax-snapclass-id 8 | parameters: 9 | SYMID: 10 | # If using custom driver name, change the following to point to the custom name 11 | # Optional: true, Default value: csi-powermax.dellemc.com 12 | # Examples: "csi-driver-powermax", "csi-powermax.dellemc.com" 13 | driver: csi-powermax.dellemc.com 14 | # Configure what happens to a VolumeSnapshotContent when VolumeSnapshot object 15 | # it is bound to is to be deleted 16 | # Allowed values: 17 | # Delete: the underlying storage snapshot will be deleted 18 | # along with the VolumeSnapshotContent object 19 | # Retain: both the underlying snapshot and VolumeSnapshotContent remain 20 | # Optional: true, Default value: None 21 | deletionPolicy: Delete 22 | -------------------------------------------------------------------------------- /samples/volumesnapshotclass/v1_powermax-snapclass.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # This sample manifest file should be used to create VolumeSnapshotClass 3 | # if you have v1 VolumeSnapshotClass CRD installed in your cluster 4 | apiVersion: snapshot.storage.k8s.io/v1 5 | kind: VolumeSnapshotClass 6 | metadata: 7 | name: powermax-snapclass 8 | # If using custom driver name, change the following to point to the custom name 9 | # Optional: true, Default value: csi-powermax.dellemc.com 10 | # Examples: "csi-driver-powermax", "csi-powermax.dellemc.com" 11 | driver: csi-powermax.dellemc.com 12 | # Configure what happens to a VolumeSnapshotContent when VolumeSnapshot object 13 | # it is bound to is to be deleted 14 | # Allowed values: 15 | # Delete: the underlying storage snapshot will be deleted 16 | # along with the VolumeSnapshotContent object 17 | # Retain: both the underlying snapshot and VolumeSnapshotContent remain 18 | # Optional: true, Default value: None 19 | deletionPolicy: Delete 20 | -------------------------------------------------------------------------------- /service/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright © 2020 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 | unit-test: 14 | go test -v -race -coverprofile=c.out ./... 15 | 16 | godog: 17 | go test -v -race -coverprofile=c.out -test.run TestGoDog ./... 18 | 19 | gocover: 20 | go tool cover -html=c.out 21 | 22 | clean: 23 | go clean -cache 24 | -------------------------------------------------------------------------------- /service/README.md: -------------------------------------------------------------------------------- 1 | # csi-powermax - service 2 | This directory contains the CSI Driver source code 3 | 4 | ## Unit Tests 5 | Unit Tests exist for the CSI Driver. These tests do not modify the array or 6 | require a Kubernetes instance. 7 | 8 | #### Running Unit Tests 9 | To run these tests, from the root directory of the repository, run: 10 | ``` 11 | make unit-test 12 | ``` 13 | 14 | ## Integration Tests 15 | Integration Tests exist for the CSI Driver. These tests do not require a 16 | Kubernetes instance but WILL MODIFY the array 17 | 18 | #### Pre-requisites 19 | Before running integration tests, examine the env.sh script in the root of 20 | the repository. Within that file, two variables are defined: 21 | * X_CSI_POWERMAX_USER 22 | * X_CSI_POWERMAX_PASSWORD 23 | 24 | Either change those variables to match an existing user in Unisphere, or create 25 | a new user in Unisphere matching those credentials. 26 | 27 | #### Running Integration Tests 28 | To run these tests, from the root directory of the repository, run: 29 | ``` 30 | make integration-test 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- /service/csi_ctrl_to_node_connectivity.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023-2024 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 | "context" 19 | "encoding/json" 20 | "fmt" 21 | "io" 22 | "net/http" 23 | "time" 24 | 25 | log "github.com/sirupsen/logrus" 26 | ) 27 | 28 | // ArrayConnectivityStatus Status of the array probe 29 | type ArrayConnectivityStatus struct { 30 | LastSuccess int64 `json:"lastSuccess"` // connectivity status 31 | LastAttempt int64 `json:"lastAttempt"` // last timestamp attempted to check connectivity 32 | } 33 | 34 | const ( 35 | // Timeout for making http requests 36 | Timeout = time.Second * 5 37 | ) 38 | 39 | // QueryArrayStatus make API call to the specified url to retrieve connection status 40 | func (s *service) QueryArrayStatus(ctx context.Context, url string) (bool, error) { 41 | defer func() { 42 | if err := recover(); err != nil { 43 | log.Println("panic occurred in queryStatus:", err) 44 | } 45 | }() 46 | client := http.Client{ 47 | Timeout: Timeout, 48 | } 49 | resp, err := client.Get(url) 50 | 51 | log.Debugf("Received response %+v for url %s", resp, url) 52 | if err != nil { 53 | log.Errorf("failed to call API %s due to %s ", url, err.Error()) 54 | return false, err 55 | } 56 | defer resp.Body.Close() // #nosec G307 57 | bodyBytes, err := io.ReadAll(resp.Body) 58 | if err != nil { 59 | log.Errorf("failed to read API response due to %s ", err.Error()) 60 | return false, err 61 | } 62 | if resp.StatusCode != 200 { 63 | log.Errorf("Found unexpected response from the server while fetching array status %d ", resp.StatusCode) 64 | return false, fmt.Errorf("unexpected response from the server") 65 | } 66 | var statusResponse ArrayConnectivityStatus 67 | err = json.Unmarshal(bodyBytes, &statusResponse) 68 | if err != nil { 69 | log.Errorf("unable to unmarshal and determine connectivity due to %s ", err) 70 | return false, err 71 | } 72 | log.Infof("API Response received is %+v\n", statusResponse) 73 | // responseObject has last success and last attempt timestamp in Unix format 74 | timeDiff := statusResponse.LastAttempt - statusResponse.LastSuccess 75 | tolerance := s.SetPollingFrequency(ctx) 76 | currTime := time.Now().Unix() 77 | // checking if the status response is stale and connectivity test is still running 78 | // since nodeProbe is run at frequency tolerance/2, ideally below check should never be true 79 | if (currTime - statusResponse.LastAttempt) > tolerance*2 { 80 | log.Errorf("seems like connectivity test is not being run, current time is %d and last run was at %d", currTime, statusResponse.LastAttempt) 81 | // considering connectivity is broken 82 | return false, nil 83 | } 84 | log.Debugf("last connectivity was %d sec back, tolerance is %d sec", timeDiff, tolerance) 85 | // give 2s leeway for tolerance check 86 | if timeDiff <= tolerance+2 { 87 | return true, nil 88 | } 89 | return false, nil 90 | } 91 | -------------------------------------------------------------------------------- /service/csi_ctrl_to_node_connectivity_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 service 16 | 17 | import ( 18 | "net/http" 19 | "net/http/httptest" 20 | "testing" 21 | "time" 22 | 23 | "github.com/stretchr/testify/assert" 24 | "golang.org/x/net/context" 25 | ) 26 | 27 | // TestQueryArrayStatus tests the query array status 28 | func TestQueryArrayStatus(t *testing.T) { 29 | testCases := []struct { 30 | name string 31 | server *httptest.Server 32 | }{ 33 | { 34 | name: "Failed: Context Timeout", 35 | server: fakeServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 36 | time.Sleep(10 * time.Second) 37 | w.WriteHeader(http.StatusBadRequest) 38 | })), 39 | }, 40 | } 41 | 42 | for _, tc := range testCases { 43 | t.Run(tc.name, func(t *testing.T) { 44 | s := &service{ 45 | mode: "node", 46 | } 47 | 48 | ctx, cancelCtx := context.WithDeadline(context.Background(), time.Now().Add(10*time.Millisecond)) 49 | defer cancelCtx() 50 | 51 | _, err := s.QueryArrayStatus(ctx, tc.server.URL) 52 | assert.Error(t, err) 53 | }) 54 | } 55 | } 56 | 57 | func fakeServer(t *testing.T, h http.Handler) *httptest.Server { 58 | s := httptest.NewServer(h) 59 | t.Cleanup(func() { 60 | s.Close() 61 | }) 62 | return s 63 | } 64 | -------------------------------------------------------------------------------- /service/features/initiatorname.iscsi: -------------------------------------------------------------------------------- 1 | InitiatorName=iqn.1993-08.org.debian:01:a86eeef2c837 -------------------------------------------------------------------------------- /service/features/multiple_iqn.iscsi: -------------------------------------------------------------------------------- 1 | InitiatorName=iqn.1993-08.org.debian:01:a86ee7364819 2 | 3 | # This next initiator has some extra spaces for readability 4 | InitiatorName = iqn.1993-08.org.debian:01:a86eeef2c837 5 | -------------------------------------------------------------------------------- /service/features/no_iqn.iscsi: -------------------------------------------------------------------------------- 1 | # InitiatorName=iqn.1993-08.org.debian:01:a86eeef2c837 -------------------------------------------------------------------------------- /service/features/valid.iscsi: -------------------------------------------------------------------------------- 1 | InitiatorName=iqn.1993-08.org.debian:01:a86eeef2c837 -------------------------------------------------------------------------------- /service/features/with_comments.iscsi: -------------------------------------------------------------------------------- 1 | ## DO NOT EDIT OR REMOVE THIS FILE! 2 | ## If you remove this file, the iSCSI daemon will not start. 3 | ## If you change the InitiatorName, existing access control lists 4 | ## may reject this initiator. The InitiatorName must be unique 5 | ## for each iSCSI initiator. Do NOT duplicate iSCSI InitiatorNames. 6 | InitiatorName=iqn.1993-08.org.debian:01:a86eeef2c837 -------------------------------------------------------------------------------- /service/gosystemd_mock.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 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 | "fmt" 19 | "time" 20 | 21 | "github.com/coreos/go-systemd/v22/dbus" 22 | ) 23 | 24 | var mockgosystemdInducedErrors struct { 25 | ListUnitsError bool 26 | ListUnitISCSIDNotPresentError bool 27 | ISCSIDInactiveError bool 28 | StartUnitError bool 29 | StartUnitMaskedError bool 30 | JobFailure bool 31 | } 32 | 33 | func mockgosystemdReset() { 34 | mockgosystemdInducedErrors.ListUnitsError = false 35 | mockgosystemdInducedErrors.ListUnitISCSIDNotPresentError = false 36 | mockgosystemdInducedErrors.ISCSIDInactiveError = false 37 | mockgosystemdInducedErrors.StartUnitError = false 38 | mockgosystemdInducedErrors.StartUnitMaskedError = false 39 | mockgosystemdInducedErrors.JobFailure = false 40 | } 41 | 42 | type mockDbusConnection struct { 43 | err error 44 | } 45 | 46 | func (c *mockDbusConnection) Close() { 47 | // Do nothing 48 | } 49 | 50 | func (c *mockDbusConnection) ListUnits() ([]dbus.UnitStatus, error) { 51 | units := make([]dbus.UnitStatus, 0) 52 | if mockgosystemdInducedErrors.ListUnitsError { 53 | return units, fmt.Errorf("mock - failed to list the units") 54 | } 55 | if mockgosystemdInducedErrors.ListUnitISCSIDNotPresentError { 56 | return units, nil 57 | } 58 | iscsidStatus := dbus.UnitStatus{Name: "iscsid.service", ActiveState: "active"} 59 | if mockgosystemdInducedErrors.ISCSIDInactiveError { 60 | iscsidStatus.ActiveState = "inactive" 61 | units = append(units, iscsidStatus) 62 | return units, nil 63 | } 64 | units = append(units, iscsidStatus) 65 | return units, nil 66 | } 67 | 68 | func (c *mockDbusConnection) StartUnit(_ string, _ string, ch chan<- string) (int, error) { 69 | if mockgosystemdInducedErrors.StartUnitError { 70 | fmt.Println("Induced start unit error") 71 | return 0, fmt.Errorf("mock - failed to start the unit") 72 | } 73 | if mockgosystemdInducedErrors.StartUnitMaskedError { 74 | return 0, fmt.Errorf("mock - unit is masked - failed to start the unit") 75 | } 76 | go responseChannel(ch) 77 | return 0, nil 78 | } 79 | 80 | func responseChannel(ch chan<- string) { 81 | time.Sleep(100 * time.Millisecond) 82 | if mockgosystemdInducedErrors.JobFailure { 83 | ch <- "mock - job to start the unit failed" 84 | return 85 | } 86 | ch <- "done" 87 | } 88 | -------------------------------------------------------------------------------- /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 | 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 | "context" 19 | "testing" 20 | 21 | "github.com/container-storage-interface/spec/lib/go/csi" 22 | commonext "github.com/dell/dell-csi-extensions/common" 23 | "github.com/stretchr/testify/assert" 24 | "google.golang.org/grpc/codes" 25 | "google.golang.org/grpc/status" 26 | ) 27 | 28 | func TestProbe(t *testing.T) { 29 | testCases := []struct { 30 | name string 31 | myService *service 32 | err error 33 | }{ 34 | { 35 | name: "Failed: Node Probe", 36 | myService: &service{ 37 | mode: "node", 38 | opts: Opts{ 39 | NodeName: "", 40 | }, 41 | }, 42 | err: status.Errorf(codes.FailedPrecondition, "Error getting NodeName from the environment"), 43 | }, 44 | } 45 | 46 | for _, tc := range testCases { 47 | t.Run(tc.name, func(t *testing.T) { 48 | _, err := tc.myService.Probe(context.Background(), &csi.ProbeRequest{}) 49 | assert.Equal(t, tc.err, err) 50 | }) 51 | } 52 | } 53 | 54 | func TestProbeController(t *testing.T) { 55 | testCases := []struct { 56 | name string 57 | myService *service 58 | err error 59 | }{ 60 | { 61 | name: "Failed: Controller Probe - missing endpoint", 62 | myService: &service{ 63 | mode: "controller", 64 | opts: Opts{}, 65 | }, 66 | err: status.Error(codes.FailedPrecondition, "missing Unisphere endpoint"), 67 | }, 68 | } 69 | 70 | for _, tc := range testCases { 71 | t.Run(tc.name, func(t *testing.T) { 72 | _, err := tc.myService.ProbeController(context.Background(), &commonext.ProbeControllerRequest{}) 73 | assert.Equal(t, tc.err, err) 74 | }) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /service/mock-data/directorIDList.json: -------------------------------------------------------------------------------- 1 | { 2 | "directorId": [ 3 | "RF-1F", 4 | "RF-2F", 5 | "SE-1E", 6 | "SE-2E" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /service/mock-data/director_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "availability": "Online", 3 | "director_number": 66, 4 | "director_slot_number": 2, 5 | "directorId": "__DIRECTOR_ID__", 6 | "num_of_ports": 4, 7 | "num_of_cores": 4 8 | } 9 | -------------------------------------------------------------------------------- /service/mock-data/hostIDList.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostId": [ 3 | "IS_lqam9024_IG", 4 | "IS_LQAM9025_IG", 5 | "l2se0042_ig", 6 | "l2se0042_iscsi_ig", 7 | "l2se0046_ig", 8 | "l2se0046_iscsi_ig", 9 | "l2se0049_ig", 10 | "l2se0132_IG", 11 | "l2se0221_IG", 12 | "l2se0226_Initiator", 13 | "lqam9025_FC", 14 | "LQTF0116_IG", 15 | "lqtf0118_ig", 16 | "Test", 17 | "csi-node-node1" 18 | ] 19 | } -------------------------------------------------------------------------------- /service/mock-data/host_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostId": "__HOST_ID__", 3 | "num_of_masking_views": 1, 4 | "num_of_initiators": 1, 5 | "num_of_host_groups": 0, 6 | "port_flags_override": false, 7 | "consistent_lun": false, 8 | "enabled_flags": "", 9 | "disabled_flags": "", 10 | "type": "iSCSI", 11 | "initiator": [ 12 | "10000000c9f3b094" 13 | ], 14 | "maskingview": [ 15 | "__MV_ID__" 16 | ], 17 | "num_of_powerpath_hosts": 0 18 | } -------------------------------------------------------------------------------- /service/mock-data/initiatorIDList.json: -------------------------------------------------------------------------------- 1 | { 2 | "initiatorId": [ 3 | "FA-1D:6:10000000c98096f0", 4 | "FA-1D:5:10000000c9ae2ada", 5 | "FA-2D:5:10000000c9ae2ada", 6 | "FA-1D:4:10000000c9f3adff", 7 | "FA-2D:4:10000000c9f3adff", 8 | "FA-2D:5:10000000c9f3b07c", 9 | "FA-1D:5:10000000c9f3b07c", 10 | "FA-2D:4:10000000c9f3b07d", 11 | "FA-2D:5:10000000c9f3b07d", 12 | "FA-1D:5:10000000c9f3b07d", 13 | "FA-2D:4:10000000c9f3b07f", 14 | "FA-1D:4:10000000c9f3b07f", 15 | "FA-2D:5:10000000c9f3b08f", 16 | "FA-1D:5:10000000c9f3b094", 17 | "FA-2D:5:10000000c9f3b0a1", 18 | "FA-1D:5:10000000c9f3b0a1", 19 | "FA-1D:4:10000000c9fbfd36", 20 | "FA-1D:4:10000000c9fc3f82", 21 | "FA-1D:9:10000090fa19149b", 22 | "FA-1D:4:10000090fa1cdf76", 23 | "FA-1D:4:10000090fa1cecea", 24 | "FA-2D:4:10000090fa1cecea", 25 | "FA-2D:4:10000090fa1cef5a", 26 | "FA-1D:4:10000090fa1cef5a", 27 | "FA-2D:4:10000090fa1cf2b2", 28 | "FA-2D:5:10000090fa1cf2b2", 29 | "FA-1D:4:10000090fa1cf2b2", 30 | "FA-1D:5:10000090fa1cf2b2", 31 | "FA-2D:4:10000090fa1cf2b3", 32 | "FA-2D:5:10000090fa1cf2b3", 33 | "FA-1D:4:10000090fa1cf2b3", 34 | "FA-1D:5:10000090fa1cf2b3", 35 | "FA-1D:4:10000090fa1cf36a", 36 | "FA-2D:4:10000090fa1cf36a", 37 | "FA-1D:4:10000090fa1cf36b", 38 | "FA-2D:4:10000090fa1cf36b", 39 | "FA-1D:4:10000090fa6603b6", 40 | "FA-1D:5:10000090fa6603b6", 41 | "FA-1D:4:10000090fa6603b7", 42 | "FA-1D:5:10000090fa6603b7", 43 | "FA-1D:4:10000090fa66060a", 44 | "FA-1D:5:10000090fa66060b", 45 | "FA-1D:4:10000090fa662bc4", 46 | "FA-1D:5:10000090fa662bc5", 47 | "FA-1D:4:10000090fa662dc4", 48 | "FA-1D:5:10000090fa662dc5", 49 | "FA-1D:4:10000090fa9278dc", 50 | "FA-1D:5:10000090fa9278dd", 51 | "FA-1D:4:10000090fa9278f8", 52 | "FA-1D:4:10000090fa9278f9", 53 | "FA-2D:4:10000090fa927908", 54 | "FA-1D:4:10000090fa927908", 55 | "FA-2D:4:10000090fa927909", 56 | "FA-1D:4:10000090fa927909", 57 | "FA-2D:4:10000090fa927924", 58 | "FA-2D:5:10000090fa927925", 59 | "FA-2D:4:21fd000533c50c00", 60 | "FA-2D:5:21fd000533c50c00", 61 | "FA-1D:4:21fd000533c50c00", 62 | "FA-1D:5:21fd000533c50c00", 63 | "FA-1D:5:50000972f8023918", 64 | "FA-2D:5:50000972f8023918", 65 | "FA-1D:5:50000972f8023919", 66 | "FA-2D:5:50000972f8023919", 67 | "FA-2D:5:50000973b000b805", 68 | "FA-1D:5:50000973b000b845", 69 | "FA-2D:4:50000973b000b847", 70 | "FA-2D:5:50000973b000b84b", 71 | "SE-1E:000:iqn.1993-08.org.debian:01:5ae293b352a2", 72 | "SE-1E:000:iqn.1993-08.org.debian:01:8f21cc8ad2a7", 73 | "SE-2E:000:iqn.1993-08.org.debian:01:8f21cc8ad2a7", 74 | "SE-1E:000:iqn.1993-08.org.debian:01:a86eeef2c837", 75 | "SE-2E:001:iqn.1993-08.org.debian:01:a86eeef2c837", 76 | "SE-2E:000:iqn.1993-08.org.debian:01:a86eeef2c837" 77 | ] 78 | } -------------------------------------------------------------------------------- /service/mock-data/initiator_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "initiatorId": "__INITIATOR_ID__", 3 | "symmetrixPortKey": [ 4 | { 5 | "directorId": "FA-1D", 6 | "portId": "6" 7 | } 8 | ], 9 | "type": "GigE", 10 | "host": "__INITIATOR_HOST__", 11 | "ip_address": "192.168.1.175", 12 | "logged_in": true, 13 | "on_fabric": true, 14 | "flags_in_effect": "Common_Serial_Number(C), SCSI_3(SC3), SPC2_Protocol_Version(SPC2)", 15 | "num_of_vols": 0, 16 | "num_of_host_groups": 0, 17 | "num_of_masking_views": 0, 18 | "num_of_powerpath_hosts": 0 19 | } 20 | -------------------------------------------------------------------------------- /service/mock-data/masking_view_connections_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "maskingViewConnection": [ 3 | { 4 | "volumeId": "__VOLUME_ID__", 5 | "host_lun_address": "0001", 6 | "cap_gb": "0.1", 7 | "initiatorId": "iqn.1993-08.org.debian:01:8f21cc8ad2a7", 8 | "dir_port": "SE-1E:000", 9 | "logged_in": false, 10 | "on_fabric": true 11 | }, 12 | { 13 | "volumeId": "__VOLUME_ID__", 14 | "host_lun_address": "0001", 15 | "cap_gb": "0.1", 16 | "initiatorId": "iqn.1993-08.org.debian:01:8f21cc8ad2a7", 17 | "dir_port": "SE-2E:000", 18 | "logged_in": false, 19 | "on_fabric": true 20 | }, 21 | { 22 | "volumeId": "22222", 23 | "host_lun_address": "0002", 24 | "cap_gb": "0.1", 25 | "initiatorId": "iqn.1993-08.org.debian:01:8f21cc8ad2a7", 26 | "dir_port": "SE-2E:000", 27 | "logged_in": false, 28 | "on_fabric": true 29 | }, 30 | { 31 | "volumeId": "__VOLUME_ID__", 32 | "host_lun_address": "0001", 33 | "cap_gb": "0.1", 34 | "initiatorId": "iqn.1993-08.org.debian:01:8f21cc8ad2a7", 35 | "dir_port": "SE-1E:000", 36 | "logged_in": false, 37 | "on_fabric": true 38 | }, 39 | { 40 | "volumeId": "__VOLUME_ID__", 41 | "host_lun_address": "0001", 42 | "cap_gb": "0.1", 43 | "initiatorId": "iqn.1993-08.org.debian:01:8f21cc8ad2a7", 44 | "dir_port": "ED-1B:000", 45 | "logged_in": false, 46 | "on_fabric": true 47 | }, 48 | { 49 | "volumeId": "__VOLUME_ID__", 50 | "host_lun_address": "0001", 51 | "cap_gb": "0.1", 52 | "initiatorId": "iqn.1993-08.org.debian:01:8f21cc8ad2a7", 53 | "dir_port": "DF-1C:000", 54 | "logged_in": false, 55 | "on_fabric": true 56 | } 57 | ] 58 | } 59 | 60 | -------------------------------------------------------------------------------- /service/mock-data/masking_view_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "maskingViewId": "__MASKING_VIEW_ID__", 3 | "hostId": "__HOST_ID__", 4 | "hostGroupId": "__HOST_GROUP_ID__", 5 | "portGroupId": "__PORT_GROUP_ID__", 6 | "storageGroupId": "__STORAGE_GROUP_ID__" 7 | } 8 | -------------------------------------------------------------------------------- /service/mock-data/nvme_port_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "symmetrixPort": { 3 | "symmetrixPortKey": { 4 | "directorId": "__DIRECTOR_ID__", 5 | "portId": "__PORT_ID__" 6 | }, 7 | "port_status": "ON", 8 | "director_status": "Online", 9 | "type": "OSAndHostRDF", 10 | "num_of_cores": 4, 11 | "identifier": "nqn.1988-11.com.dell.mock:00:e6e2d5b871f1403E169D0", 12 | "negotiated_speed": "0", 13 | "num_of_port_groups": 3, 14 | "num_of_masking_views": 2, 15 | "num_of_mapped_vols": 10, 16 | "vcm_state": "Enabled", 17 | "aclx": true, 18 | "vnx_attached": false, 19 | "avoid_reset_broadcast": false, 20 | "environ_set": false, 21 | "disable_q_reset_on_ua": false, 22 | "soft_reset": false, 23 | "scsi_3": true, 24 | "scsi_support1": true, 25 | "spc2_protocol_version": true, 26 | "portgroup": [ 27 | "IS_lqam9024_PG", 28 | "IS_lqam9024_2E_PG", 29 | "IS_lqam9025_PG" 30 | ], 31 | "maskingview": [ 32 | "IS_lqam9024_view", 33 | "lqam9024_diamond_sg_mv_MV" 34 | ], 35 | "max_speed": "0", 36 | "iscsi_target": false, 37 | "ip_addresses": [ 38 | "1.1.1.1" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /service/mock-data/portGroupIDList.json: -------------------------------------------------------------------------------- 1 | { 2 | "portGroupId": [ 3 | "CSI-FC-PG", 4 | "IS_lqam9024_2E_PG", 5 | "IS_lqam9024_PG", 6 | "IS_lqam9025_PG", 7 | "IS_OWC2_PG", 8 | "l2se0042_iscsi_pg", 9 | "l2se0042_pg", 10 | "l2se0046_iscsi_pg", 11 | "l2se0046_pg", 12 | "l2se0049_pg", 13 | "l2se0132_PG", 14 | "l2se0133_PG", 15 | "l2se0221_PG", 16 | "l2se0226_Port", 17 | "LQTF0116_PG", 18 | "lqtf0118_pg" 19 | ] 20 | } -------------------------------------------------------------------------------- /service/mock-data/portIDList.json: -------------------------------------------------------------------------------- 1 | { 2 | "symmetrixPortKey": [ 3 | { 4 | "directorId": "__DIRECTOR_ID__", 5 | "portId": "0" 6 | }, 7 | { 8 | "directorId": "__DIRECTOR_ID__", 9 | "portId": "1" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /service/mock-data/port_group_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "portGroupId": "__PORT_GROUP_ID__", 3 | "symmetrixPortKey": [ 4 | { 5 | "directorId": "SE-2E", 6 | "portId": "SE-2E:000" 7 | }, 8 | { 9 | "directorId": "SE-1E", 10 | "portId": "SE-1E:000" 11 | } 12 | ], 13 | "num_of_ports": 2, 14 | "num_of_masking_views": 6, 15 | "type": "iSCSI", 16 | "maskingview": [ 17 | "csi-mv-lqam9031", 18 | "csi-mv-lqam9030", 19 | "csi-mv-KPC-lqam9025", 20 | "csi-mv-XYZ-Node1", 21 | "csi-mv-ERY-lqam9024-10-243-149-24", 22 | "csi-mv-TW-lqam9032" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /service/mock-data/port_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "symmetrixPort": { 3 | "symmetrixPortKey": { 4 | "directorId": "__DIRECTOR_ID__", 5 | "portId": "__PORT_ID__" 6 | }, 7 | "port_status": "ON", 8 | "director_status": "Online", 9 | "type": "GigE", 10 | "num_of_cores": 4, 11 | "identifier": "iqn.1992-04.com.emc:600009700bcbb70e3287017400000001", 12 | "negotiated_speed": "0", 13 | "num_of_port_groups": 3, 14 | "num_of_masking_views": 2, 15 | "num_of_mapped_vols": 10, 16 | "vcm_state": "Enabled", 17 | "aclx": true, 18 | "vnx_attached": false, 19 | "avoid_reset_broadcast": false, 20 | "environ_set": false, 21 | "disable_q_reset_on_ua": false, 22 | "soft_reset": false, 23 | "scsi_3": true, 24 | "scsi_support1": true, 25 | "spc2_protocol_version": true, 26 | "portgroup": [ 27 | "IS_lqam9024_PG", 28 | "IS_lqam9024_2E_PG", 29 | "IS_lqam9025_PG" 30 | ], 31 | "maskingview": [ 32 | "IS_lqam9024_view", 33 | "lqam9024_diamond_sg_mv_MV" 34 | ], 35 | "max_speed": "0", 36 | "iscsi_target": true, 37 | "tcp_port": 80, 38 | "ip_addresses": [ 39 | "127.0.0.1" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /service/mock-data/storageGroupIDList.json: -------------------------------------------------------------------------------- 1 | { 2 | "storageGroupId": [ 3 | "CSI-lqam9025-F-PG_1D4-SG", 4 | "CSI-lqam9025-F-PG_1D4-SG_1", 5 | "CSI-lqam9025-SRP_1-PG_1D4-Diamond-SG", 6 | "CSI-lqam9025-SRP_1-PG_1D4-Silver-SG", 7 | "CSI-None-no-slo-SG", 8 | "CSI-None-None-SG", 9 | "CSI-SRP_1-Diamond-SG", 10 | "CSI-SRP_1-None-SG", 11 | "CSI-SRP_1-Optimized-SG", 12 | "CSI-SRP_1-Silver-SG", 13 | "l2se0049_sg", 14 | "lqam9020_SG_MV", 15 | "lqam9024_sg", 16 | "lqam9024_sq2", 17 | "lqam9026_sg2", 18 | "csi-no-srp-sg-node1" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /service/mock-data/storageResourcePool.json: -------------------------------------------------------------------------------- 1 | { 2 | "srpId": [ 3 | "SRP_1" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /service/mock-data/storage_group_snapshot_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "symmetrixID": "000000000001", 3 | "snapshot_policy_id": "IntSPA", 4 | "storage_group_id": "IntSGA", 5 | "compliance": "NONE", 6 | "snapshots_in_time_window": 0, 7 | "total_snapshots": 0, 8 | "suspended": false 9 | } -------------------------------------------------------------------------------- /service/mock-data/storage_group_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "storageGroupId": "__STORAGE_GROUP_ID__", 3 | "slo": "Optimized", 4 | "base_slo_name": "Optimized", 5 | "service_level": "Optimized", 6 | "srp": "__SRP_ID__", 7 | "slo_compliance": "STABLE", 8 | "num_of_vols": __NUMBER_OF_VOLUMES__, 9 | "num_of_child_sgs": 0, 10 | "num_of_parent_sgs": 0, 11 | "num_of_masking_views": __NUM_OF_MASKING_VIEWS__, 12 | "num_of_snapshots": 0, 13 | "cap_gb": 534.04, 14 | "device_emulation": "FBA", 15 | "type": "Standalone", 16 | "unprotected": true, 17 | "compression": true, 18 | "VPSaved": "100.0%", 19 | "vp_saved_percent": 100, 20 | "maskingview": [ 21 | "csi-mv-node1" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /service/mock-data/storage_pool_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "effective_used_capacity_percent": 41, 3 | "rdfa_dse": true, 4 | "num_of_disk_groups": 1, 5 | "srpId": "__SRP_ID__", 6 | "emulation": "FBA", 7 | "total_srdf_dse_allocated_cap_gb": 0.0, 8 | "srp_capacity": { 9 | "usable_total_tb": 3.42, 10 | "subscribed_total_tb": 0.4, 11 | "subscribed_allocated_tb": 0.18, 12 | "snapshot_total_tb": 0.0, 13 | "usable_used_tb": 1.39, 14 | "snapshot_modified_tb": 0.0 15 | }, 16 | "reserved_cap_percent": 10, 17 | "diskGroupId": [ 18 | "1" 19 | ], 20 | "srp_efficiency": { 21 | "overall_efficiency_ratio_to_one": 2.2, 22 | "virtual_provisioning_savings_ratio_to_one": 2.2, 23 | "data_reduction_enabled_percent": 0.0 24 | }, 25 | "compression_state": "Disabled" 26 | } 27 | -------------------------------------------------------------------------------- /service/mock-data/symmetrix13.json: -------------------------------------------------------------------------------- 1 | { 2 | "symmetrixId": "000000000013", 3 | "local": true, 4 | "model": "PowerMax_2500", 5 | "microcode": "6079.175.0", 6 | "microcode_date": "10-12-2023", 7 | "microcode_registered_build": 268, 8 | "microcode_package_version": "10.0.1.3 (HotFix 10108, Build 6079_175/0268, 2023-10-12 19:17:49)", 9 | "cache_size_mb": 454656, 10 | "fe_dir_count": 4, 11 | "be_dir_count": 4, 12 | "rdf_dir_count": 4, 13 | "max_hyper_per_disk": 512, 14 | "dev_masking_aclx_config": "Enabled", 15 | "aclx_lun_addr": 0, 16 | "config_change_state": "Enabled", 17 | "disk_group_assignment": "In Use", 18 | "raid_config": "RAID-5 (4+1)", 19 | "pav_model": "DynamicStandardPAV", 20 | "pav_alias_limit": 255, 21 | "sddf_state": "Enabled", 22 | "srdfa_max_throttle": 0, 23 | "srdfa_cache_usage": 75, 24 | "max_sys_slot": 1656000, 25 | "max_dev_slot": 82800, 26 | "last_ipl_time": "2023-02-20 16:34:27", 27 | "last_fast_ipl_time": "2023-10-16 23:46:25", 28 | "symm_alert": "11/39", 29 | "service_level_rt_multiplier": "Default", 30 | "fba_geo_emulation": "Native", 31 | "cache_partition": "Disabled", 32 | "disk_service_state": "Deferred", 33 | "data_encryption": "Enabled", 34 | "rep_cache_usage": 0, 35 | "device_count": 15458, 36 | "disk_count": 33, 37 | "spare_disk_count": 1, 38 | "unconfig_disk_count": 0, 39 | "reliability_state": "Degraded-NoRebuild", 40 | "all_flash": true, 41 | "system_sized_property": [ 42 | { 43 | "srp_name": "SRP_1", 44 | "sized_fba_data_reduction_ratio": "4.0:1", 45 | "sized_fba_capacity_tb": 1196, 46 | "sized_fba_reducible_percent": 90 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /service/mock-data/symmetrix46.json: -------------------------------------------------------------------------------- 1 | { 2 | "symmetrixId": "000197900046", 3 | "device_count": 1045, 4 | "ucode": "5978.221.221", 5 | "model": "PowerMax_2000", 6 | "local": true, 7 | "all_flash": true, 8 | "disk_count": 8, 9 | "cache_size_mb": 203776, 10 | "data_encryption": "Disabled" 11 | } 12 | -------------------------------------------------------------------------------- /service/mock-data/symmetrix47.json: -------------------------------------------------------------------------------- 1 | { 2 | "symmetrixId": "000197900047", 3 | "device_count": 1045, 4 | "ucode": "5978.441.441", 5 | "model": "PowerMax_2000", 6 | "local": true, 7 | "all_flash": true, 8 | "disk_count": 8, 9 | "cache_size_mb": 203776, 10 | "data_encryption": "Disabled" 11 | } 12 | -------------------------------------------------------------------------------- /service/mock-data/symmetrixList.json: -------------------------------------------------------------------------------- 1 | { 2 | "symmetrixId": [ 3 | "000197802104", 4 | "000197900046", 5 | "000197900047" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /service/mock-data/volume_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "volumeId": "__VOLUME_ID__", 3 | "type": "TDEV", 4 | "emulation": "FBA", 5 | "ssid": "FFFFFFFF", 6 | "allocated_percent": 0, 7 | "cap_gb": 1, 8 | "cap_mb": 1026, 9 | "cap_cyl": __CAPACITY_CYL__, 10 | "status": "Ready", 11 | "reserved": false, 12 | "pinned": false, 13 | "volume_identifier": "__VOLUME_IDENTIFIER__", 14 | "wwn": "60000970000197900046533030324538", 15 | "encapsulated": false, 16 | "num_of_storage_groups": __NUMBER_OF_STORAGE_GROUPS__, 17 | "num_of_front_end_paths": __NUMBER_OF_FRONT_END_PATHS__, 18 | "storageGroupId": __STORAGE_GROUP_IDS__, 19 | "snapvx_source": false, 20 | "snapvx_target": false, 21 | "has_effective_wwn": false, 22 | "effective_wwn": "60000970000197900046533030324538" 23 | } 24 | -------------------------------------------------------------------------------- /service/pending.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 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 | "sync" 19 | "time" 20 | 21 | log "github.com/sirupsen/logrus" 22 | "google.golang.org/grpc/codes" 23 | "google.golang.org/grpc/status" 24 | ) 25 | 26 | type volumeIDType string 27 | 28 | var ( 29 | induceOverloadError bool // for testing only 30 | inducePendingError bool // for testing only 31 | overloadMutex sync.Mutex 32 | ) 33 | 34 | // SetInduceOverloadError sets the value of induceOverloadError. 35 | func SetInduceOverloadError(value bool) { 36 | overloadMutex.Lock() 37 | defer overloadMutex.Unlock() 38 | induceOverloadError = value 39 | } 40 | 41 | // GetInduceOverloadError gets the value of induceOverloadError. 42 | func GetInduceOverloadError() bool { 43 | overloadMutex.Lock() 44 | defer overloadMutex.Unlock() 45 | return induceOverloadError 46 | } 47 | 48 | // SetInducePendingError sets the value of inducePendingError. 49 | func SetInducePendingError(value bool) { 50 | overloadMutex.Lock() 51 | defer overloadMutex.Unlock() 52 | inducePendingError = value 53 | } 54 | 55 | // GetInducePendingError gets the value of inducePendingError. 56 | func GetInducePendingError() bool { 57 | overloadMutex.Lock() 58 | defer overloadMutex.Unlock() 59 | return inducePendingError 60 | } 61 | 62 | // pendingState type limits the number of pending requests by making sure there are no other requests for the same volumeID, 63 | // otherwise a "pending" error is returned. 64 | // Additionally, no more than maxPending requests are processed at a time without returning an "overload" error. 65 | type pendingState struct { 66 | maxPending int 67 | npending int 68 | pendingMutex *sync.Mutex 69 | pendingMap map[volumeIDType]time.Time 70 | } 71 | 72 | func (volID volumeIDType) checkAndUpdatePendingState(ps *pendingState) error { 73 | ps.pendingMutex.Lock() 74 | defer ps.pendingMutex.Unlock() 75 | if ps.pendingMap == nil { 76 | ps.pendingMap = make(map[volumeIDType]time.Time) 77 | } 78 | startTime := ps.pendingMap[volID] 79 | if startTime.IsZero() == false || GetInducePendingError() { 80 | log.Infof("volumeID %s pending %s", volID, time.Now().Sub(startTime)) 81 | return status.Errorf(codes.Unavailable, "pending") 82 | } 83 | if ps.maxPending > 0 && ps.npending >= ps.maxPending || GetInduceOverloadError() { 84 | return status.Errorf(codes.Unavailable, "overload") 85 | } 86 | ps.pendingMap[volID] = time.Now() 87 | ps.npending++ 88 | return nil 89 | } 90 | 91 | func (volID volumeIDType) clearPending(ps *pendingState) { 92 | ps.pendingMutex.Lock() 93 | defer ps.pendingMutex.Unlock() 94 | if ps.pendingMap == nil { 95 | ps.pendingMap = make(map[volumeIDType]time.Time) 96 | } 97 | delete(ps.pendingMap, volID) 98 | ps.npending-- 99 | } 100 | -------------------------------------------------------------------------------- /service/string_slice.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 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 | "reflect" 19 | "regexp" 20 | "sort" 21 | 22 | log "github.com/sirupsen/logrus" 23 | ) 24 | 25 | // stringSlicesEqual returns true IFF two slices contain same members 26 | // The input slices are sorted 27 | func stringSlicesEqual(a, b []string) bool { 28 | sort.Strings(a) 29 | sort.Strings(b) 30 | 31 | if len(a) != len(b) { 32 | return false 33 | } 34 | return reflect.DeepEqual(a, b) 35 | } 36 | 37 | // stringSliceRegexMatcher returns an updated slice with members from the original slice that matched the regexpPattern 38 | // The input slice is not changed 39 | func stringSliceRegexMatcher(slice []string, regexpPattern string) []string { 40 | // log.Debug("Regex pattern %s slice: %s", regexpPattern, slice) 41 | out := make([]string, 0) 42 | for _, str := range slice { 43 | matched, err := regexp.MatchString(regexpPattern, str) 44 | if err != nil { 45 | log.Error("Regex: " + regexpPattern + " error: " + err.Error()) 46 | break 47 | } 48 | if matched { 49 | out = append(out, str) 50 | } 51 | } 52 | // log.Debug("Regex out: %s", out) 53 | return out 54 | } 55 | 56 | // stringSliceReplaceAll returns an updated slice with the indicated regex replacement made. 57 | // The input slice is changed in place 58 | func stringSliceRegexReplace(slice []string, regexpPattern string, replacement string) { 59 | re := regexp.MustCompile(regexpPattern) 60 | for i := range slice { 61 | slice[i] = re.ReplaceAllString(slice[i], replacement) 62 | } 63 | // log.Debug("ReplaceAll slice: %s", slice) 64 | } 65 | 66 | // AppendIfMissing - Appends a string to a slice if not already present 67 | // in slice 68 | func appendIfMissing(slice []string, str string) []string { 69 | for _, ele := range slice { 70 | if ele == str { 71 | return slice 72 | } 73 | } 74 | return append(slice, str) 75 | } 76 | -------------------------------------------------------------------------------- /service/system.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 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 | "os/exec" 19 | ) 20 | 21 | var execCommand = exec.Command 22 | -------------------------------------------------------------------------------- /test/helm/.gitignore: -------------------------------------------------------------------------------- 1 | __*__.yaml 2 | 3 | -------------------------------------------------------------------------------- /test/helm/10block/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: 10block 2 | version: 1.0.0 3 | apiVersion: v1 4 | appVersion: 1.0.0 5 | description: | 6 | Tests PowerMax CSI deployments. 7 | icon: https://avatars1.githubusercontent.com/u/20958494?s=200&v=4 8 | keywords: 9 | - csi-powermax 10 | - storage 11 | engine: gotpl 12 | -------------------------------------------------------------------------------- /test/helm/10block/values.yaml: -------------------------------------------------------------------------------- 1 | sc: "" 2 | scxfs: "" 3 | -------------------------------------------------------------------------------- /test/helm/10vols/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: 10vols 2 | version: 1.0.0 3 | apiVersion: v1 4 | appVersion: 1.0.0 5 | description: | 6 | Tests PowerMax CSI deployments. 7 | icon: https://avatars1.githubusercontent.com/u/20958494?s=200&v=4 8 | keywords: 9 | - csi-powermax 10 | - storage 11 | engine: gotpl 12 | -------------------------------------------------------------------------------- /test/helm/10vols/templates/pvc.yaml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: pvol0 5 | namespace: {{ .Values.namespace }} 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | volumeMode: Filesystem 10 | resources: 11 | requests: 12 | storage: 8Gi 13 | storageClassName: {{ .Values.sc }} 14 | --- 15 | kind: PersistentVolumeClaim 16 | apiVersion: v1 17 | metadata: 18 | name: pvol1 19 | namespace: {{ .Values.namespace }} 20 | spec: 21 | accessModes: 22 | - ReadWriteOnce 23 | volumeMode: Filesystem 24 | resources: 25 | requests: 26 | storage: 8Gi 27 | storageClassName: {{ .Values.sc }} 28 | --- 29 | kind: PersistentVolumeClaim 30 | apiVersion: v1 31 | metadata: 32 | name: pvol2 33 | namespace: {{ .Values.namespace }} 34 | spec: 35 | accessModes: 36 | - ReadWriteOnce 37 | volumeMode: Filesystem 38 | resources: 39 | requests: 40 | storage: 8Gi 41 | storageClassName: {{ .Values.sc }} 42 | --- 43 | kind: PersistentVolumeClaim 44 | apiVersion: v1 45 | metadata: 46 | name: pvol3 47 | namespace: {{ .Values.namespace }} 48 | spec: 49 | accessModes: 50 | - ReadWriteOnce 51 | volumeMode: Filesystem 52 | resources: 53 | requests: 54 | storage: 8Gi 55 | storageClassName: {{ .Values.sc }} 56 | --- 57 | kind: PersistentVolumeClaim 58 | apiVersion: v1 59 | metadata: 60 | name: pvol4 61 | namespace: {{ .Values.namespace }} 62 | spec: 63 | accessModes: 64 | - ReadWriteOnce 65 | volumeMode: Filesystem 66 | resources: 67 | requests: 68 | storage: 8Gi 69 | storageClassName: {{ .Values.sc }} 70 | --- 71 | kind: PersistentVolumeClaim 72 | apiVersion: v1 73 | metadata: 74 | name: pvol5 75 | namespace: {{ .Values.namespace }} 76 | spec: 77 | accessModes: 78 | - ReadWriteOnce 79 | volumeMode: Filesystem 80 | resources: 81 | requests: 82 | storage: 8Gi 83 | storageClassName: {{ .Values.scxfs }} 84 | --- 85 | kind: PersistentVolumeClaim 86 | apiVersion: v1 87 | metadata: 88 | name: pvol6 89 | namespace: {{ .Values.namespace }} 90 | spec: 91 | accessModes: 92 | - ReadWriteOnce 93 | volumeMode: Filesystem 94 | resources: 95 | requests: 96 | storage: 8Gi 97 | storageClassName: powermax-xfs 98 | --- 99 | kind: PersistentVolumeClaim 100 | apiVersion: v1 101 | metadata: 102 | name: pvol7 103 | namespace: {{ .Values.namespace }} 104 | spec: 105 | accessModes: 106 | - ReadWriteOnce 107 | volumeMode: Filesystem 108 | resources: 109 | requests: 110 | storage: 8Gi 111 | storageClassName: {{ .Values.scxfs }} 112 | --- 113 | kind: PersistentVolumeClaim 114 | apiVersion: v1 115 | metadata: 116 | name: pvol8 117 | namespace: {{ .Values.namespace }} 118 | spec: 119 | accessModes: 120 | - ReadWriteOnce 121 | volumeMode: Filesystem 122 | resources: 123 | requests: 124 | storage: 8Gi 125 | storageClassName: {{ .Values.scxfs }} 126 | --- 127 | kind: PersistentVolumeClaim 128 | apiVersion: v1 129 | metadata: 130 | name: pvol9 131 | namespace: {{ .Values.namespace }} 132 | spec: 133 | accessModes: 134 | - ReadWriteOnce 135 | volumeMode: Filesystem 136 | resources: 137 | requests: 138 | storage: 8Gi 139 | storageClassName: {{ .Values.scxfs }} 140 | --- 141 | -------------------------------------------------------------------------------- /test/helm/10vols/templates/test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: powermaxtest 5 | namespace: {{ .Values.namespace }} 6 | --- 7 | kind: StatefulSet 8 | apiVersion: apps/v1 9 | metadata: 10 | name: powermaxtest 11 | namespace: {{ .Values.namespace }} 12 | spec: 13 | selector: 14 | matchLabels: 15 | app: powermaxtest 16 | serviceName: test2vols 17 | template: 18 | metadata: 19 | labels: 20 | app: powermaxtest 21 | spec: 22 | serviceAccount: powermaxtest 23 | containers: 24 | - name: test 25 | image: quay.io/centos/centos:latest 26 | command: [ "/bin/sleep", "3600" ] 27 | volumeMounts: 28 | - mountPath: "/data0" 29 | name: pvol0 30 | - mountPath: "/data1" 31 | name: pvol1 32 | - mountPath: "/data2" 33 | name: pvol2 34 | - mountPath: "/data3" 35 | name: pvol3 36 | - mountPath: "/data4" 37 | name: pvol4 38 | - mountPath: "/data5" 39 | name: pvol5 40 | - mountPath: "/data6" 41 | name: pvol6 42 | - mountPath: "/data7" 43 | name: pvol7 44 | - mountPath: "/data8" 45 | name: pvol8 46 | - mountPath: "/data9" 47 | name: pvol9 48 | volumes: 49 | - name: pvol0 50 | persistentVolumeClaim: 51 | claimName: pvol0 52 | - name: pvol1 53 | persistentVolumeClaim: 54 | claimName: pvol1 55 | - name: pvol2 56 | persistentVolumeClaim: 57 | claimName: pvol2 58 | - name: pvol3 59 | persistentVolumeClaim: 60 | claimName: pvol3 61 | - name: pvol4 62 | persistentVolumeClaim: 63 | claimName: pvol4 64 | - name: pvol5 65 | persistentVolumeClaim: 66 | claimName: pvol5 67 | - name: pvol6 68 | persistentVolumeClaim: 69 | claimName: pvol6 70 | - name: pvol7 71 | persistentVolumeClaim: 72 | claimName: pvol7 73 | - name: pvol8 74 | persistentVolumeClaim: 75 | claimName: pvol8 76 | - name: pvol9 77 | persistentVolumeClaim: 78 | claimName: pvol9 79 | -------------------------------------------------------------------------------- /test/helm/10vols/values.yaml: -------------------------------------------------------------------------------- 1 | sc: "" 2 | scxfs: "" 3 | -------------------------------------------------------------------------------- /test/helm/1bigvol/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: 1bigvol 2 | version: 1.0.0 3 | apiVersion: v1 4 | appVersion: 1.0.0 5 | description: | 6 | Tests PowerMax CSI deployments. 7 | icon: https://avatars1.githubusercontent.com/u/20958494?s=200&v=4 8 | keywords: 9 | - csi-powermax 10 | - storage 11 | engine: gotpl 12 | -------------------------------------------------------------------------------- /test/helm/1bigvol/templates/pvc0.yaml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: pvol0 5 | namespace: {{ .Values.namespace }} 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | volumeMode: Filesystem 10 | resources: 11 | requests: 12 | storage: 8Ti 13 | storageClassName: {{ .Values.sc }} 14 | -------------------------------------------------------------------------------- /test/helm/1bigvol/templates/test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: powermaxtest 5 | namespace: {{ .Values.namespace }} 6 | --- 7 | kind: StatefulSet 8 | apiVersion: apps/v1 9 | metadata: 10 | name: powermaxtest 11 | namespace: {{ .Values.namespace }} 12 | spec: 13 | selector: 14 | matchLabels: 15 | app: powermaxtest 16 | serviceName: test2vols 17 | template: 18 | metadata: 19 | labels: 20 | app: powermaxtest 21 | spec: 22 | serviceAccount: powermaxtest 23 | containers: 24 | - name: test 25 | image: quay.io/centos/centos:latest 26 | command: [ "/bin/sleep", "3600" ] 27 | volumeMounts: 28 | - mountPath: "/data0" 29 | name: pvol0 30 | volumes: 31 | - name: pvol0 32 | persistentVolumeClaim: 33 | claimName: pvol0 34 | -------------------------------------------------------------------------------- /test/helm/1bigvol/values.yaml: -------------------------------------------------------------------------------- 1 | sc: "" 2 | scxfs: "" 3 | -------------------------------------------------------------------------------- /test/helm/1clonevol/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: 1clonevol 2 | version: 1.0.0 3 | apiVersion: v1 4 | appVersion: 1.0.0 5 | description: | 6 | Tests PowerMax CSI deployments. 7 | icon: https://avatars1.githubusercontent.com/u/20958494?s=200&v=4 8 | keywords: 9 | - csi-powermax 10 | - storage 11 | engine: gotpl 12 | -------------------------------------------------------------------------------- /test/helm/1clonevol/templates/pvc0.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: restorepvc 5 | namespace: {{ .Values.namespace }} 6 | spec: 7 | storageClassName: {{ .Values.sc }} 8 | dataSource: 9 | name: pvol1-snap1 10 | kind: VolumeSnapshot 11 | apiGroup: snapshot.storage.k8s.io 12 | accessModes: 13 | - ReadWriteOnce 14 | resources: 15 | requests: 16 | storage: 12Gi 17 | -------------------------------------------------------------------------------- /test/helm/1clonevol/templates/test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: powermaxclonetest 5 | namespace: {{ .Values.namespace }} 6 | --- 7 | kind: StatefulSet 8 | apiVersion: apps/v1 9 | metadata: 10 | name: powermaxclonetest 11 | namespace: {{ .Values.namespace }} 12 | spec: 13 | selector: 14 | matchLabels: 15 | app: powermaxclonetest 16 | serviceName: test1clonevol 17 | template: 18 | metadata: 19 | labels: 20 | app: powermaxclonetest 21 | spec: 22 | affinity: 23 | podAffinity: 24 | requiredDuringSchedulingIgnoredDuringExecution: 25 | - labelSelector: 26 | matchExpressions: 27 | - key: app 28 | operator: In 29 | values: 30 | - "powermaxtest" 31 | topologyKey: kubernetes.io/hostname 32 | serviceAccount: powermaxclonetest 33 | containers: 34 | - name: test 35 | image: quay.io/centos/centos:latest 36 | command: [ "/bin/sleep", "3600" ] 37 | volumeMounts: 38 | - mountPath: "/data0" 39 | name: restorepvol 40 | volumes: 41 | - name: restorepvol 42 | persistentVolumeClaim: 43 | claimName: restorepvc 44 | -------------------------------------------------------------------------------- /test/helm/1clonevol/values.yaml: -------------------------------------------------------------------------------- 1 | sc: "" 2 | scxfs: "" 3 | -------------------------------------------------------------------------------- /test/helm/1vol-rep/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: 1vol-rep 2 | version: 1.0.0 3 | apiVersion: v1 4 | appVersion: 1.0.0 5 | description: | 6 | Tests PowerMax CSI deployments. 7 | icon: https://avatars1.githubusercontent.com/u/20958494?s=200&v=4 8 | keywords: 9 | - csi-powermax 10 | - storage 11 | engine: gotpl 12 | -------------------------------------------------------------------------------- /test/helm/1vol-rep/templates/pvc0.yaml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: rep-pvol0 5 | namespace: {{ .Values.namespace }} 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | volumeMode: Filesystem 10 | resources: 11 | requests: 12 | storage: 8Gi 13 | storageClassName: {{ .Values.sc }} 14 | -------------------------------------------------------------------------------- /test/helm/1vol-rep/templates/test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: rep-powermaxtest 5 | namespace: {{ .Values.namespace }} 6 | --- 7 | kind: StatefulSet 8 | apiVersion: apps/v1 9 | metadata: 10 | name: rep-powermaxtest 11 | namespace: {{ .Values.namespace }} 12 | spec: 13 | selector: 14 | matchLabels: 15 | app: rep-powermaxtest 16 | serviceName: test2vols 17 | template: 18 | metadata: 19 | labels: 20 | app: rep-powermaxtest 21 | spec: 22 | serviceAccount: rep-powermaxtest 23 | containers: 24 | - name: test 25 | image: quay.io/centos/centos:latest 26 | command: [ "/bin/sleep", "3600" ] 27 | volumeMounts: 28 | - mountPath: "/data0" 29 | name: rep-pvol0 30 | volumes: 31 | - name: rep-pvol0 32 | persistentVolumeClaim: 33 | claimName: rep-pvol0 34 | -------------------------------------------------------------------------------- /test/helm/1vol-rep/values.yaml: -------------------------------------------------------------------------------- 1 | sc: "" 2 | scxfs: "" 3 | -------------------------------------------------------------------------------- /test/helm/1vol/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: 1vol 2 | version: 1.0.0 3 | apiVersion: v1 4 | appVersion: 1.0.0 5 | description: | 6 | Tests PowerMax CSI deployments. 7 | icon: https://avatars1.githubusercontent.com/u/20958494?s=200&v=4 8 | keywords: 9 | - csi-powermax 10 | - storage 11 | engine: gotpl 12 | -------------------------------------------------------------------------------- /test/helm/1vol/templates/pvc0.yaml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: pvol0 5 | namespace: {{ .Values.namespace }} 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | volumeMode: Filesystem 10 | resources: 11 | requests: 12 | storage: 8Gi 13 | storageClassName: {{ .Values.sc }} 14 | -------------------------------------------------------------------------------- /test/helm/1vol/templates/test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: powermaxtest 5 | namespace: {{ .Values.namespace }} 6 | --- 7 | kind: StatefulSet 8 | apiVersion: apps/v1 9 | metadata: 10 | name: powermaxtest 11 | namespace: {{ .Values.namespace }} 12 | spec: 13 | selector: 14 | matchLabels: 15 | app: powermaxtest 16 | serviceName: test2vols 17 | template: 18 | metadata: 19 | labels: 20 | app: powermaxtest 21 | spec: 22 | serviceAccount: powermaxtest 23 | containers: 24 | - name: test 25 | image: quay.io/centos/centos:latest 26 | command: [ "/bin/sleep", "3600" ] 27 | volumeMounts: 28 | - mountPath: "/data0" 29 | name: pvol0 30 | volumes: 31 | - name: pvol0 32 | persistentVolumeClaim: 33 | claimName: pvol0 34 | -------------------------------------------------------------------------------- /test/helm/1vol/values.yaml: -------------------------------------------------------------------------------- 1 | sc: "" 2 | scxfs: "" 3 | -------------------------------------------------------------------------------- /test/helm/2block/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: 2block 2 | version: 1.0.0 3 | apiVersion: v1 4 | appVersion: 1.0.0 5 | description: | 6 | Tests PowerMax CSI deployments. 7 | icon: https://avatars1.githubusercontent.com/u/20958494?s=200&v=4 8 | keywords: 9 | - csi-powermax 10 | - storage 11 | engine: gotpl 12 | -------------------------------------------------------------------------------- /test/helm/2block/templates/test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: powermaxtest 5 | namespace: {{ .Values.namespace }} 6 | --- 7 | kind: StatefulSet 8 | apiVersion: apps/v1 9 | metadata: 10 | name: powermaxtest 11 | namespace: {{ .Values.namespace }} 12 | spec: 13 | selector: 14 | matchLabels: 15 | app: powermaxtest 16 | serviceName: test2vols 17 | template: 18 | metadata: 19 | labels: 20 | app: powermaxtest 21 | spec: 22 | serviceAccount: powermaxtest 23 | containers: 24 | - name: test 25 | image: quay.io/centos/centos:latest 26 | command: [ "/bin/sleep", "3600" ] 27 | volumeDevices: 28 | - devicePath: "/dev/data0" 29 | name: pvol0 30 | - devicePath: "/dev/data1" 31 | name: pvol1 32 | volumeClaimTemplates: 33 | - metadata: 34 | name: pvol0 35 | spec: 36 | accessModes: 37 | - ReadWriteOnce 38 | volumeMode: Block 39 | storageClassName: {{ .Values.sc }} 40 | resources: 41 | requests: 42 | storage: 1Gi 43 | - metadata: 44 | name: pvol1 45 | spec: 46 | accessModes: 47 | - ReadWriteOnce 48 | volumeMode: Block 49 | storageClassName: {{ .Values.sc }} 50 | resources: 51 | requests: 52 | storage: 1Gi 53 | -------------------------------------------------------------------------------- /test/helm/2block/values.yaml: -------------------------------------------------------------------------------- 1 | sc: "" 2 | scxfs: "" 3 | -------------------------------------------------------------------------------- /test/helm/2vols+clone/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: 2vols+clone 2 | version: 1.0.0 3 | apiVersion: v1 4 | appVersion: 1.0.0 5 | description: | 6 | Tests PowerMax CSI deployments. 7 | icon: https://avatars1.githubusercontent.com/u/20958494?s=200&v=4 8 | keywords: 9 | - csi-powermax 10 | - storage 11 | engine: gotpl 12 | -------------------------------------------------------------------------------- /test/helm/2vols+clone/templates/createFromVolume.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: clonedpvc 5 | namespace: {{ .Values.namespace }} 6 | spec: 7 | storageClassName: {{ .Values.sc }} 8 | dataSource: 9 | name: pvol0 10 | kind: PersistentVolumeClaim 11 | accessModes: 12 | - ReadWriteOnce 13 | resources: 14 | requests: 15 | storage: 8Gi 16 | -------------------------------------------------------------------------------- /test/helm/2vols+clone/templates/pvc0.yaml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: pvol0 5 | namespace: {{ .Values.namespace }} 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | volumeMode: Filesystem 10 | resources: 11 | requests: 12 | storage: 8Gi 13 | storageClassName: {{ .Values.sc }} 14 | -------------------------------------------------------------------------------- /test/helm/2vols+clone/templates/pvc1.yaml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: pvol1 5 | namespace: {{ .Values.namespace }} 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | volumeMode: Filesystem 10 | resources: 11 | requests: 12 | storage: 12Gi 13 | storageClassName: {{ .Values.scxfs }} 14 | -------------------------------------------------------------------------------- /test/helm/2vols+clone/templates/test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: powermaxtest 5 | namespace: {{ .Values.namespace }} 6 | --- 7 | kind: StatefulSet 8 | apiVersion: apps/v1 9 | metadata: 10 | name: powermaxtest 11 | namespace: {{ .Values.namespace }} 12 | spec: 13 | selector: 14 | matchLabels: 15 | app: powermaxtest 16 | serviceName: test2vols 17 | template: 18 | metadata: 19 | labels: 20 | app: powermaxtest 21 | spec: 22 | serviceAccount: powermaxtest 23 | containers: 24 | - name: test 25 | image: quay.io/centos/centos:latest 26 | command: [ "/bin/sleep", "3600" ] 27 | volumeMounts: 28 | - mountPath: "/data0" 29 | name: pvol0 30 | - mountPath: "/data1" 31 | name: pvol1 32 | - mountPath: "/data2" 33 | name: pvol2 34 | volumes: 35 | - name: pvol0 36 | persistentVolumeClaim: 37 | claimName: pvol0 38 | - name: pvol1 39 | persistentVolumeClaim: 40 | claimName: pvol1 41 | - name: pvol2 42 | persistentVolumeClaim: 43 | claimName: clonedpvc 44 | -------------------------------------------------------------------------------- /test/helm/2vols+clone/values.yaml: -------------------------------------------------------------------------------- 1 | sc: "" 2 | scxfs: "" 3 | -------------------------------------------------------------------------------- /test/helm/2vols+restore/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: 2vols+restore 2 | version: 1.0.0 3 | appVersion: v1 4 | description: Tests Powermax CSI deployments. 5 | icon: https://avatars1.githubusercontent.com/u/20958494?s=200&v=4 6 | keywords: 7 | - csi-powermax 8 | - storage 9 | engine: gotpl 10 | -------------------------------------------------------------------------------- /test/helm/2vols+restore/templates/createFromSnap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: restorepvc 5 | namespace: {{ .Values.namespace }} 6 | spec: 7 | storageClassName: {{ .Values.sc }} 8 | dataSource: 9 | name: pvol0-snap1 10 | kind: VolumeSnapshot 11 | apiGroup: snapshot.storage.k8s.io 12 | accessModes: 13 | - ReadWriteOnce 14 | resources: 15 | requests: 16 | storage: 8194Mi 17 | -------------------------------------------------------------------------------- /test/helm/2vols+restore/templates/pvc0.yaml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: pvol0 5 | namespace: {{ .Values.namespace }} 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | volumeMode: Filesystem 10 | resources: 11 | requests: 12 | storage: 8Gi 13 | storageClassName: {{ .Values.sc }} 14 | -------------------------------------------------------------------------------- /test/helm/2vols+restore/templates/pvc1.yaml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: pvol1 5 | namespace: {{ .Values.namespace }} 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | volumeMode: Filesystem 10 | resources: 11 | requests: 12 | storage: 12Gi 13 | storageClassName: {{ .Values.scxfs }} 14 | -------------------------------------------------------------------------------- /test/helm/2vols+restore/templates/test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: powermaxtest 5 | namespace: {{ .Values.namespace }} 6 | --- 7 | kind: StatefulSet 8 | apiVersion: apps/v1 9 | metadata: 10 | name: powermaxtest 11 | namespace: {{ .Values.namespace }} 12 | spec: 13 | selector: 14 | matchLabels: 15 | app: powermaxtest 16 | serviceName: test2vols 17 | template: 18 | metadata: 19 | labels: 20 | app: powermaxtest 21 | spec: 22 | serviceAccount: powermaxtest 23 | containers: 24 | - name: test 25 | image: quay.io/centos/centos:latest 26 | command: [ "/bin/sleep", "3600" ] 27 | volumeMounts: 28 | - mountPath: "/data0" 29 | name: pvol0 30 | - mountPath: "/data1" 31 | name: pvol1 32 | - mountPath: "/data2" 33 | name: pvol2 34 | volumes: 35 | - name: pvol0 36 | persistentVolumeClaim: 37 | claimName: pvol0 38 | - name: pvol1 39 | persistentVolumeClaim: 40 | claimName: pvol1 41 | - name: pvol2 42 | persistentVolumeClaim: 43 | claimName: restorepvc 44 | -------------------------------------------------------------------------------- /test/helm/2vols/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: 2vols 2 | version: 1.0.0 3 | apiVersion: v1 4 | appVersion: 1.0.0 5 | description: | 6 | Tests PowerMax CSI deployments. 7 | icon: https://avatars1.githubusercontent.com/u/20958494?s=200&v=4 8 | keywords: 9 | - csi-powermax 10 | - storage 11 | engine: gotpl 12 | -------------------------------------------------------------------------------- /test/helm/2vols/templates/pvc0.yaml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: pvol0 5 | namespace: {{ .Values.namespace }} 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | volumeMode: Filesystem 10 | resources: 11 | requests: 12 | storage: 8Gi 13 | storageClassName: {{ .Values.sc }} 14 | -------------------------------------------------------------------------------- /test/helm/2vols/templates/pvc1.yaml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: pvol1 5 | namespace: {{ .Values.namespace }} 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | volumeMode: Filesystem 10 | resources: 11 | requests: 12 | storage: 12Gi 13 | storageClassName: {{ .Values.scxfs }} 14 | -------------------------------------------------------------------------------- /test/helm/2vols/templates/test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: powermaxtest 5 | namespace: {{ .Values.namespace }} 6 | --- 7 | kind: StatefulSet 8 | apiVersion: apps/v1 9 | metadata: 10 | name: powermaxtest 11 | namespace: {{ .Values.namespace }} 12 | spec: 13 | selector: 14 | matchLabels: 15 | app: powermaxtest 16 | serviceName: test2vols 17 | template: 18 | metadata: 19 | labels: 20 | app: powermaxtest 21 | spec: 22 | serviceAccount: powermaxtest 23 | containers: 24 | - name: test 25 | image: quay.io/centos/centos:latest 26 | command: [ "/bin/sleep", "3600" ] 27 | volumeMounts: 28 | - mountPath: "/data0" 29 | name: pvol0 30 | - mountPath: "/data1" 31 | name: pvol1 32 | volumes: 33 | - name: pvol0 34 | persistentVolumeClaim: 35 | claimName: pvol0 36 | - name: pvol1 37 | persistentVolumeClaim: 38 | claimName: pvol1 39 | -------------------------------------------------------------------------------- /test/helm/2vols/values.yaml: -------------------------------------------------------------------------------- 1 | sc: "" 2 | scxfs: "" 3 | -------------------------------------------------------------------------------- /test/helm/7vols/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: 7vols 2 | version: 1.0.0 3 | apiVersion: v1 4 | appVersion: 1.0.0 5 | description: | 6 | Tests CSI deployments. 7 | icon: https://avatars1.githubusercontent.com/u/20958494?s=200&v=4 8 | keywords: 9 | - csi-powermax 10 | - storage 11 | engine: gotpl 12 | -------------------------------------------------------------------------------- /test/helm/7vols/templates/pvc.yaml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: pvol0 5 | namespace: {{ .Values.namespace }} 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | volumeMode: Filesystem 10 | resources: 11 | requests: 12 | storage: 8Gi 13 | storageClassName: {{ .Values.sc }} 14 | --- 15 | kind: PersistentVolumeClaim 16 | apiVersion: v1 17 | metadata: 18 | name: pvol1 19 | namespace: {{ .Values.namespace }} 20 | spec: 21 | accessModes: 22 | - ReadWriteOnce 23 | volumeMode: Filesystem 24 | resources: 25 | requests: 26 | storage: 8Gi 27 | storageClassName: {{ .Values.sc }} 28 | --- 29 | kind: PersistentVolumeClaim 30 | apiVersion: v1 31 | metadata: 32 | name: pvol2 33 | namespace: {{ .Values.namespace }} 34 | spec: 35 | accessModes: 36 | - ReadWriteOnce 37 | volumeMode: Filesystem 38 | resources: 39 | requests: 40 | storage: 8Gi 41 | storageClassName: {{ .Values.sc }} 42 | --- 43 | kind: PersistentVolumeClaim 44 | apiVersion: v1 45 | metadata: 46 | name: pvol3 47 | namespace: {{ .Values.namespace }} 48 | spec: 49 | accessModes: 50 | - ReadWriteOnce 51 | volumeMode: Filesystem 52 | resources: 53 | requests: 54 | storage: 8Gi 55 | storageClassName: {{ .Values.sc }} 56 | --- 57 | kind: PersistentVolumeClaim 58 | apiVersion: v1 59 | metadata: 60 | name: pvol4 61 | namespace: {{ .Values.namespace }} 62 | spec: 63 | accessModes: 64 | - ReadWriteOnce 65 | volumeMode: Filesystem 66 | resources: 67 | requests: 68 | storage: 8Gi 69 | storageClassName: {{ .Values.sc }} 70 | --- 71 | kind: PersistentVolumeClaim 72 | apiVersion: v1 73 | metadata: 74 | name: pvol5 75 | namespace: {{ .Values.namespace }} 76 | spec: 77 | accessModes: 78 | - ReadWriteOnce 79 | volumeMode: Filesystem 80 | resources: 81 | requests: 82 | storage: 8Gi 83 | storageClassName: {{ .Values.scxfs }} 84 | --- 85 | kind: PersistentVolumeClaim 86 | apiVersion: v1 87 | metadata: 88 | name: pvol6 89 | namespace: {{ .Values.namespace }} 90 | spec: 91 | accessModes: 92 | - ReadWriteOnce 93 | volumeMode: Filesystem 94 | resources: 95 | requests: 96 | storage: 8Gi 97 | storageClassName: {{ .Values.scxfs }} 98 | -------------------------------------------------------------------------------- /test/helm/7vols/templates/test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: powermaxtest 5 | namespace: { { .Values.namespace } } 6 | --- 7 | kind: StatefulSet 8 | apiVersion: apps/v1 9 | metadata: 10 | name: powermaxtest 11 | namespace: { { .Values.namespace } } 12 | spec: 13 | selector: 14 | matchLabels: 15 | app: powermaxtest 16 | serviceName: test2vols 17 | template: 18 | metadata: 19 | labels: 20 | app: powermaxtest 21 | spec: 22 | serviceAccount: powermaxtest 23 | containers: 24 | - name: test 25 | image: quay.io/centos/centos:latest 26 | command: ["/bin/sleep", "3600"] 27 | volumeMounts: 28 | - mountPath: "/data0" 29 | name: pvol0 30 | - mountPath: "/data1" 31 | name: pvol1 32 | - mountPath: "/data2" 33 | name: pvol2 34 | - mountPath: "/data3" 35 | name: pvol3 36 | - mountPath: "/data4" 37 | name: pvol4 38 | - mountPath: "/data5" 39 | name: pvol5 40 | - mountPath: "/data6" 41 | name: pvol6 42 | volumes: 43 | - name: pvol0 44 | persistentVolumeClaim: 45 | claimName: pvol0 46 | - name: pvol1 47 | persistentVolumeClaim: 48 | claimName: pvol1 49 | - name: pvol2 50 | persistentVolumeClaim: 51 | claimName: pvol2 52 | - name: pvol3 53 | persistentVolumeClaim: 54 | claimName: pvol3 55 | - name: pvol4 56 | persistentVolumeClaim: 57 | claimName: pvol4 58 | - name: pvol5 59 | persistentVolumeClaim: 60 | claimName: pvol5 61 | - name: pvol6 62 | persistentVolumeClaim: 63 | claimName: pvol6 64 | -------------------------------------------------------------------------------- /test/helm/7vols/values.yaml: -------------------------------------------------------------------------------- 1 | sc: "" 2 | scxfs: "" 3 | -------------------------------------------------------------------------------- /test/helm/README.md: -------------------------------------------------------------------------------- 1 | # Helm tests 2 | This folder contains Helm charts and shell scripts which can be used to test various features of the CSI PowerMax driver. 3 | 4 | The Helm charts typically deploy a StatefulSet with a number of PVCs created using the provided storage class names. 5 | For e.g. - the test `2vols` will deploy a StatefulSet which runs a single CentOS container and uses `2` PVCs. 6 | 7 | Additionally, some tests create cloned volumes using a source `Volume` or a `VolumeSnapshot` 8 | 9 | ## Helm charts 10 | 11 | | Name | Description | 12 | |---------|-------| 13 | |2vols | Creates 2 filesystem mounts | 14 | |7vols | Creates 7 filesystem mounts | 15 | |10vols | Creates 10 filesystem mounts | 16 | |xfspre | Create an XFS formated PV and attaches to a pod | 17 | |2block | Create 2 raw block volumes and attach to a pod | 18 | | 2vols + clone | Creates 2 initial volumes and a third volume that is a clone of one of the initial volumes | 19 | 20 | 21 | ## Scripts 22 | | Name | Description | 23 | |----------------|-------| 24 | | deletepvcs.sh | Script to delete all PVCS in a namespace 25 | | get.volume.ids | Script to list the volume IDs for all PVS in a namespace 26 | | logit.sh | Script to print number of pods and pvcs in a namespace 27 | | postgres.sh | Script used to startup a postgres pod that is backed by a PV 28 | | starttest.sh | Used to instantiate one of the Helm charts above. Requires argument of Helm chart 29 | | stoptest.sh | Stops currently running Helm chart and deletes all PVCS 30 | | volumeclonetest.sh | Tests volume clones 31 | | snaprestoretest.sh | Tests restore from a VolumeSnapshot (using clones) 32 | | volumeexpansiontest.sh | Tests volume expansion 33 | 34 | 35 | ## Usage 36 | All the test scripts require these 2 optional arguments 37 | * -n \ => Namespace where the sample applications will be deployed 38 | * -s \ => Name of the storage class which will be used for provisioning PVCs. 39 | 40 | Some tests also require another storage class which provision XFS mount volumes. If you provided `sc-name` as the storage class name, ensure that a storage class with the name `sc-name-xfs` exists before running the tests. 41 | 42 | If you don't provide the namespace option, the test scripts will default to using `test` namespace. 43 | 44 | If you don't provide the storage class name, the test scripts will default to `powermax` & `powermax-xfs` storage class names. 45 | 46 | 47 | ### starttest.sh 48 | The starttest.sh script is used to deploy Helm charts that test the deployment of a simple pod 49 | with various storage configurations. The stoptest.sh script will delete the Helm chart and cleanup after the test. 50 | Procedure 51 | 1. Navigate to the `test/helm` directory, which contains the `starttest.sh` and various Helm charts. 52 | 53 | 2. Run the `starttest.sh` script with an argument of the specific Helm chart to deploy and test: `bash starttest.sh -t -n -s ` 54 | 55 | Example: 56 | 57 | ``` 58 | bash starttest.sh -t 2vols -n test -s powermax 59 | ``` 60 | 61 | 3. After the test has completed, run the stoptest.sh script to delete the Helm chart and cleanup the volumes: `bash stoptest.sh -t -n ` 62 | 63 | Example: 64 | 65 | ``` 66 | bash stoptest.sh -t 2vols -n test 67 | ``` 68 | 69 | ### Other scripts 70 | The following set of scripts have been provided to help test additonal CSI functionalities like snapshots, clones, volume expansion. 71 | 72 | * snaprestoretest.sh 73 | * volumeclonetest.sh 74 | * volumeexpansiontest.sh 75 | * volumeexpansiontest.sh 76 | 77 | To run these tests, follow the procedure given below: 78 | 1. Navigate to the test/helm directory 79 | 80 | 2. Run the desired script with the following command: 81 | 82 | ``` 83 | bash -n -s 84 | ``` 85 | -------------------------------------------------------------------------------- /test/helm/block/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: block 2 | version: 1.0.0 3 | apiVersion: v1 4 | appVersion: 1.0.0 5 | description: | 6 | Tests PowerMax CSI deployments. 7 | icon: https://avatars1.githubusercontent.com/u/20958494?s=200&v=4 8 | keywords: 9 | - csi-powermax 10 | - storage 11 | engine: gotpl 12 | -------------------------------------------------------------------------------- /test/helm/block/templates/test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: powermaxtest 5 | namespace: {{ .Values.namespace }} 6 | --- 7 | kind: StatefulSet 8 | apiVersion: apps/v1 9 | metadata: 10 | name: powermaxtest 11 | namespace: {{ .Values.namespace }} 12 | spec: 13 | selector: 14 | matchLabels: 15 | app: powermaxtest 16 | serviceName: test2vols 17 | template: 18 | metadata: 19 | labels: 20 | app: powermaxtest 21 | spec: 22 | serviceAccount: powermaxtest 23 | containers: 24 | - name: test 25 | image: quay.io/centos/centos:latest 26 | command: [ "/bin/sleep", "3600" ] 27 | volumeDevices: 28 | - devicePath: "/dev/data0" 29 | name: pvol0 30 | volumeClaimTemplates: 31 | - metadata: 32 | name: pvol0 33 | spec: 34 | accessModes: 35 | - ReadWriteOnce 36 | volumeMode: Block 37 | storageClassName: {{ .Values.sc }} 38 | resources: 39 | requests: 40 | storage: 8Gi 41 | -------------------------------------------------------------------------------- /test/helm/block/values.yaml: -------------------------------------------------------------------------------- 1 | sc: "" 2 | scxfs: "" 3 | -------------------------------------------------------------------------------- /test/helm/deletepvcs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | force=no 3 | NAMESPACE="test" 4 | 5 | # Usage information 6 | function usage { 7 | echo 8 | echo "`basename ${0}`" 9 | echo " -n namespace - Namespace in which the release is running. Default is: ${NAMESPACE}" 10 | echo " -f - force delete" 11 | exit 1 12 | } 13 | 14 | # Parse the options passed on the command line 15 | while getopts "fn:" opt; do 16 | case $opt in 17 | n) 18 | NAMESPACE="${OPTARG}" 19 | ;; 20 | f) 21 | force=yes 22 | ;; 23 | \?) 24 | echo "Invalid option: -$OPTARG" >&2 25 | usage 26 | ;; 27 | :) 28 | echo "Option -$OPTARG requires an argument." >&2 29 | usage 30 | ;; 31 | esac 32 | done 33 | 34 | pvcs=$(kubectl get pvc -n $NAMESPACE | awk '/pvol/ { print $1; }') 35 | echo deleting... $pvcs 36 | for pvc in $pvcs 37 | do 38 | if [ $force == "yes" ]; 39 | then 40 | echo kubectl delete --force --grace-period=0 pvc $pvc -n $NAMESPACE 41 | kubectl delete --force --grace-period=0 pvc $pvc -n $NAMESPACE 42 | else 43 | echo kubectl delete pvc $pvc -n $NAMESPACE 44 | kubectl delete pvc $pvc -n $NAMESPACE 45 | fi 46 | done 47 | 48 | -------------------------------------------------------------------------------- /test/helm/get.volume.ids: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | ids=$(kubectl describe persistentvolume -n test | grep VolumeHandle | awk ' { print $2; }') 3 | echo ids $ids 4 | ids=$(echo $ids | tr ' ' ',' ) 5 | echo ids $ids 6 | -------------------------------------------------------------------------------- /test/helm/helm.lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | FILES="10block 10vols 1bigvol 1vol 2block 2vols 7vols block xfspre" 3 | 4 | for i in $FILES; 5 | do 6 | helm lint -n test $i 7 | done 8 | -------------------------------------------------------------------------------- /test/helm/logit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | while true 3 | do 4 | date 5 | date >>log.output 6 | running=$(kubectl get pods -n test | grep "Running" | wc -l) 7 | creating=$(kubectl get pods -n test | grep "ContainerCreating" | wc -l) 8 | pvcs=$(kubectl get pvc -n test | wc -l) 9 | echo running $running creating $creating pvcs $pvcs 10 | echo running $running creating $creating pvcs $pvcs >>log.output 11 | sleep 30 12 | done 13 | 14 | -------------------------------------------------------------------------------- /test/helm/snap1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: snapshot.storage.k8s.io/v1 2 | kind: VolumeSnapshot 3 | metadata: 4 | name: pvol0-snap1 5 | spec: 6 | volumeSnapshotClassName: powermax-snapclass 7 | source: 8 | persistentVolumeClaimName: pvol0 9 | -------------------------------------------------------------------------------- /test/helm/snap2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: snapshot.storage.k8s.io/v1 2 | kind: VolumeSnapshot 3 | metadata: 4 | name: pvol1-snap1 5 | spec: 6 | volumeSnapshotClassName: powermax-snapclass 7 | source: 8 | persistentVolumeClaimName: pvol1 9 | -------------------------------------------------------------------------------- /test/helm/stoptest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TEST="" 4 | NAMESPACE="test" 5 | 6 | # Usage information 7 | function usage { 8 | echo 9 | echo "`basename ${0}`" 10 | echo " -t test - Test to stop" 11 | echo " -n namespace - Namespace in which the release is running. Default is: ${NAMESPACE}" 12 | echo " -h help - Help" 13 | } 14 | 15 | # Parse the options passed on the command line 16 | while getopts "t:n:h" opt; do 17 | case $opt in 18 | t) 19 | TEST="${OPTARG}" 20 | ;; 21 | n) 22 | NAMESPACE="${OPTARG}" 23 | ;; 24 | h) 25 | usage 26 | exit 0 27 | ;; 28 | \?) 29 | echo "Invalid option: -$OPTARG" >&2 30 | usage 31 | exit 1 32 | ;; 33 | :) 34 | echo "Option -$OPTARG requires an argument." >&2 35 | usage 36 | exit 1 37 | ;; 38 | esac 39 | done 40 | 41 | # Ensure a test was named and that it exists 42 | if [ "${TEST}" == "" ]; then 43 | echo "The name of a test must be specified" 44 | usage 45 | fi 46 | if [ ! -d "${TEST}" ]; then 47 | echo "Unable to find test named: ${TEST}" 48 | usage 49 | fi 50 | 51 | # the helm release name will be the basename of the test 52 | RELEASE=`basename "${TEST}"` 53 | 54 | VALUES="__${NAMESPACE}-${RELEASE}__.yaml" 55 | 56 | helm -n ${NAMESPACE} delete "${RELEASE}" 57 | sleep 10 58 | kubectl get pods -n "${NAMESPACE}" 59 | echo "waiting for persistent volumes to be cleaned up" 60 | sleep 90 61 | bash deletepvcs.sh -n "${NAMESPACE}" 62 | kubectl get persistentvolumes -o wide 63 | 64 | if [ -f "${VALUES}" ]; then 65 | rm "${VALUES}" 66 | fi 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /test/helm/xfspre/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: xfspre 2 | version: 0.0.1 3 | apiVersion: v1 4 | appVersion: 2.6.0 5 | description: | 6 | Tests PowerMax CSI deployments. 7 | icon: https://avatars1.githubusercontent.com/u/20958494?s=200&v=4 8 | keywords: 9 | - csi-powermax 10 | - storage 11 | engine: gotpl 12 | -------------------------------------------------------------------------------- /test/helm/xfspre/templates/pv.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolume 3 | metadata: 4 | name: vol4 5 | namespace: {{ .Values.namespace }} 6 | spec: 7 | capacity: 8 | storage: 16Gi 9 | csi: 10 | driver: powermax.emc.dell.com 11 | fsType: xfs 12 | volumeHandle: 72cebf0c00000001 13 | volumeMode: Filesystem 14 | accessModes: 15 | - ReadWriteOnce 16 | storageClassName: {{ .Values.sc }} 17 | -------------------------------------------------------------------------------- /test/helm/xfspre/templates/pvc4.yaml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolumeClaim 2 | apiVersion: v1 3 | metadata: 4 | name: pvol4 5 | namespace: {{ .Values.namespace }} 6 | spec: 7 | accessModes: 8 | - ReadWriteOnce 9 | volumeMode: Filesystem 10 | volumeName: vol4 11 | resources: 12 | requests: 13 | storage: 16Gi 14 | storageClassName: {{ .Values.sc }} 15 | -------------------------------------------------------------------------------- /test/helm/xfspre/templates/test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: powermaxtest 5 | namespace: {{ .Values.namespace }} 6 | --- 7 | kind: StatefulSet 8 | apiVersion: apps/v1 9 | metadata: 10 | name: powermaxtest 11 | namespace: {{ .Values.namespace }} 12 | spec: 13 | selector: 14 | matchLabels: 15 | app: powermaxtest 16 | serviceName: test2vols 17 | template: 18 | metadata: 19 | labels: 20 | app: powermaxtest 21 | spec: 22 | serviceAccount: powermaxtest 23 | containers: 24 | - name: test 25 | image: quay.io/centos/centos:latest 26 | command: [ "/bin/sleep", "3600" ] 27 | volumeMounts: 28 | - mountPath: "/data4" 29 | name: pvol4 30 | volumes: 31 | - name: pvol4 32 | persistentVolumeClaim: 33 | claimName: pvol4 34 | -------------------------------------------------------------------------------- /test/helm/xfspre/values.yaml: -------------------------------------------------------------------------------- 1 | sc: "" 2 | scxfs: "" 3 | -------------------------------------------------------------------------------- /test/integration/README.md: -------------------------------------------------------------------------------- 1 | # CSI-PowerMax Integration Tests 2 | 3 | ## How to run the integration tests 4 | 1. Update csi-powermax/env.sh with values for your Powermax system. 5 | 2. run `export PATH=$PATH:/usr/local/go/bin` 6 | 3. run `export KUBECONFIG=/root/.kube/config` 7 | 4. run `bash run.sh` 8 | 9 | ### If you want to only run some test scenarios: 10 | 1. Add a `tag` on the tests 11 | 2. Update the `Tags` section in file _integration_test.go_ with the same tag 12 | 13 | ### Notes on the replication test scenarios: 14 | - Scenarios `SRDF Actions on a protected SG` and `Get Status of a protected SG` are not currently running due to timing issues in the tests themselves -- they both execute a failover on Unisphere and expect it to be done in less than a second, which is not realisitic. 15 | - Scenarios `Create and delete replication volume with auto SRDF group` and `Create and delete replication volume with HostIOLimits` only pass with manual intervention. When the test gets stuck deleting a volume, the user needs to go to the remote array in Unisphere and manually remove the volume from any storage groups it is in and then delete it. 16 | -------------------------------------------------------------------------------- /test/integration/pool.yml: -------------------------------------------------------------------------------- 1 | storagepool: viki_pool_HDD_20181031 2 | -------------------------------------------------------------------------------- /test/integration/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This will run coverage analysis using the integration testing. 3 | # The env.sh must point to a valid Unisphere deployment and the iscsi packages must be installed 4 | # on this system. This will make real calls to Unisphere 5 | 6 | rm -f unix_sock 7 | . ../../env.sh 8 | rm -rf /dev/disk/csi-powermax/* 9 | rm -rf datadir* 10 | echo ENDPOINT $X_CSI_POWERMAX_ENDPOINT 11 | 12 | GOOS=linux CGO_ENABLED=0 GO111MODULE=on go test -v -coverprofile=c.linux.out -timeout 180m -coverpkg=../../service *test.go 13 | -------------------------------------------------------------------------------- /test/integration/validate_http_unauthorized.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source ../../env.sh 3 | rm -rf unix_sock 4 | nonhttp=$(echo $X_CSI_POWERMAX_ENDPOINT | sed 's/https:/http:/') 5 | echo "testing http validation with URL: " $nonhttp 6 | export X_CSI_POWERMAX_ENDPOINT=$nonhttp 7 | 8 | ../../csi-powermax 2>stderr 9 | grep "Unauthorized" stderr 10 | rc=$? 11 | echo rc $rc 12 | if [ $rc -ne 0 ]; then echo "failed..."; else echo "passed"; fi 13 | exit $rc 14 | -------------------------------------------------------------------------------- /test/multi-az/README.md: -------------------------------------------------------------------------------- 1 | # Multi-Access Zone Testing Suite 2 | 3 | ## Description 4 | This folder and all files within are a set of simple tests to automate the process of verifying multi-access zone support for the CSI PowerMax driver. The intention is to create a simple and reusable pattern that can be exercised in all Dell CSI drivers that will support multi-access zone partitioning, while reusing and standardizing tools across Dell storage platforms. 5 | 6 | ## Prerequisites 7 | All of the following will be necessary to perform the test. 8 | - A Kubernetes cluster 9 | - This cluster must have two worker nodes to validate node zoning 10 | - This cluster must have the latest version of CSM Operator deployed for driver installations 11 | - This cluster must have a valid kubeconfig file in place on the machine running the test to access it via `kubectl` 12 | - `openssl` installed on the build machine for TLS secret generation (PowerMax requirement) 13 | - Two PowerMax storage arrays (one for each zone to verify zoning functionality) 14 | - These storage arrays must be on the same Unisphere/have a common port group for ISCSI connection. PowerMax driver does not currently support storage arrays with differing port groups. 15 | - Routes must be established between the Unisphere and the Kubernetes nodes under test so that they can connect to the storage arrays. 16 | 17 | ## Running the Test 18 | Running the tests is intended to be a simple process: fill in a configuration file with real PowerMax storage array values, run the included `run.sh` shell script with CSM Operator installed to manage driver deployments, and receive a passing or failing result. 19 | 20 | 1. Populate the `csi-powermax/test/multi-az/config` file with values that satisfy both PowerMax arrays under test. 21 | 2. Run the shell script using `./run.sh` from within the `csi-powermax/test/multi-az` directory. 22 | 3. That's it! The `run.sh` script will produce a log of the test run. 23 | 24 | ## Design Methodology 25 | This script-based testing method was born from a need to repeatably test and verify multi-access zone support for PowerMax. Leveraging the existing end-to-end work in [csm-operator](https://github.com/dell/csm-operator/blob/main/tests/e2e/modify_zoning_labels.sh), as well as a similar structure of templates that are filled with user-provided values for the user. 26 | 27 | It is the hope that this script setup can be extended to other storage platforms as multi-access zone support is added to them with minimal effort between platforms. -------------------------------------------------------------------------------- /test/multi-az/config: -------------------------------------------------------------------------------- 1 | # Image for PowerMax driver 2 | REPLACE_POWERMAX_IMAGE=quay.io/dell/container-storage-modules/csi-powermax:nightly 3 | 4 | # Storage array 1: first access zone 5 | REPLACE_STORAGE_ID_1=0001 6 | REPLACE_PRIMARY_ENDPOINT_1=0.0.0.0 7 | REPLACE_PRIMARY_PORT_1=8081 8 | REPLACE_SECONDARY_ENDPOINT_1=0.0.0.1 9 | REPLACE_SECONDARY_PORT_1=8082 10 | REPLACE_USER_1=user 11 | REPLACE_PASS_1=pass 12 | 13 | # Storage array 2: second access zone 14 | REPLACE_STORAGE_ID_2=0002 15 | REPLACE_PRIMARY_ENDPOINT_2=0.0.0.2 16 | REPLACE_PRIMARY_PORT_2=8083 17 | REPLACE_SECONDARY_ENDPOINT_2=0.0.0.3 18 | REPLACE_SECONDARY_PORT_2=8084 19 | REPLACE_USER_2=user 20 | REPLACE_PASS_2=pass 21 | 22 | # fields for configmap 23 | REPLACE_TRANSPORT_PROTOCOL=ISCSI 24 | REPLACE_PORTGROUP=group 25 | 26 | # storage class params 27 | REPLACE_RESOURCE_POOL=pool 28 | REPLACE_SERVICE_LEVEL=Bronze -------------------------------------------------------------------------------- /test/multi-az/scripts/log_utils.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 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 | SCREEN_W=${SCREEN_W:-120} 15 | NL=$'\n' 16 | 17 | print_box() 18 | { 19 | local SIDE_W=1 20 | local SIDE_CH='|' 21 | local PAD_W=2 22 | local PAD_CH=' ' 23 | local HEADER_CH='-' 24 | 25 | fp_print_multiline "$1" 26 | } 27 | 28 | print_msg() 29 | { 30 | local SIDE_W=3 31 | local SIDE_CH='-' 32 | local PAD_W=2 33 | local PAD_CH=' ' 34 | local HEADER_CH='' 35 | 36 | fp_print_multiline "$1" 37 | } 38 | 39 | print_err() 40 | { 41 | local SIDE_W=1 42 | local SIDE_CH='#' 43 | local PAD_W=2 44 | local PAD_CH=' ' 45 | local HEADER_CH='#' 46 | 47 | fp_print_multiline "$1" >&2 48 | } 49 | 50 | print_on_err() 51 | { 52 | local RC=$? 53 | [ $RC -ne 0 ] && print_err "$1 (rc=$RC)" 54 | return $RC 55 | } 56 | 57 | exit_on_err() 58 | { 59 | print_on_err "$1" || exit $? 60 | } 61 | 62 | 63 | fp_gen_string() 64 | { 65 | printf -- "$1%.0s" $(seq 1 $2) 66 | } 67 | 68 | fp_print_line() 69 | { 70 | local MSG_LINE="$1" 71 | local MSG_W=$(echo -n "$MSG_LINE" | wc -m) 72 | 73 | if [ "$2" == "c" ]; then 74 | local LPAD_W=$(( ($SCREEN_W-$MSG_W-$SIDE_W*2)/2 )) 75 | local RPAD_W=$(( $SCREEN_W-$MSG_W-$SIDE_W*2-$LPAD_W )) 76 | else 77 | local LPAD_W=$PAD_W 78 | local RPAD_W=$(( $SCREEN_W-$MSG_W-$SIDE_W*2-$PAD_W )) 79 | fi 80 | 81 | local SIDE=$(fp_gen_string "$SIDE_CH" $SIDE_W) 82 | local LPAD=$(fp_gen_string "$PAD_CH" $LPAD_W) 83 | local RPAD=$(fp_gen_string "$PAD_CH" $RPAD_W) 84 | 85 | echo "${SIDE}${LPAD}${MSG_LINE}${RPAD}${SIDE}" 86 | } 87 | 88 | fp_print_multiline() 89 | { 90 | local MSG="$1" 91 | local MAX_MSG_W=$(( $SCREEN_W-($SIDE_W+$PAD_W)*2 )) 92 | local HEADER="" 93 | 94 | [ -n "$HEADER_CH" ] && HEADER=$(fp_gen_string "$HEADER_CH" $SCREEN_W) 95 | 96 | [ -n "$HEADER" ] && echo "$HEADER" 97 | 98 | local MSG_LINES=$(echo "$MSG" | fold -s -w$MAX_MSG_W) 99 | echo "$MSG_LINES" | while IFS= read ML; do 100 | fp_print_line "$ML" 101 | done 102 | 103 | [ -n "$HEADER" ] && echo "$HEADER" || true 104 | } 105 | 106 | : ' 107 | print_box "\ 108 | First line 109 | Final line" 110 | 111 | print_box "First line 112 | Final line" 113 | 114 | print_box "First line${NL}Final line" 115 | 116 | print_msg "Below is a long message:${NL}Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law ..." 117 | ' 118 | -------------------------------------------------------------------------------- /test/multi-az/scripts/replace.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | config="$1" 4 | template="$2" 5 | destination="$3" 6 | 7 | cp "$template" "$destination" 8 | 9 | while IFS= read -r line || [ -n "$line" ]; do 10 | # Skip lines that begin with a # or are empty 11 | [[ $line == \#* || -z $line ]] && continue 12 | 13 | setting="$( echo "$line" | cut -d '=' -f 1 )" 14 | value="$( echo "$line" | cut -d '=' -f 2- )" 15 | 16 | sed -i -e "s;${setting};${value};g" "$destination" 17 | done < "$config" -------------------------------------------------------------------------------- /test/multi-az/testfiles/openssl.cnf: -------------------------------------------------------------------------------- 1 | [ req ] 2 | default_bits = 2048 3 | distinguished_name = req_distinguished_name 4 | req_extensions = req_ext 5 | prompt = no 6 | [ req_distinguished_name ] 7 | C = XX 8 | L = Default City 9 | O = Default Company Ltd 10 | [ req_ext ] 11 | subjectAltName = @alt_names 12 | [ alt_names ] 13 | DNS.1 = "csipowermax-reverseproxy" 14 | IP.1 = "0.0.0.0" 15 | -------------------------------------------------------------------------------- /test/multi-az/testfiles/templates/template-powermax-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: powermax-array-config 5 | namespace: powermax 6 | data: 7 | powermax-array-config.yaml: | 8 | # List of comma-separated port groups (ISCSI only). Example: PortGroup1, portGroup2 Required for iSCSI only 9 | X_CSI_POWERMAX_PORTGROUPS: "REPLACE_PORTGROUP" 10 | # Choose which transport protocol to use (ISCSI, FC, NVMETCP, auto) defaults to auto if nothing is specified 11 | X_CSI_TRANSPORT_PROTOCOL: "REPLACE_TRANSPORT_PROTOCOL" 12 | # List of comma-separated array ID(s) which will be managed by the driver (Required) 13 | X_CSI_MANAGED_ARRAYS: "REPLACE_STORAGE_ID_1,REPLACE_STORAGE_ID_2" 14 | -------------------------------------------------------------------------------- /test/multi-az/testfiles/templates/template-powermax-secret.yaml: -------------------------------------------------------------------------------- 1 | storageArrays: 2 | - storageArrayId: "REPLACE_STORAGE_ID_1" 3 | primaryEndpoint: https://REPLACE_PRIMARY_ENDPOINT_1:REPLACE_PRIMARY_PORT_1 4 | backupEndpoint: https://REPLACE_SECONDARY_ENDPOINT_1:REPLACE_SECONDARY_PORT_1 5 | labels: 6 | zone.topology.kubernetes.io/region: zoneA 7 | zone.topology.kubernetes.io/zone: zoneC 8 | - storageArrayId: "REPLACE_STORAGE_ID_2" 9 | primaryEndpoint: https://REPLACE_PRIMARY_ENDPOINT_2:REPLACE_PRIMARY_PORT_2 10 | backupEndpoint: https://REPLACE_SECONDARY_ENDPOINT_2:REPLACE_SECONDARY_PORT_2 11 | labels: 12 | zone.topology.kubernetes.io/region: zoneB 13 | zone.topology.kubernetes.io/zone: zoneD 14 | managementservers: 15 | - endpoint: https://REPLACE_PRIMARY_ENDPOINT_1:REPLACE_PRIMARY_PORT_1 16 | username: REPLACE_USER_1 17 | password: REPLACE_PASS_1 18 | skipCertificateValidation: true 19 | - endpoint: https://REPLACE_SECONDARY_ENDPOINT_1:REPLACE_SECONDARY_PORT_1 20 | username: REPLACE_USER_1 21 | password: REPLACE_PASS_1 22 | skipCertificateValidation: true 23 | - endpoint: https://REPLACE_PRIMARY_ENDPOINT_2:REPLACE_PRIMARY_PORT_2 24 | username: REPLACE_USER_2 25 | password: REPLACE_PASS_2 26 | skipCertificateValidation: true 27 | - endpoint: https://REPLACE_SECONDARY_ENDPOINT_2:REPLACE_SECONDARY_PORT_2 28 | username: REPLACE_USER_2 29 | password: REPLACE_PASS_2 30 | skipCertificateValidation: true 31 | -------------------------------------------------------------------------------- /test/multi-az/testfiles/templates/template-powermax-storageclass-1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: storage.k8s.io/v1 2 | kind: StorageClass 3 | metadata: 4 | # authorization expects this storage class name 5 | # please do not change 6 | name: pmax-mz-1 7 | parameters: 8 | SRP: "REPLACE_RESOURCE_POOL" 9 | ServiceLevel: "REPLACE_SERVICE_LEVEL" 10 | provisioner: csi-powermax.dellemc.com 11 | reclaimPolicy: Delete 12 | volumeBindingMode: WaitForFirstConsumer 13 | allowVolumeExpansion: true 14 | allowedTopologies: 15 | - matchLabelExpressions: 16 | - key: zone.topology.kubernetes.io/region 17 | values: 18 | - zoneA 19 | - key: zone.topology.kubernetes.io/zone 20 | values: 21 | - zoneC 22 | -------------------------------------------------------------------------------- /test/multi-az/testfiles/templates/template-powermax-storageclass-2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: storage.k8s.io/v1 2 | kind: StorageClass 3 | metadata: 4 | # authorization expects this storage class name 5 | # please do not change 6 | name: pmax-mz-2 7 | parameters: 8 | SRP: "REPLACE_RESOURCE_POOL" 9 | ServiceLevel: "REPLACE_SERVICE_LEVEL" 10 | provisioner: csi-powermax.dellemc.com 11 | reclaimPolicy: Delete 12 | volumeBindingMode: WaitForFirstConsumer 13 | allowVolumeExpansion: true 14 | allowedTopologies: 15 | - matchLabelExpressions: 16 | - key: zone.topology.kubernetes.io/region 17 | values: 18 | - zoneB 19 | - key: zone.topology.kubernetes.io/zone 20 | values: 21 | - zoneD 22 | -------------------------------------------------------------------------------- /test/multi-az/testfiles/templates/template-powermax-storageclass-zoneless-arrayid.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: storage.k8s.io/v1 2 | kind: StorageClass 3 | metadata: 4 | # authorization expects this storage class name 5 | # please do not change 6 | name: pmax-mz-none-symid 7 | parameters: 8 | SYMID: "REPLACE_STORAGE_ID_1" 9 | SRP: "REPLACE_RESOURCE_POOL" 10 | ServiceLevel: "REPLACE_SERVICE_LEVEL" 11 | provisioner: csi-powermax.dellemc.com 12 | reclaimPolicy: Delete 13 | volumeBindingMode: WaitForFirstConsumer 14 | allowVolumeExpansion: true 15 | -------------------------------------------------------------------------------- /test/multi-az/testfiles/templates/template-powermax-storageclass-zoneless.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: storage.k8s.io/v1 2 | kind: StorageClass 3 | metadata: 4 | # authorization expects this storage class name 5 | # please do not change 6 | name: pmax-mz-none 7 | parameters: 8 | SRP: "REPLACE_RESOURCE_POOL" 9 | ServiceLevel: "REPLACE_SERVICE_LEVEL" 10 | provisioner: csi-powermax.dellemc.com 11 | reclaimPolicy: Delete 12 | volumeBindingMode: WaitForFirstConsumer 13 | allowVolumeExpansion: true 14 | -------------------------------------------------------------------------------- /test/sanity/README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Sanity Script Test 2 | 3 | This test runs the Kubernetes sanity test at https://github.com/kubernetes-csi/csi-test. 4 | To run the test, follow these steps: 5 | 6 | 1. "go get github.com/kubernetes-csi/csi-test" 7 | 2. Build and install the executable, csi-sanity, in a directory in your $PATH. 8 | 3. Make sure your env.sh is up to date so the CSI driver can be run. 9 | 4. Edit the secrets.yaml to have the correct SYMID, ServiceLevel, SRP, and ApplicationPrefix. 10 | 5. Use the start_driver.sh to start the driver. USE AN OTHERWISE IDLE ARRAY FOR THE TEST. 11 | 6. Wait until the driver has fully come up and completed node setup. If you remain attached the logs will print on the screen. 12 | 7. Use the script run.sh to start csi-sanity (best if you do this in a separate window.) 13 | 14 | ## Excluded Tests 15 | 16 | The following tests were excluded for the reasons specified: 17 | 18 | 1. GetCapacity -- the test does not support supplying the Parameters fields that are required (for things like SYMID). 19 | The test returns an appropriate error: "GetCapacity: Required StoragePool and SymID in parameters" 20 | 21 | 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; 22 | the new size is over the maximum capability, and we detect that error first and disqualify the request, which I think is valid. 23 | The error message is: " rpc error: code = OutOfRange desc = bad capacity: size in bytes 10738728960 exceeds limit size bytes 10737418240" 24 | 25 | 3. A test to create a volume from an existing source snapshot. Same problem as 2. 26 | 27 | 4. A test to create a volume from a non-exiting snapshot. Same problem as 2. 28 | 29 | 5. A test to delete a snapshot. Same problem as 2. 30 | 31 | 6. An idempotent test to create a snapshot with already existing name and same source volume_id. Same problem as 2. 32 | 33 | 7. A test to create a snapshot with maximum-length name. Same problem as 2. 34 | 35 | 8. A test to delete a snapshot with an invalid name. The test fails because the csi-powermax driver follows a certain naming convention and the passed invalid snapshot name doesn't comply with that. 36 | 37 | 9. A test to create a volume from an existing source volume. Same problem as 2. 38 | 39 | 10. A test to expand volume fails because the returned expanded byte size is approximate size in cylinder bytes. Whereas the test expects the volume size to be same as requested size. 40 | 41 | 11. A volume lifecycle test, the cleanup section of this test fails because the csi-powermax driver is getting DeleteVolume call before ControllerUnpublishVolume call, which is not a supported scenario for a published volume. 42 | -------------------------------------------------------------------------------- /test/sanity/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NRUNS=10 4 | 5 | run() { 6 | rm -rf /tmp/csi-mount 7 | rm -rf /tmp/csi-staging 8 | export GRPC_GO_LOG_SEVERITY_LEVEL="info" 9 | export GRPC_GO_LOG_VERBOSITY_LEVEL=2 10 | csi-sanity --ginkgo.v \ 11 | --csi.endpoint=$(pwd)/unix_sock \ 12 | --csi.secrets=secrets.yaml \ 13 | --ginkgo.skip "GetCapacity|ListSnapshots|create a volume with already existing name and different capacity" \ 14 | 15 | } 16 | 17 | i=0 18 | while [ $i -lt $NRUNS ]; 19 | do 20 | i=$(expr $i + 1) 21 | echo "*********************** run $i **************************" 22 | run 23 | done 24 | -------------------------------------------------------------------------------- /test/sanity/secrets.yaml: -------------------------------------------------------------------------------- 1 | CreateVolumeSecret: 2 | secretKey: secretval1 3 | SYMID: "" 4 | ServiceLevel: Bronze 5 | SRP: SRP_1 6 | ApplicationPrefix: sanity 7 | DeleteVolumeSecret: 8 | secretKey: secretval2 9 | ControllerPublishVolumeSecret: 10 | secretKey: secretval3 11 | ControllerUnpublishVolumeSecret: 12 | secretKey: secretval4 13 | NodeStageVolumeSecret: 14 | secretKey: secretval5 15 | NodePublishVolumeSecret: 16 | secretKey: secretval6 17 | ControllerValidateVolumeCapabilitiesSecret: 18 | secretKey: secretval7 19 | -------------------------------------------------------------------------------- /test/sanity/start_driver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This will run coverage analysis using the integration testing. 3 | # The env.sh must point to a valid Unisphere deployment and the iscsi packages must be installed 4 | # on this system. This will make real calls to Unisphere 5 | 6 | rm -f unix_sock 7 | . ../../env.sh 8 | export X_CSI_POWERMAX_RESPONSE_TIMES="true" 9 | echo ENDPOINT $X_CSI_POWERMAX_ENDPOINT 10 | echo "Starting the csi-powermax driver. You should wait until the node setup is complete before running tests." 11 | ../../csi-powermax 12 | -------------------------------------------------------------------------------- /test/scale/README.md: -------------------------------------------------------------------------------- 1 | ## Scalability and Longevity Tests 2 | 3 | The Scalability and Longevity tests have been refactored into a seperate git repository. 4 | 5 | --------------------------------------------------------------------------------