commit
						8cec5cb3c5
					
				|  | @ -1,9 +1,9 @@ | ||||||
| chart | ** | ||||||
| dev | 
 | ||||||
| examples | !/bin | ||||||
| contrib | !/csi_proto | ||||||
| node_modules | !/csi_proxy_proto | ||||||
| Dockerfile* | !/docker | ||||||
| TODO.md | !/LICENSE | ||||||
| .git | !/package*.json | ||||||
| /ci | !/src | ||||||
|  |  | ||||||
|  | @ -0,0 +1,44 @@ | ||||||
|  | #!/bin/bash | ||||||
|  | 
 | ||||||
|  | set -e | ||||||
|  | 
 | ||||||
|  | echo "$DOCKER_PASSWORD" | docker login docker.io -u "$DOCKER_USERNAME" --password-stdin | ||||||
|  | echo "$GHCR_PASSWORD"   | docker login ghcr.io   -u "$GHCR_USERNAME"   --password-stdin | ||||||
|  | 
 | ||||||
|  | export DOCKER_ORG="democraticcsi" | ||||||
|  | export DOCKER_PROJECT="democratic-csi" | ||||||
|  | export DOCKER_REPO="docker.io/${DOCKER_ORG}/${DOCKER_PROJECT}" | ||||||
|  | 
 | ||||||
|  | export GHCR_ORG="democratic-csi" | ||||||
|  | export GHCR_PROJECT="democratic-csi" | ||||||
|  | export GHCR_REPO="ghcr.io/${GHCR_ORG}/${GHCR_PROJECT}" | ||||||
|  | 
 | ||||||
|  | export MANIFEST_NAME="democratic-csi-combined:${IMAGE_TAG}" | ||||||
|  | 
 | ||||||
|  | if [[ -n "${IMAGE_TAG}" ]]; then | ||||||
|  |   # create local manifest to work with | ||||||
|  |   buildah manifest rm "${MANIFEST_NAME}" || true | ||||||
|  |   buildah manifest create "${MANIFEST_NAME}" | ||||||
|  |    | ||||||
|  |   # all all the existing linux data to the manifest | ||||||
|  |   buildah manifest add "${MANIFEST_NAME}" --all "${DOCKER_REPO}:${IMAGE_TAG}" | ||||||
|  |   buildah manifest inspect "${MANIFEST_NAME}" | ||||||
|  |    | ||||||
|  |   # import pre-built images | ||||||
|  |   buildah pull docker-archive:democratic-csi-windows-ltsc2019.tar | ||||||
|  |   buildah pull docker-archive:democratic-csi-windows-ltsc2022.tar | ||||||
|  | 
 | ||||||
|  |   # add pre-built images to manifest | ||||||
|  |   buildah manifest add "${MANIFEST_NAME}" democratic-csi-windows:${GITHUB_RUN_ID}-ltsc2019 | ||||||
|  |   buildah manifest add "${MANIFEST_NAME}" democratic-csi-windows:${GITHUB_RUN_ID}-ltsc2022 | ||||||
|  |   buildah manifest inspect "${MANIFEST_NAME}" | ||||||
|  | 
 | ||||||
|  |   # push manifest | ||||||
|  |   buildah manifest push --all "${MANIFEST_NAME}" docker://${DOCKER_REPO}:${IMAGE_TAG} | ||||||
|  |   buildah manifest push --all "${MANIFEST_NAME}" docker://${GHCR_REPO}:${IMAGE_TAG} | ||||||
|  | 
 | ||||||
|  |   # cleanup | ||||||
|  |   buildah manifest rm "${MANIFEST_NAME}" || true | ||||||
|  | else | ||||||
|  |   : | ||||||
|  | fi | ||||||
|  | @ -11,20 +11,12 @@ export GHCR_ORG="democratic-csi" | ||||||
| export GHCR_PROJECT="democratic-csi" | export GHCR_PROJECT="democratic-csi" | ||||||
| export GHCR_REPO="ghcr.io/${GHCR_ORG}/${GHCR_PROJECT}" | export GHCR_REPO="ghcr.io/${GHCR_ORG}/${GHCR_PROJECT}" | ||||||
| 
 | 
 | ||||||
| if [[ $GITHUB_REF == refs/tags/* ]]; then | if [[ -n "${IMAGE_TAG}" ]]; then | ||||||
|   export GIT_TAG=${GITHUB_REF#refs/tags/} |   # -t ${GHCR_REPO}:${IMAGE_TAG} | ||||||
| else |   docker buildx build --progress plain --pull --push --platform "${DOCKER_BUILD_PLATFORM}" -t ${DOCKER_REPO}:${IMAGE_TAG} \ | ||||||
|   export GIT_BRANCH=${GITHUB_REF#refs/heads/} |   --label "org.opencontainers.image.created=$(date -u --iso-8601=seconds)" \ | ||||||
| fi |   --label "org.opencontainers.image.revision=${GITHUB_SHA}" \ | ||||||
| 
 |   . | ||||||
| if [[ -n "${GIT_TAG}" ]]; then |  | ||||||
|   docker buildx build --progress plain --pull --push --platform "${DOCKER_BUILD_PLATFORM}" -t ${DOCKER_REPO}:${GIT_TAG} -t ${GHCR_REPO}:${GIT_TAG} . |  | ||||||
| elif [[ -n "${GIT_BRANCH}" ]]; then |  | ||||||
|   if [[ "${GIT_BRANCH}" == "master" ]]; then |  | ||||||
|     docker buildx build --progress plain --pull --push --platform "${DOCKER_BUILD_PLATFORM}" -t ${DOCKER_REPO}:latest -t ${GHCR_REPO}:latest . |  | ||||||
|   else |  | ||||||
|     docker buildx build --progress plain --pull --push --platform "${DOCKER_BUILD_PLATFORM}" -t ${DOCKER_REPO}:${GIT_BRANCH} -t ${GHCR_REPO}:${GIT_BRANCH} . |  | ||||||
|   fi |  | ||||||
| else | else | ||||||
|   : |   : | ||||||
| fi | fi | ||||||
|  |  | ||||||
|  | @ -17,12 +17,13 @@ jobs: | ||||||
|         with: |         with: | ||||||
|           access_token: ${{ github.token }} |           access_token: ${{ github.token }} | ||||||
| 
 | 
 | ||||||
|   build-npm: |   build-npm-linux-amd64: | ||||||
|     name: build-npm |     runs-on: ubuntu-20.04 | ||||||
|     runs-on: |  | ||||||
|       - self-hosted |  | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|  |       - uses: actions/setup-node@v3 | ||||||
|  |         with: | ||||||
|  |           node-version: 16 | ||||||
|       - shell: bash |       - shell: bash | ||||||
|         name: npm install |         name: npm install | ||||||
|         run: | |         run: | | ||||||
|  | @ -30,35 +31,84 @@ jobs: | ||||||
|       - name: upload build |       - name: upload build | ||||||
|         uses: actions/upload-artifact@v2 |         uses: actions/upload-artifact@v2 | ||||||
|         with: |         with: | ||||||
|           name: node-modules |           name: node-modules-linux-amd64 | ||||||
|           #path: node_modules/ |           path: node_modules-linux-amd64.tar.gz | ||||||
|           path: node_modules.tar.gz |           retention-days: 1 | ||||||
|           retention-days: 7 |  | ||||||
| 
 | 
 | ||||||
|   csi-sanity-synology: |   build-npm-windows-amd64: | ||||||
|  |     runs-on: windows-2022 | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - uses: actions/setup-node@v3 | ||||||
|  |         with: | ||||||
|  |           node-version: 16 | ||||||
|  |       - shell: pwsh | ||||||
|  |         name: npm install | ||||||
|  |         run: | | ||||||
|  |           ci\bin\build.ps1 | ||||||
|  |       - name: upload build | ||||||
|  |         uses: actions/upload-artifact@v2 | ||||||
|  |         with: | ||||||
|  |           name: node-modules-windows-amd64 | ||||||
|  |           path: node_modules-windows-amd64.tar.gz | ||||||
|  |           retention-days: 1 | ||||||
|  | 
 | ||||||
|  |   csi-sanity-synology-dsm6: | ||||||
|     needs: |     needs: | ||||||
|       - build-npm |       - build-npm-linux-amd64 | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|         config: |         config: | ||||||
|           - synlogy/iscsi.yaml |           - synlogy/dsm6/iscsi.yaml | ||||||
|     runs-on: |     runs-on: | ||||||
|       - self-hosted |       - self-hosted | ||||||
|  |       - Linux | ||||||
|  |       - X64 | ||||||
|       - csi-sanity-synology |       - csi-sanity-synology | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - uses: actions/download-artifact@v2 |       - uses: actions/download-artifact@v2 | ||||||
|         with: |         with: | ||||||
|           name: node-modules |           name: node-modules-linux-amd64 | ||||||
|       - name: csi-sanity |       - name: csi-sanity | ||||||
|         run: | |         run: | | ||||||
|           # run tests |           # run tests | ||||||
|           ci/bin/run.sh |           ci/bin/run.sh | ||||||
|         env: |         env: | ||||||
|           TEMPLATE_CONFIG_FILE: "./ci/configs/${{ matrix.config }}" |           TEMPLATE_CONFIG_FILE: "./ci/configs/${{ matrix.config }}" | ||||||
|           SYNOLOGY_HOST: ${{ secrets.SANITY_SYNOLOGY_HOST }} |           SYNOLOGY_HOST: ${{ secrets.SANITY_SYNOLOGY_DSM6_HOST }} | ||||||
|           SYNOLOGY_PORT: ${{ secrets.SANITY_SYNOLOGY_PORT }} |           SYNOLOGY_PORT: ${{ secrets.SANITY_SYNOLOGY_DSM6_PORT }} | ||||||
|  |           SYNOLOGY_USERNAME: ${{ secrets.SANITY_SYNOLOGY_USERNAME }} | ||||||
|  |           SYNOLOGY_PASSWORD: ${{ secrets.SANITY_SYNOLOGY_PASSWORD }} | ||||||
|  |           SYNOLOGY_VOLUME: ${{ secrets.SANITY_SYNOLOGY_VOLUME }} | ||||||
|  | 
 | ||||||
|  |   csi-sanity-synology-dsm7: | ||||||
|  |     needs: | ||||||
|  |       - build-npm-linux-amd64 | ||||||
|  |     strategy: | ||||||
|  |       fail-fast: false | ||||||
|  |       matrix: | ||||||
|  |         config: | ||||||
|  |           - synlogy/dsm7/iscsi.yaml | ||||||
|  |     runs-on: | ||||||
|  |       - self-hosted | ||||||
|  |       - Linux | ||||||
|  |       - X64 | ||||||
|  |       - csi-sanity-synology | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - uses: actions/download-artifact@v2 | ||||||
|  |         with: | ||||||
|  |           name: node-modules-linux-amd64 | ||||||
|  |       - name: csi-sanity | ||||||
|  |         run: | | ||||||
|  |           # run tests | ||||||
|  |           ci/bin/run.sh | ||||||
|  |         env: | ||||||
|  |           TEMPLATE_CONFIG_FILE: "./ci/configs/${{ matrix.config }}" | ||||||
|  |           SYNOLOGY_HOST: ${{ secrets.SANITY_SYNOLOGY_DSM7_HOST }} | ||||||
|  |           SYNOLOGY_PORT: ${{ secrets.SANITY_SYNOLOGY_DSM7_PORT }} | ||||||
|           SYNOLOGY_USERNAME: ${{ secrets.SANITY_SYNOLOGY_USERNAME }} |           SYNOLOGY_USERNAME: ${{ secrets.SANITY_SYNOLOGY_USERNAME }} | ||||||
|           SYNOLOGY_PASSWORD: ${{ secrets.SANITY_SYNOLOGY_PASSWORD }} |           SYNOLOGY_PASSWORD: ${{ secrets.SANITY_SYNOLOGY_PASSWORD }} | ||||||
|           SYNOLOGY_VOLUME: ${{ secrets.SANITY_SYNOLOGY_VOLUME }} |           SYNOLOGY_VOLUME: ${{ secrets.SANITY_SYNOLOGY_VOLUME }} | ||||||
|  | @ -66,7 +116,7 @@ jobs: | ||||||
|   # api-based drivers |   # api-based drivers | ||||||
|   csi-sanity-truenas-scale-22_02: |   csi-sanity-truenas-scale-22_02: | ||||||
|     needs: |     needs: | ||||||
|       - build-npm |       - build-npm-linux-amd64 | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|  | @ -74,16 +124,18 @@ jobs: | ||||||
|           - truenas/scale/22.02/scale-iscsi.yaml |           - truenas/scale/22.02/scale-iscsi.yaml | ||||||
|           - truenas/scale/22.02/scale-nfs.yaml |           - truenas/scale/22.02/scale-nfs.yaml | ||||||
|           # 80 char limit |           # 80 char limit | ||||||
|           #- truenas/scale-smb.yaml |           - truenas/scale/22.02/scale-smb.yaml | ||||||
|     runs-on: |     runs-on: | ||||||
|       - self-hosted |       - self-hosted | ||||||
|       - csi-sanity-zfs-local |       - Linux | ||||||
|       #- csi-sanity-truenas-scale |       - X64 | ||||||
|  |       - csi-sanity-truenas | ||||||
|  |       #- csi-sanity-zfs-generic | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - uses: actions/download-artifact@v2 |       - uses: actions/download-artifact@v2 | ||||||
|         with: |         with: | ||||||
|           name: node-modules |           name: node-modules-linux-amd64 | ||||||
|       - name: csi-sanity |       - name: csi-sanity | ||||||
|         run: | |         run: | | ||||||
|           # run tests |           # run tests | ||||||
|  | @ -97,25 +149,27 @@ jobs: | ||||||
|   # ssh-based drivers |   # ssh-based drivers | ||||||
|   csi-sanity-truenas-core-12_0: |   csi-sanity-truenas-core-12_0: | ||||||
|     needs: |     needs: | ||||||
|       - build-npm |       - build-npm-linux-amd64 | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|         config: |         config: | ||||||
|           # 63 char limit |           # 63 char limit | ||||||
|           #- truenas/core-iscsi.yaml |           - truenas/core/12.0/core-iscsi.yaml | ||||||
|           - truenas/core/12.0/core-nfs.yaml |           - truenas/core/12.0/core-nfs.yaml | ||||||
|           # 80 char limit |           # 80 char limit | ||||||
|           #- truenas/core-smb.yaml |           - truenas/core/12.0/core-smb.yaml | ||||||
|     runs-on: |     runs-on: | ||||||
|       - self-hosted |       - self-hosted | ||||||
|       - csi-sanity-zfs-local |       - Linux | ||||||
|       #- csi-sanity-truenas-core |       - X64 | ||||||
|  |       #- csi-sanity-truenas | ||||||
|  |       - csi-sanity-zfs-generic | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - uses: actions/download-artifact@v2 |       - uses: actions/download-artifact@v2 | ||||||
|         with: |         with: | ||||||
|           name: node-modules |           name: node-modules-linux-amd64 | ||||||
|       - name: csi-sanity |       - name: csi-sanity | ||||||
|         run: | |         run: | | ||||||
|           # run tests |           # run tests | ||||||
|  | @ -129,7 +183,7 @@ jobs: | ||||||
|   # ssh-based drivers |   # ssh-based drivers | ||||||
|   csi-sanity-truenas-core-13_0: |   csi-sanity-truenas-core-13_0: | ||||||
|     needs: |     needs: | ||||||
|       - build-npm |       - build-npm-linux-amd64 | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|  | @ -137,16 +191,18 @@ jobs: | ||||||
|           - truenas/core/13.0/core-iscsi.yaml |           - truenas/core/13.0/core-iscsi.yaml | ||||||
|           - truenas/core/13.0/core-nfs.yaml |           - truenas/core/13.0/core-nfs.yaml | ||||||
|           # 80 char limit |           # 80 char limit | ||||||
|           #- truenas/core-smb.yaml |           - truenas/core/13.0/core-smb.yaml | ||||||
|     runs-on: |     runs-on: | ||||||
|       - self-hosted |       - self-hosted | ||||||
|       - csi-sanity-zfs-local |       - Linux | ||||||
|       #- csi-sanity-truenas-core |       - X64 | ||||||
|  |       #- csi-sanity-truenas | ||||||
|  |       - csi-sanity-zfs-generic | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - uses: actions/download-artifact@v2 |       - uses: actions/download-artifact@v2 | ||||||
|         with: |         with: | ||||||
|           name: node-modules |           name: node-modules-linux-amd64 | ||||||
|       - name: csi-sanity |       - name: csi-sanity | ||||||
|         run: | |         run: | | ||||||
|           # run tests |           # run tests | ||||||
|  | @ -160,21 +216,24 @@ jobs: | ||||||
|   # ssh-based drivers |   # ssh-based drivers | ||||||
|   csi-sanity-zfs-generic: |   csi-sanity-zfs-generic: | ||||||
|     needs: |     needs: | ||||||
|       - build-npm |       - build-npm-linux-amd64 | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|         config: |         config: | ||||||
|           - zfs-generic/iscsi.yaml |           - zfs-generic/iscsi.yaml | ||||||
|           - zfs-generic/nfs.yaml |           - zfs-generic/nfs.yaml | ||||||
|  |           - zfs-generic/smb.yaml | ||||||
|     runs-on: |     runs-on: | ||||||
|       - self-hosted |       - self-hosted | ||||||
|  |       - Linux | ||||||
|  |       - X64 | ||||||
|       - csi-sanity-zfs-generic |       - csi-sanity-zfs-generic | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - uses: actions/download-artifact@v2 |       - uses: actions/download-artifact@v2 | ||||||
|         with: |         with: | ||||||
|           name: node-modules |           name: node-modules-linux-amd64 | ||||||
|       - name: csi-sanity |       - name: csi-sanity | ||||||
|         run: | |         run: | | ||||||
|           # run tests |           # run tests | ||||||
|  | @ -185,10 +244,70 @@ jobs: | ||||||
|           SERVER_USERNAME: ${{ secrets.SANITY_ZFS_GENERIC_USERNAME }} |           SERVER_USERNAME: ${{ secrets.SANITY_ZFS_GENERIC_USERNAME }} | ||||||
|           SERVER_PASSWORD: ${{ secrets.SANITY_ZFS_GENERIC_PASSWORD }} |           SERVER_PASSWORD: ${{ secrets.SANITY_ZFS_GENERIC_PASSWORD }} | ||||||
| 
 | 
 | ||||||
|  |   # client drivers | ||||||
|  |   csi-sanity-client: | ||||||
|  |     needs: | ||||||
|  |       - build-npm-linux-amd64 | ||||||
|  |     strategy: | ||||||
|  |       fail-fast: false | ||||||
|  |       matrix: | ||||||
|  |         config: | ||||||
|  |           - client/nfs.yaml | ||||||
|  |           - client/smb.yaml | ||||||
|  |     runs-on: | ||||||
|  |       - self-hosted | ||||||
|  |       - Linux | ||||||
|  |       - X64 | ||||||
|  |       - csi-sanity-client | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - uses: actions/download-artifact@v2 | ||||||
|  |         with: | ||||||
|  |           name: node-modules-linux-amd64 | ||||||
|  |       - name: csi-sanity | ||||||
|  |         run: | | ||||||
|  |           # run tests | ||||||
|  |           ci/bin/run.sh | ||||||
|  |         env: | ||||||
|  |           TEMPLATE_CONFIG_FILE: "./ci/configs/${{ matrix.config }}" | ||||||
|  |           SERVER_HOST: ${{ secrets.SANITY_ZFS_GENERIC_HOST }} | ||||||
|  |           SHARE_NAME: tank_client_smb | ||||||
|  |           CSI_SANITY_SKIP: "should fail when requesting to create a snapshot with already existing name and different source volume ID|should fail when requesting to create a volume with already existing name and different capacity" | ||||||
|  | 
 | ||||||
|  |   csi-sanity-client-windows: | ||||||
|  |     needs: | ||||||
|  |       - build-npm-windows-amd64 | ||||||
|  |     strategy: | ||||||
|  |       fail-fast: false | ||||||
|  |       matrix: | ||||||
|  |         config: | ||||||
|  |           - client\smb.yaml | ||||||
|  |     runs-on: | ||||||
|  |       - self-hosted | ||||||
|  |       - Windows | ||||||
|  |       - X64 | ||||||
|  |       - csi-sanity-client | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - uses: actions/download-artifact@v2 | ||||||
|  |         with: | ||||||
|  |           name: node-modules-windows-amd64 | ||||||
|  |       - name: csi-sanity | ||||||
|  |         run: | | ||||||
|  |           # run tests | ||||||
|  |           ci\bin\run.ps1 | ||||||
|  |         env: | ||||||
|  |           TEMPLATE_CONFIG_FILE: ".\\ci\\configs\\${{ matrix.config }}" | ||||||
|  |           SERVER_HOST: ${{ secrets.SANITY_ZFS_GENERIC_HOST }} | ||||||
|  |           SHARE_NAME: tank_client_smb | ||||||
|  |           CSI_SANITY_SKIP: "should fail when requesting to create a snapshot with already existing name and different source volume ID|should fail when requesting to create a volume with already existing name and different capacity" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|   # zfs-local drivers |   # zfs-local drivers | ||||||
|   csi-sanity-zfs-local: |   csi-sanity-zfs-local: | ||||||
|     needs: |     needs: | ||||||
|       - build-npm |       - build-npm-linux-amd64 | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|  | @ -197,12 +316,14 @@ jobs: | ||||||
|           - zfs-local/dataset.yaml |           - zfs-local/dataset.yaml | ||||||
|     runs-on: |     runs-on: | ||||||
|       - self-hosted |       - self-hosted | ||||||
|  |       - Linux | ||||||
|  |       - X64 | ||||||
|       - csi-sanity-zfs-local |       - csi-sanity-zfs-local | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - uses: actions/download-artifact@v2 |       - uses: actions/download-artifact@v2 | ||||||
|         with: |         with: | ||||||
|           name: node-modules |           name: node-modules-linux-amd64 | ||||||
|       - name: csi-sanity |       - name: csi-sanity | ||||||
|         run: | |         run: | | ||||||
|           # run tests |           # run tests | ||||||
|  | @ -213,36 +334,108 @@ jobs: | ||||||
|   # local-hostpath driver |   # local-hostpath driver | ||||||
|   csi-sanity-local-hostpath: |   csi-sanity-local-hostpath: | ||||||
|     needs: |     needs: | ||||||
|       - build-npm |       - build-npm-linux-amd64 | ||||||
|  |       - build-npm-windows-amd64 | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|         config: |         os: [Linux, Windows] | ||||||
|           - local-hostpath/basic.yaml |         include: | ||||||
|  |           - os: Linux | ||||||
|  |             npmartifact: node-modules-linux-amd64 | ||||||
|  |             template: "./ci/configs/local-hostpath/basic.yaml" | ||||||
|  |             run: | | ||||||
|  |               # run tests | ||||||
|  |               ci/bin/run.sh | ||||||
|  |           - os: Windows | ||||||
|  |             npmartifact: node-modules-windows-amd64 | ||||||
|  |             template: ".\\ci\\configs\\local-hostpath\\basic.yaml" | ||||||
|  |             run: | | ||||||
|  |               # run tests | ||||||
|  |               ci\bin\run.ps1 | ||||||
|     runs-on: |     runs-on: | ||||||
|       - self-hosted |       - self-hosted | ||||||
|  |       - ${{ matrix.os }} | ||||||
|  |       - X64 | ||||||
|  |       - csi-sanity-local-hostpath | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - uses: actions/download-artifact@v2 |       - uses: actions/download-artifact@v2 | ||||||
|         with: |         with: | ||||||
|           name: node-modules |           name: ${{ matrix.npmartifact }} | ||||||
|  |       - name: csi-sanity | ||||||
|  |         run: ${{ matrix.run }} | ||||||
|  |         env: | ||||||
|  |           TEMPLATE_CONFIG_FILE: "${{ matrix.template }}" | ||||||
|  |           CSI_SANITY_SKIP: "should fail when requesting to create a snapshot with already existing name and different source volume ID|should fail when requesting to create a volume with already existing name and different capacity" | ||||||
|  | 
 | ||||||
|  |   csi-sanity-windows-node: | ||||||
|  |     needs: | ||||||
|  |       - build-npm-windows-amd64 | ||||||
|  |     strategy: | ||||||
|  |       fail-fast: false | ||||||
|  |       matrix: | ||||||
|  |         config: | ||||||
|  |           - windows\iscsi.yaml | ||||||
|  |           - windows\smb.yaml | ||||||
|  |     runs-on: | ||||||
|  |       - self-hosted | ||||||
|  |       - Windows | ||||||
|  |       - X64 | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - uses: actions/download-artifact@v2 | ||||||
|  |         with: | ||||||
|  |           name: node-modules-windows-amd64 | ||||||
|       - name: csi-sanity |       - name: csi-sanity | ||||||
|         run: | |         run: | | ||||||
|           # run tests |           # run tests | ||||||
|           ci/bin/run.sh |           ci\bin\run.ps1 | ||||||
|         env: |         env: | ||||||
|           TEMPLATE_CONFIG_FILE: "./ci/configs/${{ matrix.config }}" |           TEMPLATE_CONFIG_FILE: ".\\ci\\configs\\${{ matrix.config }}" | ||||||
|           CSI_SANITY_SKIP: "should fail when requesting to create a snapshot with already existing name and different source volume ID|should fail when requesting to create a volume with already existing name and different capacity" |           SERVER_HOST: ${{ secrets.SANITY_ZFS_GENERIC_HOST }} | ||||||
|  |           SERVER_USERNAME: ${{ secrets.SANITY_ZFS_GENERIC_USERNAME }} | ||||||
|  |           SERVER_PASSWORD: ${{ secrets.SANITY_ZFS_GENERIC_PASSWORD }} | ||||||
|  |           CSI_SANITY_FOCUS: "Node Service" | ||||||
| 
 | 
 | ||||||
|   build-docker: |   determine-image-tag: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     outputs: | ||||||
|  |       tag: ${{ steps.tag.outputs.tag }} | ||||||
|  |     steps: | ||||||
|  |       - id: tag | ||||||
|  |         run: | | ||||||
|  |           if [[ $GITHUB_REF == refs/tags/* ]]; then | ||||||
|  |             export GIT_TAG=${GITHUB_REF#refs/tags/} | ||||||
|  |           else | ||||||
|  |             export GIT_BRANCH=${GITHUB_REF#refs/heads/} | ||||||
|  |           fi | ||||||
|  |           if [[ -n "${GIT_TAG}" ]]; then | ||||||
|  |             echo "::set-output name=tag::${GIT_TAG}" | ||||||
|  |           elif [[ -n "${GIT_BRANCH}" ]]; then | ||||||
|  |             if [[ "${GIT_BRANCH}" == "master" ]]; then | ||||||
|  |               echo "::set-output name=tag::latest" | ||||||
|  |             else | ||||||
|  |               echo "::set-output name=tag::${GIT_BRANCH}" | ||||||
|  |             fi | ||||||
|  |           else | ||||||
|  |             : | ||||||
|  |           fi | ||||||
|  | 
 | ||||||
|  |   build-docker-linux: | ||||||
|     needs: |     needs: | ||||||
|       - csi-sanity-synology |       - determine-image-tag | ||||||
|  |       - csi-sanity-synology-dsm6 | ||||||
|  |       - csi-sanity-synology-dsm7 | ||||||
|       - csi-sanity-truenas-scale-22_02 |       - csi-sanity-truenas-scale-22_02 | ||||||
|       - csi-sanity-truenas-core-12_0 |       - csi-sanity-truenas-core-12_0 | ||||||
|       - csi-sanity-truenas-core-13_0 |       - csi-sanity-truenas-core-13_0 | ||||||
|       - csi-sanity-zfs-generic |       - csi-sanity-zfs-generic | ||||||
|  |       - csi-sanity-client | ||||||
|  |       - csi-sanity-client-windows | ||||||
|       - csi-sanity-zfs-local |       - csi-sanity-zfs-local | ||||||
|       - csi-sanity-local-hostpath |       - csi-sanity-local-hostpath | ||||||
|  |       - csi-sanity-windows-node | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|  | @ -250,7 +443,7 @@ jobs: | ||||||
|         run: | |         run: | | ||||||
|           export ARCH=$([ $(uname -m) = "x86_64" ] && echo "amd64" || echo "arm64") |           export ARCH=$([ $(uname -m) = "x86_64" ] && echo "amd64" || echo "arm64") | ||||||
|           mkdir -p ~/.docker/cli-plugins/ |           mkdir -p ~/.docker/cli-plugins/ | ||||||
|           wget -qO ~/.docker/cli-plugins/docker-buildx https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.linux-${ARCH} |           wget -qO ~/.docker/cli-plugins/docker-buildx https://github.com/docker/buildx/releases/download/v0.8.2/buildx-v0.8.2.linux-${ARCH} | ||||||
|           chmod a+x ~/.docker/cli-plugins/docker-buildx |           chmod a+x ~/.docker/cli-plugins/docker-buildx | ||||||
|           docker info |           docker info | ||||||
|           docker buildx version |           docker buildx version | ||||||
|  | @ -267,3 +460,78 @@ jobs: | ||||||
|           GHCR_PASSWORD: ${{ secrets.GHCR_PASSWORD }} |           GHCR_PASSWORD: ${{ secrets.GHCR_PASSWORD }} | ||||||
|           DOCKER_CLI_EXPERIMENTAL: enabled |           DOCKER_CLI_EXPERIMENTAL: enabled | ||||||
|           DOCKER_BUILD_PLATFORM: linux/amd64,linux/arm64,linux/arm/v7,linux/s390x,linux/ppc64le |           DOCKER_BUILD_PLATFORM: linux/amd64,linux/arm64,linux/arm/v7,linux/s390x,linux/ppc64le | ||||||
|  |           IMAGE_TAG: ${{needs.determine-image-tag.outputs.tag}} | ||||||
|  | 
 | ||||||
|  |   build-docker-windows: | ||||||
|  |     needs: | ||||||
|  |       - csi-sanity-synology-dsm6 | ||||||
|  |       - csi-sanity-synology-dsm7 | ||||||
|  |       - csi-sanity-truenas-scale-22_02 | ||||||
|  |       - csi-sanity-truenas-core-12_0 | ||||||
|  |       - csi-sanity-truenas-core-13_0 | ||||||
|  |       - csi-sanity-zfs-generic | ||||||
|  |       - csi-sanity-client | ||||||
|  |       - csi-sanity-client-windows | ||||||
|  |       - csi-sanity-zfs-local | ||||||
|  |       - csi-sanity-local-hostpath | ||||||
|  |       - csi-sanity-windows-node | ||||||
|  |     runs-on: ${{ matrix.os }} | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |         os: [windows-2019, windows-2022] | ||||||
|  |         include: | ||||||
|  |           - os: windows-2019 | ||||||
|  |             core_base_tag: ltsc2019 | ||||||
|  |             nano_base_tag: "1809" | ||||||
|  |             file: Dockerfile.Windows | ||||||
|  |           - os: windows-2022 | ||||||
|  |             core_base_tag: ltsc2022 | ||||||
|  |             nano_base_tag: ltsc2022 | ||||||
|  |             file: Dockerfile.Windows | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - name: docker build | ||||||
|  |         shell: bash | ||||||
|  |         run: | | ||||||
|  |           docker info | ||||||
|  |           docker build --pull -f ${{ matrix.file }} --build-arg NANO_BASE_TAG=${{ matrix.nano_base_tag }} --build-arg CORE_BASE_TAG=${{ matrix.core_base_tag }} -t democratic-csi-windows:${GITHUB_RUN_ID}-${{ matrix.core_base_tag }} \ | ||||||
|  |           --label "org.opencontainers.image.created=$(date -u --iso-8601=seconds)" \ | ||||||
|  |           --label "org.opencontainers.image.revision=${GITHUB_SHA}" \ | ||||||
|  |           . | ||||||
|  |           docker inspect democratic-csi-windows:${GITHUB_RUN_ID}-${{ matrix.core_base_tag }} | ||||||
|  |           docker save democratic-csi-windows:${GITHUB_RUN_ID}-${{ matrix.core_base_tag }} -o democratic-csi-windows-${{ matrix.core_base_tag }}.tar | ||||||
|  |       - name: upload image tar | ||||||
|  |         uses: actions/upload-artifact@v2 | ||||||
|  |         with: | ||||||
|  |           name: democratic-csi-windows-${{ matrix.core_base_tag }}.tar | ||||||
|  |           path: democratic-csi-windows-${{ matrix.core_base_tag }}.tar | ||||||
|  |           retention-days: 1 | ||||||
|  | 
 | ||||||
|  |   push-docker-windows: | ||||||
|  |     needs: | ||||||
|  |       - build-docker-linux | ||||||
|  |       - build-docker-windows | ||||||
|  |       - determine-image-tag | ||||||
|  |     runs-on: | ||||||
|  |       - self-hosted | ||||||
|  |       - buildah | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - uses: actions/download-artifact@v3 | ||||||
|  |         with: | ||||||
|  |           name: democratic-csi-windows-ltsc2019.tar | ||||||
|  |       - uses: actions/download-artifact@v3 | ||||||
|  |         with: | ||||||
|  |           name: democratic-csi-windows-ltsc2022.tar | ||||||
|  |       - name: push windows images with buildah | ||||||
|  |         run: | | ||||||
|  |           #.github/bin/install_latest_buildah.sh | ||||||
|  |           buildah version | ||||||
|  |           .github/bin/docker-release-windows.sh | ||||||
|  |         env: | ||||||
|  |           DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} | ||||||
|  |           DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} | ||||||
|  |           GHCR_USERNAME: ${{ secrets.GHCR_USERNAME }} | ||||||
|  |           GHCR_PASSWORD: ${{ secrets.GHCR_PASSWORD }} | ||||||
|  |           DOCKER_CLI_EXPERIMENTAL: enabled | ||||||
|  |           IMAGE_TAG: ${{needs.determine-image-tag.outputs.tag}} | ||||||
|  |  | ||||||
|  | @ -1,2 +1,4 @@ | ||||||
|  | **~ | ||||||
| node_modules | node_modules | ||||||
| dev | dev | ||||||
|  | /ci/bin/*dev* | ||||||
|  |  | ||||||
							
								
								
									
										30
									
								
								CHANGELOG.md
								
								
								
								
							
							
						
						
									
										30
									
								
								CHANGELOG.md
								
								
								
								
							|  | @ -1,3 +1,33 @@ | ||||||
|  | # v1.7.0 | ||||||
|  | 
 | ||||||
|  | Released 2022-06-08 | ||||||
|  | 
 | ||||||
|  | The windows release. | ||||||
|  | 
 | ||||||
|  | - windows smb, iscsi, and local-hostpath support (requires chart `v0.13.0+`) | ||||||
|  | - ntfs, exfat, vfat fs support | ||||||
|  | - `zfs-generic-smb` driver | ||||||
|  | - synology improvements | ||||||
|  |   - DSM7 support | ||||||
|  |   - synology enhancements to allow templates to be configured at various | ||||||
|  |     'levels' | ||||||
|  | - testing improvements | ||||||
|  |   - support (for testing) generating volume_id from name | ||||||
|  |   - test all the smb variants | ||||||
|  |   - test all nfs/smb client drivers | ||||||
|  | - misc fixes | ||||||
|  |   - wait for chown/chmod jobs to complete (freenas) | ||||||
|  |   - general improvement to smb behavior throughout | ||||||
|  |   - better logging | ||||||
|  |   - better sudo logic throughout | ||||||
|  |   - minor fixes throughout | ||||||
|  | - more robust logic for connecting to iscsi devices with partition tables | ||||||
|  | - massive performance improvement for ssh-based drivers (reusing existing | ||||||
|  |   connection instead of new connection per-command) | ||||||
|  | - dep bumps | ||||||
|  | - trimmed container images | ||||||
|  | - windows container images for 2019 and 2022 | ||||||
|  | 
 | ||||||
| # v1.6.3 | # v1.6.3 | ||||||
| 
 | 
 | ||||||
| Released 2022-04-08 | Released 2022-04-08 | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ RUN apt-get update && apt-get install -y locales && rm -rf /var/lib/apt/lists/* | ||||||
|         && localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 |         && localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 | ||||||
| 
 | 
 | ||||||
| ENV LANG=en_US.utf8 | ENV LANG=en_US.utf8 | ||||||
| ENV NODE_VERSION=v16.14.2 | ENV NODE_VERSION=v16.15.1 | ||||||
| ENV NODE_ENV=production | ENV NODE_ENV=production | ||||||
| 
 | 
 | ||||||
| # install build deps | # install build deps | ||||||
|  | @ -43,6 +43,8 @@ RUN rm -rf docker | ||||||
| FROM debian:11-slim | FROM debian:11-slim | ||||||
| 
 | 
 | ||||||
| LABEL org.opencontainers.image.source https://github.com/democratic-csi/democratic-csi | LABEL org.opencontainers.image.source https://github.com/democratic-csi/democratic-csi | ||||||
|  | LABEL org.opencontainers.image.url https://github.com/democratic-csi/democratic-csi | ||||||
|  | LABEL org.opencontainers.image.licenses MIT | ||||||
| 
 | 
 | ||||||
| ENV DEBIAN_FRONTEND=noninteractive | ENV DEBIAN_FRONTEND=noninteractive | ||||||
| 
 | 
 | ||||||
|  | @ -73,7 +75,7 @@ COPY --from=build /usr/local/lib/nodejs/bin/node /usr/local/bin/node | ||||||
| # netbase is required by rpcbind/rpcinfo to work properly | # netbase is required by rpcbind/rpcinfo to work properly | ||||||
| # /etc/{services,rpc} are required | # /etc/{services,rpc} are required | ||||||
| RUN apt-get update && \ | RUN apt-get update && \ | ||||||
|         apt-get install -y netbase socat e2fsprogs xfsprogs btrfs-progs fatresize dosfstools nfs-common cifs-utils sudo rsync && \ |         apt-get install -y netbase socat e2fsprogs exfatprogs xfsprogs btrfs-progs fatresize dosfstools ntfs-3g nfs-common cifs-utils fdisk gdisk cloud-guest-utils sudo rsync && \ | ||||||
|         rm -rf /var/lib/apt/lists/* |         rm -rf /var/lib/apt/lists/* | ||||||
| 
 | 
 | ||||||
| # controller requirements | # controller requirements | ||||||
|  |  | ||||||
|  | @ -0,0 +1,100 @@ | ||||||
|  | # | ||||||
|  | # https://github.com/kubernetes/kubernetes/blob/master/test/images/windows/powershell-helper/Dockerfile_windows | ||||||
|  | # https://github.com/kubernetes/kubernetes/blob/master/test/images/busybox/Dockerfile_windows | ||||||
|  | # https://github.com/kubernetes/kubernetes/tree/master/test/images#windows-test-images-considerations | ||||||
|  | # https://stefanscherer.github.io/find-dependencies-in-windows-containers/ | ||||||
|  | #  | ||||||
|  | # docker build --build-arg NANO_BASE_TAG=1809 --build-arg CORE_BASE_TAG=ltsc2019 -t foobar -f Dockerfile.Windows . | ||||||
|  | # docker run --rm -ti --entrypoint powershell foobar | ||||||
|  | # docker run --rm foobar | ||||||
|  | # docker save foobar -o foobar.tar | ||||||
|  | # buildah pull docker-archive:foobar.tar | ||||||
|  | 
 | ||||||
|  | # mcr.microsoft.com/windows/servercore:ltsc2019 | ||||||
|  | # mcr.microsoft.com/windows/nanoserver:1809 | ||||||
|  | 
 | ||||||
|  | ARG NANO_BASE_TAG | ||||||
|  | ARG CORE_BASE_TAG | ||||||
|  | 
 | ||||||
|  | FROM mcr.microsoft.com/windows/servercore:${CORE_BASE_TAG} as powershell | ||||||
|  | 
 | ||||||
|  | # install powershell | ||||||
|  | ENV PS_VERSION=6.2.7 | ||||||
|  | ADD https://github.com/PowerShell/PowerShell/releases/download/v$PS_VERSION/PowerShell-$PS_VERSION-win-x64.zip /PowerShell/powershell.zip | ||||||
|  | 
 | ||||||
|  | RUN cd C:\PowerShell &\ | ||||||
|  |     tar.exe -xf powershell.zip &\ | ||||||
|  |     del powershell.zip &\ | ||||||
|  |     mklink powershell.exe pwsh.exe | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | FROM mcr.microsoft.com/windows/servercore:${CORE_BASE_TAG} as build | ||||||
|  | 
 | ||||||
|  | SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] | ||||||
|  | 
 | ||||||
|  | #ENV GPG_VERSION 4.0.2 | ||||||
|  | ENV GPG_VERSION 2.3.4 | ||||||
|  | 
 | ||||||
|  | RUN Invoke-WebRequest $('https://files.gpg4win.org/gpg4win-vanilla-{0}.exe' -f $env:GPG_VERSION) -OutFile 'gpg4win.exe' -UseBasicParsing ; \ | ||||||
|  |     Start-Process .\gpg4win.exe -ArgumentList '/S' -NoNewWindow -Wait | ||||||
|  | 
 | ||||||
|  | # https://github.com/nodejs/node#release-keys | ||||||
|  | RUN @( \ | ||||||
|  |     '4ED778F539E3634C779C87C6D7062848A1AB005C', \ | ||||||
|  |     '141F07595B7B3FFE74309A937405533BE57C7D57', \ | ||||||
|  |     '94AE36675C464D64BAFA68DD7434390BDBE9B9C5', \ | ||||||
|  |     '74F12602B6F1C4E913FAA37AD3A89613643B6201', \ | ||||||
|  |     '71DCFD284A79C3B38668286BC97EC7A07EDE3FC1', \ | ||||||
|  |     '61FC681DFB92A079F1685E77973F295594EC4689', \ | ||||||
|  |     '8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600', \ | ||||||
|  |     'C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8', \ | ||||||
|  |     'C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C', \ | ||||||
|  |     'DD8F2338BAE7501E3DD5AC78C273792F7D83545D', \ | ||||||
|  |     'A48C2BEE680E841632CD4E44F07496B3EB3C1762', \ | ||||||
|  |     '108F52B48DB57BB0CC439B2997B01419BD92F80A', \ | ||||||
|  |     'B9E2F5981AA6E0CD28160D9FF13993A75599653C' \ | ||||||
|  |     ) | foreach { \ | ||||||
|  |       gpg --keyserver hkps://keys.openpgp.org --recv-keys $_ ; \ | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | ENV NODE_VERSION 16.15.1 | ||||||
|  | 
 | ||||||
|  | RUN Invoke-WebRequest $('https://nodejs.org/dist/v{0}/SHASUMS256.txt.asc' -f $env:NODE_VERSION) -OutFile 'SHASUMS256.txt.asc' -UseBasicParsing ; | ||||||
|  | #RUN Invoke-WebRequest $('https://nodejs.org/dist/v{0}/SHASUMS256.txt.asc' -f $env:NODE_VERSION) -OutFile 'SHASUMS256.txt.asc' -UseBasicParsing ; \ | ||||||
|  | #    gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc | ||||||
|  | #gpg --verify SHASUMS256.txt.sig SHASUMS256.txt | ||||||
|  | 
 | ||||||
|  | RUN Invoke-WebRequest $('https://nodejs.org/dist/v{0}/node-v{0}-win-x64.zip' -f $env:NODE_VERSION) -OutFile 'node.zip' -UseBasicParsing ; \ | ||||||
|  |     $sum = $(cat SHASUMS256.txt.asc | sls $('  node-v{0}-win-x64.zip' -f $env:NODE_VERSION)) -Split ' ' ; \ | ||||||
|  |     if ((Get-FileHash node.zip -Algorithm sha256).Hash -ne $sum[0]) { Write-Error 'SHA256 mismatch' } ; \ | ||||||
|  |     Expand-Archive node.zip -DestinationPath C:\ ; \ | ||||||
|  |     Rename-Item -Path $('C:\node-v{0}-win-x64' -f $env:NODE_VERSION) -NewName 'C:\nodejs' | ||||||
|  | 
 | ||||||
|  | #RUN setx /M PATH "%PATH%;C:\nodejs" | ||||||
|  | RUN setx /M PATH $(${Env:PATH} + \";C:\nodejs\") | ||||||
|  | 
 | ||||||
|  | RUN node --version; npm --version; | ||||||
|  | 
 | ||||||
|  | RUN mkdir /app | ||||||
|  | WORKDIR /app | ||||||
|  | 
 | ||||||
|  | COPY package*.json ./ | ||||||
|  | RUN npm install --only=production; ls / | ||||||
|  | COPY . . | ||||||
|  | 
 | ||||||
|  | FROM mcr.microsoft.com/windows/nanoserver:${NANO_BASE_TAG} | ||||||
|  | 
 | ||||||
|  | LABEL org.opencontainers.image.source https://github.com/democratic-csi/democratic-csi | ||||||
|  | LABEL org.opencontainers.image.url https://github.com/democratic-csi/democratic-csi | ||||||
|  | LABEL org.opencontainers.image.licenses MIT | ||||||
|  | 
 | ||||||
|  | # if additional dlls are required can copy like this | ||||||
|  | #COPY --from=build /Windows/System32/nltest.exe /Windows/System32/nltest.exe | ||||||
|  | 
 | ||||||
|  | COPY --from=build /app /app | ||||||
|  | WORKDIR /app | ||||||
|  | 
 | ||||||
|  | # this works for both host-process and non-host-process container semantics | ||||||
|  | COPY --from=build /nodejs/node.exe ./bin | ||||||
|  | 
 | ||||||
|  | ENTRYPOINT [ "bin/node.exe", "--expose-gc", "bin/democratic-csi" ] | ||||||
							
								
								
									
										71
									
								
								README.md
								
								
								
								
							
							
						
						
									
										71
									
								
								README.md
								
								
								
								
							|  | @ -64,6 +64,16 @@ Predominantly 3 things are needed: | ||||||
| 
 | 
 | ||||||
| You should install/configure the requirements for both nfs and iscsi. | You should install/configure the requirements for both nfs and iscsi. | ||||||
| 
 | 
 | ||||||
|  | ### cifs | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | RHEL / CentOS | ||||||
|  | sudo yum install -y cifs-utils | ||||||
|  | 
 | ||||||
|  | Ubuntu / Debian | ||||||
|  | sudo apt-get install -y cifs-utils | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| ### nfs | ### nfs | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
|  | @ -176,6 +186,35 @@ volume is/was provisioned. | ||||||
| The nature of this `driver` also prevents the enforcement of quotas. In short | The nature of this `driver` also prevents the enforcement of quotas. In short | ||||||
| the requested volume size is generally ignored. | the requested volume size is generally ignored. | ||||||
| 
 | 
 | ||||||
|  | ### windows | ||||||
|  | 
 | ||||||
|  | Support for Windows was introduced in `v1.7.0`. Currently support is limited | ||||||
|  | to kubernetes nodes capabale of running `HostProcess` containers. Support was | ||||||
|  | tested against `Windows Server 2019` using `rke2-v1.24`. Currently any of the | ||||||
|  | `-smb` and `-iscsi` drivers will work. Support for `ntfs` was added to the | ||||||
|  | linux nodes as well (using the `ntfs3` driver) so volumes created can be | ||||||
|  | utilized by nodes with either operating system (in the case of `cifs` by both | ||||||
|  | simultaneously). | ||||||
|  | 
 | ||||||
|  | Due to current limits in the kubernetes tooling it is not possible to use the | ||||||
|  | `local-hostpath` driver but support is implemented in this project and will | ||||||
|  | work as soon as kubernetes support is available. | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | # ensure all updates are installed | ||||||
|  | 
 | ||||||
|  | # enable the container feature | ||||||
|  | Enable-WindowsOptionalFeature -Online -FeatureName Containers –All | ||||||
|  | 
 | ||||||
|  | # create symbolic link due to current limitations in the driver-registrar container | ||||||
|  | New-Item -ItemType SymbolicLink -Path "C:\registration\" -Target "C:\var\lib\kubelet\plugins_registry\" | ||||||
|  | 
 | ||||||
|  | # install a HostProcess compatible kubernetes | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | - https://kubernetes.io/blog/2021/08/16/windows-hostprocess-containers/ | ||||||
|  | - https://kubernetes.io/docs/tasks/configure-pod-container/create-hostprocess-pod/ | ||||||
|  | 
 | ||||||
| ## Server Prep | ## Server Prep | ||||||
| 
 | 
 | ||||||
| Server preparation depends slightly on which `driver` you are using. | Server preparation depends slightly on which `driver` you are using. | ||||||
|  | @ -201,6 +240,7 @@ Ensure the following services are configurged and running: | ||||||
| - ensure `zsh`, `bash`, or `sh` is set as the root shell, `csh` gives false errors due to quoting | - ensure `zsh`, `bash`, or `sh` is set as the root shell, `csh` gives false errors due to quoting | ||||||
| - nfs | - nfs | ||||||
| - iscsi | - iscsi | ||||||
|  | 
 | ||||||
|   - (fixed in 12.0-U2+) when using the FreeNAS API concurrently the |   - (fixed in 12.0-U2+) when using the FreeNAS API concurrently the | ||||||
|     `/etc/ctl.conf` file on the server can become invalid, some sample scripts |     `/etc/ctl.conf` file on the server can become invalid, some sample scripts | ||||||
|     are provided in the `contrib` directory to clean things up ie: copy the |     are provided in the `contrib` directory to clean things up ie: copy the | ||||||
|  | @ -216,7 +256,7 @@ Ensure the following services are configurged and running: | ||||||
|       - `curl --header "Accept: application/json" --user root:<password> 'http(s)://<ip>/api/v2.0/iscsi/initiator'` |       - `curl --header "Accept: application/json" --user root:<password> 'http(s)://<ip>/api/v2.0/iscsi/initiator'` | ||||||
|       - `curl --header "Accept: application/json" --user root:<password> 'http(s)://<ip>/api/v2.0/iscsi/auth'` |       - `curl --header "Accept: application/json" --user root:<password> 'http(s)://<ip>/api/v2.0/iscsi/auth'` | ||||||
|   - The maximum number of volumes is limited to 255 by default on FreeBSD (physical devices such as disks and CD-ROM drives count against this value). |   - The maximum number of volumes is limited to 255 by default on FreeBSD (physical devices such as disks and CD-ROM drives count against this value). | ||||||
|   Be sure to properly adjust both [tunables](https://www.freebsd.org/cgi/man.cgi?query=ctl&sektion=4#end) `kern.cam.ctl.max_ports` and `kern.cam.ctl.max_luns` to avoid running out of resources when dynamically provisioning iSCSI volumes on FreeNAS or TrueNAS Core. |     Be sure to properly adjust both [tunables](https://www.freebsd.org/cgi/man.cgi?query=ctl&sektion=4#end) `kern.cam.ctl.max_ports` and `kern.cam.ctl.max_luns` to avoid running out of resources when dynamically provisioning iSCSI volumes on FreeNAS or TrueNAS Core. | ||||||
| 
 | 
 | ||||||
| - smb | - smb | ||||||
| 
 | 
 | ||||||
|  | @ -273,17 +313,38 @@ Issues to review: | ||||||
| - https://jira.ixsystems.com/browse/NAS-108522 | - https://jira.ixsystems.com/browse/NAS-108522 | ||||||
| - https://jira.ixsystems.com/browse/NAS-107219 | - https://jira.ixsystems.com/browse/NAS-107219 | ||||||
| 
 | 
 | ||||||
| ### ZoL (zfs-generic-nfs, zfs-generic-iscsi) | ### ZoL (zfs-generic-nfs, zfs-generic-iscsi, zfs-generic-smb) | ||||||
| 
 | 
 | ||||||
| Ensure ssh and zfs is installed on the nfs/iscsi server and that you have installed | Ensure ssh and zfs is installed on the nfs/iscsi server and that you have installed | ||||||
| `targetcli`. | `targetcli`. | ||||||
| 
 | 
 | ||||||
| - `sudo yum install targetcli -y` | The driver executes many commands over an ssh connection. You may consider | ||||||
| - `sudo apt-get -y install targetcli-fb` | disabling all the `motd` details for the ssh user as it can spike the cpu | ||||||
|  | unecessarily: | ||||||
|  | 
 | ||||||
|  | - https://askubuntu.com/questions/318592/how-can-i-remove-the-landscape-canonical-com-greeting-from-motd | ||||||
|  | - https://linuxconfig.org/disable-dynamic-motd-and-news-on-ubuntu-20-04-focal-fossa-linux | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | ####### iscsi | ||||||
|  | yum install targetcli -y | ||||||
|  | apt-get -y install targetcli-fb | ||||||
|  | 
 | ||||||
|  | ####### smb | ||||||
|  | apt-get install -y samba smbclient | ||||||
|  | 
 | ||||||
|  | # create posix user | ||||||
|  | groupadd -g 1001 smbroot | ||||||
|  | useradd -u 1001 -g 1001 -M -N -s /sbin/nologin smbroot | ||||||
|  | passwd smbroot (optional) | ||||||
|  | 
 | ||||||
|  | # create smb user and set password | ||||||
|  | smbpasswd -L -a smbroot | ||||||
|  | ``` | ||||||
| 
 | 
 | ||||||
| ### Synology (synology-iscsi) | ### Synology (synology-iscsi) | ||||||
| 
 | 
 | ||||||
| Ensure iscsi manager has been installed and is generally setup/configured. | Ensure iscsi manager has been installed and is generally setup/configured. DSM 6.3+ is supported. | ||||||
| 
 | 
 | ||||||
| ## Helm Installation | ## Helm Installation | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,10 +1,18 @@ | ||||||
| #!/usr/bin/env -S node --expose-gc ${NODE_OPTIONS_CSI_1} ${NODE_OPTIONS_CSI_2} ${NODE_OPTIONS_CSI_3} ${NODE_OPTIONS_CSI_4} ${NODE_OPTIONS_CSI_5} | #!/usr/bin/env -S node --expose-gc ${NODE_OPTIONS_CSI_1} ${NODE_OPTIONS_CSI_2} ${NODE_OPTIONS_CSI_3} ${NODE_OPTIONS_CSI_4} | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * keep the shebang line length under 128 | ||||||
|  |  * https://github.com/democratic-csi/democratic-csi/issues/171 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | // polyfills | ||||||
|  | require("../src/utils/polyfills"); | ||||||
| const yaml = require("js-yaml"); | const yaml = require("js-yaml"); | ||||||
| const fs = require("fs"); | const fs = require("fs"); | ||||||
| const { grpc } = require("../src/utils/grpc"); | const { grpc } = require("../src/utils/grpc"); | ||||||
| const { stringify } = require("../src/utils/general"); | const { stringify, stripWindowsDriveLetter } = require("../src/utils/general"); | ||||||
| 
 | 
 | ||||||
|  | let driverConfigFile; | ||||||
| let options; | let options; | ||||||
| const args = require("yargs") | const args = require("yargs") | ||||||
|   .env("DEMOCRATIC_CSI") |   .env("DEMOCRATIC_CSI") | ||||||
|  | @ -14,17 +22,29 @@ const args = require("yargs") | ||||||
|     describe: "provide a path to driver config file", |     describe: "provide a path to driver config file", | ||||||
|     config: true, |     config: true, | ||||||
|     configParser: (path) => { |     configParser: (path) => { | ||||||
|       try { |       // normalize path for host-process containers | ||||||
|         options = JSON.parse(fs.readFileSync(path, "utf-8")); |       // CONTAINER_SANDBOX_MOUNT_POINT  C:\C\0eac9a8da76f6d7119c5d9f86c8b3106d67dbbf01dbeb22fdc0192476b7e31cb\ | ||||||
|         return true; |       // path is injected as C:\config\driver-config-file.yaml | ||||||
|       } catch (e) {} |       if (process.env.CONTAINER_SANDBOX_MOUNT_POINT) { | ||||||
|  |         path = `${ | ||||||
|  |           process.env.CONTAINER_SANDBOX_MOUNT_POINT | ||||||
|  |         }${stripWindowsDriveLetter(path)}`; | ||||||
|  |       } | ||||||
| 
 | 
 | ||||||
|       try { |       try { | ||||||
|         options = yaml.load(fs.readFileSync(path, "utf8")); |         options = yaml.load(fs.readFileSync(path, "utf8")); | ||||||
|         return true; |         try { | ||||||
|       } catch (e) {} |           driverConfigFile = fs.realpathSync(path); | ||||||
|  |         } catch (e) { | ||||||
|  |           console.log("failed finding config file realpath: " + e.toString()); | ||||||
|  |           driverConfigFile = path; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|       throw new Error("failed parsing config file: " + path); |         return true; | ||||||
|  |       } catch (e) { | ||||||
|  |         console.log("failed parsing config file: " + path); | ||||||
|  |         throw e; | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|   }) |   }) | ||||||
|   .demandOption(["driver-config-file"], "driver-config-file is required") |   .demandOption(["driver-config-file"], "driver-config-file is required") | ||||||
|  | @ -136,6 +156,14 @@ let operationLock = new Set(); | ||||||
| 
 | 
 | ||||||
| async function requestHandlerProxy(call, callback, serviceMethodName) { | async function requestHandlerProxy(call, callback, serviceMethodName) { | ||||||
|   const cleansedCall = JSON.parse(stringify(call)); |   const cleansedCall = JSON.parse(stringify(call)); | ||||||
|  | 
 | ||||||
|  |   delete cleansedCall.call; | ||||||
|  |   delete cleansedCall.canceled; | ||||||
|  |   for (const key in cleansedCall) { | ||||||
|  |     if (key.startsWith("_")) { | ||||||
|  |       delete cleansedCall[key]; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|   for (const key in cleansedCall.request) { |   for (const key in cleansedCall.request) { | ||||||
|     if (key.includes("secret")) { |     if (key.includes("secret")) { | ||||||
|       cleansedCall.request[key] = "redacted"; |       cleansedCall.request[key] = "redacted"; | ||||||
|  | @ -165,6 +193,18 @@ async function requestHandlerProxy(call, callback, serviceMethodName) { | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // for testing purposes | ||||||
|  |     //await GeneralUtils.sleep(10000); | ||||||
|  |     //throw new Error("fake error"); | ||||||
|  | 
 | ||||||
|  |     // for CI/testing purposes | ||||||
|  |     if (["NodePublishVolume", "NodeStageVolume"].includes(serviceMethodName)) { | ||||||
|  |       await driver.setVolumeContextCache( | ||||||
|  |         call.request.volume_id, | ||||||
|  |         call.request.volume_context | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     let response; |     let response; | ||||||
|     let responseError; |     let responseError; | ||||||
|     try { |     try { | ||||||
|  | @ -190,12 +230,21 @@ async function requestHandlerProxy(call, callback, serviceMethodName) { | ||||||
|       throw responseError; |       throw responseError; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // for CI/testing purposes | ||||||
|  |     if (serviceMethodName == "CreateVolume") { | ||||||
|  |       await driver.setVolumeContextCache( | ||||||
|  |         response.volume.volume_id, | ||||||
|  |         response.volume.volume_context | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     logger.info( |     logger.info( | ||||||
|       "new response - driver: %s method: %s response: %j", |       "new response - driver: %s method: %s response: %j", | ||||||
|       driver.constructor.name, |       driver.constructor.name, | ||||||
|       serviceMethodName, |       serviceMethodName, | ||||||
|       response |       response | ||||||
|     ); |     ); | ||||||
|  | 
 | ||||||
|     callback(null, response); |     callback(null, response); | ||||||
|   } catch (e) { |   } catch (e) { | ||||||
|     let message; |     let message; | ||||||
|  | @ -205,7 +254,7 @@ async function requestHandlerProxy(call, callback, serviceMethodName) { | ||||||
|         message += ` ${e.stack}`; |         message += ` ${e.stack}`; | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       message = JSON.stringify(e); |       message = stringify(e); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     logger.error( |     logger.error( | ||||||
|  | @ -336,9 +385,11 @@ if (args.serverSocket) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| logger.info( | logger.info( | ||||||
|   "starting csi server - name: %s, version: %s, driver: %s, mode: %s, csi version: %s, address: %s, socket: %s", |   "starting csi server - node version: %s, package version: %s, config file: %s, csi-name: %s, csi-driver: %s, csi-mode: %s, csi-version: %s, address: %s, socket: %s", | ||||||
|   args.csiName, |   process.version, | ||||||
|   args.version, |   args.version, | ||||||
|  |   driverConfigFile, | ||||||
|  |   args.csiName, | ||||||
|   options.driver, |   options.driver, | ||||||
|   args.csiMode.join(","), |   args.csiMode.join(","), | ||||||
|   args.csiVersion, |   args.csiVersion, | ||||||
|  |  | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | Write-Output "current user" | ||||||
|  | whoami | ||||||
|  | Write-Output "current working directory" | ||||||
|  | (Get-Location).Path | ||||||
|  | Write-Output "current PATH" | ||||||
|  | $Env:PATH | ||||||
|  | 
 | ||||||
|  | Write-Output "node version" | ||||||
|  | node --version | ||||||
|  | Write-Output "npm version" | ||||||
|  | npm --version | ||||||
|  | 
 | ||||||
|  | # install deps | ||||||
|  | Write-Output "running npm i" | ||||||
|  | npm i | ||||||
|  | 
 | ||||||
|  | Write-Output "creating tar.gz" | ||||||
|  | # tar node_modules to keep the number of files low to upload | ||||||
|  | tar -zcf node_modules-windows-amd64.tar.gz node_modules | ||||||
|  | @ -12,4 +12,4 @@ npm --version | ||||||
| npm i | npm i | ||||||
| 
 | 
 | ||||||
| # tar node_modules to keep the number of files low to upload | # tar node_modules to keep the number of files low to upload | ||||||
| tar -zcf node_modules.tar.gz node_modules | tar -zcf node_modules-linux-amd64.tar.gz node_modules | ||||||
|  |  | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | #Set-StrictMode -Version Latest | ||||||
|  | #$ErrorActionPreference = "Stop" | ||||||
|  | #$PSDefaultParameterValues['*:ErrorAction'] = "Stop" | ||||||
|  | function ThrowOnNativeFailure { | ||||||
|  |   if (-not $?) { | ||||||
|  |     throw 'Native Failure' | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function psenvsubstr($data) { | ||||||
|  |   foreach($v in Get-ChildItem env:) { | ||||||
|  |     $key = '${' + $v.Name + '}' | ||||||
|  |     $data = $data.Replace($key, $v.Value) | ||||||
|  |   } | ||||||
|  |   return $data | ||||||
|  | } | ||||||
|  | @ -0,0 +1,15 @@ | ||||||
|  | if (! $PSScriptRoot) { | ||||||
|  |   $PSScriptRoot = $args[0] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | . "${PSScriptRoot}\helper.ps1" | ||||||
|  | 
 | ||||||
|  | Set-Location $env:PWD | ||||||
|  | 
 | ||||||
|  | Write-Output "launching csi-grpc-proxy" | ||||||
|  | 
 | ||||||
|  | $env:PROXY_TO = "npipe://" + $env:NPIPE_ENDPOINT | ||||||
|  | $env:BIND_TO = "unix://" + $env:CSI_ENDPOINT | ||||||
|  | 
 | ||||||
|  | # https://stackoverflow.com/questions/2095088/error-when-calling-3rd-party-executable-from-powershell-when-using-an-ide | ||||||
|  | csi-grpc-proxy.exe 2>&1 | % { "$_" } | ||||||
|  | @ -0,0 +1,69 @@ | ||||||
|  | if (! $PSScriptRoot) { | ||||||
|  |   $PSScriptRoot = $args[0] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | . "${PSScriptRoot}\helper.ps1" | ||||||
|  | 
 | ||||||
|  | Set-Location $env:PWD | ||||||
|  | 
 | ||||||
|  | $exit_code = 0 | ||||||
|  | $tmpdir = New-Item -ItemType Directory -Path ([System.IO.Path]::GetTempPath()) -Name ([System.IO.Path]::GetRandomFileName()) | ||||||
|  | $env:CSI_SANITY_TEMP_DIR = $tmpdir.FullName | ||||||
|  | 
 | ||||||
|  | # cleanse endpoint to something csi-sanity plays nicely with | ||||||
|  | $endpoint = ${env:CSI_ENDPOINT} | ||||||
|  | $endpoint = $endpoint.replace("C:\", "/") | ||||||
|  | $endpoint = $endpoint.replace("\", "/") | ||||||
|  | 
 | ||||||
|  | if (! $env:CSI_SANITY_FAILFAST) { | ||||||
|  |   $env:CSI_SANITY_FAILFAST = "false" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | $failfast = "" | ||||||
|  | 
 | ||||||
|  | if ($env:CSI_SANITY_FAILFAST -eq "true") { | ||||||
|  |   $failfast = "-ginkgo.failFast" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Write-Output "launching csi-sanity" | ||||||
|  | Write-Output "connecting to: ${endpoint}" | ||||||
|  | Write-Output "failfast: ${env:CSI_SANITY_FAILFAST}" | ||||||
|  | Write-Output "skip: ${env:CSI_SANITY_SKIP}" | ||||||
|  | Write-Output "focus: ${env:CSI_SANITY_FOCUS}" | ||||||
|  | 
 | ||||||
|  | $skip = '"' + ${env:CSI_SANITY_SKIP} + '"' | ||||||
|  | $focus = '"' + ${env:CSI_SANITY_FOCUS} + '"' | ||||||
|  | 
 | ||||||
|  | csi-sanity.exe -"csi.endpoint" "unix://${endpoint}" ` | ||||||
|  |   $failfast ` | ||||||
|  |   -"csi.mountdir" "${env:CSI_SANITY_TEMP_DIR}\mnt" ` | ||||||
|  |   -"csi.stagingdir" "${env:CSI_SANITY_TEMP_DIR}\stage" ` | ||||||
|  |   -"csi.testvolumeexpandsize" 2147483648 ` | ||||||
|  |   -"csi.testvolumesize" 1073741824 ` | ||||||
|  |   -"ginkgo.skip" $skip ` | ||||||
|  |   -"ginkgo.focus" $focus | ||||||
|  | 
 | ||||||
|  | # does not work the same as linux for some reason | ||||||
|  | # -"ginkgo.skip" "'" + ${env:CSI_SANITY_SKIP} + "'" ` | ||||||
|  | 
 | ||||||
|  | if (-not $?) { | ||||||
|  |   $exit_code = $LASTEXITCODE | ||||||
|  |   Write-Output "csi-sanity exit code: ${exit_code}" | ||||||
|  |   if ($exit_code -gt 0) { | ||||||
|  |     $exit_code = 1 | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # remove tmp dir | ||||||
|  | Remove-Item -Path "$env:CSI_SANITY_TEMP_DIR" -Force -Recurse | ||||||
|  | 
 | ||||||
|  | #Exit $exit_code | ||||||
|  | Write-Output "exiting with exit code: ${exit_code}" | ||||||
|  | 
 | ||||||
|  | if ($exit_code -gt 0) { | ||||||
|  |   throw "csi-sanity failed" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # these do not work for whatever reason | ||||||
|  | #Exit $exit_code | ||||||
|  | #[System.Environment]::Exit($exit_code) | ||||||
|  | @ -0,0 +1,29 @@ | ||||||
|  | if (! $PSScriptRoot) { | ||||||
|  |   $PSScriptRoot = $args[0] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | . "${PSScriptRoot}\helper.ps1" | ||||||
|  | 
 | ||||||
|  | Set-Location $env:PWD | ||||||
|  | Write-Output "launching server" | ||||||
|  | 
 | ||||||
|  | $env:LOG_LEVEL = "debug" | ||||||
|  | $env:CSI_VERSION = "1.5.0" | ||||||
|  | $env:CSI_NAME = "driver-test" | ||||||
|  | $env:CSI_SANITY = "1" | ||||||
|  | 
 | ||||||
|  | if (! ${env:CONFIG_FILE}) { | ||||||
|  |   $env:CONFIG_FILE = $env:TEMP + "\csi-config-" + $env:CI_BUILD_KEY + ".yaml" | ||||||
|  |   if ($env:TEMPLATE_CONFIG_FILE) { | ||||||
|  |     $config_data = Get-Content "${env:TEMPLATE_CONFIG_FILE}" -Raw | ||||||
|  |     $config_data = psenvsubstr($config_data) | ||||||
|  |     $config_data | Set-Content "${env:CONFIG_FILE}" | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | node "${PSScriptRoot}\..\..\bin\democratic-csi" ` | ||||||
|  |   --log-level "$env:LOG_LEVEL" ` | ||||||
|  |   --driver-config-file "$env:CONFIG_FILE" ` | ||||||
|  |   --csi-version "$env:CSI_VERSION" ` | ||||||
|  |   --csi-name "$env:CSI_NAME" ` | ||||||
|  |   --server-socket "${env:NPIPE_ENDPOINT}" 2>&1 | % { "$_" } | ||||||
|  | @ -3,6 +3,9 @@ | ||||||
| set -e | set -e | ||||||
| set -x | set -x | ||||||
| 
 | 
 | ||||||
|  | export PATH="/usr/local/lib/nodejs/bin:${PATH}" | ||||||
|  | echo "current launch-server PATH: ${PATH}" | ||||||
|  | 
 | ||||||
| : ${CI_BUILD_KEY:="local"} | : ${CI_BUILD_KEY:="local"} | ||||||
| : ${TEMPLATE_CONFIG_FILE:=${1}} | : ${TEMPLATE_CONFIG_FILE:=${1}} | ||||||
| : ${CSI_MODE:=""} | : ${CSI_MODE:=""} | ||||||
|  |  | ||||||
|  | @ -0,0 +1,133 @@ | ||||||
|  | # https://stackoverflow.com/questions/2095088/error-when-calling-3rd-party-executable-from-powershell-when-using-an-ide | ||||||
|  | #  | ||||||
|  | # Examples: | ||||||
|  | # | ||||||
|  | # $mypath = $MyInvocation.MyCommand.Path | ||||||
|  | # Get-ChildItem env:\ | ||||||
|  | # Get-Job | Where-Object -Property State -eq “Running” | ||||||
|  | # Get-Location (like pwd) | ||||||
|  | # if ($null -eq $env:FOO) { $env:FOO = 'bar' } | ||||||
|  | 
 | ||||||
|  | . "${PSScriptRoot}\helper.ps1" | ||||||
|  | 
 | ||||||
|  | #Set-PSDebug -Trace 2 | ||||||
|  | 
 | ||||||
|  | Write-Output "current user" | ||||||
|  | whoami | ||||||
|  | Write-Output "current working directory" | ||||||
|  | (Get-Location).Path | ||||||
|  | Write-Output "current PATH" | ||||||
|  | $Env:PATH | ||||||
|  | 
 | ||||||
|  | function Job-Cleanup() { | ||||||
|  |   Get-Job | Stop-Job | ||||||
|  |   Get-Job | Remove-Job | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # start clean | ||||||
|  | Job-Cleanup | ||||||
|  | 
 | ||||||
|  | # install from artifacts | ||||||
|  | if ((Test-Path "node_modules-windows-amd64.tar.gz") -and !(Test-Path "node_modules")) { | ||||||
|  |   Write-Output "extracting node_modules-windows-amd64.tar.gz" | ||||||
|  |   tar -zxf node_modules-windows-amd64.tar.gz | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # setup env | ||||||
|  | $env:PWD = (Get-Location).Path | ||||||
|  | $env:CI_BUILD_KEY = ([guid]::NewGuid() -Split "-")[0] | ||||||
|  | $env:CSI_ENDPOINT = $env:TEMP + "\csi-sanity-" + $env:CI_BUILD_KEY + ".sock" | ||||||
|  | $env:NPIPE_ENDPOINT = "//./pipe/csi-sanity-" + $env:CI_BUILD_KEY + "csi.sock" | ||||||
|  | 
 | ||||||
|  | # testing values | ||||||
|  | if (Test-Path "${PSScriptRoot}\run-dev.ps1") { | ||||||
|  |   . "${PSScriptRoot}\run-dev.ps1" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # launch server | ||||||
|  | $server_job = Start-Job -FilePath .\ci\bin\launch-server.ps1 -InitializationScript {} -ArgumentList $PSScriptRoot | ||||||
|  | 
 | ||||||
|  | # launch csi-grpc-proxy | ||||||
|  | $csi_grpc_proxy_job = Start-Job -FilePath .\ci\bin\launch-csi-grpc-proxy.ps1 -InitializationScript {} -ArgumentList $PSScriptRoot | ||||||
|  | 
 | ||||||
|  | # wait for socket to appear | ||||||
|  | $iter = 0 | ||||||
|  | $max_iter = 60 | ||||||
|  | $started = 1 | ||||||
|  | while (!(Test-Path "${env:CSI_ENDPOINT}")) { | ||||||
|  |   $iter++ | ||||||
|  |   Write-Output "Waiting for ${env:CSI_ENDPOINT} to appear" | ||||||
|  |   Start-Sleep 1 | ||||||
|  |   try { | ||||||
|  |     Get-Job | Receive-Job | ||||||
|  |   } catch {} | ||||||
|  |   if ($iter -gt $max_iter) { | ||||||
|  |     Write-Output "${env:CSI_ENDPOINT} failed to appear" | ||||||
|  |     $started = 0 | ||||||
|  |     break | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # launch csi-sanity | ||||||
|  | if ($started -eq 1) { | ||||||
|  |   $csi_sanity_job = Start-Job -FilePath .\ci\bin\launch-csi-sanity.ps1 -InitializationScript {} -ArgumentList $PSScriptRoot | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/get-job?view=powershell-7.2 | ||||||
|  | # -ChildJobState | ||||||
|  | $iter = 0 | ||||||
|  | while ($csi_sanity_job -and ($csi_sanity_job.State -eq "Running" -or $csi_sanity_job.State -eq "NotStarted")) { | ||||||
|  |   $iter++ | ||||||
|  |   foreach ($job in Get-Job) { | ||||||
|  |     if (($job -eq $csi_grpc_proxy_job) -and ($iter -gt 20)) { | ||||||
|  |       continue | ||||||
|  |     } | ||||||
|  |     if (!$job.HasMoreData) { | ||||||
|  |       continue | ||||||
|  |     } | ||||||
|  |     try { | ||||||
|  |       $job | Receive-Job | ||||||
|  |     } | ||||||
|  |     catch { | ||||||
|  |       if ($job.State -ne "Failed") { | ||||||
|  |         Write-Output "failure receiving job data: ${_}" | ||||||
|  |         # just swallow the errors as it seems there are various reasons errors | ||||||
|  |         # may show up (perhaps no data currently, etc) | ||||||
|  |         #$job | fl | ||||||
|  |         #throw $_ | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # spew any remaining job output to the console | ||||||
|  | foreach ($job in Get-Job) { | ||||||
|  |   if ($job -eq $csi_grpc_proxy_job) { | ||||||
|  |     continue | ||||||
|  |   } | ||||||
|  |   try { | ||||||
|  |     $job | Receive-Job | ||||||
|  |   } | ||||||
|  |   catch {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # wait for good measure | ||||||
|  | if ($csi_sanity_job) { | ||||||
|  |   Wait-Job -Job $csi_sanity_job | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #Get-Job | fl | ||||||
|  | 
 | ||||||
|  | $exit_code = 0 | ||||||
|  | 
 | ||||||
|  | if (! $csi_sanity_job) { | ||||||
|  |   $exit_code = 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | if ($csi_sanity_job -and $csi_sanity_job.State -eq "Failed") { | ||||||
|  |   $exit_code = 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # cleanup after ourselves | ||||||
|  | Job-Cleanup | ||||||
|  | Exit $exit_code | ||||||
|  | @ -15,8 +15,8 @@ export PATH="/usr/local/lib/nodejs/bin:${PATH}" | ||||||
| # install deps | # install deps | ||||||
| #npm i | #npm i | ||||||
| # install from artifacts | # install from artifacts | ||||||
| if [[ -f "node_modules.tar.gz" ]];then | if [[ -f "node_modules-linux-amd64.tar.gz" && ! -d "node_modules" ]];then | ||||||
|   tar -zxf node_modules.tar.gz |   tar -zxf node_modules-linux-amd64.tar.gz | ||||||
| fi | fi | ||||||
| 
 | 
 | ||||||
| # generate key for paths etc | # generate key for paths etc | ||||||
|  |  | ||||||
|  | @ -0,0 +1,10 @@ | ||||||
|  | driver: nfs-client | ||||||
|  | instance_id: | ||||||
|  | nfs: | ||||||
|  |   shareHost: ${SERVER_HOST} | ||||||
|  |   shareBasePath: "/mnt/tank/client/nfs/${CI_BUILD_KEY}" | ||||||
|  |   # shareHost:shareBasePath should be mounted at this location in the controller container | ||||||
|  |   controllerBasePath: "/mnt/client/nfs/${CI_BUILD_KEY}" | ||||||
|  |   dirPermissionsMode: "0777" | ||||||
|  |   dirPermissionsUser: 0 | ||||||
|  |   dirPermissionsGroup: 0 | ||||||
|  | @ -0,0 +1,14 @@ | ||||||
|  | driver: smb-client | ||||||
|  | instance_id: | ||||||
|  | smb: | ||||||
|  |   shareHost: ${SERVER_HOST} | ||||||
|  |   shareBasePath: "${SHARE_NAME}/${CI_BUILD_KEY}" | ||||||
|  |   # shareHost:shareBasePath should be mounted at this location in the controller container | ||||||
|  |   controllerBasePath: "/mnt/client/smb/${CI_BUILD_KEY}" | ||||||
|  |   dirPermissionsMode: "0777" | ||||||
|  |   dirPermissionsUser: 0 | ||||||
|  |   dirPermissionsGroup: 0 | ||||||
|  | 
 | ||||||
|  | node: | ||||||
|  |   mount: | ||||||
|  |     mount_flags: "username=smbroot,password=smbroot" | ||||||
|  | @ -16,8 +16,8 @@ iscsi: | ||||||
|   targetPortal: ${SYNOLOGY_HOST} |   targetPortal: ${SYNOLOGY_HOST} | ||||||
|   targetPortals: [] |   targetPortals: [] | ||||||
|   baseiqn: "iqn.2000-01.com.synology:XpenoDsm62x." |   baseiqn: "iqn.2000-01.com.synology:XpenoDsm62x." | ||||||
|   namePrefix: "csi-${CI_BUILD_KEY}-" |   namePrefix: "csi-ci-${CI_BUILD_KEY}-" | ||||||
|   nameSuffix: "-ci" |   nameSuffix: "" | ||||||
| 
 | 
 | ||||||
|   lunTemplate: |   lunTemplate: | ||||||
|     # btrfs thin provisioning |     # btrfs thin provisioning | ||||||
|  | @ -0,0 +1,77 @@ | ||||||
|  | driver: synology-iscsi | ||||||
|  | httpConnection: | ||||||
|  |   protocol: http | ||||||
|  |   host: ${SYNOLOGY_HOST} | ||||||
|  |   port: ${SYNOLOGY_PORT} | ||||||
|  |   username: ${SYNOLOGY_USERNAME} | ||||||
|  |   password: ${SYNOLOGY_PASSWORD} | ||||||
|  |   allowInsecure: true | ||||||
|  |   session: "democratic-csi-${CI_BUILD_KEY}" | ||||||
|  |   serialize: true | ||||||
|  | 
 | ||||||
|  | synology: | ||||||
|  |   volume: ${SYNOLOGY_VOLUME} | ||||||
|  | 
 | ||||||
|  | iscsi: | ||||||
|  |   targetPortal: ${SYNOLOGY_HOST} | ||||||
|  |   targetPortals: [] | ||||||
|  |   baseiqn: "iqn.2000-01.com.synology:XpenoDsm62x." | ||||||
|  |   namePrefix: "csi-ci-${CI_BUILD_KEY}-" | ||||||
|  |   nameSuffix: "" | ||||||
|  | 
 | ||||||
|  |   lunTemplate: | ||||||
|  |     # btrfs thin provisioning | ||||||
|  |     type: "BLUN" | ||||||
|  |     # tpws = Hardware-assisted zeroing | ||||||
|  |     # caw = Hardware-assisted locking | ||||||
|  |     # 3pc = Hardware-assisted data transfer | ||||||
|  |     # tpu = Space reclamation | ||||||
|  |     # can_snapshot = Snapshot | ||||||
|  |     #dev_attribs: | ||||||
|  |     #- dev_attrib: emulate_tpws | ||||||
|  |     #  enable: 1 | ||||||
|  |     #- dev_attrib: emulate_caw | ||||||
|  |     #  enable: 1 | ||||||
|  |     #- dev_attrib: emulate_3pc | ||||||
|  |     #  enable: 1 | ||||||
|  |     #- dev_attrib: emulate_tpu | ||||||
|  |     #  enable: 0 | ||||||
|  |     #- dev_attrib: can_snapshot | ||||||
|  |     #  enable: 1 | ||||||
|  | 
 | ||||||
|  |     # btfs thick provisioning | ||||||
|  |     # only zeroing and locking supported | ||||||
|  |     #type: "BLUN_THICK" | ||||||
|  |     # tpws = Hardware-assisted zeroing | ||||||
|  |     # caw = Hardware-assisted locking | ||||||
|  |     #dev_attribs: | ||||||
|  |     #- dev_attrib: emulate_tpws | ||||||
|  |     #  enable: 1 | ||||||
|  |     #- dev_attrib: emulate_caw | ||||||
|  |     #  enable: 1 | ||||||
|  | 
 | ||||||
|  |     # ext4 thinn provisioning UI sends everything with enabled=0 | ||||||
|  |     #type: "THIN" | ||||||
|  | 
 | ||||||
|  |     # ext4 thin with advanced legacy features set | ||||||
|  |     # can only alter tpu (all others are set as enabled=1) | ||||||
|  |     #type: "ADV" | ||||||
|  |     #dev_attribs: | ||||||
|  |     #- dev_attrib: emulate_tpu | ||||||
|  |     #  enable: 1 | ||||||
|  | 
 | ||||||
|  |     # ext4 thick | ||||||
|  |     # can only alter caw | ||||||
|  |     #type: "FILE" | ||||||
|  |     #dev_attribs: | ||||||
|  |     #- dev_attrib: emulate_caw | ||||||
|  |     #  enable: 1 | ||||||
|  | 
 | ||||||
|  |   lunSnapshotTemplate: | ||||||
|  |     is_locked: true | ||||||
|  |     # https://kb.synology.com/en-me/DSM/tutorial/What_is_file_system_consistent_snapshot | ||||||
|  |     is_app_consistent: true | ||||||
|  | 
 | ||||||
|  |   targetTemplate: | ||||||
|  |     auth_type: 0 | ||||||
|  |     max_sessions: 0 | ||||||
|  | @ -26,7 +26,7 @@ zfs: | ||||||
| iscsi: | iscsi: | ||||||
|   targetPortal: ${TRUENAS_HOST} |   targetPortal: ${TRUENAS_HOST} | ||||||
|   interface: "" |   interface: "" | ||||||
|   namePrefix: "csi-ci-${CI_BUILD_KEY}" |   namePrefix: "csi-ci-${CI_BUILD_KEY}-" | ||||||
|   nameSuffix: "" |   nameSuffix: "" | ||||||
|   targetGroups: |   targetGroups: | ||||||
|     - targetGroupPortalGroup: 1 |     - targetGroupPortalGroup: 1 | ||||||
|  | @ -35,3 +35,10 @@ iscsi: | ||||||
|       targetGroupAuthGroup: |       targetGroupAuthGroup: | ||||||
|   # 0-100 (0 == ignore) |   # 0-100 (0 == ignore) | ||||||
|   extentAvailThreshold: 0 |   extentAvailThreshold: 0 | ||||||
|  | 
 | ||||||
|  | # overcome the 63 char limit for testing purposes only | ||||||
|  | _private: | ||||||
|  |   csi: | ||||||
|  |     volume: | ||||||
|  |       idHash: | ||||||
|  |         strategy: crc16 | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ zfs: | ||||||
|   detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s |   detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s | ||||||
| 
 | 
 | ||||||
|   datasetEnableQuotas: true |   datasetEnableQuotas: true | ||||||
|   datasetEnableReservation: true |   datasetEnableReservation: false | ||||||
|   datasetPermissionsMode: "0777" |   datasetPermissionsMode: "0777" | ||||||
|   datasetPermissionsUser: 0 |   datasetPermissionsUser: 0 | ||||||
|   datasetPermissionsGroup: 0 |   datasetPermissionsGroup: 0 | ||||||
|  |  | ||||||
|  | @ -17,33 +17,29 @@ sshConnection: | ||||||
| zfs: | zfs: | ||||||
|   datasetProperties: |   datasetProperties: | ||||||
|     # smb options |     # smb options | ||||||
|     #aclmode: restricted |     aclmode: restricted | ||||||
|     #casesensitivity: mixed |     aclinherit: passthrough | ||||||
|  |     acltype: nfsv4 | ||||||
|  |     casesensitivity: insensitive | ||||||
| 
 | 
 | ||||||
|   datasetParentName: tank/ci/${CI_BUILD_KEY}/v |   datasetParentName: tank/ci/${CI_BUILD_KEY}/v | ||||||
|   detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s |   detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s | ||||||
| 
 | 
 | ||||||
|   datasetEnableQuotas: true |   datasetEnableQuotas: true | ||||||
|   datasetEnableReservation: true |   datasetEnableReservation: false | ||||||
|   datasetPermissionsMode: "0777" |   datasetPermissionsMode: "0770" | ||||||
|   datasetPermissionsUser: 0 |   datasetPermissionsUser: 1001 | ||||||
|   datasetPermissionsGroup: 0 |   datasetPermissionsGroup: 1001 | ||||||
|    |    | ||||||
|   # for smb with guest |   datasetPermissionsAcls: | ||||||
|   #datasetPermissionsUser: nobody |   - "-m g:builtin_users:full_set:fd:allow" | ||||||
|   #datasetPermissionsGroup: nobody |   - "-m group@:modify_set:fd:allow" | ||||||
|    |   - "-m owner@:full_set:fd:allow" | ||||||
|   #datasetPermissionsGroup: root |  | ||||||
|   #datasetPermissionsAcls: |  | ||||||
|   #- "-m everyone@:full_set:allow" |  | ||||||
| 
 |  | ||||||
|   #datasetPermissionsAcls: |  | ||||||
|   #- "-m u:kube:full_set:allow" |  | ||||||
| 
 | 
 | ||||||
| smb: | smb: | ||||||
|   shareHost: ${TRUENAS_HOST} |   shareHost: ${TRUENAS_HOST} | ||||||
|   #nameTemplate: "" |   #nameTemplate: "" | ||||||
|   namePrefix: "csi-ci-${CI_BUILD_KEY}" |   namePrefix: "csi-ci-${CI_BUILD_KEY}-" | ||||||
|   nameSuffix: "" |   nameSuffix: "" | ||||||
|   shareAuxiliaryConfigurationTemplate: | |   shareAuxiliaryConfigurationTemplate: | | ||||||
|     #guest ok = yes |     #guest ok = yes | ||||||
|  | @ -52,11 +48,21 @@ smb: | ||||||
|   shareAllowedHosts: [] |   shareAllowedHosts: [] | ||||||
|   shareDeniedHosts: [] |   shareDeniedHosts: [] | ||||||
|   #shareDefaultPermissions: true |   #shareDefaultPermissions: true | ||||||
|   shareGuestOk: true |   shareGuestOk: false | ||||||
|   #shareGuestOnly: true |   #shareGuestOnly: true | ||||||
|   #shareShowHiddenFiles: true |   #shareShowHiddenFiles: true | ||||||
|   shareRecycleBin: true |   shareRecycleBin: false | ||||||
|   shareBrowsable: false |   shareBrowsable: false | ||||||
|   shareAccessBasedEnumeration: true |   shareAccessBasedEnumeration: true | ||||||
|   shareTimeMachine: false |   shareTimeMachine: false | ||||||
|   #shareStorageTask: |   #shareStorageTask: | ||||||
|  | 
 | ||||||
|  | node: | ||||||
|  |   mount: | ||||||
|  |     mount_flags: "username=smbroot,password=smbroot" | ||||||
|  | 
 | ||||||
|  | _private: | ||||||
|  |   csi: | ||||||
|  |     volume: | ||||||
|  |       idHash: | ||||||
|  |         strategy: crc16 | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ zfs: | ||||||
| iscsi: | iscsi: | ||||||
|   targetPortal: ${TRUENAS_HOST} |   targetPortal: ${TRUENAS_HOST} | ||||||
|   interface: "" |   interface: "" | ||||||
|   namePrefix: "csi-ci-${CI_BUILD_KEY}" |   namePrefix: "csi-ci-${CI_BUILD_KEY}-" | ||||||
|   nameSuffix: "" |   nameSuffix: "" | ||||||
|   targetGroups: |   targetGroups: | ||||||
|     - targetGroupPortalGroup: 1 |     - targetGroupPortalGroup: 1 | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ zfs: | ||||||
|   detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s |   detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s | ||||||
| 
 | 
 | ||||||
|   datasetEnableQuotas: true |   datasetEnableQuotas: true | ||||||
|   datasetEnableReservation: true |   datasetEnableReservation: false | ||||||
|   datasetPermissionsMode: "0777" |   datasetPermissionsMode: "0777" | ||||||
|   datasetPermissionsUser: 0 |   datasetPermissionsUser: 0 | ||||||
|   datasetPermissionsGroup: 0 |   datasetPermissionsGroup: 0 | ||||||
|  |  | ||||||
|  | @ -17,33 +17,29 @@ sshConnection: | ||||||
| zfs: | zfs: | ||||||
|   datasetProperties: |   datasetProperties: | ||||||
|     # smb options |     # smb options | ||||||
|     #aclmode: restricted |     aclmode: restricted | ||||||
|     #casesensitivity: mixed |     aclinherit: passthrough | ||||||
|  |     acltype: nfsv4 | ||||||
|  |     casesensitivity: insensitive | ||||||
| 
 | 
 | ||||||
|   datasetParentName: tank/ci/${CI_BUILD_KEY}/v |   datasetParentName: tank/ci/${CI_BUILD_KEY}/v | ||||||
|   detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s |   detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s | ||||||
| 
 | 
 | ||||||
|   datasetEnableQuotas: true |   datasetEnableQuotas: true | ||||||
|   datasetEnableReservation: true |   datasetEnableReservation: false | ||||||
|   datasetPermissionsMode: "0777" |   datasetPermissionsMode: "0770" | ||||||
|   datasetPermissionsUser: 0 |   datasetPermissionsUser: 1001 | ||||||
|   datasetPermissionsGroup: 0 |   datasetPermissionsGroup: 1001 | ||||||
|    |    | ||||||
|   # for smb with guest |   datasetPermissionsAcls: | ||||||
|   #datasetPermissionsUser: nobody |   - "-m g:builtin_users:full_set:fd:allow" | ||||||
|   #datasetPermissionsGroup: nobody |   - "-m group@:modify_set:fd:allow" | ||||||
|    |   - "-m owner@:full_set:fd:allow" | ||||||
|   #datasetPermissionsGroup: root |  | ||||||
|   #datasetPermissionsAcls: |  | ||||||
|   #- "-m everyone@:full_set:allow" |  | ||||||
| 
 |  | ||||||
|   #datasetPermissionsAcls: |  | ||||||
|   #- "-m u:kube:full_set:allow" |  | ||||||
| 
 | 
 | ||||||
| smb: | smb: | ||||||
|   shareHost: ${TRUENAS_HOST} |   shareHost: ${TRUENAS_HOST} | ||||||
|   #nameTemplate: "" |   #nameTemplate: "" | ||||||
|   namePrefix: "csi-ci-${CI_BUILD_KEY}" |   namePrefix: "csi-ci-${CI_BUILD_KEY}-" | ||||||
|   nameSuffix: "" |   nameSuffix: "" | ||||||
|   shareAuxiliaryConfigurationTemplate: | |   shareAuxiliaryConfigurationTemplate: | | ||||||
|     #guest ok = yes |     #guest ok = yes | ||||||
|  | @ -52,11 +48,21 @@ smb: | ||||||
|   shareAllowedHosts: [] |   shareAllowedHosts: [] | ||||||
|   shareDeniedHosts: [] |   shareDeniedHosts: [] | ||||||
|   #shareDefaultPermissions: true |   #shareDefaultPermissions: true | ||||||
|   shareGuestOk: true |   shareGuestOk: false | ||||||
|   #shareGuestOnly: true |   #shareGuestOnly: true | ||||||
|   #shareShowHiddenFiles: true |   #shareShowHiddenFiles: true | ||||||
|   shareRecycleBin: true |   shareRecycleBin: false | ||||||
|   shareBrowsable: false |   shareBrowsable: false | ||||||
|   shareAccessBasedEnumeration: true |   shareAccessBasedEnumeration: true | ||||||
|   shareTimeMachine: false |   shareTimeMachine: false | ||||||
|   #shareStorageTask: |   #shareStorageTask: | ||||||
|  | 
 | ||||||
|  | node: | ||||||
|  |   mount: | ||||||
|  |     mount_flags: "username=smbroot,password=smbroot" | ||||||
|  | 
 | ||||||
|  | _private: | ||||||
|  |   csi: | ||||||
|  |     volume: | ||||||
|  |       idHash: | ||||||
|  |         strategy: crc16 | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ zfs: | ||||||
| iscsi: | iscsi: | ||||||
|   targetPortal: ${TRUENAS_HOST} |   targetPortal: ${TRUENAS_HOST} | ||||||
|   interface: "" |   interface: "" | ||||||
|   namePrefix: "csi-ci-${CI_BUILD_KEY}" |   namePrefix: "csi-ci-${CI_BUILD_KEY}-" | ||||||
|   nameSuffix: "" |   nameSuffix: "" | ||||||
|   targetGroups: |   targetGroups: | ||||||
|     - targetGroupPortalGroup: 1 |     - targetGroupPortalGroup: 1 | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ zfs: | ||||||
|   detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s |   detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s | ||||||
| 
 | 
 | ||||||
|   datasetEnableQuotas: true |   datasetEnableQuotas: true | ||||||
|   datasetEnableReservation: true |   datasetEnableReservation: false | ||||||
|   datasetPermissionsMode: "0777" |   datasetPermissionsMode: "0777" | ||||||
|   datasetPermissionsUser: 0 |   datasetPermissionsUser: 0 | ||||||
|   datasetPermissionsGroup: 0 |   datasetPermissionsGroup: 0 | ||||||
|  |  | ||||||
|  | @ -9,35 +9,19 @@ httpConnection: | ||||||
|   password: ${TRUENAS_PASSWORD} |   password: ${TRUENAS_PASSWORD} | ||||||
| 
 | 
 | ||||||
| zfs: | zfs: | ||||||
|   datasetProperties: |  | ||||||
|     # smb options |  | ||||||
|     #aclmode: restricted |  | ||||||
|     #casesensitivity: mixed |  | ||||||
| 
 |  | ||||||
|   datasetParentName: tank/ci/${CI_BUILD_KEY}/v |   datasetParentName: tank/ci/${CI_BUILD_KEY}/v | ||||||
|   detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s |   detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s | ||||||
| 
 | 
 | ||||||
|   datasetEnableQuotas: true |   datasetEnableQuotas: true | ||||||
|   datasetEnableReservation: true |   datasetEnableReservation: false | ||||||
|   datasetPermissionsMode: "0777" |   datasetPermissionsMode: "0770" | ||||||
|   datasetPermissionsUser: 0 |   datasetPermissionsUser: 1001 | ||||||
|   datasetPermissionsGroup: 0 |   datasetPermissionsGroup: 1001 | ||||||
|    |    | ||||||
|   # for smb with guest |  | ||||||
|   #datasetPermissionsUser: nobody |  | ||||||
|   #datasetPermissionsGroup: nobody |  | ||||||
|    |  | ||||||
|   #datasetPermissionsGroup: root |  | ||||||
|   #datasetPermissionsAcls: |  | ||||||
|   #- "-m everyone@:full_set:allow" |  | ||||||
| 
 |  | ||||||
|   #datasetPermissionsAcls: |  | ||||||
|   #- "-m u:kube:full_set:allow" |  | ||||||
| 
 |  | ||||||
| smb: | smb: | ||||||
|   shareHost: ${TRUENAS_HOST} |   shareHost: ${TRUENAS_HOST} | ||||||
|   #nameTemplate: "" |   #nameTemplate: "" | ||||||
|   namePrefix: "csi-ci-${CI_BUILD_KEY}" |   namePrefix: "csi-ci-${CI_BUILD_KEY}-" | ||||||
|   nameSuffix: "" |   nameSuffix: "" | ||||||
|   shareAuxiliaryConfigurationTemplate: | |   shareAuxiliaryConfigurationTemplate: | | ||||||
|     #guest ok = yes |     #guest ok = yes | ||||||
|  | @ -46,11 +30,21 @@ smb: | ||||||
|   shareAllowedHosts: [] |   shareAllowedHosts: [] | ||||||
|   shareDeniedHosts: [] |   shareDeniedHosts: [] | ||||||
|   #shareDefaultPermissions: true |   #shareDefaultPermissions: true | ||||||
|   shareGuestOk: true |   shareGuestOk: false | ||||||
|   #shareGuestOnly: true |   #shareGuestOnly: true | ||||||
|   #shareShowHiddenFiles: true |   #shareShowHiddenFiles: true | ||||||
|   shareRecycleBin: true |   shareRecycleBin: false | ||||||
|   shareBrowsable: false |   shareBrowsable: false | ||||||
|   shareAccessBasedEnumeration: true |   shareAccessBasedEnumeration: true | ||||||
|   shareTimeMachine: false |   shareTimeMachine: false | ||||||
|   #shareStorageTask: |   #shareStorageTask: | ||||||
|  | 
 | ||||||
|  | node: | ||||||
|  |   mount: | ||||||
|  |     mount_flags: "username=smbroot,password=smbroot" | ||||||
|  | 
 | ||||||
|  | _private: | ||||||
|  |   csi: | ||||||
|  |     volume: | ||||||
|  |       idHash: | ||||||
|  |         strategy: crc16 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,31 @@ | ||||||
|  | driver: zfs-generic-iscsi | ||||||
|  | 
 | ||||||
|  | sshConnection: | ||||||
|  |   host: ${SERVER_HOST} | ||||||
|  |   port: 22 | ||||||
|  |   username: ${SERVER_USERNAME} | ||||||
|  |   password: ${SERVER_PASSWORD} | ||||||
|  | 
 | ||||||
|  | zfs: | ||||||
|  |   datasetParentName: tank/ci/${CI_BUILD_KEY}/v | ||||||
|  |   detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s | ||||||
|  | 
 | ||||||
|  |   zvolCompression: | ||||||
|  |   zvolDedup: | ||||||
|  |   zvolEnableReservation: false | ||||||
|  |   zvolBlocksize: | ||||||
|  | 
 | ||||||
|  | iscsi: | ||||||
|  |   targetPortal: ${SERVER_HOST} | ||||||
|  |   interface: "" | ||||||
|  |   namePrefix: "csi-ci-${CI_BUILD_KEY}" | ||||||
|  |   nameSuffix: "" | ||||||
|  |   shareStrategy: "targetCli" | ||||||
|  |   shareStrategyTargetCli: | ||||||
|  |     basename: "iqn.2003-01.org.linux-iscsi.ubuntu-19.x8664" | ||||||
|  |     tpg: | ||||||
|  |       attributes: | ||||||
|  |         authentication: 0 | ||||||
|  |         generate_node_acls: 1 | ||||||
|  |         cache_dynamic_acls: 1 | ||||||
|  |         demo_mode_write_protect: 0 | ||||||
|  | @ -0,0 +1,40 @@ | ||||||
|  | driver: zfs-generic-smb | ||||||
|  | 
 | ||||||
|  | sshConnection: | ||||||
|  |   host: ${SERVER_HOST} | ||||||
|  |   port: 22 | ||||||
|  |   username: ${SERVER_USERNAME} | ||||||
|  |   password: ${SERVER_PASSWORD} | ||||||
|  | 
 | ||||||
|  | zfs: | ||||||
|  |   datasetParentName: tank/ci/${CI_BUILD_KEY}/v | ||||||
|  |   detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s | ||||||
|  | 
 | ||||||
|  |   datasetProperties: | ||||||
|  |     #aclmode: restricted | ||||||
|  |     #aclinherit: passthrough | ||||||
|  |     #acltype: nfsv4 | ||||||
|  |     casesensitivity: insensitive | ||||||
|  | 
 | ||||||
|  |   datasetEnableQuotas: true | ||||||
|  |   datasetEnableReservation: false | ||||||
|  |   datasetPermissionsMode: "0770" | ||||||
|  |   datasetPermissionsUser: smbroot | ||||||
|  |   datasetPermissionsGroup: smbroot | ||||||
|  | 
 | ||||||
|  | smb: | ||||||
|  |   shareHost: ${SERVER_HOST} | ||||||
|  |   shareStrategy: "setDatasetProperties" | ||||||
|  |   shareStrategySetDatasetProperties: | ||||||
|  |     properties: | ||||||
|  |       sharesmb: "on" | ||||||
|  | 
 | ||||||
|  | node: | ||||||
|  |   mount: | ||||||
|  |     mount_flags: "username=smbroot,password=smbroot" | ||||||
|  | 
 | ||||||
|  | _private: | ||||||
|  |   csi: | ||||||
|  |     volume: | ||||||
|  |       idHash: | ||||||
|  |         strategy: crc16 | ||||||
|  | @ -18,7 +18,7 @@ zfs: | ||||||
| iscsi: | iscsi: | ||||||
|   targetPortal: ${SERVER_HOST} |   targetPortal: ${SERVER_HOST} | ||||||
|   interface: "" |   interface: "" | ||||||
|   namePrefix: "csi-ci-${CI_BUILD_KEY}" |   namePrefix: "csi-ci-${CI_BUILD_KEY}-" | ||||||
|   nameSuffix: "" |   nameSuffix: "" | ||||||
|   shareStrategy: "targetCli" |   shareStrategy: "targetCli" | ||||||
|   shareStrategyTargetCli: |   shareStrategyTargetCli: | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ zfs: | ||||||
|   detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s |   detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s | ||||||
| 
 | 
 | ||||||
|   datasetEnableQuotas: true |   datasetEnableQuotas: true | ||||||
|   datasetEnableReservation: true |   datasetEnableReservation: false | ||||||
|   datasetPermissionsMode: "0777" |   datasetPermissionsMode: "0777" | ||||||
|   datasetPermissionsUser: 0 |   datasetPermissionsUser: 0 | ||||||
|   datasetPermissionsGroup: 0 |   datasetPermissionsGroup: 0 | ||||||
|  | @ -21,4 +21,5 @@ nfs: | ||||||
|   shareStrategy: "setDatasetProperties" |   shareStrategy: "setDatasetProperties" | ||||||
|   shareStrategySetDatasetProperties: |   shareStrategySetDatasetProperties: | ||||||
|     properties: |     properties: | ||||||
|       sharenfs: "on" |       #sharenfs: "on" | ||||||
|  |       sharenfs: "rw,no_subtree_check,no_root_squash" | ||||||
|  |  | ||||||
|  | @ -0,0 +1,40 @@ | ||||||
|  | driver: zfs-generic-smb | ||||||
|  | 
 | ||||||
|  | sshConnection: | ||||||
|  |   host: ${SERVER_HOST} | ||||||
|  |   port: 22 | ||||||
|  |   username: ${SERVER_USERNAME} | ||||||
|  |   password: ${SERVER_PASSWORD} | ||||||
|  | 
 | ||||||
|  | zfs: | ||||||
|  |   datasetParentName: tank/ci/${CI_BUILD_KEY}/v | ||||||
|  |   detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s | ||||||
|  | 
 | ||||||
|  |   datasetProperties: | ||||||
|  |     #aclmode: restricted | ||||||
|  |     #aclinherit: passthrough | ||||||
|  |     #acltype: nfsv4 | ||||||
|  |     casesensitivity: insensitive | ||||||
|  | 
 | ||||||
|  |   datasetEnableQuotas: true | ||||||
|  |   datasetEnableReservation: false | ||||||
|  |   datasetPermissionsMode: "0770" | ||||||
|  |   datasetPermissionsUser: smbroot | ||||||
|  |   datasetPermissionsGroup: smbroot | ||||||
|  | 
 | ||||||
|  | smb: | ||||||
|  |   shareHost: ${SERVER_HOST} | ||||||
|  |   shareStrategy: "setDatasetProperties" | ||||||
|  |   shareStrategySetDatasetProperties: | ||||||
|  |     properties: | ||||||
|  |       sharesmb: "on" | ||||||
|  | 
 | ||||||
|  | node: | ||||||
|  |   mount: | ||||||
|  |     mount_flags: "username=smbroot,password=smbroot" | ||||||
|  | 
 | ||||||
|  | _private: | ||||||
|  |   csi: | ||||||
|  |     volume: | ||||||
|  |       idHash: | ||||||
|  |         strategy: crc16 | ||||||
|  | @ -0,0 +1,111 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/disk/v1"; | ||||||
|  | 
 | ||||||
|  | service Disk { | ||||||
|  |     // ListDiskLocations returns locations <Adapter, Bus, Target, LUN ID> of all | ||||||
|  |     // disk devices enumerated by the host. | ||||||
|  |     rpc ListDiskLocations(ListDiskLocationsRequest) returns (ListDiskLocationsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // PartitionDisk initializes and partitions a disk device with the GPT partition style | ||||||
|  |     // (if the disk has not been partitioned already) and returns the resulting volume device ID. | ||||||
|  |     rpc PartitionDisk(PartitionDiskRequest) returns (PartitionDiskResponse) {} | ||||||
|  | 
 | ||||||
|  |     // Rescan refreshes the host's storage cache. | ||||||
|  |     rpc Rescan(RescanRequest) returns (RescanResponse) {} | ||||||
|  | 
 | ||||||
|  |     // ListDiskIDs returns a map of DiskID objects where the key is the disk number. | ||||||
|  |     rpc ListDiskIDs(ListDiskIDsRequest) returns (ListDiskIDsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // GetDiskStats returns the stats of a disk (currently it returns the disk size). | ||||||
|  |     rpc GetDiskStats(GetDiskStatsRequest) returns (GetDiskStatsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // SetDiskState sets the offline/online state of a disk. | ||||||
|  |     rpc SetDiskState(SetDiskStateRequest) returns (SetDiskStateResponse) {} | ||||||
|  | 
 | ||||||
|  |     // GetDiskState gets the offline/online state of a disk. | ||||||
|  |     rpc GetDiskState(GetDiskStateRequest) returns (GetDiskStateResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskLocationsRequest { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DiskLocation { | ||||||
|  |     string Adapter = 1; | ||||||
|  |     string Bus = 2; | ||||||
|  |     string Target = 3; | ||||||
|  |     string LUNID = 4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskLocationsResponse { | ||||||
|  |     // Map of disk number and <adapter, bus, target, lun ID> associated with each disk device. | ||||||
|  |     map <uint32, DiskLocation> disk_locations = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PartitionDiskRequest { | ||||||
|  |     // Disk device number of the disk to partition. | ||||||
|  |     uint32 disk_number = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PartitionDiskResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RescanRequest { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RescanResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskIDsRequest { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DiskIDs { | ||||||
|  |     // The disk page83 id. | ||||||
|  |     string page83 = 1; | ||||||
|  |     // The disk serial number. | ||||||
|  |     string serial_number = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskIDsResponse { | ||||||
|  |     // Map of disk numbers and disk identifiers associated with each disk device. | ||||||
|  |     map <uint32, DiskIDs> diskIDs = 1;  // the case is intentional for protoc to generate the field as DiskIDs | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetDiskStatsRequest { | ||||||
|  |     // Disk device number of the disk to get the stats from. | ||||||
|  |     uint32 disk_number = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetDiskStatsResponse { | ||||||
|  |     // Total size of the volume. | ||||||
|  |     int64 total_bytes = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message SetDiskStateRequest { | ||||||
|  |     // Disk device number of the disk. | ||||||
|  |     uint32 disk_number = 1; | ||||||
|  | 
 | ||||||
|  |     // Online state to set for the disk. true for online, false for offline. | ||||||
|  |     bool is_online = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message SetDiskStateResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetDiskStateRequest { | ||||||
|  |     // Disk device number of the disk. | ||||||
|  |     uint32 disk_number = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetDiskStateResponse { | ||||||
|  |     // Online state of the disk. true for online, false for offline. | ||||||
|  |     bool is_online = 1; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,62 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1alpha1; | ||||||
|  | 
 | ||||||
|  | service Disk { | ||||||
|  |     // ListDiskLocations returns locations <Adapter, Bus, Target, LUN ID> of all | ||||||
|  |     // disk devices enumerated by the host | ||||||
|  |     rpc ListDiskLocations(ListDiskLocationsRequest) returns (ListDiskLocationsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // PartitionDisk initializes and partitions a disk device (if the disk has not | ||||||
|  |     // been partitioned already) and returns the resulting volume device ID | ||||||
|  |     rpc PartitionDisk(PartitionDiskRequest) returns (PartitionDiskResponse) {} | ||||||
|  | 
 | ||||||
|  |     // Rescan refreshes the host's storage cache | ||||||
|  |     rpc Rescan(RescanRequest) returns (RescanResponse) {} | ||||||
|  | 
 | ||||||
|  |     // GetDiskNumberByName returns disk number based on the passing disk name information | ||||||
|  |     rpc GetDiskNumberByName(GetDiskNumberByNameRequest) returns (GetDiskNumberByNameResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskLocationsRequest { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DiskLocation { | ||||||
|  |     string Adapter = 1; | ||||||
|  |     string Bus = 2; | ||||||
|  |     string Target = 3; | ||||||
|  |     string LUNID = 4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskLocationsResponse { | ||||||
|  |     // Map of disk device IDs and <adapter, bus, target, lun ID> associated with each disk device | ||||||
|  |     map <string, DiskLocation> disk_locations = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PartitionDiskRequest { | ||||||
|  |     // Disk device ID of the disk to partition | ||||||
|  |     string diskID = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PartitionDiskResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RescanRequest { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RescanResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetDiskNumberByNameRequest { | ||||||
|  |     // Disk ID | ||||||
|  |     string disk_name = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetDiskNumberByNameResponse { | ||||||
|  |     // Disk number | ||||||
|  |     string disk_number = 1; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,81 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1beta1; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/disk/v1beta1"; | ||||||
|  | 
 | ||||||
|  | service Disk { | ||||||
|  |     // ListDiskLocations returns locations <Adapter, Bus, Target, LUN ID> of all | ||||||
|  |     // disk devices enumerated by the host | ||||||
|  |     rpc ListDiskLocations(ListDiskLocationsRequest) returns (ListDiskLocationsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // PartitionDisk initializes and partitions a disk device (if the disk has not | ||||||
|  |     // been partitioned already) and returns the resulting volume device ID | ||||||
|  |     rpc PartitionDisk(PartitionDiskRequest) returns (PartitionDiskResponse) {} | ||||||
|  | 
 | ||||||
|  |     // Rescan refreshes the host's storage cache | ||||||
|  |     rpc Rescan(RescanRequest) returns (RescanResponse) {} | ||||||
|  | 
 | ||||||
|  |     // ListDiskIDs returns a map of DiskID objects where the key is the disk number | ||||||
|  |     rpc ListDiskIDs(ListDiskIDsRequest) returns (ListDiskIDsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // DiskStats returns the stats for the disk | ||||||
|  |     rpc DiskStats(DiskStatsRequest) returns (DiskStatsResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskLocationsRequest { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DiskLocation { | ||||||
|  |     string Adapter = 1; | ||||||
|  |     string Bus = 2; | ||||||
|  |     string Target = 3; | ||||||
|  |     string LUNID = 4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskLocationsResponse { | ||||||
|  |     // Map of disk device IDs and <adapter, bus, target, lun ID> associated with each disk device | ||||||
|  |     map <string, DiskLocation> disk_locations = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PartitionDiskRequest { | ||||||
|  |     // Disk device ID of the disk to partition | ||||||
|  |     string diskID = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PartitionDiskResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RescanRequest { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RescanResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskIDsRequest { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DiskIDs { | ||||||
|  |     // Map of Disk ID types and Disk ID values | ||||||
|  |     map <string, string> identifiers = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskIDsResponse { | ||||||
|  |     // Map of disk device numbers and IDs <page83> associated with each disk device | ||||||
|  |     map <string, DiskIDs> diskIDs = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DiskStatsRequest { | ||||||
|  |     // Disk device ID of the disk to get the size from | ||||||
|  |     string diskID = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DiskStatsResponse { | ||||||
|  |     //Total size of the volume | ||||||
|  |     int64 diskSize = 1; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,109 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1beta2; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/disk/v1beta2"; | ||||||
|  | 
 | ||||||
|  | service Disk { | ||||||
|  |     // ListDiskLocations returns locations <Adapter, Bus, Target, LUN ID> of all | ||||||
|  |     // disk devices enumerated by the host | ||||||
|  |     rpc ListDiskLocations(ListDiskLocationsRequest) returns (ListDiskLocationsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // PartitionDisk initializes and partitions a disk device (if the disk has not | ||||||
|  |     // been partitioned already) and returns the resulting volume device ID | ||||||
|  |     rpc PartitionDisk(PartitionDiskRequest) returns (PartitionDiskResponse) {} | ||||||
|  | 
 | ||||||
|  |     // Rescan refreshes the host's storage cache | ||||||
|  |     rpc Rescan(RescanRequest) returns (RescanResponse) {} | ||||||
|  | 
 | ||||||
|  |     // ListDiskIDs returns a map of DiskID objects where the key is the disk number | ||||||
|  |     rpc ListDiskIDs(ListDiskIDsRequest) returns (ListDiskIDsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // DiskStats returns the stats for the disk | ||||||
|  |     rpc DiskStats(DiskStatsRequest) returns (DiskStatsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // SetAttachState sets the offline/online state of a disk | ||||||
|  |     rpc SetAttachState(SetAttachStateRequest) returns (SetAttachStateResponse) {} | ||||||
|  | 
 | ||||||
|  |     // GetAttachState gets the offline/online state of a disk | ||||||
|  |     rpc GetAttachState(GetAttachStateRequest) returns (GetAttachStateResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskLocationsRequest { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DiskLocation { | ||||||
|  |     string Adapter = 1; | ||||||
|  |     string Bus = 2; | ||||||
|  |     string Target = 3; | ||||||
|  |     string LUNID = 4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskLocationsResponse { | ||||||
|  |     // Map of disk device IDs and <adapter, bus, target, lun ID> associated with each disk device | ||||||
|  |     map <string, DiskLocation> disk_locations = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PartitionDiskRequest { | ||||||
|  |     // Disk device ID of the disk to partition | ||||||
|  |     string diskID = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PartitionDiskResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RescanRequest { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RescanResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskIDsRequest { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DiskIDs { | ||||||
|  |     // Map of Disk ID types and Disk ID values | ||||||
|  |     map <string, string> identifiers = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskIDsResponse { | ||||||
|  |     // Map of disk device numbers and IDs <page83> associated with each disk device | ||||||
|  |     map <string, DiskIDs> diskIDs = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DiskStatsRequest { | ||||||
|  |     // Disk device ID of the disk to get the size from | ||||||
|  |     string diskID = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DiskStatsResponse { | ||||||
|  |     //Total size of the volume | ||||||
|  |     int64 diskSize = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message SetAttachStateRequest { | ||||||
|  |     // Disk device ID (number) of the disk which state will change | ||||||
|  |     string diskID = 1; | ||||||
|  | 
 | ||||||
|  |     // Online state to set for the disk. true for online, false for offline | ||||||
|  |     bool isOnline = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message SetAttachStateResponse { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetAttachStateRequest { | ||||||
|  |     // Disk device ID (number) of the disk | ||||||
|  |     string diskID = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetAttachStateResponse { | ||||||
|  |     // Online state of the disk. true for online, false for offline | ||||||
|  |     bool isOnline = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,111 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1beta3; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/disk/v1beta3"; | ||||||
|  | 
 | ||||||
|  | service Disk { | ||||||
|  |     // ListDiskLocations returns locations <Adapter, Bus, Target, LUN ID> of all | ||||||
|  |     // disk devices enumerated by the host. | ||||||
|  |     rpc ListDiskLocations(ListDiskLocationsRequest) returns (ListDiskLocationsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // PartitionDisk initializes and partitions a disk device with the GPT partition style | ||||||
|  |     // (if the disk has not been partitioned already) and returns the resulting volume device ID. | ||||||
|  |     rpc PartitionDisk(PartitionDiskRequest) returns (PartitionDiskResponse) {} | ||||||
|  | 
 | ||||||
|  |     // Rescan refreshes the host's storage cache. | ||||||
|  |     rpc Rescan(RescanRequest) returns (RescanResponse) {} | ||||||
|  | 
 | ||||||
|  |     // ListDiskIDs returns a map of DiskID objects where the key is the disk number. | ||||||
|  |     rpc ListDiskIDs(ListDiskIDsRequest) returns (ListDiskIDsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // GetDiskStats returns the stats of a disk (currently it returns the disk size). | ||||||
|  |     rpc GetDiskStats(GetDiskStatsRequest) returns (GetDiskStatsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // SetDiskState sets the offline/online state of a disk. | ||||||
|  |     rpc SetDiskState(SetDiskStateRequest) returns (SetDiskStateResponse) {} | ||||||
|  | 
 | ||||||
|  |     // GetDiskState gets the offline/online state of a disk. | ||||||
|  |     rpc GetDiskState(GetDiskStateRequest) returns (GetDiskStateResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskLocationsRequest { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DiskLocation { | ||||||
|  |     string Adapter = 1; | ||||||
|  |     string Bus = 2; | ||||||
|  |     string Target = 3; | ||||||
|  |     string LUNID = 4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskLocationsResponse { | ||||||
|  |     // Map of disk number and <adapter, bus, target, lun ID> associated with each disk device. | ||||||
|  |     map <uint32, DiskLocation> disk_locations = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PartitionDiskRequest { | ||||||
|  |     // Disk device number of the disk to partition. | ||||||
|  |     uint32 disk_number = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PartitionDiskResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RescanRequest { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RescanResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskIDsRequest { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DiskIDs { | ||||||
|  |     // The disk page83 id. | ||||||
|  |     string page83 = 1; | ||||||
|  |     // The disk serial number. | ||||||
|  |     string serial_number = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListDiskIDsResponse { | ||||||
|  |     // Map of disk numbers and disk identifiers associated with each disk device. | ||||||
|  |     map <uint32, DiskIDs> diskIDs = 1;  // the case is intentional for protoc to generate the field as DiskIDs | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetDiskStatsRequest { | ||||||
|  |     // Disk device number of the disk to get the stats from. | ||||||
|  |     uint32 disk_number = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetDiskStatsResponse { | ||||||
|  |     // Total size of the volume. | ||||||
|  |     int64 total_bytes = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message SetDiskStateRequest { | ||||||
|  |     // Disk device number of the disk. | ||||||
|  |     uint32 disk_number = 1; | ||||||
|  | 
 | ||||||
|  |     // Online state to set for the disk. true for online, false for offline. | ||||||
|  |     bool is_online = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message SetDiskStateResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetDiskStateRequest { | ||||||
|  |     // Disk device number of the disk. | ||||||
|  |     uint32 disk_number = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetDiskStateResponse { | ||||||
|  |     // Online state of the disk. true for online, false for offline. | ||||||
|  |     bool is_online = 1; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,17 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package api; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api"; | ||||||
|  | 
 | ||||||
|  | // CommandError details errors yielded by cmdlet calls. | ||||||
|  | message CmdletError { | ||||||
|  |     // Name of the cmdlet that errored out. | ||||||
|  |     string cmdlet_name = 1; | ||||||
|  | 
 | ||||||
|  |     // Error code that got returned. | ||||||
|  |     uint32 code = 2; | ||||||
|  | 
 | ||||||
|  |     // Human-readable error message - can be empty. | ||||||
|  |     string message = 3; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,136 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/filesystem/v1"; | ||||||
|  | 
 | ||||||
|  | service Filesystem { | ||||||
|  |     // PathExists checks if the requested path exists in the host filesystem. | ||||||
|  |     rpc PathExists(PathExistsRequest) returns (PathExistsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // Mkdir creates a directory at the requested path in the host filesystem. | ||||||
|  |     rpc Mkdir(MkdirRequest) returns (MkdirResponse) {} | ||||||
|  | 
 | ||||||
|  |     // Rmdir removes the directory at the requested path in the host filesystem. | ||||||
|  |     // This may be used for unlinking a symlink created through CreateSymlink. | ||||||
|  |     rpc Rmdir(RmdirRequest) returns (RmdirResponse) {} | ||||||
|  | 
 | ||||||
|  |     // CreateSymlink creates a symbolic link called target_path that points to source_path | ||||||
|  |     // in the host filesystem (target_path is the name of the symbolic link created, | ||||||
|  |     // source_path is the existing path). | ||||||
|  |     rpc CreateSymlink(CreateSymlinkRequest) returns (CreateSymlinkResponse) {} | ||||||
|  | 
 | ||||||
|  |     // IsSymlink checks if a given path is a symlink. | ||||||
|  |     rpc IsSymlink(IsSymlinkRequest) returns (IsSymlinkResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PathExistsRequest { | ||||||
|  |     // The path whose existence we want to check in the host's filesystem | ||||||
|  |     string path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PathExistsResponse { | ||||||
|  |     // Indicates whether the path in PathExistsRequest exists in the host's filesystem | ||||||
|  |     bool exists = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MkdirRequest { | ||||||
|  |     // The path to create in the host's filesystem. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // Non-existent parent directories in the path will be automatically created. | ||||||
|  |     // Directories will be created with Read and Write privileges of the Windows | ||||||
|  |     // User account under which csi-proxy is started (typically LocalSystem). | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // Depending on the context parameter of this function, the path prefix needs | ||||||
|  |     // to match the paths specified either as kubelet-csi-plugins-path | ||||||
|  |     // or as kubelet-pod-path parameters of csi-proxy. | ||||||
|  |     // The path parameter cannot already exist in the host's filesystem. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MkdirResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RmdirRequest { | ||||||
|  |     // The path to remove in the host's filesystem. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // Depending on the context parameter of this function, the path prefix needs | ||||||
|  |     // to match the paths specified either as kubelet-csi-plugins-path | ||||||
|  |     // or as kubelet-pod-path parameters of csi-proxy. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // Path cannot be a file of type symlink. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string path = 1; | ||||||
|  | 
 | ||||||
|  |     // Force remove all contents under path (if any). | ||||||
|  |     bool force = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RmdirResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message CreateSymlinkRequest { | ||||||
|  |     // The path of the existing directory to be linked. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // The path prefix needs needs to match the paths specified as | ||||||
|  |     // kubelet-csi-plugins-path parameter of csi-proxy. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // source_path cannot already exist in the host filesystem. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string source_path = 1; | ||||||
|  | 
 | ||||||
|  |     // Target path is the location of the new directory entry to be created in the host's filesystem. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // The path prefix needs to match the paths specified as | ||||||
|  |     // kubelet-pod-path parameter of csi-proxy. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // target_path needs to exist as a directory in the host that is empty. | ||||||
|  |     // target_path cannot be a symbolic link. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string target_path = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message CreateSymlinkResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsSymlinkRequest { | ||||||
|  |     // The path whose existence as a symlink we want to check in the host's filesystem. | ||||||
|  |     string path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsSymlinkResponse { | ||||||
|  |     // Indicates whether the path in IsSymlinkRequest is a symlink. | ||||||
|  |     bool is_symlink = 1; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,168 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1alpha1; | ||||||
|  | 
 | ||||||
|  | service Filesystem { | ||||||
|  |     // PathExists checks if the requested path exists in the host's filesystem | ||||||
|  |     rpc PathExists(PathExistsRequest) returns (PathExistsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // Mkdir creates a directory at the requested path in the host's filesystem | ||||||
|  |     rpc Mkdir(MkdirRequest) returns (MkdirResponse) {} | ||||||
|  | 
 | ||||||
|  |     // Rmdir removes the directory at the requested path in the host's filesystem. | ||||||
|  |     // This may be used for unlinking a symlink created through LinkPath | ||||||
|  |     rpc Rmdir(RmdirRequest) returns (RmdirResponse) {} | ||||||
|  | 
 | ||||||
|  |     // LinkPath creates a local directory symbolic link between a source path  | ||||||
|  |     // and target path in the host's filesystem | ||||||
|  |     rpc LinkPath(LinkPathRequest) returns (LinkPathResponse) {} | ||||||
|  | 
 | ||||||
|  |     //IsMountPoint checks if a given path is mount or not | ||||||
|  |     rpc IsMountPoint(IsMountPointRequest) returns (IsMountPointResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Context of the paths used for path prefix validation | ||||||
|  | enum PathContext { | ||||||
|  |     // Indicates the kubelet-csi-plugins-path parameter of csi-proxy be used as | ||||||
|  |     // the path context. This may be used while handling NodeStageVolume where | ||||||
|  |     // a volume may need to be mounted at a plugin-specific path like: | ||||||
|  |     // kubelet\plugins\kubernetes.io\csi\pv\<pv-name>\globalmount | ||||||
|  |     PLUGIN = 0; | ||||||
|  |     // Indicates the kubelet-pod-path parameter of csi-proxy be used as the path | ||||||
|  |     // context. This may be used while handling NodePublishVolume where a staged | ||||||
|  |     // volume may be need to be symlinked to a pod-specific path like: | ||||||
|  |     // kubelet\pods\<pod-uuid>\volumes\kubernetes.io~csi\<pvc-name>\mount | ||||||
|  |     POD = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PathExistsRequest { | ||||||
|  |     // The path whose existence we want to check in the host's filesystem | ||||||
|  |     string path = 1; | ||||||
|  | 
 | ||||||
|  |     // Context of the path parameter. | ||||||
|  |     // This is used to validate prefix for absolute paths passed | ||||||
|  |     PathContext context = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PathExistsResponse { | ||||||
|  |     // Error message if any. Empty string indicates success | ||||||
|  |     string error = 1; | ||||||
|  | 
 | ||||||
|  |     // Indicates whether the path in PathExistsRequest exists in the host's filesystem | ||||||
|  |     bool exists = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MkdirRequest { | ||||||
|  |     // The path to create in the host's filesystem. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // Non-existent parent directories in the path will be automatically created. | ||||||
|  |     // Directories will be created with Read and Write privileges of the Windows | ||||||
|  |     // User account under which csi-proxy is started (typically LocalSystem). | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // Depending on the context parameter of this function, the path prefix needs  | ||||||
|  |     // to match the paths specified either as kubelet-csi-plugins-path | ||||||
|  |     // or as kubelet-pod-path parameters of csi-proxy. | ||||||
|  |     // The path parameter cannot already exist in the host's filesystem. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string path = 1; | ||||||
|  | 
 | ||||||
|  |     // Context of the path parameter. | ||||||
|  |     // This is used to validate prefix for absolute paths passed | ||||||
|  |     PathContext context = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MkdirResponse { | ||||||
|  |     // Error message if any. Empty string indicates success | ||||||
|  |     string error = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RmdirRequest { | ||||||
|  |     // The path to remove in the host's filesystem. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // Depending on the context parameter of this function, the path prefix needs  | ||||||
|  |     // to match the paths specified either as kubelet-csi-plugins-path | ||||||
|  |     // or as kubelet-pod-path parameters of csi-proxy. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // Path cannot be a file of type symlink. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string path = 1; | ||||||
|  | 
 | ||||||
|  |     // Context of the path parameter. | ||||||
|  |     // This is used to validate prefix for absolute paths passed | ||||||
|  |     PathContext context = 2; | ||||||
|  | 
 | ||||||
|  |     // Force remove all contents under path (if any). | ||||||
|  |     bool force = 3; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RmdirResponse { | ||||||
|  |     // Error message if any. Empty string indicates success | ||||||
|  |     string error = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message LinkPathRequest { | ||||||
|  |     // The path where the symlink is created in the host's filesystem. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // The path prefix needs needs to match the paths specified as  | ||||||
|  |     // kubelet-csi-plugins-path parameter of csi-proxy. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // source_path cannot already exist in the host filesystem. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string source_path = 1; | ||||||
|  | 
 | ||||||
|  |     // Target path in the host's filesystem used for the symlink creation. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // The path prefix needs to match the paths specified as | ||||||
|  |     // kubelet-pod-path parameter of csi-proxy. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // target_path needs to exist as a directory in the host that is empty. | ||||||
|  |     // target_path cannot be a symbolic link. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string target_path = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message LinkPathResponse { | ||||||
|  |     // Error message if any. Empty string indicates success | ||||||
|  |     string error = 1; | ||||||
|  | } | ||||||
|  |   | ||||||
|  | message IsMountPointRequest { | ||||||
|  |     // The path whose existence we want to check in the host's filesystem | ||||||
|  |     string path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsMountPointResponse { | ||||||
|  |     // Error message if any. Empty string indicates success | ||||||
|  |     string error = 1; | ||||||
|  | 
 | ||||||
|  |     // Indicates whether the path in PathExistsRequest exists in the host's filesystem | ||||||
|  |     bool is_mount_point = 2; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,168 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1beta1; | ||||||
|  | 
 | ||||||
|  | service Filesystem { | ||||||
|  |     // PathExists checks if the requested path exists in the host's filesystem | ||||||
|  |     rpc PathExists(PathExistsRequest) returns (PathExistsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // Mkdir creates a directory at the requested path in the host's filesystem | ||||||
|  |     rpc Mkdir(MkdirRequest) returns (MkdirResponse) {} | ||||||
|  | 
 | ||||||
|  |     // Rmdir removes the directory at the requested path in the host's filesystem. | ||||||
|  |     // This may be used for unlinking a symlink created through LinkPath | ||||||
|  |     rpc Rmdir(RmdirRequest) returns (RmdirResponse) {} | ||||||
|  | 
 | ||||||
|  |     // LinkPath creates a local directory symbolic link between a source path  | ||||||
|  |     // and target path in the host's filesystem | ||||||
|  |     rpc LinkPath(LinkPathRequest) returns (LinkPathResponse) {} | ||||||
|  | 
 | ||||||
|  |     //IsMountPoint checks if a given path is mount or not | ||||||
|  |     rpc IsMountPoint(IsMountPointRequest) returns (IsMountPointResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Context of the paths used for path prefix validation | ||||||
|  | enum PathContext { | ||||||
|  |     // Indicates the kubelet-csi-plugins-path parameter of csi-proxy be used as | ||||||
|  |     // the path context. This may be used while handling NodeStageVolume where | ||||||
|  |     // a volume may need to be mounted at a plugin-specific path like: | ||||||
|  |     // kubelet\plugins\kubernetes.io\csi\pv\<pv-name>\globalmount | ||||||
|  |     PLUGIN = 0; | ||||||
|  |     // Indicates the kubelet-pod-path parameter of csi-proxy be used as the path | ||||||
|  |     // context. This may be used while handling NodePublishVolume where a staged | ||||||
|  |     // volume may be need to be symlinked to a pod-specific path like: | ||||||
|  |     // kubelet\pods\<pod-uuid>\volumes\kubernetes.io~csi\<pvc-name>\mount | ||||||
|  |     POD = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PathExistsRequest { | ||||||
|  |     // The path whose existence we want to check in the host's filesystem | ||||||
|  |     string path = 1; | ||||||
|  | 
 | ||||||
|  |     // Context of the path parameter. | ||||||
|  |     // This is used to validate prefix for absolute paths passed | ||||||
|  |     PathContext context = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PathExistsResponse { | ||||||
|  |     // Error message if any. Empty string indicates success | ||||||
|  |     string error = 1; | ||||||
|  | 
 | ||||||
|  |     // Indicates whether the path in PathExistsRequest exists in the host's filesystem | ||||||
|  |     bool exists = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MkdirRequest { | ||||||
|  |     // The path to create in the host's filesystem. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // Non-existent parent directories in the path will be automatically created. | ||||||
|  |     // Directories will be created with Read and Write privileges of the Windows | ||||||
|  |     // User account under which csi-proxy is started (typically LocalSystem). | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // Depending on the context parameter of this function, the path prefix needs  | ||||||
|  |     // to match the paths specified either as kubelet-csi-plugins-path | ||||||
|  |     // or as kubelet-pod-path parameters of csi-proxy. | ||||||
|  |     // The path parameter cannot already exist in the host's filesystem. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string path = 1; | ||||||
|  | 
 | ||||||
|  |     // Context of the path parameter. | ||||||
|  |     // This is used to validate prefix for absolute paths passed | ||||||
|  |     PathContext context = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MkdirResponse { | ||||||
|  |     // Error message if any. Empty string indicates success | ||||||
|  |     string error = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RmdirRequest { | ||||||
|  |     // The path to remove in the host's filesystem. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // Depending on the context parameter of this function, the path prefix needs  | ||||||
|  |     // to match the paths specified either as kubelet-csi-plugins-path | ||||||
|  |     // or as kubelet-pod-path parameters of csi-proxy. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // Path cannot be a file of type symlink. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string path = 1; | ||||||
|  | 
 | ||||||
|  |     // Context of the path parameter. | ||||||
|  |     // This is used to validate prefix for absolute paths passed | ||||||
|  |     PathContext context = 2; | ||||||
|  | 
 | ||||||
|  |     // Force remove all contents under path (if any). | ||||||
|  |     bool force = 3; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RmdirResponse { | ||||||
|  |     // Error message if any. Empty string indicates success | ||||||
|  |     string error = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message LinkPathRequest { | ||||||
|  |     // The path where the symlink is created in the host's filesystem. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // The path prefix needs needs to match the paths specified as  | ||||||
|  |     // kubelet-csi-plugins-path parameter of csi-proxy. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // source_path cannot already exist in the host filesystem. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string source_path = 1; | ||||||
|  | 
 | ||||||
|  |     // Target path in the host's filesystem used for the symlink creation. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // The path prefix needs to match the paths specified as | ||||||
|  |     // kubelet-pod-path parameter of csi-proxy. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // target_path needs to exist as a directory in the host that is empty. | ||||||
|  |     // target_path cannot be a symbolic link. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string target_path = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message LinkPathResponse { | ||||||
|  |     // Error message if any. Empty string indicates success | ||||||
|  |     string error = 1; | ||||||
|  | } | ||||||
|  |   | ||||||
|  | message IsMountPointRequest { | ||||||
|  |     // The path whose existence we want to check in the host's filesystem | ||||||
|  |     string path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsMountPointResponse { | ||||||
|  |     // Error message if any. Empty string indicates success | ||||||
|  |     string error = 1; | ||||||
|  | 
 | ||||||
|  |     // Indicates whether the path in PathExistsRequest exists in the host's filesystem | ||||||
|  |     bool is_mount_point = 2; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,136 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1beta2; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/filesystem/v1beta2"; | ||||||
|  | 
 | ||||||
|  | service Filesystem { | ||||||
|  |     // PathExists checks if the requested path exists in the host filesystem. | ||||||
|  |     rpc PathExists(PathExistsRequest) returns (PathExistsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // Mkdir creates a directory at the requested path in the host filesystem. | ||||||
|  |     rpc Mkdir(MkdirRequest) returns (MkdirResponse) {} | ||||||
|  | 
 | ||||||
|  |     // Rmdir removes the directory at the requested path in the host filesystem. | ||||||
|  |     // This may be used for unlinking a symlink created through CreateSymlink. | ||||||
|  |     rpc Rmdir(RmdirRequest) returns (RmdirResponse) {} | ||||||
|  | 
 | ||||||
|  |     // CreateSymlink creates a symbolic link called target_path that points to source_path | ||||||
|  |     // in the host filesystem (target_path is the name of the symbolic link created, | ||||||
|  |     // source_path is the existing path). | ||||||
|  |     rpc CreateSymlink(CreateSymlinkRequest) returns (CreateSymlinkResponse) {} | ||||||
|  | 
 | ||||||
|  |     // IsSymlink checks if a given path is a symlink. | ||||||
|  |     rpc IsSymlink(IsSymlinkRequest) returns (IsSymlinkResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PathExistsRequest { | ||||||
|  |     // The path whose existence we want to check in the host's filesystem | ||||||
|  |     string path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PathExistsResponse { | ||||||
|  |     // Indicates whether the path in PathExistsRequest exists in the host's filesystem | ||||||
|  |     bool exists = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MkdirRequest { | ||||||
|  |     // The path to create in the host's filesystem. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // Non-existent parent directories in the path will be automatically created. | ||||||
|  |     // Directories will be created with Read and Write privileges of the Windows | ||||||
|  |     // User account under which csi-proxy is started (typically LocalSystem). | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // Depending on the context parameter of this function, the path prefix needs | ||||||
|  |     // to match the paths specified either as kubelet-csi-plugins-path | ||||||
|  |     // or as kubelet-pod-path parameters of csi-proxy. | ||||||
|  |     // The path parameter cannot already exist in the host's filesystem. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MkdirResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RmdirRequest { | ||||||
|  |     // The path to remove in the host's filesystem. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // Depending on the context parameter of this function, the path prefix needs | ||||||
|  |     // to match the paths specified either as kubelet-csi-plugins-path | ||||||
|  |     // or as kubelet-pod-path parameters of csi-proxy. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // Path cannot be a file of type symlink. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string path = 1; | ||||||
|  | 
 | ||||||
|  |     // Force remove all contents under path (if any). | ||||||
|  |     bool force = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RmdirResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message CreateSymlinkRequest { | ||||||
|  |     // The path of the existing directory to be linked. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // The path prefix needs needs to match the paths specified as | ||||||
|  |     // kubelet-csi-plugins-path parameter of csi-proxy. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // source_path cannot already exist in the host filesystem. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string source_path = 1; | ||||||
|  | 
 | ||||||
|  |     // Target path is the location of the new directory entry to be created in the host's filesystem. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // The path prefix needs to match the paths specified as | ||||||
|  |     // kubelet-pod-path parameter of csi-proxy. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // target_path needs to exist as a directory in the host that is empty. | ||||||
|  |     // target_path cannot be a symbolic link. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string target_path = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message CreateSymlinkResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsSymlinkRequest { | ||||||
|  |     // The path whose existence as a symlink we want to check in the host's filesystem. | ||||||
|  |     string path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsSymlinkResponse { | ||||||
|  |     // Indicates whether the path in IsSymlinkRequest is a symlink. | ||||||
|  |     bool is_symlink = 1; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,163 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v2alpha1; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/filesystem/v2alpha1"; | ||||||
|  | 
 | ||||||
|  | service Filesystem { | ||||||
|  |     // PathExists checks if the requested path exists in the host filesystem. | ||||||
|  |     rpc PathExists(PathExistsRequest) returns (PathExistsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // Mkdir creates a directory at the requested path in the host filesystem. | ||||||
|  |     rpc Mkdir(MkdirRequest) returns (MkdirResponse) {} | ||||||
|  | 
 | ||||||
|  |     // Rmdir removes the directory at the requested path in the host filesystem. | ||||||
|  |     // This may be used for unlinking a symlink created through CreateSymlink. | ||||||
|  |     rpc Rmdir(RmdirRequest) returns (RmdirResponse) {} | ||||||
|  | 
 | ||||||
|  |     // RmdirContents removes the contents of a directory in the host filesystem. | ||||||
|  |     // Unlike Rmdir it won't delete the requested path, it'll only delete its contents. | ||||||
|  |     rpc RmdirContents(RmdirContentsRequest) returns (RmdirContentsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // CreateSymlink creates a symbolic link called target_path that points to source_path | ||||||
|  |     // in the host filesystem (target_path is the name of the symbolic link created, | ||||||
|  |     // source_path is the existing path). | ||||||
|  |     rpc CreateSymlink(CreateSymlinkRequest) returns (CreateSymlinkResponse) {} | ||||||
|  | 
 | ||||||
|  |     // IsSymlink checks if a given path is a symlink. | ||||||
|  |     rpc IsSymlink(IsSymlinkRequest) returns (IsSymlinkResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PathExistsRequest { | ||||||
|  |     // The path whose existence we want to check in the host's filesystem | ||||||
|  |     string path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message PathExistsResponse { | ||||||
|  |     // Indicates whether the path in PathExistsRequest exists in the host's filesystem | ||||||
|  |     bool exists = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MkdirRequest { | ||||||
|  |     // The path to create in the host's filesystem. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // Non-existent parent directories in the path will be automatically created. | ||||||
|  |     // Directories will be created with Read and Write privileges of the Windows | ||||||
|  |     // User account under which csi-proxy is started (typically LocalSystem). | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // Depending on the context parameter of this function, the path prefix needs | ||||||
|  |     // to match the paths specified either as kubelet-csi-plugins-path | ||||||
|  |     // or as kubelet-pod-path parameters of csi-proxy. | ||||||
|  |     // The path parameter cannot already exist in the host's filesystem. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MkdirResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RmdirRequest { | ||||||
|  |     // The path to remove in the host's filesystem. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // Depending on the context parameter of this function, the path prefix needs | ||||||
|  |     // to match the paths specified either as kubelet-csi-plugins-path | ||||||
|  |     // or as kubelet-pod-path parameters of csi-proxy. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // Path cannot be a file of type symlink. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string path = 1; | ||||||
|  | 
 | ||||||
|  |     // Force remove all contents under path (if any). | ||||||
|  |     bool force = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RmdirResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RmdirContentsRequest { | ||||||
|  |     // The path whose contents will be removed in the host's filesystem. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // Depending on the context parameter of this function, the path prefix needs | ||||||
|  |     // to match the paths specified either as kubelet-csi-plugins-path | ||||||
|  |     // or as kubelet-pod-path parameters of csi-proxy. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // Path cannot be a file of type symlink. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RmdirContentsResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message CreateSymlinkRequest { | ||||||
|  |     // The path of the existing directory to be linked. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // The path prefix needs needs to match the paths specified as | ||||||
|  |     // kubelet-csi-plugins-path parameter of csi-proxy. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // source_path cannot already exist in the host filesystem. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string source_path = 1; | ||||||
|  | 
 | ||||||
|  |     // Target path is the location of the new directory entry to be created in the host's filesystem. | ||||||
|  |     // All special characters allowed by Windows in path names will be allowed | ||||||
|  |     // except for restrictions noted below. For details, please check: | ||||||
|  |     // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted. | ||||||
|  |     // The path prefix needs to match the paths specified as | ||||||
|  |     // kubelet-pod-path parameter of csi-proxy. | ||||||
|  |     // UNC paths of the form "\\server\share\path\file" are not allowed. | ||||||
|  |     // All directory separators need to be backslash character: "\". | ||||||
|  |     // Characters: .. / : | ? * in the path are not allowed. | ||||||
|  |     // target_path needs to exist as a directory in the host that is empty. | ||||||
|  |     // target_path cannot be a symbolic link. | ||||||
|  |     // Maximum path length will be capped to 260 characters. | ||||||
|  |     string target_path = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message CreateSymlinkResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsSymlinkRequest { | ||||||
|  |     // The path whose existence as a symlink we want to check in the host's filesystem. | ||||||
|  |     string path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsSymlinkResponse { | ||||||
|  |     // Indicates whether the path in IsSymlinkRequest is a symlink. | ||||||
|  |     bool is_symlink = 1; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,153 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1alpha1; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/iscsi/v1alpha1"; | ||||||
|  | 
 | ||||||
|  | service Iscsi { | ||||||
|  |   // AddTargetPortal registers an iSCSI target network address for later | ||||||
|  |   // discovery. | ||||||
|  |   // AddTargetPortal currently does not support selecting different NICs or | ||||||
|  |   // a different iSCSI initiator (e.g a hardware initiator). This means that | ||||||
|  |   // Windows will select the initiator NIC and instance on its own. | ||||||
|  |   rpc AddTargetPortal(AddTargetPortalRequest) | ||||||
|  |       returns (AddTargetPortalResponse) {} | ||||||
|  | 
 | ||||||
|  |   // DiscoverTargetPortal initiates discovery on an iSCSI target network address | ||||||
|  |   // and returns discovered IQNs. | ||||||
|  |   rpc DiscoverTargetPortal(DiscoverTargetPortalRequest) | ||||||
|  |       returns (DiscoverTargetPortalResponse) {} | ||||||
|  | 
 | ||||||
|  |   // RemoveTargetPortal removes an iSCSI target network address registration. | ||||||
|  |   rpc RemoveTargetPortal(RemoveTargetPortalRequest) | ||||||
|  |       returns (RemoveTargetPortalResponse) {} | ||||||
|  | 
 | ||||||
|  |   // ListTargetPortal lists all currently registered iSCSI target network | ||||||
|  |   // addresses. | ||||||
|  |   rpc ListTargetPortals(ListTargetPortalsRequest) | ||||||
|  |       returns (ListTargetPortalsResponse) {} | ||||||
|  | 
 | ||||||
|  |   // ConnectTarget connects to an iSCSI Target | ||||||
|  |   rpc ConnectTarget(ConnectTargetRequest) returns (ConnectTargetResponse) {} | ||||||
|  | 
 | ||||||
|  |   // DisconnectTarget disconnects from an iSCSI Target | ||||||
|  |   rpc DisconnectTarget(DisconnectTargetRequest) | ||||||
|  |       returns (DisconnectTargetResponse) {} | ||||||
|  | 
 | ||||||
|  |   // GetTargetDisks returns the disk addresses that correspond to an iSCSI | ||||||
|  |   // target | ||||||
|  |   rpc GetTargetDisks(GetTargetDisksRequest) returns (GetTargetDisksResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TargetPortal is an address and port pair for a specific iSCSI storage | ||||||
|  | // target. | ||||||
|  | message TargetPortal { | ||||||
|  |   // iSCSI Target (server) address | ||||||
|  |   string target_address = 1; | ||||||
|  | 
 | ||||||
|  |   // iSCSI Target port (default iSCSI port is 3260) | ||||||
|  |   uint32 target_port = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message AddTargetPortalRequest { | ||||||
|  |   // iSCSI Target Portal to register in the initiator | ||||||
|  |   TargetPortal target_portal = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message AddTargetPortalResponse { | ||||||
|  |   // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DiscoverTargetPortalRequest { | ||||||
|  |   // iSCSI Target Portal on which to initiate discovery | ||||||
|  |   TargetPortal target_portal = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DiscoverTargetPortalResponse { | ||||||
|  |   // List of discovered IQN addresses | ||||||
|  |   // follows IQN format: iqn.yyyy-mm.naming-authority:unique-name | ||||||
|  |   repeated string iqns = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RemoveTargetPortalRequest { | ||||||
|  |   // iSCSI Target Portal | ||||||
|  |   TargetPortal target_portal = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RemoveTargetPortalResponse { | ||||||
|  |   // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListTargetPortalsRequest { | ||||||
|  |   // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListTargetPortalsResponse { | ||||||
|  |   // A list of Target Portals currently registered in the initiator | ||||||
|  |   repeated TargetPortal target_portals = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | enum AuthenticationType { | ||||||
|  |   // No authentication is used | ||||||
|  |   NONE = 0; | ||||||
|  | 
 | ||||||
|  |   // One way CHAP authentication. The target authenticates the initiator. | ||||||
|  |   ONE_WAY_CHAP = 1; | ||||||
|  | 
 | ||||||
|  |   // Mutual CHAP authentication. The target and initiator authenticate each | ||||||
|  |   // other. | ||||||
|  |   MUTUAL_CHAP = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ConnectTargetRequest { | ||||||
|  |   // Target portal to which the initiator will connect | ||||||
|  |   TargetPortal target_portal = 1; | ||||||
|  | 
 | ||||||
|  |   // IQN of the iSCSI Target | ||||||
|  |   string iqn = 2; | ||||||
|  | 
 | ||||||
|  |   // Connection authentication type, None by default | ||||||
|  |   // | ||||||
|  |   // One Way Chap uses the chap_username and chap_secret | ||||||
|  |   // fields mentioned below to authenticate the initiator. | ||||||
|  |   // | ||||||
|  |   // Mutual Chap uses both the user/secret mentioned below | ||||||
|  |   // and the Initiator Chap Secret to authenticate the target and initiator. | ||||||
|  |   AuthenticationType auth_type = 3; | ||||||
|  | 
 | ||||||
|  |   // CHAP Username used to authenticate the initiator | ||||||
|  |   string chap_username = 4; | ||||||
|  | 
 | ||||||
|  |   // CHAP password used to authenticate the initiator | ||||||
|  |   string chap_secret = 5; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ConnectTargetResponse { | ||||||
|  |   // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetTargetDisksRequest { | ||||||
|  |   // Target portal whose disks will be queried | ||||||
|  |   TargetPortal target_portal = 1; | ||||||
|  | 
 | ||||||
|  |   // IQN of the iSCSI Target | ||||||
|  |   string iqn = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetTargetDisksResponse { | ||||||
|  |   // List composed of disk ids (numbers) that are associated with the | ||||||
|  |   // iSCSI target | ||||||
|  |   repeated string diskIDs = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DisconnectTargetRequest { | ||||||
|  |   // Target portal from which initiator will disconnect | ||||||
|  |   TargetPortal target_portal = 1; | ||||||
|  | 
 | ||||||
|  |   // IQN of the iSCSI Target | ||||||
|  |   string iqn = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DisconnectTargetResponse { | ||||||
|  |   // Intentionally empty | ||||||
|  | } | ||||||
|  | @ -0,0 +1,175 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1alpha2; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/iscsi/v1alpha2"; | ||||||
|  | 
 | ||||||
|  | service Iscsi { | ||||||
|  |   // AddTargetPortal registers an iSCSI target network address for later | ||||||
|  |   // discovery. | ||||||
|  |   // AddTargetPortal currently does not support selecting different NICs or | ||||||
|  |   // a different iSCSI initiator (e.g a hardware initiator). This means that | ||||||
|  |   // Windows will select the initiator NIC and instance on its own. | ||||||
|  |   rpc AddTargetPortal(AddTargetPortalRequest) | ||||||
|  |       returns (AddTargetPortalResponse) {} | ||||||
|  | 
 | ||||||
|  |   // DiscoverTargetPortal initiates discovery on an iSCSI target network address | ||||||
|  |   // and returns discovered IQNs. | ||||||
|  |   rpc DiscoverTargetPortal(DiscoverTargetPortalRequest) | ||||||
|  |       returns (DiscoverTargetPortalResponse) {} | ||||||
|  | 
 | ||||||
|  |   // RemoveTargetPortal removes an iSCSI target network address registration. | ||||||
|  |   rpc RemoveTargetPortal(RemoveTargetPortalRequest) | ||||||
|  |       returns (RemoveTargetPortalResponse) {} | ||||||
|  | 
 | ||||||
|  |   // ListTargetPortal lists all currently registered iSCSI target network | ||||||
|  |   // addresses. | ||||||
|  |   rpc ListTargetPortals(ListTargetPortalsRequest) | ||||||
|  |       returns (ListTargetPortalsResponse) {} | ||||||
|  | 
 | ||||||
|  |   // ConnectTarget connects to an iSCSI Target | ||||||
|  |   rpc ConnectTarget(ConnectTargetRequest) returns (ConnectTargetResponse) {} | ||||||
|  | 
 | ||||||
|  |   // DisconnectTarget disconnects from an iSCSI Target | ||||||
|  |   rpc DisconnectTarget(DisconnectTargetRequest) | ||||||
|  |       returns (DisconnectTargetResponse) {} | ||||||
|  | 
 | ||||||
|  |   // GetTargetDisks returns the disk addresses that correspond to an iSCSI | ||||||
|  |   // target | ||||||
|  |   rpc GetTargetDisks(GetTargetDisksRequest) returns (GetTargetDisksResponse) {} | ||||||
|  | 
 | ||||||
|  |   // SetMutualChapSecret sets the default CHAP secret that all initiators on | ||||||
|  |   // this machine (node) use to authenticate the target on mutual CHAP | ||||||
|  |   // authentication. | ||||||
|  |   // NOTE: This method affects global node state and should only be used | ||||||
|  |   //       with consideration to other CSI drivers that run concurrently. | ||||||
|  |   rpc SetMutualChapSecret(SetMutualChapSecretRequest) | ||||||
|  |       returns (SetMutualChapSecretResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TargetPortal is an address and port pair for a specific iSCSI storage | ||||||
|  | // target. | ||||||
|  | message TargetPortal { | ||||||
|  |   // iSCSI Target (server) address | ||||||
|  |   string target_address = 1; | ||||||
|  | 
 | ||||||
|  |   // iSCSI Target port (default iSCSI port is 3260) | ||||||
|  |   uint32 target_port = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message AddTargetPortalRequest { | ||||||
|  |   // iSCSI Target Portal to register in the initiator | ||||||
|  |   TargetPortal target_portal = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message AddTargetPortalResponse { | ||||||
|  |   // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DiscoverTargetPortalRequest { | ||||||
|  |   // iSCSI Target Portal on which to initiate discovery | ||||||
|  |   TargetPortal target_portal = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DiscoverTargetPortalResponse { | ||||||
|  |   // List of discovered IQN addresses | ||||||
|  |   // follows IQN format: iqn.yyyy-mm.naming-authority:unique-name | ||||||
|  |   repeated string iqns = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RemoveTargetPortalRequest { | ||||||
|  |   // iSCSI Target Portal | ||||||
|  |   TargetPortal target_portal = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RemoveTargetPortalResponse { | ||||||
|  |   // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListTargetPortalsRequest { | ||||||
|  |   // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListTargetPortalsResponse { | ||||||
|  |   // A list of Target Portals currently registered in the initiator | ||||||
|  |   repeated TargetPortal target_portals = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // iSCSI logon authentication type | ||||||
|  | enum AuthenticationType { | ||||||
|  |   // No authentication is used | ||||||
|  |   NONE = 0; | ||||||
|  | 
 | ||||||
|  |   // One way CHAP authentication. The target authenticates the initiator. | ||||||
|  |   ONE_WAY_CHAP = 1; | ||||||
|  | 
 | ||||||
|  |   // Mutual CHAP authentication. The target and initiator authenticate each | ||||||
|  |   // other. | ||||||
|  |   MUTUAL_CHAP = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ConnectTargetRequest { | ||||||
|  |   // Target portal to which the initiator will connect | ||||||
|  |   TargetPortal target_portal = 1; | ||||||
|  | 
 | ||||||
|  |   // IQN of the iSCSI Target | ||||||
|  |   string iqn = 2; | ||||||
|  | 
 | ||||||
|  |   // Connection authentication type, None by default | ||||||
|  |   // | ||||||
|  |   // One Way Chap uses the chap_username and chap_secret | ||||||
|  |   // fields mentioned below to authenticate the initiator. | ||||||
|  |   // | ||||||
|  |   // Mutual Chap uses both the user/secret mentioned below | ||||||
|  |   // and the Initiator Chap Secret (See `SetMutualChapSecret`) | ||||||
|  |   // to authenticate the target and initiator. | ||||||
|  |   AuthenticationType auth_type = 3; | ||||||
|  | 
 | ||||||
|  |   // CHAP Username used to authenticate the initiator | ||||||
|  |   string chap_username = 4; | ||||||
|  | 
 | ||||||
|  |   // CHAP password used to authenticate the initiator | ||||||
|  |   string chap_secret = 5; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ConnectTargetResponse { | ||||||
|  |   // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetTargetDisksRequest { | ||||||
|  |   // Target portal whose disks will be queried | ||||||
|  |   TargetPortal target_portal = 1; | ||||||
|  | 
 | ||||||
|  |   // IQN of the iSCSI Target | ||||||
|  |   string iqn = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetTargetDisksResponse { | ||||||
|  |   // List composed of disk ids (numbers) that are associated with the | ||||||
|  |   // iSCSI target | ||||||
|  |   repeated string diskIDs = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DisconnectTargetRequest { | ||||||
|  |   // Target portal from which initiator will disconnect | ||||||
|  |   TargetPortal target_portal = 1; | ||||||
|  | 
 | ||||||
|  |   // IQN of the iSCSI Target | ||||||
|  |   string iqn = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DisconnectTargetResponse { | ||||||
|  |   // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message SetMutualChapSecretRequest { | ||||||
|  |   // the default CHAP secret that all initiators on this machine (node) use to | ||||||
|  |   // authenticate the target on mutual CHAP authentication. | ||||||
|  |   // Must be at least 12 byte long for non-Ipsec connections, at least one | ||||||
|  |   // byte long for Ipsec connections, and at most 16 bytes long. | ||||||
|  |   string MutualChapSecret = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message SetMutualChapSecretResponse { | ||||||
|  |   // Intentionally empty | ||||||
|  | } | ||||||
|  | @ -0,0 +1,58 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/smb/v1"; | ||||||
|  | 
 | ||||||
|  | service Smb { | ||||||
|  |     // NewSmbGlobalMapping creates an SMB mapping on the SMB client to an SMB share. | ||||||
|  |     rpc NewSmbGlobalMapping(NewSmbGlobalMappingRequest) returns (NewSmbGlobalMappingResponse) {} | ||||||
|  | 
 | ||||||
|  |     // RemoveSmbGlobalMapping removes the SMB mapping to an SMB share. | ||||||
|  |     rpc RemoveSmbGlobalMapping(RemoveSmbGlobalMappingRequest) returns (RemoveSmbGlobalMappingResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | message NewSmbGlobalMappingRequest { | ||||||
|  |     // A remote SMB share to mount | ||||||
|  |     // All unicode characters allowed in SMB server name specifications are | ||||||
|  |     // permitted except for restrictions below | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // SMB remote path specified in the format: \\server-name\sharename, \\server.fqdn\sharename or \\a.b.c.d\sharename | ||||||
|  |     // If not an IP address, share name has to be a valid DNS name. | ||||||
|  |     // UNC specifications to local paths or prefix: \\?\ is not allowed. | ||||||
|  |     // Characters: + [ ] " / : ; | < > , ? * = $ are not allowed. | ||||||
|  |     string remote_path = 1; | ||||||
|  | 
 | ||||||
|  |     // Optional local path to mount the smb on | ||||||
|  |     string local_path = 2; | ||||||
|  | 
 | ||||||
|  |     // Username credential associated with the share | ||||||
|  |     string username = 3; | ||||||
|  | 
 | ||||||
|  |     // Password credential associated with the share | ||||||
|  |     string password = 4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message NewSmbGlobalMappingResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | message RemoveSmbGlobalMappingRequest { | ||||||
|  |     // A remote SMB share mapping to remove | ||||||
|  |     // All unicode characters allowed in SMB server name specifications are | ||||||
|  |     // permitted except for restrictions below | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // SMB share specified in the format: \\server-name\sharename, \\server.fqdn\sharename or \\a.b.c.d\sharename | ||||||
|  |     // If not an IP address, share name has to be a valid DNS name. | ||||||
|  |     // UNC specifications to local paths or prefix: \\?\ is not allowed. | ||||||
|  |     // Characters: + [ ] " / : ; | < > , ? * = $ are not allowed. | ||||||
|  |     string remote_path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RemoveSmbGlobalMappingResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | @ -0,0 +1,59 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1alpha1; | ||||||
|  | 
 | ||||||
|  | service Smb { | ||||||
|  |     // NewSmbGlobalMapping creates an SMB mapping on the SMB client to an SMB share. | ||||||
|  |     rpc NewSmbGlobalMapping(NewSmbGlobalMappingRequest) returns (NewSmbGlobalMappingResponse) {} | ||||||
|  | 
 | ||||||
|  |     // RemoveSmbGlobalMapping removes the SMB mapping to an SMB share. | ||||||
|  |     rpc RemoveSmbGlobalMapping(RemoveSmbGlobalMappingRequest) returns (RemoveSmbGlobalMappingResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | message NewSmbGlobalMappingRequest { | ||||||
|  |     // A remote SMB share to mount | ||||||
|  |     // All unicode characters allowed in SMB server name specifications are | ||||||
|  |     // permitted except for restrictions below | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // SMB remote path specified in the format: \\server-name\sharename, \\server.fqdn\sharename or \\a.b.c.d\sharename | ||||||
|  |     // If not an IP address, share name has to be a valid DNS name. | ||||||
|  |     // UNC specifications to local paths or prefix: \\?\ is not allowed. | ||||||
|  |     // Characters: + [ ] " / : ; | < > , ? * = $ are not allowed. | ||||||
|  |     string remote_path = 1; | ||||||
|  |     // Optional local path to mount the smb on | ||||||
|  |     string local_path = 2; | ||||||
|  | 
 | ||||||
|  |     // Username credential associated with the share | ||||||
|  |     string username = 3; | ||||||
|  | 
 | ||||||
|  |     // Password credential associated with the share | ||||||
|  |     string password = 4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message NewSmbGlobalMappingResponse { | ||||||
|  |     // Windows error code | ||||||
|  |     // Success is represented as 0 | ||||||
|  |     string error = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | message RemoveSmbGlobalMappingRequest { | ||||||
|  |     // A remote SMB share mapping to remove | ||||||
|  |     // All unicode characters allowed in SMB server name specifications are | ||||||
|  |     // permitted except for restrictions below | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // SMB share specified in the format: \\server-name\sharename, \\server.fqdn\sharename or \\a.b.c.d\sharename | ||||||
|  |     // If not an IP address, share name has to be a valid DNS name. | ||||||
|  |     // UNC specifications to local paths or prefix: \\?\ is not allowed. | ||||||
|  |     // Characters: + [ ] " / : ; | < > , ? * = $ are not allowed. | ||||||
|  |     string remote_path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RemoveSmbGlobalMappingResponse { | ||||||
|  |     // Windows error code | ||||||
|  |     // Success is represented as 0 | ||||||
|  |     string error = 1; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,59 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1beta1; | ||||||
|  | 
 | ||||||
|  | service Smb { | ||||||
|  |     // NewSmbGlobalMapping creates an SMB mapping on the SMB client to an SMB share. | ||||||
|  |     rpc NewSmbGlobalMapping(NewSmbGlobalMappingRequest) returns (NewSmbGlobalMappingResponse) {} | ||||||
|  | 
 | ||||||
|  |     // RemoveSmbGlobalMapping removes the SMB mapping to an SMB share. | ||||||
|  |     rpc RemoveSmbGlobalMapping(RemoveSmbGlobalMappingRequest) returns (RemoveSmbGlobalMappingResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | message NewSmbGlobalMappingRequest { | ||||||
|  |     // A remote SMB share to mount | ||||||
|  |     // All unicode characters allowed in SMB server name specifications are | ||||||
|  |     // permitted except for restrictions below | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // SMB remote path specified in the format: \\server-name\sharename, \\server.fqdn\sharename or \\a.b.c.d\sharename | ||||||
|  |     // If not an IP address, share name has to be a valid DNS name. | ||||||
|  |     // UNC specifications to local paths or prefix: \\?\ is not allowed. | ||||||
|  |     // Characters: + [ ] " / : ; | < > , ? * = $ are not allowed. | ||||||
|  |     string remote_path = 1; | ||||||
|  |     // Optional local path to mount the smb on | ||||||
|  |     string local_path = 2; | ||||||
|  | 
 | ||||||
|  |     // Username credential associated with the share | ||||||
|  |     string username = 3; | ||||||
|  | 
 | ||||||
|  |     // Password credential associated with the share | ||||||
|  |     string password = 4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message NewSmbGlobalMappingResponse { | ||||||
|  |     // Windows error code | ||||||
|  |     // Success is represented as 0 | ||||||
|  |     string error = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | message RemoveSmbGlobalMappingRequest { | ||||||
|  |     // A remote SMB share mapping to remove | ||||||
|  |     // All unicode characters allowed in SMB server name specifications are | ||||||
|  |     // permitted except for restrictions below | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // SMB share specified in the format: \\server-name\sharename, \\server.fqdn\sharename or \\a.b.c.d\sharename | ||||||
|  |     // If not an IP address, share name has to be a valid DNS name. | ||||||
|  |     // UNC specifications to local paths or prefix: \\?\ is not allowed. | ||||||
|  |     // Characters: + [ ] " / : ; | < > , ? * = $ are not allowed. | ||||||
|  |     string remote_path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RemoveSmbGlobalMappingResponse { | ||||||
|  |     // Windows error code | ||||||
|  |     // Success is represented as 0 | ||||||
|  |     string error = 1; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,58 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1beta2; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/smb/v1beta2"; | ||||||
|  | 
 | ||||||
|  | service Smb { | ||||||
|  |     // NewSmbGlobalMapping creates an SMB mapping on the SMB client to an SMB share. | ||||||
|  |     rpc NewSmbGlobalMapping(NewSmbGlobalMappingRequest) returns (NewSmbGlobalMappingResponse) {} | ||||||
|  | 
 | ||||||
|  |     // RemoveSmbGlobalMapping removes the SMB mapping to an SMB share. | ||||||
|  |     rpc RemoveSmbGlobalMapping(RemoveSmbGlobalMappingRequest) returns (RemoveSmbGlobalMappingResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | message NewSmbGlobalMappingRequest { | ||||||
|  |     // A remote SMB share to mount | ||||||
|  |     // All unicode characters allowed in SMB server name specifications are | ||||||
|  |     // permitted except for restrictions below | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // SMB remote path specified in the format: \\server-name\sharename, \\server.fqdn\sharename or \\a.b.c.d\sharename | ||||||
|  |     // If not an IP address, share name has to be a valid DNS name. | ||||||
|  |     // UNC specifications to local paths or prefix: \\?\ is not allowed. | ||||||
|  |     // Characters: + [ ] " / : ; | < > , ? * = $ are not allowed. | ||||||
|  |     string remote_path = 1; | ||||||
|  | 
 | ||||||
|  |     // Optional local path to mount the smb on | ||||||
|  |     string local_path = 2; | ||||||
|  | 
 | ||||||
|  |     // Username credential associated with the share | ||||||
|  |     string username = 3; | ||||||
|  | 
 | ||||||
|  |     // Password credential associated with the share | ||||||
|  |     string password = 4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message NewSmbGlobalMappingResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | message RemoveSmbGlobalMappingRequest { | ||||||
|  |     // A remote SMB share mapping to remove | ||||||
|  |     // All unicode characters allowed in SMB server name specifications are | ||||||
|  |     // permitted except for restrictions below | ||||||
|  |     // | ||||||
|  |     // Restrictions: | ||||||
|  |     // SMB share specified in the format: \\server-name\sharename, \\server.fqdn\sharename or \\a.b.c.d\sharename | ||||||
|  |     // If not an IP address, share name has to be a valid DNS name. | ||||||
|  |     // UNC specifications to local paths or prefix: \\?\ is not allowed. | ||||||
|  |     // Characters: + [ ] " / : ; | < > , ? * = $ are not allowed. | ||||||
|  |     string remote_path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message RemoveSmbGlobalMappingResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | @ -0,0 +1,93 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1alpha1; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/system/v1alpha1"; | ||||||
|  | 
 | ||||||
|  | service System { | ||||||
|  |   // GetBIOSSerialNumber returns the device's serial number | ||||||
|  |   rpc GetBIOSSerialNumber(GetBIOSSerialNumberRequest) | ||||||
|  |       returns (GetBIOSSerialNumberResponse) {} | ||||||
|  | 
 | ||||||
|  |   // StartService starts a Windows service | ||||||
|  |   // NOTE: This method affects global node state and should only be used | ||||||
|  |   //       with consideration to other CSI drivers that run concurrently. | ||||||
|  |   rpc StartService(StartServiceRequest) returns (StartServiceResponse) {} | ||||||
|  | 
 | ||||||
|  |   // StopService stops a Windows service | ||||||
|  |   // NOTE: This method affects global node state and should only be used | ||||||
|  |   //       with consideration to other CSI drivers that run concurrently. | ||||||
|  |   rpc StopService(StopServiceRequest) returns (StopServiceResponse) {} | ||||||
|  | 
 | ||||||
|  |   // GetService queries a Windows service state | ||||||
|  |   rpc GetService(GetServiceRequest) returns (GetServiceResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetBIOSSerialNumberRequest { | ||||||
|  |   // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetBIOSSerialNumberResponse { | ||||||
|  |   // Serial number | ||||||
|  |   string serial_number = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message StartServiceRequest { | ||||||
|  |   // Service name (as listed in System\CCS\Services keys) | ||||||
|  |   string name = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message StartServiceResponse { | ||||||
|  |   // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message StopServiceRequest { | ||||||
|  |   // Service name (as listed in System\CCS\Services keys) | ||||||
|  |   string name = 1; | ||||||
|  | 
 | ||||||
|  |   // Forces stopping of services that has dependent services | ||||||
|  |   bool force = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message StopServiceResponse { | ||||||
|  |   // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // https://docs.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_status#members | ||||||
|  | enum ServiceStatus { | ||||||
|  |   UNKNOWN = 0; | ||||||
|  |   STOPPED = 1; | ||||||
|  |   START_PENDING = 2; | ||||||
|  |   STOP_PENDING = 3; | ||||||
|  |   RUNNING = 4; | ||||||
|  |   CONTINUE_PENDING = 5; | ||||||
|  |   PAUSE_PENDING = 6; | ||||||
|  |   PAUSED = 7; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-changeserviceconfiga | ||||||
|  | enum StartType { | ||||||
|  |   BOOT = 0; | ||||||
|  |   SYSTEM = 1; | ||||||
|  |   AUTOMATIC = 2; | ||||||
|  |   MANUAL = 3; | ||||||
|  |   DISABLED = 4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetServiceRequest { | ||||||
|  |   // Service name (as listed in System\CCS\Services keys) | ||||||
|  |   string name = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetServiceResponse { | ||||||
|  |   // Service display name | ||||||
|  |   string display_name = 1; | ||||||
|  | 
 | ||||||
|  |   // Service start type. | ||||||
|  |   // Used to control whether a service will start on boot, and if so on which | ||||||
|  |   // boot phase. | ||||||
|  |   StartType start_type = 2; | ||||||
|  | 
 | ||||||
|  |   // Service status, e.g. stopped, running, paused | ||||||
|  |   ServiceStatus status = 3; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,143 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/volume/v1"; | ||||||
|  | 
 | ||||||
|  | service Volume { | ||||||
|  |     // ListVolumesOnDisk returns the volume IDs (in \\.\Volume{GUID} format) for all volumes from a | ||||||
|  |     // given disk number and partition number (optional) | ||||||
|  |     rpc ListVolumesOnDisk(ListVolumesOnDiskRequest) returns (ListVolumesOnDiskResponse) {} | ||||||
|  | 
 | ||||||
|  |     // MountVolume mounts the volume at the requested global staging path. | ||||||
|  |     rpc MountVolume(MountVolumeRequest) returns (MountVolumeResponse) {} | ||||||
|  | 
 | ||||||
|  |     // UnmountVolume flushes data cache to disk and removes the global staging path. | ||||||
|  |     rpc UnmountVolume(UnmountVolumeRequest) returns (UnmountVolumeResponse) {} | ||||||
|  | 
 | ||||||
|  |     // IsVolumeFormatted checks if a volume is formatted. | ||||||
|  |     rpc IsVolumeFormatted(IsVolumeFormattedRequest) returns (IsVolumeFormattedResponse) {} | ||||||
|  | 
 | ||||||
|  |     // FormatVolume formats a volume with NTFS. | ||||||
|  |     rpc FormatVolume(FormatVolumeRequest) returns (FormatVolumeResponse) {} | ||||||
|  | 
 | ||||||
|  |     // ResizeVolume performs resizing of the partition and file system for a block based volume. | ||||||
|  |     rpc ResizeVolume(ResizeVolumeRequest) returns (ResizeVolumeResponse) {} | ||||||
|  | 
 | ||||||
|  |     // GetVolumeStats gathers total bytes and used bytes for a volume. | ||||||
|  |     rpc GetVolumeStats(GetVolumeStatsRequest) returns (GetVolumeStatsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // GetDiskNumberFromVolumeID gets the disk number of the disk where the volume is located. | ||||||
|  |     rpc GetDiskNumberFromVolumeID(GetDiskNumberFromVolumeIDRequest) returns (GetDiskNumberFromVolumeIDResponse ) {} | ||||||
|  | 
 | ||||||
|  |     // GetVolumeIDFromTargetPath gets the volume id for a given target path. | ||||||
|  |     rpc GetVolumeIDFromTargetPath(GetVolumeIDFromTargetPathRequest) returns (GetVolumeIDFromTargetPathResponse) {} | ||||||
|  | 
 | ||||||
|  |     // WriteVolumeCache write volume cache to disk. | ||||||
|  |     rpc WriteVolumeCache(WriteVolumeCacheRequest) returns (WriteVolumeCacheResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListVolumesOnDiskRequest { | ||||||
|  |     // Disk device number of the disk to query for volumes. | ||||||
|  |     uint32 disk_number = 1; | ||||||
|  |     // The partition number (optional), by default it uses the first partition of the disk. | ||||||
|  |     uint32 partition_number = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListVolumesOnDiskResponse { | ||||||
|  |     // Volume device IDs of volumes on the specified disk. | ||||||
|  |     repeated string volume_ids = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MountVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to mount. | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // Path in the host's file system where the volume needs to be mounted. | ||||||
|  |     string target_path = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MountVolumeResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message UnmountVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to dismount. | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // Path where the volume has been mounted. | ||||||
|  |     string target_path = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message UnmountVolumeResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsVolumeFormattedRequest { | ||||||
|  |     // Volume device ID of the volume to check. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsVolumeFormattedResponse { | ||||||
|  |     // Is the volume formatted with NTFS. | ||||||
|  |     bool formatted = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message FormatVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to format. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message FormatVolumeResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ResizeVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to resize. | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // New size in bytes of the volume. | ||||||
|  |     int64 size_bytes = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ResizeVolumeResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetVolumeStatsRequest{ | ||||||
|  |     // Volume device Id of the volume to get the stats for. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetVolumeStatsResponse{ | ||||||
|  |     // Total bytes | ||||||
|  |     int64 total_bytes = 1; | ||||||
|  |     // Used bytes | ||||||
|  |     int64 used_bytes = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetDiskNumberFromVolumeIDRequest { | ||||||
|  |     // Volume device ID of the volume to get the disk number for. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetDiskNumberFromVolumeIDResponse { | ||||||
|  |     // Corresponding disk number. | ||||||
|  |     uint32 disk_number = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetVolumeIDFromTargetPathRequest { | ||||||
|  |     // The target path. | ||||||
|  |     string target_path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetVolumeIDFromTargetPathResponse { | ||||||
|  |     // The volume device ID. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message WriteVolumeCacheRequest { | ||||||
|  |     // Volume device ID of the volume to flush the cache. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message WriteVolumeCacheResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | @ -0,0 +1,69 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1alpha1; | ||||||
|  | 
 | ||||||
|  | service Volume { | ||||||
|  |     // ListVolumesOnDisk returns the volume IDs (in \\.\Volume{GUID} format) for | ||||||
|  |     // all volumes on a Disk device | ||||||
|  |     rpc ListVolumesOnDisk(ListVolumesOnDiskRequest) returns (ListVolumesOnDiskResponse) {} | ||||||
|  |     // MountVolume mounts the volume at the requested global staging path | ||||||
|  |     rpc MountVolume(MountVolumeRequest) returns (MountVolumeResponse) {} | ||||||
|  |     // DismountVolume gracefully dismounts a volume | ||||||
|  |     rpc DismountVolume(DismountVolumeRequest) returns (DismountVolumeResponse) {} | ||||||
|  |     // IsVolumeFormatted checks if a volume is formatted with NTFS | ||||||
|  |     rpc IsVolumeFormatted(IsVolumeFormattedRequest) returns (IsVolumeFormattedResponse) {} | ||||||
|  |     // FormatVolume formats a volume with the provided file system | ||||||
|  |     rpc FormatVolume(FormatVolumeRequest) returns (FormatVolumeResponse) {} | ||||||
|  |     // ResizeVolume performs resizing of the partition and file system for a block based volume | ||||||
|  |     rpc ResizeVolume(ResizeVolumeRequest) returns (ResizeVolumeResponse) {} | ||||||
|  | } | ||||||
|  | message ListVolumesOnDiskRequest { | ||||||
|  |     // Disk device ID of the disk to query for volumes | ||||||
|  |     string disk_id = 1; | ||||||
|  | } | ||||||
|  | message ListVolumesOnDiskResponse { | ||||||
|  |     // Volume device IDs of volumes on the specified disk | ||||||
|  |     repeated string volume_ids = 1; | ||||||
|  | } | ||||||
|  | message MountVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to mount | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // Path in the host's file system where the volume needs to be mounted | ||||||
|  |     string path = 2; | ||||||
|  | } | ||||||
|  | message MountVolumeResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | message DismountVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to dismount | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // Path where the volume has been mounted. | ||||||
|  |     string path = 2; | ||||||
|  | } | ||||||
|  | message DismountVolumeResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | message IsVolumeFormattedRequest { | ||||||
|  |     // Volume device ID of the volume to check | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | message IsVolumeFormattedResponse { | ||||||
|  |     // Is the volume formatted with NTFS | ||||||
|  |     bool formatted = 1; | ||||||
|  | } | ||||||
|  | message FormatVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to format | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | message FormatVolumeResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | message ResizeVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to dismount | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // New size of the volume | ||||||
|  |     int64 size = 2; | ||||||
|  | } | ||||||
|  | message ResizeVolumeResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | @ -0,0 +1,121 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1beta1; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/volume/v1beta1"; | ||||||
|  | 
 | ||||||
|  | service Volume { | ||||||
|  |     // ListVolumesOnDisk returns the volume IDs (in \\.\Volume{GUID} format) for | ||||||
|  |     // all volumes on a Disk device | ||||||
|  |     rpc ListVolumesOnDisk(ListVolumesOnDiskRequest) returns (ListVolumesOnDiskResponse) {} | ||||||
|  |     // MountVolume mounts the volume at the requested global staging path | ||||||
|  |     rpc MountVolume(MountVolumeRequest) returns (MountVolumeResponse) {} | ||||||
|  |     // DismountVolume gracefully dismounts a volume | ||||||
|  |     rpc DismountVolume(DismountVolumeRequest) returns (DismountVolumeResponse) {} | ||||||
|  |     // IsVolumeFormatted checks if a volume is formatted with NTFS | ||||||
|  |     rpc IsVolumeFormatted(IsVolumeFormattedRequest) returns (IsVolumeFormattedResponse) {} | ||||||
|  |     // FormatVolume formats a volume with the provided file system | ||||||
|  |     rpc FormatVolume(FormatVolumeRequest) returns (FormatVolumeResponse) {} | ||||||
|  |     // ResizeVolume performs resizing of the partition and file system for a block based volume | ||||||
|  |     rpc ResizeVolume(ResizeVolumeRequest) returns (ResizeVolumeResponse) {} | ||||||
|  |     // VolumeStats gathers DiskSize, VolumeSize and VolumeUsedSize for a volume | ||||||
|  |     rpc VolumeStats(VolumeStatsRequest) returns (VolumeStatsResponse) {} | ||||||
|  |     // GetVolumeDiskNumber gets the disk number of the disk where the volume is located | ||||||
|  |     rpc GetVolumeDiskNumber(VolumeDiskNumberRequest) returns (VolumeDiskNumberResponse) {} | ||||||
|  |     // GetVolumeIDFromMount gets the volume id for a given mount | ||||||
|  |     rpc GetVolumeIDFromMount(VolumeIDFromMountRequest) returns (VolumeIDFromMountResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListVolumesOnDiskRequest { | ||||||
|  |     // Disk device ID of the disk to query for volumes | ||||||
|  |     string disk_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListVolumesOnDiskResponse { | ||||||
|  |     // Volume device IDs of volumes on the specified disk | ||||||
|  |     repeated string volume_ids = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MountVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to mount | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // Path in the host's file system where the volume needs to be mounted | ||||||
|  |     string path = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MountVolumeResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DismountVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to dismount | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // Path where the volume has been mounted. | ||||||
|  |     string path = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DismountVolumeResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsVolumeFormattedRequest { | ||||||
|  |     // Volume device ID of the volume to check | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsVolumeFormattedResponse { | ||||||
|  |     // Is the volume formatted with NTFS | ||||||
|  |     bool formatted = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message FormatVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to format | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message FormatVolumeResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ResizeVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to dismount | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // New size of the volume | ||||||
|  |     int64 size = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ResizeVolumeResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message VolumeStatsRequest{ | ||||||
|  |     // Volume device Id of the volume to get the stats for | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message VolumeStatsResponse{ | ||||||
|  |     // Capacity of the volume | ||||||
|  |     int64 volumeSize = 1; | ||||||
|  |     // Used bytes | ||||||
|  |     int64 volumeUsedSize = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message VolumeDiskNumberRequest{ | ||||||
|  |     // Volume device Id of the volume to get the disk number for | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message VolumeDiskNumberResponse{ | ||||||
|  |     // Corresponding disk number | ||||||
|  |     int64 diskNumber = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message VolumeIDFromMountRequest { | ||||||
|  |     // Mount | ||||||
|  |     string mount = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message VolumeIDFromMountResponse { | ||||||
|  |     // Mount | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,132 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1beta2; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/volume/v1beta2"; | ||||||
|  | 
 | ||||||
|  | service Volume { | ||||||
|  |     // ListVolumesOnDisk returns the volume IDs (in \\.\Volume{GUID} format) for | ||||||
|  |     // all volumes on a Disk device | ||||||
|  |     rpc ListVolumesOnDisk(ListVolumesOnDiskRequest) returns (ListVolumesOnDiskResponse) {} | ||||||
|  |     // MountVolume mounts the volume at the requested global staging path | ||||||
|  |     rpc MountVolume(MountVolumeRequest) returns (MountVolumeResponse) {} | ||||||
|  |     // DismountVolume gracefully dismounts a volume | ||||||
|  |     rpc DismountVolume(DismountVolumeRequest) returns (DismountVolumeResponse) {} | ||||||
|  |     // IsVolumeFormatted checks if a volume is formatted with NTFS | ||||||
|  |     rpc IsVolumeFormatted(IsVolumeFormattedRequest) returns (IsVolumeFormattedResponse) {} | ||||||
|  |     // FormatVolume formats a volume with the provided file system | ||||||
|  |     rpc FormatVolume(FormatVolumeRequest) returns (FormatVolumeResponse) {} | ||||||
|  |     // ResizeVolume performs resizing of the partition and file system for a block based volume | ||||||
|  |     rpc ResizeVolume(ResizeVolumeRequest) returns (ResizeVolumeResponse) {} | ||||||
|  |     // VolumeStats gathers DiskSize, VolumeSize and VolumeUsedSize for a volume | ||||||
|  |     rpc VolumeStats(VolumeStatsRequest) returns (VolumeStatsResponse) {} | ||||||
|  |     // GetVolumeDiskNumber gets the disk number of the disk where the volume is located | ||||||
|  |     rpc GetVolumeDiskNumber(VolumeDiskNumberRequest) returns (VolumeDiskNumberResponse) {} | ||||||
|  |     // GetVolumeIDFromMount gets the volume id for a given mount | ||||||
|  |     rpc GetVolumeIDFromMount(VolumeIDFromMountRequest) returns (VolumeIDFromMountResponse) {} | ||||||
|  |     // WriteVolumeCache write volume cache to disk | ||||||
|  |     rpc WriteVolumeCache(WriteVolumeCacheRequest) returns (WriteVolumeCacheResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListVolumesOnDiskRequest { | ||||||
|  |     // Disk device ID of the disk to query for volumes | ||||||
|  |     string disk_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListVolumesOnDiskResponse { | ||||||
|  |     // Volume device IDs of volumes on the specified disk | ||||||
|  |     repeated string volume_ids = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MountVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to mount | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // Path in the host's file system where the volume needs to be mounted | ||||||
|  |     string path = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MountVolumeResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DismountVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to dismount | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // Path where the volume has been mounted. | ||||||
|  |     string path = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message DismountVolumeResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsVolumeFormattedRequest { | ||||||
|  |     // Volume device ID of the volume to check | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsVolumeFormattedResponse { | ||||||
|  |     // Is the volume formatted with NTFS | ||||||
|  |     bool formatted = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message FormatVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to format | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message FormatVolumeResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ResizeVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to dismount | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // New size of the volume | ||||||
|  |     int64 size = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ResizeVolumeResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message VolumeStatsRequest{ | ||||||
|  |     // Volume device Id of the volume to get the stats for | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message VolumeStatsResponse{ | ||||||
|  |     // Capacity of the volume | ||||||
|  |     int64 volumeSize = 1; | ||||||
|  |     // Used bytes | ||||||
|  |     int64 volumeUsedSize = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message VolumeDiskNumberRequest{ | ||||||
|  |     // Volume device Id of the volume to get the disk number for | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message VolumeDiskNumberResponse{ | ||||||
|  |     // Corresponding disk number | ||||||
|  |     int64 diskNumber = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message VolumeIDFromMountRequest { | ||||||
|  |     // Mount | ||||||
|  |     string mount = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message VolumeIDFromMountResponse { | ||||||
|  |     // Mount | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message WriteVolumeCacheRequest { | ||||||
|  |     // Volume device ID of the volume to flush the cache | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message WriteVolumeCacheResponse { | ||||||
|  |     // Intentionally empty | ||||||
|  | } | ||||||
|  | @ -0,0 +1,143 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v1beta3; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/volume/v1beta3"; | ||||||
|  | 
 | ||||||
|  | service Volume { | ||||||
|  |     // ListVolumesOnDisk returns the volume IDs (in \\.\Volume{GUID} format) for all volumes from a | ||||||
|  |     // given disk number and partition number (optional) | ||||||
|  |     rpc ListVolumesOnDisk(ListVolumesOnDiskRequest) returns (ListVolumesOnDiskResponse) {} | ||||||
|  | 
 | ||||||
|  |     // MountVolume mounts the volume at the requested global staging path. | ||||||
|  |     rpc MountVolume(MountVolumeRequest) returns (MountVolumeResponse) {} | ||||||
|  | 
 | ||||||
|  |     // UnmountVolume flushes data cache to disk and removes the global staging path. | ||||||
|  |     rpc UnmountVolume(UnmountVolumeRequest) returns (UnmountVolumeResponse) {} | ||||||
|  | 
 | ||||||
|  |     // IsVolumeFormatted checks if a volume is formatted. | ||||||
|  |     rpc IsVolumeFormatted(IsVolumeFormattedRequest) returns (IsVolumeFormattedResponse) {} | ||||||
|  | 
 | ||||||
|  |     // FormatVolume formats a volume with NTFS. | ||||||
|  |     rpc FormatVolume(FormatVolumeRequest) returns (FormatVolumeResponse) {} | ||||||
|  | 
 | ||||||
|  |     // ResizeVolume performs resizing of the partition and file system for a block based volume. | ||||||
|  |     rpc ResizeVolume(ResizeVolumeRequest) returns (ResizeVolumeResponse) {} | ||||||
|  | 
 | ||||||
|  |     // GetVolumeStats gathers total bytes and used bytes for a volume. | ||||||
|  |     rpc GetVolumeStats(GetVolumeStatsRequest) returns (GetVolumeStatsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // GetDiskNumberFromVolumeID gets the disk number of the disk where the volume is located. | ||||||
|  |     rpc GetDiskNumberFromVolumeID(GetDiskNumberFromVolumeIDRequest) returns (GetDiskNumberFromVolumeIDResponse ) {} | ||||||
|  | 
 | ||||||
|  |     // GetVolumeIDFromTargetPath gets the volume id for a given target path. | ||||||
|  |     rpc GetVolumeIDFromTargetPath(GetVolumeIDFromTargetPathRequest) returns (GetVolumeIDFromTargetPathResponse) {} | ||||||
|  | 
 | ||||||
|  |     // WriteVolumeCache write volume cache to disk. | ||||||
|  |     rpc WriteVolumeCache(WriteVolumeCacheRequest) returns (WriteVolumeCacheResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListVolumesOnDiskRequest { | ||||||
|  |     // Disk device number of the disk to query for volumes. | ||||||
|  |     uint32 disk_number = 1; | ||||||
|  |     // The partition number (optional), by default it uses the first partition of the disk. | ||||||
|  |     uint32 partition_number = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListVolumesOnDiskResponse { | ||||||
|  |     // Volume device IDs of volumes on the specified disk. | ||||||
|  |     repeated string volume_ids = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MountVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to mount. | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // Path in the host's file system where the volume needs to be mounted. | ||||||
|  |     string target_path = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MountVolumeResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message UnmountVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to dismount. | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // Path where the volume has been mounted. | ||||||
|  |     string target_path = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message UnmountVolumeResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsVolumeFormattedRequest { | ||||||
|  |     // Volume device ID of the volume to check. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsVolumeFormattedResponse { | ||||||
|  |     // Is the volume formatted with NTFS. | ||||||
|  |     bool formatted = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message FormatVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to format. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message FormatVolumeResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ResizeVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to resize. | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // New size in bytes of the volume. | ||||||
|  |     int64 size_bytes = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ResizeVolumeResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetVolumeStatsRequest{ | ||||||
|  |     // Volume device Id of the volume to get the stats for. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetVolumeStatsResponse{ | ||||||
|  |     // Total bytes | ||||||
|  |     int64 total_bytes = 1; | ||||||
|  |     // Used bytes | ||||||
|  |     int64 used_bytes = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetDiskNumberFromVolumeIDRequest { | ||||||
|  |     // Volume device ID of the volume to get the disk number for. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetDiskNumberFromVolumeIDResponse { | ||||||
|  |     // Corresponding disk number. | ||||||
|  |     uint32 disk_number = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetVolumeIDFromTargetPathRequest { | ||||||
|  |     // The target path. | ||||||
|  |     string target_path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetVolumeIDFromTargetPathResponse { | ||||||
|  |     // The volume device ID. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message WriteVolumeCacheRequest { | ||||||
|  |     // Volume device ID of the volume to flush the cache. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message WriteVolumeCacheResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | @ -0,0 +1,158 @@ | ||||||
|  | syntax = "proto3"; | ||||||
|  | 
 | ||||||
|  | package v2alpha1; | ||||||
|  | 
 | ||||||
|  | option go_package = "github.com/kubernetes-csi/csi-proxy/client/api/volume/v2alpha1"; | ||||||
|  | 
 | ||||||
|  | service Volume { | ||||||
|  |     // ListVolumesOnDisk returns the volume IDs (in \\.\Volume{GUID} format) for all volumes from a | ||||||
|  |     // given disk number and partition number (optional) | ||||||
|  |     rpc ListVolumesOnDisk(ListVolumesOnDiskRequest) returns (ListVolumesOnDiskResponse) {} | ||||||
|  | 
 | ||||||
|  |     // MountVolume mounts the volume at the requested global staging path. | ||||||
|  |     rpc MountVolume(MountVolumeRequest) returns (MountVolumeResponse) {} | ||||||
|  | 
 | ||||||
|  |     // UnmountVolume flushes data cache to disk and removes the global staging path. | ||||||
|  |     rpc UnmountVolume(UnmountVolumeRequest) returns (UnmountVolumeResponse) {} | ||||||
|  | 
 | ||||||
|  |     // IsVolumeFormatted checks if a volume is formatted. | ||||||
|  |     rpc IsVolumeFormatted(IsVolumeFormattedRequest) returns (IsVolumeFormattedResponse) {} | ||||||
|  | 
 | ||||||
|  |     // FormatVolume formats a volume with NTFS. | ||||||
|  |     rpc FormatVolume(FormatVolumeRequest) returns (FormatVolumeResponse) {} | ||||||
|  | 
 | ||||||
|  |     // ResizeVolume performs resizing of the partition and file system for a block based volume. | ||||||
|  |     rpc ResizeVolume(ResizeVolumeRequest) returns (ResizeVolumeResponse) {} | ||||||
|  | 
 | ||||||
|  |     // GetVolumeStats gathers total bytes and used bytes for a volume. | ||||||
|  |     rpc GetVolumeStats(GetVolumeStatsRequest) returns (GetVolumeStatsResponse) {} | ||||||
|  | 
 | ||||||
|  |     // GetDiskNumberFromVolumeID gets the disk number of the disk where the volume is located. | ||||||
|  |     rpc GetDiskNumberFromVolumeID(GetDiskNumberFromVolumeIDRequest) returns (GetDiskNumberFromVolumeIDResponse ) {} | ||||||
|  | 
 | ||||||
|  |     // GetVolumeIDFromTargetPath gets the volume id for a given target path. | ||||||
|  |     rpc GetVolumeIDFromTargetPath(GetVolumeIDFromTargetPathRequest) returns (GetVolumeIDFromTargetPathResponse) {} | ||||||
|  | 
 | ||||||
|  |     // GetClosestVolumeIDFromTargetPath gets the closest volume id for a given target path | ||||||
|  |     // by following symlinks and moving up in the filesystem, if after moving up in the filesystem | ||||||
|  |     // we get to a DriveLetter then the volume corresponding to this drive letter is returned instead. | ||||||
|  |     rpc GetClosestVolumeIDFromTargetPath(GetClosestVolumeIDFromTargetPathRequest) returns (GetClosestVolumeIDFromTargetPathResponse) {} | ||||||
|  | 
 | ||||||
|  |     // WriteVolumeCache write volume cache to disk. | ||||||
|  |     rpc WriteVolumeCache(WriteVolumeCacheRequest) returns (WriteVolumeCacheResponse) {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListVolumesOnDiskRequest { | ||||||
|  |     // Disk device number of the disk to query for volumes. | ||||||
|  |     uint32 disk_number = 1; | ||||||
|  |     // The partition number (optional), by default it uses the first partition of the disk. | ||||||
|  |     uint32 partition_number = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ListVolumesOnDiskResponse { | ||||||
|  |     // Volume device IDs of volumes on the specified disk. | ||||||
|  |     repeated string volume_ids = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MountVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to mount. | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // Path in the host's file system where the volume needs to be mounted. | ||||||
|  |     string target_path = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message MountVolumeResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message UnmountVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to dismount. | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // Path where the volume has been mounted. | ||||||
|  |     string target_path = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message UnmountVolumeResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsVolumeFormattedRequest { | ||||||
|  |     // Volume device ID of the volume to check. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message IsVolumeFormattedResponse { | ||||||
|  |     // Is the volume formatted with NTFS. | ||||||
|  |     bool formatted = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message FormatVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to format. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message FormatVolumeResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ResizeVolumeRequest { | ||||||
|  |     // Volume device ID of the volume to resize. | ||||||
|  |     string volume_id = 1; | ||||||
|  |     // New size in bytes of the volume. | ||||||
|  |     int64 size_bytes = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message ResizeVolumeResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetVolumeStatsRequest{ | ||||||
|  |     // Volume device Id of the volume to get the stats for. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetVolumeStatsResponse{ | ||||||
|  |     // Total bytes | ||||||
|  |     int64 total_bytes = 1; | ||||||
|  |     // Used bytes | ||||||
|  |     int64 used_bytes = 2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetDiskNumberFromVolumeIDRequest { | ||||||
|  |     // Volume device ID of the volume to get the disk number for. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetDiskNumberFromVolumeIDResponse { | ||||||
|  |     // Corresponding disk number. | ||||||
|  |     uint32 disk_number = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetVolumeIDFromTargetPathRequest { | ||||||
|  |     // The target path. | ||||||
|  |     string target_path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetVolumeIDFromTargetPathResponse { | ||||||
|  |     // The volume device ID. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetClosestVolumeIDFromTargetPathRequest { | ||||||
|  |     // The target path. | ||||||
|  |     string target_path = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message GetClosestVolumeIDFromTargetPathResponse { | ||||||
|  |     // The volume device ID. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message WriteVolumeCacheRequest { | ||||||
|  |     // Volume device ID of the volume to flush the cache. | ||||||
|  |     string volume_id = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | message WriteVolumeCacheResponse { | ||||||
|  |     // Intentionally empty. | ||||||
|  | } | ||||||
|  | @ -2,4 +2,4 @@ | ||||||
| 
 | 
 | ||||||
| # https://engineering.docker.com/2019/07/road-to-containing-iscsi/ | # https://engineering.docker.com/2019/07/road-to-containing-iscsi/ | ||||||
| 
 | 
 | ||||||
| chroot /host /usr/bin/env -i PATH="/sbin:/bin:/usr/bin" iscsiadm "${@:1}" | chroot /host /usr/bin/env -i PATH="/usr/sbin:/usr/bin:/sbin:/bin" iscsiadm "${@:1}" | ||||||
|  |  | ||||||
							
								
								
									
										16
									
								
								docker/mount
								
								
								
								
							
							
						
						
									
										16
									
								
								docker/mount
								
								
								
								
							|  | @ -20,16 +20,18 @@ container_supported_filesystems=( | ||||||
| while getopts "t:" opt; do | while getopts "t:" opt; do | ||||||
|   case "$opt" in |   case "$opt" in | ||||||
|     t) |     t) | ||||||
|       [[ "${OPTARG,,}" == "zfs" ]]     && USE_HOST_MOUNT_TOOLS=1 |       if [[ "x${USE_HOST_MOUNT_TOOLS}" == "x" ]]; then | ||||||
|       [[ "${OPTARG,,}" == "lustre" ]]  && USE_HOST_MOUNT_TOOLS=1 |         [[ "${OPTARG,,}" == "zfs" ]]     && USE_HOST_MOUNT_TOOLS=1 | ||||||
|       [[ "${OPTARG,,}" == "onedata" ]] && USE_HOST_MOUNT_TOOLS=1 |         [[ "${OPTARG,,}" == "lustre" ]]  && USE_HOST_MOUNT_TOOLS=1 | ||||||
|       #(printf '%s\0' "${container_supported_filesystems[@]}" | grep -Fqxz -- "${OPTARG}") || USE_HOST_MOUNT_TOOLS=1 |         [[ "${OPTARG,,}" == "onedata" ]] && USE_HOST_MOUNT_TOOLS=1 | ||||||
|  |         #(printf '%s\0' "${container_supported_filesystems[@]}" | grep -Fqxz -- "${OPTARG}") || USE_HOST_MOUNT_TOOLS=1 | ||||||
|  |       fi | ||||||
|       ;; |       ;; | ||||||
|   esac |   esac | ||||||
| done | done | ||||||
| 
 | 
 | ||||||
| if [[ ${USE_HOST_MOUNT_TOOLS} -eq 1 ]];then | if [[ ${USE_HOST_MOUNT_TOOLS} -eq 1 ]]; then | ||||||
|   chroot /host /usr/bin/env -i PATH="/sbin:/bin:/usr/bin:/usr/sbin" mount "${@:1}" |   chroot /host /usr/bin/env -i PATH="/usr/sbin:/usr/bin:/sbin:/bin" mount "${@:1}" | ||||||
| else | else | ||||||
|   /usr/bin/env -i PATH="/sbin:/bin:/usr/bin:/usr/sbin" mount "${@:1}" |   /usr/bin/env -i PATH="/usr/sbin:/usr/bin:/sbin:/bin" mount "${@:1}" | ||||||
| fi | fi | ||||||
|  |  | ||||||
|  | @ -1,3 +1,3 @@ | ||||||
| #!/bin/bash | #!/bin/bash | ||||||
| 
 | 
 | ||||||
| chroot /host /usr/bin/env -i PATH="/sbin:/bin:/usr/sbin:/usr/bin" multipath "${@:1}" | chroot /host /usr/bin/env -i PATH="/usr/sbin:/usr/bin:/sbin:/bin" multipath "${@:1}" | ||||||
|  |  | ||||||
|  | @ -1,3 +1,3 @@ | ||||||
| #!/bin/bash | #!/bin/bash | ||||||
| 
 | 
 | ||||||
| chroot /host /usr/bin/env -i PATH="/sbin:/bin:/usr/bin:/usr/sbin" oneclient "${@:1}" | chroot /host /usr/bin/env -i PATH="/usr/sbin:/usr/bin:/sbin:/bin" oneclient "${@:1}" | ||||||
|  |  | ||||||
|  | @ -20,16 +20,18 @@ container_supported_filesystems=( | ||||||
| while getopts "t:" opt; do | while getopts "t:" opt; do | ||||||
|   case "$opt" in |   case "$opt" in | ||||||
|     t) |     t) | ||||||
|       [[ "${OPTARG,,}" == "zfs" ]]     && USE_HOST_MOUNT_TOOLS=1 |       if [[ "x${USE_HOST_MOUNT_TOOLS}" == "x" ]]; then | ||||||
|       [[ "${OPTARG,,}" == "lustre" ]]  && USE_HOST_MOUNT_TOOLS=1 |         [[ "${OPTARG,,}" == "zfs" ]]     && USE_HOST_MOUNT_TOOLS=1 | ||||||
|       [[ "${OPTARG,,}" == "onedata" ]] && USE_HOST_MOUNT_TOOLS=1 |         [[ "${OPTARG,,}" == "lustre" ]]  && USE_HOST_MOUNT_TOOLS=1 | ||||||
|       #(printf '%s\0' "${container_supported_filesystems[@]}" | grep -Fqxz -- "${OPTARG}") || USE_HOST_MOUNT_TOOLS=1 |         [[ "${OPTARG,,}" == "onedata" ]] && USE_HOST_MOUNT_TOOLS=1 | ||||||
|  |         #(printf '%s\0' "${container_supported_filesystems[@]}" | grep -Fqxz -- "${OPTARG}") || USE_HOST_MOUNT_TOOLS=1 | ||||||
|  |       fi | ||||||
|       ;; |       ;; | ||||||
|   esac |   esac | ||||||
| done | done | ||||||
| 
 | 
 | ||||||
| if [[ ${USE_HOST_MOUNT_TOOLS} -eq 1 ]];then | if [[ ${USE_HOST_MOUNT_TOOLS} -eq 1 ]]; then | ||||||
|   chroot /host /usr/bin/env -i PATH="/sbin:/bin:/usr/bin:/usr/sbin" umount "${@:1}" |   chroot /host /usr/bin/env -i PATH="/usr/sbin:/usr/bin:/sbin:/bin" umount "${@:1}" | ||||||
| else | else | ||||||
|   /usr/bin/env -i PATH="/sbin:/bin:/usr/bin:/usr/sbin" umount "${@:1}" |   /usr/bin/env -i PATH="/usr/sbin:/usr/bin:/sbin:/bin" umount "${@:1}" | ||||||
| fi | fi | ||||||
|  |  | ||||||
|  | @ -1,3 +1,3 @@ | ||||||
| #!/bin/bash | #!/bin/bash | ||||||
| 
 | 
 | ||||||
| chroot /host /usr/bin/env -i PATH="/sbin:/bin:/usr/bin:/usr/sbin" zfs "${@:1}" | chroot /host /usr/bin/env -i PATH="/usr/sbin:/usr/bin:/sbin:/bin" zfs "${@:1}" | ||||||
|  |  | ||||||
|  | @ -1,3 +1,3 @@ | ||||||
| #!/bin/bash | #!/bin/bash | ||||||
| 
 | 
 | ||||||
| chroot /host /usr/bin/env -i PATH="/sbin:/bin:/usr/bin:/usr/sbin" zpool "${@:1}" | chroot /host /usr/bin/env -i PATH="/usr/sbin:/usr/bin:/sbin:/bin" zpool "${@:1}" | ||||||
|  |  | ||||||
|  | @ -0,0 +1,138 @@ | ||||||
|  | # Storage Class Parameters | ||||||
|  | 
 | ||||||
|  | Some drivers support different settings for volumes. These can be configured via the driver configuration and/or storage | ||||||
|  | classes. | ||||||
|  | 
 | ||||||
|  | ## `synology-iscsi` | ||||||
|  | The `synology-iscsi` driver supports several storage class parameters. Note however that not all parameters/values are | ||||||
|  | supported for all backing file systems and LUN type. The following options are available: | ||||||
|  | 
 | ||||||
|  | ### Configure Storage Classes | ||||||
|  | ```yaml | ||||||
|  | apiVersion: storage.k8s.io/v1 | ||||||
|  | kind: StorageClass | ||||||
|  | metadata: | ||||||
|  |   name: synology-iscsi | ||||||
|  | parameters: | ||||||
|  |     fsType: ext4 | ||||||
|  |     # The following options affect the LUN representing the volume. These options are passed directly to the Synology API. | ||||||
|  |     # The following options are known. | ||||||
|  |     lunTemplate: | | ||||||
|  |       type: BLUN       # Btrfs thin provisioning | ||||||
|  |       type: BLUN_THICK # Btrfs thick provisioning | ||||||
|  |       type: THIN       # Ext4 thin provisioning | ||||||
|  |       type: ADV        # Ext4 thin provisioning with legacy advanced feature set | ||||||
|  |       type: FILE       # Ext4 thick provisioning | ||||||
|  |       description: Some Description | ||||||
|  |        | ||||||
|  |       # Only for thick provisioned volumes. Known values: | ||||||
|  |       # 0: Buffered Writes | ||||||
|  |       # 3: Direct Write | ||||||
|  |       direct_io_pattern: 0 | ||||||
|  |        | ||||||
|  |       # Device Attributes. See below for more info | ||||||
|  |       dev_attribs: | ||||||
|  |       - dev_attrib: emulate_tpws | ||||||
|  |         enable: 1 | ||||||
|  |       - ... | ||||||
|  | 
 | ||||||
|  |     # The following options affect the iSCSI target. These options will be passed directly to the Synology API. | ||||||
|  |     # The following options are known. | ||||||
|  |     targetTemplate: | | ||||||
|  |       has_header_checksum: false | ||||||
|  |       has_data_checksum: false | ||||||
|  |        | ||||||
|  |       # Note that this option requires a compatible filesystem. Use 0 for unlimited sessions. | ||||||
|  |       max_sessions: 0 | ||||||
|  |       multi_sessions: true | ||||||
|  |       max_recv_seg_bytes: 262144 | ||||||
|  |       max_send_seg_bytes: 262144 | ||||||
|  | 
 | ||||||
|  |       # Use this to disable authentication. To configure authentication see below | ||||||
|  |       auth_type: 0 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | #### About LUN Types | ||||||
|  | The availability of the different types of LUNs depends on the filesystem used on your Synology volume. For Btrfs volumes | ||||||
|  | you can use `BLUN` and `BLUN_THICK` volumes. For Ext4 volumes you can use `THIN`, `ADV` or `FILE` volumes. These | ||||||
|  | correspond to the options available in the UI. | ||||||
|  | 
 | ||||||
|  | #### About `dev_attribs` | ||||||
|  | Most of the LUN options are configured via the `dev_attribs` list. This list can be specified both in the `lunTemplate` | ||||||
|  | of the global configuration and in the `lunTemplate` of the `StorageClass`. If both lists are present they will be merged | ||||||
|  | (with the `StorageClass` taking precedence). The following  `dev_attribs` are known to work: | ||||||
|  | 
 | ||||||
|  | - `emulate_tpws`: Hardware-assisted zeroing | ||||||
|  | - `emulate_caw`: Hardware-assisted locking | ||||||
|  | - `emulate_3pc`: Hardware-assisted data transfer | ||||||
|  | - `emulate_tpu`: Space Reclamation | ||||||
|  | - `emulate_fua_write`: Enable the FUA iSCSI command (DSM 7+) | ||||||
|  | - `emulate_sync_cache`: Enable the Sync Cache iSCSI command (DSM 7+) | ||||||
|  | - `can_snapshot`: Enable snapshots for this volume. Only works for thin provisioned volumes. | ||||||
|  | 
 | ||||||
|  | ### Configure Snapshot Classes | ||||||
|  | `synology-iscsi` can also configure different parameters on snapshot classes: | ||||||
|  | 
 | ||||||
|  | ```yaml | ||||||
|  | apiVersion: snapshot.storage.k8s.io/v1 | ||||||
|  | kind: VolumeSnapshotClass | ||||||
|  | metadata: | ||||||
|  |   name: synology-iscsi-snapshot | ||||||
|  | parameters: | ||||||
|  |   # This inline yaml object will be passed to the Synology API when creating the snapshot. | ||||||
|  |   lunSnapshotTemplate: | | ||||||
|  |     is_locked: true | ||||||
|  |      | ||||||
|  |     # https://kb.synology.com/en-me/DSM/tutorial/What_is_file_system_consistent_snapshot | ||||||
|  |     # Note that app consistent snapshots require a working Synology Storage Console. Otherwise both values will have | ||||||
|  |     # equivalent behavior. | ||||||
|  |     is_app_consistent: true | ||||||
|  | ... | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Note that it is currently not supported by Synology devices to restore a snapshot onto a different volume. You can | ||||||
|  | create volumes from snapshots, but you should use the same `StorageClass` as the original volume of the snapshot did.  | ||||||
|  | 
 | ||||||
|  | ### Enabling CHAP Authentication | ||||||
|  | You can enable CHAP Authentication for `StorageClass`es by supplying an appropriate `StorageClass` secret (see the | ||||||
|  | [documentation](https://kubernetes-csi.github.io/docs/secrets-and-credentials-storage-class.html) for more details). You | ||||||
|  | can use the same password for alle volumes of a `StorageClass` or use different passwords per volume. | ||||||
|  | 
 | ||||||
|  | ```yaml | ||||||
|  | apiVersion: storage.k8s.io/v1 | ||||||
|  | kind: StorageClass | ||||||
|  | metadata: | ||||||
|  |   name: synology-iscsi-chap | ||||||
|  | parameters: | ||||||
|  |   fsType: ext4 | ||||||
|  |   lunTemplate: | | ||||||
|  |     type: BLUN | ||||||
|  |     description: iSCSI volumes with CHAP Authentication | ||||||
|  | secrets: | ||||||
|  |   # Use this to configure a single set of credentials for all volumes of this StorageClass | ||||||
|  |   csi.storage.k8s.io/provisioner-secret-name: chap-secret | ||||||
|  |   csi.storage.k8s.io/provisioner-secret-namespace: default | ||||||
|  |   # Use substitutions to use different credentials for volumes based on the PVC | ||||||
|  |   csi.storage.k8s.io/provisioner-secret-name: "${pvc.name}-chap-secret" | ||||||
|  |   csi.storage.k8s.io/provisioner-secret-namespace: "${pvc.namespace}" | ||||||
|  | ... | ||||||
|  | --- | ||||||
|  | # Use a secret like this to supply CHAP credentials. | ||||||
|  | apiVersion: v1 | ||||||
|  | kind: Secret | ||||||
|  | metadata: | ||||||
|  |   name: chap-secret | ||||||
|  | stringData: | ||||||
|  |   # Client Credentials | ||||||
|  |   user: client | ||||||
|  |   password: MySecretPassword | ||||||
|  |   # Mutual CHAP Credentials. If these are specified mutual CHAP will be enabled. | ||||||
|  |   mutualUser: server | ||||||
|  |   mutualPassword: MyOtherPassword | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Note that CHAP authentication will only be enabled if the secret contains a username and password. If e.g. a password is | ||||||
|  | missing CHAP authentication will not be enabled (but the volume will still be created). You cannot automatically | ||||||
|  | enable/disable CHAP or change the password after the volume has been created. | ||||||
|  | 
 | ||||||
|  | If the secret itself is referenced but not present, the volume will not be created. | ||||||
|  | @ -43,6 +43,8 @@ zfs: | ||||||
|   datasetPermissionsMode: "0777" |   datasetPermissionsMode: "0777" | ||||||
|   datasetPermissionsUser: 0 |   datasetPermissionsUser: 0 | ||||||
|   datasetPermissionsGroup: 0 |   datasetPermissionsGroup: 0 | ||||||
|  | 
 | ||||||
|  |   # not supported yet | ||||||
|   #datasetPermissionsAcls: |   #datasetPermissionsAcls: | ||||||
|   #- "-m everyone@:full_set:allow" |   #- "-m everyone@:full_set:allow" | ||||||
|   #- "-m u:kube:full_set:allow" |   #- "-m u:kube:full_set:allow" | ||||||
|  |  | ||||||
|  | @ -34,9 +34,10 @@ zfs: | ||||||
|   #  "org.freenas:test": "{{ parameters.foo }}" |   #  "org.freenas:test": "{{ parameters.foo }}" | ||||||
|   #  "org.freenas:test2": "some value" |   #  "org.freenas:test2": "some value" | ||||||
| 
 | 
 | ||||||
|   datasetProperties: |   # these are managed automatically via the volume creation process when flagged as an smb volume | ||||||
|     aclmode: restricted |   #datasetProperties: | ||||||
|     casesensitivity: mixed |   #  aclmode: restricted | ||||||
|  |   #  casesensitivity: mixed | ||||||
| 
 | 
 | ||||||
|   datasetParentName: tank/k8s/a/vols |   datasetParentName: tank/k8s/a/vols | ||||||
|   # do NOT make datasetParentName and detachedSnapshotsDatasetParentName overlap |   # do NOT make datasetParentName and detachedSnapshotsDatasetParentName overlap | ||||||
|  | @ -47,8 +48,10 @@ zfs: | ||||||
|   datasetPermissionsMode: "0777" |   datasetPermissionsMode: "0777" | ||||||
|   datasetPermissionsUser: 0 |   datasetPermissionsUser: 0 | ||||||
|   datasetPermissionsGroup: 0 |   datasetPermissionsGroup: 0 | ||||||
|   datasetPermissionsAcls: |    | ||||||
|   - "-m everyone@:full_set:allow" |   # not supported yet in api | ||||||
|  |   #datasetPermissionsAcls: | ||||||
|  |   #- "-m everyone@:full_set:allow" | ||||||
|   #- "-m u:kube:full_set:allow" |   #- "-m u:kube:full_set:allow" | ||||||
| 
 | 
 | ||||||
| smb: | smb: | ||||||
|  |  | ||||||
|  | @ -46,7 +46,9 @@ zfs: | ||||||
| 
 | 
 | ||||||
|   datasetProperties: |   datasetProperties: | ||||||
|     aclmode: restricted |     aclmode: restricted | ||||||
|     casesensitivity: mixed |     aclinherit: passthrough | ||||||
|  |     acltype: nfsv4 | ||||||
|  |     casesensitivity: insensitive | ||||||
| 
 | 
 | ||||||
|   datasetParentName: tank/k8s/a/vols |   datasetParentName: tank/k8s/a/vols | ||||||
|   # do NOT make datasetParentName and detachedSnapshotsDatasetParentName overlap |   # do NOT make datasetParentName and detachedSnapshotsDatasetParentName overlap | ||||||
|  | @ -54,12 +56,41 @@ zfs: | ||||||
|   detachedSnapshotsDatasetParentName: tank/k8s/a/snaps |   detachedSnapshotsDatasetParentName: tank/k8s/a/snaps | ||||||
|   datasetEnableQuotas: true |   datasetEnableQuotas: true | ||||||
|   datasetEnableReservation: false |   datasetEnableReservation: false | ||||||
|   datasetPermissionsMode: "0777" |   datasetPermissionsMode: "0770" | ||||||
|   datasetPermissionsUser: nobody | 
 | ||||||
|   datasetPermissionsGroup: nobody |   # as appropriate create a dedicated user for smb connections | ||||||
|  |   # and set this | ||||||
|  |   datasetPermissionsUser: 65534 | ||||||
|  |   datasetPermissionsGroup: 65534 | ||||||
|  | 
 | ||||||
|  |   # CORE | ||||||
|  |   #datasetPermissionsAclsBinary: setfacl | ||||||
|  |    | ||||||
|  |   # SCALE | ||||||
|  |   #datasetPermissionsAclsBinary: nfs4xdr_setfacl | ||||||
|  |    | ||||||
|  |   # if using a user other than guest/nobody comment the 'everyone@' acl | ||||||
|  |   # and uncomment the appropriate block below  | ||||||
|   datasetPermissionsAcls: |   datasetPermissionsAcls: | ||||||
|   - "-m everyone@:full_set:allow" |   - "-m everyone@:full_set:fd:allow" | ||||||
|   #- "-m u:kube:full_set:allow" | 
 | ||||||
|  |   # CORE | ||||||
|  |   # in CORE you cannot have multiple entries for the same principle | ||||||
|  |   # or said differently, they are declarative so using -m will replace | ||||||
|  |   # whatever the current value is for the principle rather than adding a | ||||||
|  |   # entry in the acl list | ||||||
|  |   #- "-m g:builtin_users:full_set:fd:allow" | ||||||
|  |   #- "-m group@:modify_set:fd:allow" | ||||||
|  |   #- "-m owner@:full_set:fd:allow" | ||||||
|  |    | ||||||
|  |   # SCALE | ||||||
|  |   # https://www.truenas.com/community/threads/get-setfacl-on-scale-with-nfsv4-acls.95231/ | ||||||
|  |   # -s replaces everything | ||||||
|  |   # so we put this in specific order to mimic the defaults of SCALE when using the api | ||||||
|  |   #- -s group:builtin_users:full_set:fd:allow | ||||||
|  |   #- -a group:builtin_users:modify_set:fd:allow | ||||||
|  |   #- -a group@:modify_set:fd:allow | ||||||
|  |   #- -a owner@:full_set:fd:allow | ||||||
| 
 | 
 | ||||||
| smb: | smb: | ||||||
|   shareHost: server address |   shareHost: server address | ||||||
|  | @ -77,7 +108,7 @@ smb: | ||||||
|   shareAllowedHosts: [] |   shareAllowedHosts: [] | ||||||
|   shareDeniedHosts: [] |   shareDeniedHosts: [] | ||||||
|   #shareDefaultPermissions: true |   #shareDefaultPermissions: true | ||||||
|   shareGuestOk: true |   shareGuestOk: false | ||||||
|   #shareGuestOnly: true |   #shareGuestOnly: true | ||||||
|   #shareShowHiddenFiles: true |   #shareShowHiddenFiles: true | ||||||
|   shareRecycleBin: true |   shareRecycleBin: true | ||||||
|  |  | ||||||
|  | @ -6,5 +6,5 @@ local-hostpath: | ||||||
|   shareBasePath:      "/var/lib/csi-local-hostpath" |   shareBasePath:      "/var/lib/csi-local-hostpath" | ||||||
|   controllerBasePath: "/var/lib/csi-local-hostpath" |   controllerBasePath: "/var/lib/csi-local-hostpath" | ||||||
|   dirPermissionsMode: "0777" |   dirPermissionsMode: "0777" | ||||||
|   dirPermissionsUser: root |   dirPermissionsUser: 0 | ||||||
|   dirPermissionsGroup: root |   dirPermissionsGroup: 0 | ||||||
|  |  | ||||||
|  | @ -2,6 +2,9 @@ | ||||||
| 
 | 
 | ||||||
| node: | node: | ||||||
|   mount: |   mount: | ||||||
|  |     # predominantly used to facilitate testing | ||||||
|  |     # mount_flags should generally be defined in storage classes, etc | ||||||
|  |     mount_flags: "" | ||||||
|     # should fsck be executed before mounting the fs |     # should fsck be executed before mounting the fs | ||||||
|     checkFilesystem: |     checkFilesystem: | ||||||
|       xfs: |       xfs: | ||||||
|  | @ -27,3 +30,30 @@ node: | ||||||
|       # ... |       # ... | ||||||
|     btrfs: |     btrfs: | ||||||
|       customOptions: [] |       customOptions: [] | ||||||
|  | 
 | ||||||
|  |   csiProxy: | ||||||
|  |     # should be left unset in most situation, will be auto-detected | ||||||
|  |     #enabled: true | ||||||
|  | 
 | ||||||
|  |     # connection attributes can be set to grpc endpoint | ||||||
|  |     # ie: hostname:port, or /some/path, or \\.\pipe\foo | ||||||
|  |     # connection and version will use internal defaults and should generally be left unset | ||||||
|  |     services: | ||||||
|  |       filesystem: | ||||||
|  |         #version: v1 | ||||||
|  |         #connection: | ||||||
|  |       disk: | ||||||
|  |         #version: v1 | ||||||
|  |         #connection: | ||||||
|  |       volume: | ||||||
|  |         #version: v1 | ||||||
|  |         #connection: | ||||||
|  |       smb: | ||||||
|  |         #version: v1 | ||||||
|  |         #connection: | ||||||
|  |       system: | ||||||
|  |         #version: v1alpha1 | ||||||
|  |         #connection: | ||||||
|  |       iscsi: | ||||||
|  |         #version: v1alpha2 | ||||||
|  |         #connection: | ||||||
|  |  | ||||||
|  | @ -0,0 +1,18 @@ | ||||||
|  | # | ||||||
|  | # these SHOULD NOT be used | ||||||
|  | # they are here for documentation purposes only and are likely to: | ||||||
|  | # - be removed | ||||||
|  | # - break things | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | _private: | ||||||
|  |   csi: | ||||||
|  |     volume: | ||||||
|  |       derivedContext: | ||||||
|  |         # driver left blank is used to auto select | ||||||
|  |         driver: memory # strictly to facilitate testing | ||||||
|  |         #driver: kubernetes | ||||||
|  |       idHash: | ||||||
|  |         strategy: crc16 | ||||||
|  |         #strategy: crc32 | ||||||
|  |         #strategy: md5 | ||||||
|  | @ -10,9 +10,9 @@ httpConnection: | ||||||
|   session: "democratic-csi" |   session: "democratic-csi" | ||||||
|   serialize: true |   serialize: true | ||||||
| 
 | 
 | ||||||
| synology: | # Choose the DSM volume this driver operates on. The default value is /volume1. | ||||||
|   # choose the proper volume for your system | # synology: | ||||||
|   volume: /volume1 | #   volume: /volume1 | ||||||
| 
 | 
 | ||||||
| iscsi: | iscsi: | ||||||
|   targetPortal: "server[:port]" |   targetPortal: "server[:port]" | ||||||
|  | @ -31,7 +31,12 @@ iscsi: | ||||||
|   # documented below are several blocks |   # documented below are several blocks | ||||||
|   # pick the option appropriate for you based on what your backing fs is and desired features |   # pick the option appropriate for you based on what your backing fs is and desired features | ||||||
|   # you do not need to alter dev_attribs under normal circumstances but they may be altered in advanced use-cases |   # you do not need to alter dev_attribs under normal circumstances but they may be altered in advanced use-cases | ||||||
|  |   # These options can also be configured per storage-class: | ||||||
|  |   # See https://github.com/democratic-csi/democratic-csi/blob/master/docs/storage-class-parameters.md | ||||||
|   lunTemplate: |   lunTemplate: | ||||||
|  |     # can be static value or handlebars template | ||||||
|  |     #description: "{{ parameters.[csi.storage.k8s.io/pvc/namespace] }}-{{ parameters.[csi.storage.k8s.io/pvc/name] }}" | ||||||
|  |      | ||||||
|     # btrfs thin provisioning |     # btrfs thin provisioning | ||||||
|     type: "BLUN" |     type: "BLUN" | ||||||
|     # tpws = Hardware-assisted zeroing |     # tpws = Hardware-assisted zeroing | ||||||
|  |  | ||||||
|  | @ -36,8 +36,8 @@ zfs: | ||||||
|   datasetEnableQuotas: true |   datasetEnableQuotas: true | ||||||
|   datasetEnableReservation: false |   datasetEnableReservation: false | ||||||
|   datasetPermissionsMode: "0777" |   datasetPermissionsMode: "0777" | ||||||
|   datasetPermissionsUser: root |   datasetPermissionsUser: 0 | ||||||
|   datasetPermissionsGroup: root |   datasetPermissionsGroup: 0 | ||||||
|   #datasetPermissionsAcls: |   #datasetPermissionsAcls: | ||||||
|   #- "-m everyone@:full_set:allow" |   #- "-m everyone@:full_set:allow" | ||||||
|   #- "-m u:kube:full_set:allow" |   #- "-m u:kube:full_set:allow" | ||||||
|  | @ -48,6 +48,7 @@ nfs: | ||||||
|   shareStrategy: "setDatasetProperties" |   shareStrategy: "setDatasetProperties" | ||||||
|   shareStrategySetDatasetProperties: |   shareStrategySetDatasetProperties: | ||||||
|     properties: |     properties: | ||||||
|  |       #sharenfs: "rw,no_subtree_check,no_root_squash" | ||||||
|       sharenfs: "on" |       sharenfs: "on" | ||||||
|       # share: "" |       # share: "" | ||||||
|   shareHost: "server address" |   shareHost: "server address" | ||||||
|  |  | ||||||
|  | @ -0,0 +1,57 @@ | ||||||
|  | driver: zfs-generic-smb | ||||||
|  | sshConnection: | ||||||
|  |   host: server address | ||||||
|  |   port: 22 | ||||||
|  |   username: root | ||||||
|  |   # use either password or key | ||||||
|  |   password: "" | ||||||
|  |   privateKey: | | ||||||
|  |     -----BEGIN RSA PRIVATE KEY----- | ||||||
|  |     ... | ||||||
|  |     -----END RSA PRIVATE KEY----- | ||||||
|  | 
 | ||||||
|  | zfs: | ||||||
|  |   # can be used to override defaults if necessary | ||||||
|  |   # the example below is useful for TrueNAS 12 | ||||||
|  |   #cli: | ||||||
|  |   #  sudoEnabled: true | ||||||
|  |   #  paths: | ||||||
|  |   #    zfs: /usr/local/sbin/zfs | ||||||
|  |   #    zpool: /usr/local/sbin/zpool | ||||||
|  |   #    sudo: /usr/local/bin/sudo | ||||||
|  |   #    chroot: /usr/sbin/chroot | ||||||
|  |    | ||||||
|  |   # can be used to set arbitrary values on the dataset/zvol | ||||||
|  |   # can use handlebars templates with the parameters from the storage class/CO | ||||||
|  |   datasetProperties: | ||||||
|  |     #aclmode: restricted | ||||||
|  |     #aclinherit: passthrough | ||||||
|  |     #acltype: nfsv4 | ||||||
|  |     casesensitivity: insensitive | ||||||
|  | 
 | ||||||
|  |   datasetParentName: tank/k8s/test | ||||||
|  |   # do NOT make datasetParentName and detachedSnapshotsDatasetParentName overlap | ||||||
|  |   # they may be siblings, but neither should be nested in the other | ||||||
|  |   detachedSnapshotsDatasetParentName: tanks/k8s/test-snapshots | ||||||
|  | 
 | ||||||
|  |   datasetEnableQuotas: true | ||||||
|  |   datasetEnableReservation: false | ||||||
|  |   datasetPermissionsMode: "0770" | ||||||
|  |   datasetPermissionsUser: smbroot | ||||||
|  |   datasetPermissionsGroup: smbroot | ||||||
|  | 
 | ||||||
|  |   #datasetPermissionsAclsBinary: nfs4_setfacl | ||||||
|  |   #datasetPermissionsAcls: | ||||||
|  |   #- "-m everyone@:full_set:allow" | ||||||
|  |   #- -s group@:modify_set:fd:allow | ||||||
|  |   #- -a owner@:full_set:fd:allow | ||||||
|  | 
 | ||||||
|  | smb: | ||||||
|  |   # https://docs.oracle.com/cd/E23824_01/html/821-1448/gayne.html | ||||||
|  |   # https://www.hiroom2.com/2016/05/18/ubuntu-16-04-share-zfs-storage-via-nfs-smb/ | ||||||
|  |   shareStrategy: "setDatasetProperties" | ||||||
|  |   shareStrategySetDatasetProperties: | ||||||
|  |     properties: | ||||||
|  |       sharesmb: "on" | ||||||
|  |       # share: "" | ||||||
|  |   shareHost: "server address" | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "democratic-csi", |   "name": "democratic-csi", | ||||||
|   "version": "1.6.3", |   "version": "1.7.0", | ||||||
|   "description": "kubernetes csi driver framework", |   "description": "kubernetes csi driver framework", | ||||||
|   "main": "bin/democratic-csi", |   "main": "bin/democratic-csi", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|  | @ -22,8 +22,9 @@ | ||||||
|     "@grpc/proto-loader": "^0.6.0", |     "@grpc/proto-loader": "^0.6.0", | ||||||
|     "@kubernetes/client-node": "^0.16.3", |     "@kubernetes/client-node": "^0.16.3", | ||||||
|     "async-mutex": "^0.3.1", |     "async-mutex": "^0.3.1", | ||||||
|     "axios": "^0.26.1", |     "axios": "^0.27.2", | ||||||
|     "bunyan": "^1.8.15", |     "bunyan": "^1.8.15", | ||||||
|  |     "fs-extra": "^10.1.0", | ||||||
|     "handlebars": "^4.7.7", |     "handlebars": "^4.7.7", | ||||||
|     "js-yaml": "^4.0.0", |     "js-yaml": "^4.0.0", | ||||||
|     "lodash": "^4.17.21", |     "lodash": "^4.17.21", | ||||||
|  |  | ||||||
|  | @ -3,6 +3,8 @@ const { CsiBaseDriver } = require("../index"); | ||||||
| const { GrpcError, grpc } = require("../../utils/grpc"); | const { GrpcError, grpc } = require("../../utils/grpc"); | ||||||
| const cp = require("child_process"); | const cp = require("child_process"); | ||||||
| const fs = require("fs"); | const fs = require("fs"); | ||||||
|  | const fse = require("fs-extra"); | ||||||
|  | const path = require("path"); | ||||||
| const semver = require("semver"); | const semver = require("semver"); | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -230,9 +232,14 @@ class ControllerClientCommonDriver extends CsiBaseDriver { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async getDirectoryUsage(path) { |   async getDirectoryUsage(path) { | ||||||
|     let result = await this.exec("du", ["-s", "--block-size=1", path]); |     if (this.getNodeIsWindows()) { | ||||||
|     let size = result.stdout.split("\t", 1)[0]; |       this.ctx.logger.warn("du not implemented on windows"); | ||||||
|     return size; |       return 0; | ||||||
|  |     } else { | ||||||
|  |       let result = await this.exec("du", ["-s", "--block-size=1", path]); | ||||||
|  |       let size = result.stdout.split("\t", 1)[0]; | ||||||
|  |       return size; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   exec(command, args, options = {}) { |   exec(command, args, options = {}) { | ||||||
|  | @ -297,20 +304,39 @@ class ControllerClientCommonDriver extends CsiBaseDriver { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async cloneDir(source_path, target_path) { |   async cloneDir(source_path, target_path) { | ||||||
|     await this.exec("mkdir", ["-p", target_path]); |     if (this.getNodeIsWindows()) { | ||||||
|  |       fse.copySync( | ||||||
|  |         this.stripTrailingSlash(source_path), | ||||||
|  |         this.stripTrailingSlash(target_path), | ||||||
|  |         { | ||||||
|  |           overwrite: true, | ||||||
|  |           dereference: true, | ||||||
|  |           preserveTimestamps: true, | ||||||
|  |           //errorOnExist: true,
 | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  |     } else { | ||||||
|  |       await this.createDir(target_path); | ||||||
| 
 | 
 | ||||||
|     /** |       /** | ||||||
|      * trailing / is important |        * trailing / is important | ||||||
|      * rsync -a /mnt/storage/s/foo/ /mnt/storage/v/PVC-111/ |        * rsync -a /mnt/storage/s/foo/ /mnt/storage/v/PVC-111/ | ||||||
|      */ |        */ | ||||||
|     await this.exec("rsync", [ |       await this.exec("rsync", [ | ||||||
|       "-a", |         "-a", | ||||||
|       this.stripTrailingSlash(source_path) + "/", |         this.stripTrailingSlash(source_path) + "/", | ||||||
|       this.stripTrailingSlash(target_path) + "/", |         this.stripTrailingSlash(target_path) + "/", | ||||||
|     ]); |       ]); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async getAvailableSpaceAtPath(path) { |   async getAvailableSpaceAtPath(path) { | ||||||
|  |     // https://www.npmjs.com/package/diskusage
 | ||||||
|  |     // https://www.npmjs.com/package/check-disk-space
 | ||||||
|  |     if (this.getNodeIsWindows()) { | ||||||
|  |       this.ctx.logger.warn("df not implemented on windows"); | ||||||
|  |       return 0; | ||||||
|  |     } | ||||||
|     //df --block-size=1 --output=avail /mnt/storage/
 |     //df --block-size=1 --output=avail /mnt/storage/
 | ||||||
|     //     Avail
 |     //     Avail
 | ||||||
|     //1481334328
 |     //1481334328
 | ||||||
|  | @ -325,11 +351,14 @@ class ControllerClientCommonDriver extends CsiBaseDriver { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async createDir(path) { |   async createDir(path) { | ||||||
|     await this.exec("mkdir", ["-p", path]); |     fs.mkdirSync(path, { | ||||||
|  |       recursive: true, | ||||||
|  |       mode: "755", | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async deleteDir(path) { |   async deleteDir(path) { | ||||||
|     await this.exec("rm", ["-rf", path]); |     fs.rmSync(path, { recursive: true, force: true }); | ||||||
| 
 | 
 | ||||||
|     return; |     return; | ||||||
| 
 | 
 | ||||||
|  | @ -346,7 +375,40 @@ class ControllerClientCommonDriver extends CsiBaseDriver { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async directoryExists(path) { |   async directoryExists(path) { | ||||||
|     return fs.existsSync(path); |     let r; | ||||||
|  |     r = fs.existsSync(path); | ||||||
|  |     if (!r) { | ||||||
|  |       return r; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!fs.statSync(path).isDirectory()) { | ||||||
|  |       throw new Error(`path [${path}] exists but is not a directory`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Have to be careful with the logic here as the controller could be running | ||||||
|  |    * on win32 for *-client vs local-hostpath | ||||||
|  |    * | ||||||
|  |    * @param {*} path | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|  |   async normalizePath(path) { | ||||||
|  |     if (process.platform == "win32") { | ||||||
|  |       return await this.noramlizePathWin32(path); | ||||||
|  |     } else { | ||||||
|  |       return await this.normalizePathPosix(path); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async normalizePathPosix(p) { | ||||||
|  |     return p.replaceAll(path.win32.sep, path.posix.sep); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async noramlizePathWin32(p) { | ||||||
|  |     return p.replaceAll(path.posix.sep, path.win32.sep); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|  | @ -441,7 +503,7 @@ class ControllerClientCommonDriver extends CsiBaseDriver { | ||||||
|     //let volume_content_source_volume_id;
 |     //let volume_content_source_volume_id;
 | ||||||
| 
 | 
 | ||||||
|     // create target dir
 |     // create target dir
 | ||||||
|     response = await driver.exec("mkdir", ["-p", volume_path]); |     await driver.createDir(volume_path); | ||||||
| 
 | 
 | ||||||
|     // create dataset
 |     // create dataset
 | ||||||
|     if (volume_content_source) { |     if (volume_content_source) { | ||||||
|  | @ -476,7 +538,7 @@ class ControllerClientCommonDriver extends CsiBaseDriver { | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       driver.ctx.logger.debug("controller source path: %s", source_path); |       driver.ctx.logger.debug("controller source path: %s", source_path); | ||||||
|       response = await driver.cloneDir(source_path, volume_path); |       await driver.cloneDir(source_path, volume_path); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // set mode
 |     // set mode
 | ||||||
|  | @ -486,10 +548,7 @@ class ControllerClientCommonDriver extends CsiBaseDriver { | ||||||
|         this.options[config_key].dirPermissionsMode, |         this.options[config_key].dirPermissionsMode, | ||||||
|         volume_path |         volume_path | ||||||
|       ); |       ); | ||||||
|       response = await driver.exec("chmod", [ |       fs.chmodSync(volume_path, this.options[config_key].dirPermissionsMode); | ||||||
|         this.options[config_key].dirPermissionsMode, |  | ||||||
|         volume_path, |  | ||||||
|       ]); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // set ownership
 |     // set ownership
 | ||||||
|  | @ -503,16 +562,20 @@ class ControllerClientCommonDriver extends CsiBaseDriver { | ||||||
|         this.options[config_key].dirPermissionsGroup, |         this.options[config_key].dirPermissionsGroup, | ||||||
|         volume_path |         volume_path | ||||||
|       ); |       ); | ||||||
|       response = await driver.exec("chown", [ |       if (this.getNodeIsWindows()) { | ||||||
|         (this.options[config_key].dirPermissionsUser |         driver.ctx.logger.warn("chown not implemented on windows"); | ||||||
|           ? this.options[config_key].dirPermissionsUser |       } else { | ||||||
|           : "") + |         await driver.exec("chown", [ | ||||||
|           ":" + |           (this.options[config_key].dirPermissionsUser | ||||||
|           (this.options[config_key].dirPermissionsGroup |             ? this.options[config_key].dirPermissionsUser | ||||||
|             ? this.options[config_key].dirPermissionsGroup |             : "") + | ||||||
|             : ""), |             ":" + | ||||||
|         volume_path, |             (this.options[config_key].dirPermissionsGroup | ||||||
|       ]); |               ? this.options[config_key].dirPermissionsGroup | ||||||
|  |               : ""), | ||||||
|  |           volume_path, | ||||||
|  |         ]); | ||||||
|  |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let volume_context = driver.getVolumeContext(name); |     let volume_context = driver.getVolumeContext(name); | ||||||
|  |  | ||||||
|  | @ -4,10 +4,81 @@ const https = require("https"); | ||||||
| const { axios_request, stringify } = require("../../../utils/general"); | const { axios_request, stringify } = require("../../../utils/general"); | ||||||
| const Mutex = require("async-mutex").Mutex; | const Mutex = require("async-mutex").Mutex; | ||||||
| const registry = require("../../../utils/registry"); | const registry = require("../../../utils/registry"); | ||||||
|  | const { GrpcError, grpc } = require("../../../utils/grpc"); | ||||||
| 
 | 
 | ||||||
| const USER_AGENT = "democratic-csi"; | const USER_AGENT = "democratic-csi"; | ||||||
| const __REGISTRY_NS__ = "SynologyHttpClient"; | const __REGISTRY_NS__ = "SynologyHttpClient"; | ||||||
| 
 | 
 | ||||||
|  | SYNO_ERRORS = { | ||||||
|  |   400: { | ||||||
|  |     status: grpc.status.UNAUTHENTICATED, | ||||||
|  |     message: "Failed to authenticate to the Synology DSM.", | ||||||
|  |   }, | ||||||
|  |   407: { | ||||||
|  |     status: grpc.status.UNAUTHENTICATED, | ||||||
|  |     message: | ||||||
|  |       "IP has been blocked to the Synology DSM due to too many failed attempts.", | ||||||
|  |   }, | ||||||
|  |   18990002: { | ||||||
|  |     status: grpc.status.RESOURCE_EXHAUSTED, | ||||||
|  |     message: "The synology volume is out of disk space.", | ||||||
|  |   }, | ||||||
|  |   18990318: { | ||||||
|  |     status: grpc.status.INVALID_ARGUMENT, | ||||||
|  |     message: | ||||||
|  |       "The requested lun type is incompatible with the Synology filesystem.", | ||||||
|  |   }, | ||||||
|  |   18990538: { | ||||||
|  |     status: grpc.status.ALREADY_EXISTS, | ||||||
|  |     message: "A LUN with this name already exists.", | ||||||
|  |   }, | ||||||
|  |   18990541: { | ||||||
|  |     status: grpc.status.RESOURCE_EXHAUSTED, | ||||||
|  |     message: "The maximum number of LUNS has been reached.", | ||||||
|  |   }, | ||||||
|  |   18990542: { | ||||||
|  |     status: grpc.status.RESOURCE_EXHAUSTED, | ||||||
|  |     message: "The maximum number if iSCSI target has been reached.", | ||||||
|  |   }, | ||||||
|  |   18990708: { | ||||||
|  |     status: grpc.status.INVALID_ARGUMENT, | ||||||
|  |     message: "Bad target auth info.", | ||||||
|  |   }, | ||||||
|  |   18990744: { | ||||||
|  |     status: grpc.status.ALREADY_EXISTS, | ||||||
|  |     message: "An iSCSI target with this name already exists.", | ||||||
|  |   }, | ||||||
|  |   18990532: { status: grpc.status.NOT_FOUND, message: "No such snapshot." }, | ||||||
|  |   18990500: { status: grpc.status.INVALID_ARGUMENT, message: "Bad LUN type" }, | ||||||
|  |   18990543: { | ||||||
|  |     status: grpc.status.RESOURCE_EXHAUSTED, | ||||||
|  |     message: "Maximum number of snapshots reached.", | ||||||
|  |   }, | ||||||
|  |   18990635: { | ||||||
|  |     status: grpc.status.INVALID_ARGUMENT, | ||||||
|  |     message: "Invalid ioPolicy.", | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class SynologyError extends GrpcError { | ||||||
|  |   constructor(code, httpCode = undefined) { | ||||||
|  |     super(0, ""); | ||||||
|  |     this.synoCode = code; | ||||||
|  |     this.httpCode = httpCode; | ||||||
|  |     if (code > 0) { | ||||||
|  |       const error = SYNO_ERRORS[code]; | ||||||
|  |       this.code = error && error.status ? error.status : grpc.status.UNKNOWN; | ||||||
|  |       this.message = | ||||||
|  |         error && error.message | ||||||
|  |           ? error.message | ||||||
|  |           : `An unknown error occurred when executing a synology command (code = ${code}).`; | ||||||
|  |     } else { | ||||||
|  |       this.code = grpc.status.UNKNOWN; | ||||||
|  |       this.message = `The synology webserver returned a status code ${httpCode}`; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| class SynologyHttpClient { | class SynologyHttpClient { | ||||||
|   constructor(options = {}) { |   constructor(options = {}) { | ||||||
|     this.options = JSON.parse(JSON.stringify(options)); |     this.options = JSON.parse(JSON.stringify(options)); | ||||||
|  | @ -44,32 +115,49 @@ class SynologyHttpClient { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   log_response(error, response, body, options) { |   log_response(error, response, body, options) { | ||||||
|     let prop; |     const cleansedBody = JSON.parse(stringify(body)); | ||||||
|     let val; |     const cleansedOptions = JSON.parse(stringify(options)); | ||||||
| 
 |     // This function handles arrays and objects
 | ||||||
|     prop = "auth.username"; |     function recursiveCleanse(obj) { | ||||||
|     val = _.get(options, prop, false); |       for (const k in obj) { | ||||||
|     if (val) { |         if (typeof obj[k] == "object" && obj[k] !== null) { | ||||||
|       _.set(options, prop, "redacted"); |           recursiveCleanse(obj[k]); | ||||||
|  |         } else { | ||||||
|  |           if ( | ||||||
|  |             [ | ||||||
|  |               "account", | ||||||
|  |               "passwd", | ||||||
|  |               "username", | ||||||
|  |               "password", | ||||||
|  |               "_sid", | ||||||
|  |               "sid", | ||||||
|  |               "Authorization", | ||||||
|  |               "authorization", | ||||||
|  |               "user", | ||||||
|  |               "mutual_user", | ||||||
|  |               "mutual_password", | ||||||
|  |             ].includes(k) | ||||||
|  |           ) { | ||||||
|  |             obj[k] = "redacted"; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|  |     recursiveCleanse(cleansedBody); | ||||||
|  |     recursiveCleanse(cleansedOptions); | ||||||
| 
 | 
 | ||||||
|     prop = "auth.password"; |     delete cleansedOptions.httpAgent; | ||||||
|     val = _.get(options, prop, false); |     delete cleansedOptions.httpsAgent; | ||||||
|     if (val) { |  | ||||||
|       _.set(options, prop, "redacted"); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     prop = "headers.Authorization"; |     this.logger.debug("SYNOLOGY HTTP REQUEST: " + stringify(cleansedOptions)); | ||||||
|     val = _.get(options, prop, false); |  | ||||||
|     if (val) { |  | ||||||
|       _.set(options, prop, "redacted"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     this.logger.debug("SYNOLOGY HTTP REQUEST: " + stringify(options)); |  | ||||||
|     this.logger.debug("SYNOLOGY HTTP ERROR: " + error); |     this.logger.debug("SYNOLOGY HTTP ERROR: " + error); | ||||||
|     this.logger.debug("SYNOLOGY HTTP STATUS: " + response.statusCode); |     this.logger.debug( | ||||||
|     this.logger.debug("SYNOLOGY HTTP HEADERS: " + stringify(response.headers)); |       "SYNOLOGY HTTP STATUS: " + _.get(response, "statusCode", "") | ||||||
|     this.logger.debug("SYNOLOGY HTTP BODY: " + stringify(body)); |     ); | ||||||
|  |     this.logger.debug( | ||||||
|  |       "SYNOLOGY HTTP HEADERS: " + stringify(_.get(response, "headers", "")) | ||||||
|  |     ); | ||||||
|  |     this.logger.debug("SYNOLOGY HTTP BODY: " + stringify(cleansedBody)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async do_request(method, path, data = {}, options = {}) { |   async do_request(method, path, data = {}, options = {}) { | ||||||
|  | @ -149,7 +237,7 @@ class SynologyHttpClient { | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           if (response.statusCode > 299 || response.statusCode < 200) { |           if (response.statusCode > 299 || response.statusCode < 200) { | ||||||
|             reject(response); |             reject(new SynologyError(null, response.statusCode)); | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           if (response.body.success === false) { |           if (response.body.success === false) { | ||||||
|  | @ -157,7 +245,9 @@ class SynologyHttpClient { | ||||||
|             if (response.body.error.code == 119 && sid == client.sid) { |             if (response.body.error.code == 119 && sid == client.sid) { | ||||||
|               client.sid = null; |               client.sid = null; | ||||||
|             } |             } | ||||||
|             reject(response); |             reject( | ||||||
|  |               new SynologyError(response.body.error.code, response.statusCode) | ||||||
|  |             ); | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           resolve(response); |           resolve(response); | ||||||
|  | @ -293,19 +383,19 @@ class SynologyHttpClient { | ||||||
|     return snapshots; |     return snapshots; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async GetSnapshotByLunIDAndName(lun_id, name) { |   async GetSnapshotByLunUUIDAndName(lun_uuid, name) { | ||||||
|     const get_snapshot_info = { |     const get_snapshot_info = { | ||||||
|       lid: lun_id, //check?
 |       api: "SYNO.Core.ISCSI.LUN", | ||||||
|       api: "SYNO.Core.Storage.iSCSILUN", |       method: "list_snapshot", | ||||||
|       method: "load_snapshot", |  | ||||||
|       version: 1, |       version: 1, | ||||||
|  |       src_lun_uuid: JSON.stringify(lun_uuid), | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     let response = await this.do_request("GET", "entry.cgi", get_snapshot_info); |     let response = await this.do_request("GET", "entry.cgi", get_snapshot_info); | ||||||
| 
 | 
 | ||||||
|     if (response.body.data) { |     if (response.body.data.snapshots) { | ||||||
|       let snapshot = response.body.data.find((i) => { |       let snapshot = response.body.data.snapshots.find((i) => { | ||||||
|         return i.desc == name; |         return i.description == name; | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       if (snapshot) { |       if (snapshot) { | ||||||
|  | @ -314,18 +404,18 @@ class SynologyHttpClient { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async GetSnapshotByLunIDAndSnapshotUUID(lun_id, snapshot_uuid) { |   async GetSnapshotByLunUUIDAndSnapshotUUID(lun_uuid, snapshot_uuid) { | ||||||
|     const get_snapshot_info = { |     const get_snapshot_info = { | ||||||
|       lid: lun_id, //check?
 |       api: "SYNO.Core.ISCSI.LUN", | ||||||
|       api: "SYNO.Core.Storage.iSCSILUN", |       method: "list_snapshot", | ||||||
|       method: "load_snapshot", |  | ||||||
|       version: 1, |       version: 1, | ||||||
|  |       src_lun_uuid: JSON.stringify(lun_uuid), | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     let response = await this.do_request("GET", "entry.cgi", get_snapshot_info); |     let response = await this.do_request("GET", "entry.cgi", get_snapshot_info); | ||||||
| 
 | 
 | ||||||
|     if (response.body.data) { |     if (response.body.data.snapshots) { | ||||||
|       let snapshot = response.body.data.find((i) => { |       let snapshot = response.body.data.snapshots.find((i) => { | ||||||
|         return i.uuid == snapshot_uuid; |         return i.uuid == snapshot_uuid; | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|  | @ -412,7 +502,7 @@ class SynologyHttpClient { | ||||||
|       response = await this.do_request("GET", "entry.cgi", iscsi_lun_create); |       response = await this.do_request("GET", "entry.cgi", iscsi_lun_create); | ||||||
|       return response.body.data.uuid; |       return response.body.data.uuid; | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       if ([18990538].includes(err.body.error.code)) { |       if (err.synoCode === 18990538) { | ||||||
|         response = await this.do_request("GET", "entry.cgi", lun_list); |         response = await this.do_request("GET", "entry.cgi", lun_list); | ||||||
|         let lun = response.body.data.luns.find((i) => { |         let lun = response.body.data.luns.find((i) => { | ||||||
|           return i.name == iscsi_lun_create.name; |           return i.name == iscsi_lun_create.name; | ||||||
|  | @ -503,7 +593,7 @@ class SynologyHttpClient { | ||||||
| 
 | 
 | ||||||
|       return response.body.data.target_id; |       return response.body.data.target_id; | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       if ([18990744].includes(err.body.error.code)) { |       if (err.synoCode === 18990744) { | ||||||
|         //do lookup
 |         //do lookup
 | ||||||
|         const iscsi_target_list = { |         const iscsi_target_list = { | ||||||
|           api: "SYNO.Core.ISCSI.Target", |           api: "SYNO.Core.ISCSI.Target", | ||||||
|  | @ -549,7 +639,7 @@ class SynologyHttpClient { | ||||||
|       /** |       /** | ||||||
|        * 18990710 = non-existant |        * 18990710 = non-existant | ||||||
|        */ |        */ | ||||||
|       //if (![18990710].includes(err.body.error.code)) {
 |       //if (err.synoCode !== 18990710) {
 | ||||||
|       throw err; |       throw err; | ||||||
|       //}
 |       //}
 | ||||||
|     } |     } | ||||||
|  | @ -572,20 +662,34 @@ class SynologyHttpClient { | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async CreateClonedVolume(src_lun_uuid, dst_lun_name) { |   async CreateClonedVolume( | ||||||
|  |     src_lun_uuid, | ||||||
|  |     dst_lun_name, | ||||||
|  |     dst_location, | ||||||
|  |     description | ||||||
|  |   ) { | ||||||
|     const create_cloned_volume = { |     const create_cloned_volume = { | ||||||
|       api: "SYNO.Core.ISCSI.LUN", |       api: "SYNO.Core.ISCSI.LUN", | ||||||
|       version: 1, |       version: 1, | ||||||
|       method: "clone", |       method: "clone", | ||||||
|       src_lun_uuid: JSON.stringify(src_lun_uuid), // src lun uuid
 |       src_lun_uuid: JSON.stringify(src_lun_uuid), // src lun uuid
 | ||||||
|       dst_lun_name: dst_lun_name, // dst lun name
 |       dst_lun_name: dst_lun_name, // dst lun name
 | ||||||
|  |       dst_location: dst_location, | ||||||
|       is_same_pool: true, // always true? string?
 |       is_same_pool: true, // always true? string?
 | ||||||
|       clone_type: "democratic-csi", // check
 |       clone_type: "democratic-csi", // check
 | ||||||
|     }; |     }; | ||||||
|  |     if (description) { | ||||||
|  |       create_cloned_volume.description = description; | ||||||
|  |     } | ||||||
|     return await this.do_request("GET", "entry.cgi", create_cloned_volume); |     return await this.do_request("GET", "entry.cgi", create_cloned_volume); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async CreateVolumeFromSnapshot(src_lun_uuid, snapshot_uuid, cloned_lun_name) { |   async CreateVolumeFromSnapshot( | ||||||
|  |     src_lun_uuid, | ||||||
|  |     snapshot_uuid, | ||||||
|  |     cloned_lun_name, | ||||||
|  |     description | ||||||
|  |   ) { | ||||||
|     const create_volume_from_snapshot = { |     const create_volume_from_snapshot = { | ||||||
|       api: "SYNO.Core.ISCSI.LUN", |       api: "SYNO.Core.ISCSI.LUN", | ||||||
|       version: 1, |       version: 1, | ||||||
|  | @ -595,6 +699,9 @@ class SynologyHttpClient { | ||||||
|       cloned_lun_name: cloned_lun_name, // cloned lun name
 |       cloned_lun_name: cloned_lun_name, // cloned lun name
 | ||||||
|       clone_type: "democratic-csi", // check
 |       clone_type: "democratic-csi", // check
 | ||||||
|     }; |     }; | ||||||
|  |     if (description) { | ||||||
|  |       create_volume_from_snapshot.description = description; | ||||||
|  |     } | ||||||
|     return await this.do_request( |     return await this.do_request( | ||||||
|       "GET", |       "GET", | ||||||
|       "entry.cgi", |       "entry.cgi", | ||||||
|  |  | ||||||
|  | @ -1,9 +1,12 @@ | ||||||
|  | const _ = require("lodash"); | ||||||
| const { CsiBaseDriver } = require("../index"); | const { CsiBaseDriver } = require("../index"); | ||||||
|  | const GeneralUtils = require("../../utils/general"); | ||||||
| const { GrpcError, grpc } = require("../../utils/grpc"); | const { GrpcError, grpc } = require("../../utils/grpc"); | ||||||
|  | const Handlebars = require("handlebars"); | ||||||
| const registry = require("../../utils/registry"); | const registry = require("../../utils/registry"); | ||||||
| const SynologyHttpClient = require("./http").SynologyHttpClient; | const SynologyHttpClient = require("./http").SynologyHttpClient; | ||||||
| const semver = require("semver"); | const semver = require("semver"); | ||||||
| const sleep = require("../../utils/general").sleep; | const yaml = require("js-yaml"); | ||||||
| 
 | 
 | ||||||
| const __REGISTRY_NS__ = "ControllerSynologyDriver"; | const __REGISTRY_NS__ = "ControllerSynologyDriver"; | ||||||
| 
 | 
 | ||||||
|  | @ -142,6 +145,37 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   getObjectFromDevAttribs(list = []) { | ||||||
|  |     if (!list) { | ||||||
|  |       return {}; | ||||||
|  |     } | ||||||
|  |     return list.reduce( | ||||||
|  |       (obj, item) => Object.assign(obj, { [item.dev_attrib]: item.enable }), | ||||||
|  |       {} | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getDevAttribsFromObject(obj, keepNull = false) { | ||||||
|  |     return Object.entries(obj) | ||||||
|  |       .filter((e) => keepNull || e[1] != null) | ||||||
|  |       .map((e) => ({ dev_attrib: e[0], enable: e[1] })); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   parseParameterYamlData(data, fieldHint = "") { | ||||||
|  |     try { | ||||||
|  |       return yaml.load(data); | ||||||
|  |     } catch { | ||||||
|  |       if (err instanceof yaml.YAMLException) { | ||||||
|  |         throw new GrpcError( | ||||||
|  |           grpc.status.INVALID_ARGUMENT, | ||||||
|  |           `${fieldHint} not a valid YAML document.`.trim() | ||||||
|  |         ); | ||||||
|  |       } else { | ||||||
|  |         throw err; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   buildIscsiName(name) { |   buildIscsiName(name) { | ||||||
|     let iscsiName = name; |     let iscsiName = name; | ||||||
|     if (this.options.iscsi.namePrefix) { |     if (this.options.iscsi.namePrefix) { | ||||||
|  | @ -155,6 +189,25 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
|     return iscsiName.toLowerCase(); |     return iscsiName.toLowerCase(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /** | ||||||
|  |    * Returns the value for the 'location' parameter indicating on which volume | ||||||
|  |    * a LUN is to be created. | ||||||
|  |    * | ||||||
|  |    * @param {Object} parameters - Parameters received from a StorageClass | ||||||
|  |    * @param {String} parameters.volume - The volume specified by the StorageClass | ||||||
|  |    * @returns {String} The location of the volume. | ||||||
|  |    */ | ||||||
|  |   getLocation() { | ||||||
|  |     let location = _.get(this.options, "synology.volume"); | ||||||
|  |     if (!location) { | ||||||
|  |       location = "volume1"; | ||||||
|  |     } | ||||||
|  |     if (!location.startsWith("/")) { | ||||||
|  |       location = "/" + location; | ||||||
|  |     } | ||||||
|  |     return location; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   assertCapabilities(capabilities) { |   assertCapabilities(capabilities) { | ||||||
|     const driverResourceType = this.getDriverResourceType(); |     const driverResourceType = this.getDriverResourceType(); | ||||||
|     this.ctx.logger.verbose("validating capabilities: %j", capabilities); |     this.ctx.logger.verbose("validating capabilities: %j", capabilities); | ||||||
|  | @ -171,7 +224,9 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
| 
 | 
 | ||||||
|           if ( |           if ( | ||||||
|             capability.mount.fs_type && |             capability.mount.fs_type && | ||||||
|             !["nfs", "cifs"].includes(capability.mount.fs_type) |             !GeneralUtils.default_supported_file_filesystems().includes( | ||||||
|  |               capability.mount.fs_type | ||||||
|  |             ) | ||||||
|           ) { |           ) { | ||||||
|             message = `invalid fs_type ${capability.mount.fs_type}`; |             message = `invalid fs_type ${capability.mount.fs_type}`; | ||||||
|             return false; |             return false; | ||||||
|  | @ -198,7 +253,7 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
|           if (capability.access_type == "mount") { |           if (capability.access_type == "mount") { | ||||||
|             if ( |             if ( | ||||||
|               capability.mount.fs_type && |               capability.mount.fs_type && | ||||||
|               !["btrfs", "ext3", "ext4", "ext4dev", "xfs"].includes( |               !GeneralUtils.default_supported_block_filesystems().includes( | ||||||
|                 capability.mount.fs_type |                 capability.mount.fs_type | ||||||
|               ) |               ) | ||||||
|             ) { |             ) { | ||||||
|  | @ -310,6 +365,9 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let volume_context = {}; |     let volume_context = {}; | ||||||
|  |     const normalizedParameters = driver.getNormalizedParameters( | ||||||
|  |       call.request.parameters | ||||||
|  |     ); | ||||||
|     switch (driver.getDriverShareType()) { |     switch (driver.getDriverShareType()) { | ||||||
|       case "nfs": |       case "nfs": | ||||||
|         // TODO: create volume here
 |         // TODO: create volume here
 | ||||||
|  | @ -327,12 +385,53 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
|         break; |         break; | ||||||
|       case "iscsi": |       case "iscsi": | ||||||
|         let iscsiName = driver.buildIscsiName(name); |         let iscsiName = driver.buildIscsiName(name); | ||||||
|  |         let lunTemplate; | ||||||
|  |         let targetTemplate; | ||||||
|         let data; |         let data; | ||||||
|         let target; |         let target; | ||||||
|         let lun_mapping; |         let lun_mapping; | ||||||
|         let lun_uuid; |         let lun_uuid; | ||||||
|         let existingLun; |         let existingLun; | ||||||
| 
 | 
 | ||||||
|  |         lunTemplate = Object.assign( | ||||||
|  |           {}, | ||||||
|  |           _.get(driver.options, "iscsi.lunTemplate", {}), | ||||||
|  |           driver.parseParameterYamlData( | ||||||
|  |             _.get(normalizedParameters, "lunTemplate", "{}"), | ||||||
|  |             "parameters.lunTemplate" | ||||||
|  |           ), | ||||||
|  |           driver.parseParameterYamlData( | ||||||
|  |             _.get(call.request, "secrets.lunTemplate", "{}"), | ||||||
|  |             "secrets.lunTemplate" | ||||||
|  |           ) | ||||||
|  |         ); | ||||||
|  |         targetTemplate = Object.assign( | ||||||
|  |           {}, | ||||||
|  |           _.get(driver.options, "iscsi.targetTemplate", {}), | ||||||
|  |           driver.parseParameterYamlData( | ||||||
|  |             _.get(normalizedParameters, "targetTemplate", "{}"), | ||||||
|  |             "parameters.targetTemplate" | ||||||
|  |           ), | ||||||
|  |           driver.parseParameterYamlData( | ||||||
|  |             _.get(call.request, "secrets.targetTemplate", "{}"), | ||||||
|  |             "secrets.targetTemplate" | ||||||
|  |           ) | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         // render the template for description
 | ||||||
|  |         if (lunTemplate.description) { | ||||||
|  |           lunTemplate.description = Handlebars.compile(lunTemplate.description)( | ||||||
|  |             { | ||||||
|  |               name: call.request.name, | ||||||
|  |               parameters: call.request.parameters, | ||||||
|  |               csi: { | ||||||
|  |                 name: this.ctx.args.csiName, | ||||||
|  |                 version: this.ctx.args.csiVersion, | ||||||
|  |               }, | ||||||
|  |             } | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         // ensure volumes with the same name being requested a 2nd time but with a different size fails
 |         // ensure volumes with the same name being requested a 2nd time but with a different size fails
 | ||||||
|         try { |         try { | ||||||
|           let lun = await httpClient.GetLunByName(iscsiName); |           let lun = await httpClient.GetLunByName(iscsiName); | ||||||
|  | @ -361,13 +460,12 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
| 
 | 
 | ||||||
|         if (volume_content_source) { |         if (volume_content_source) { | ||||||
|           let src_lun_uuid; |           let src_lun_uuid; | ||||||
|           let src_lun_id; |  | ||||||
|           switch (volume_content_source.type) { |           switch (volume_content_source.type) { | ||||||
|             case "snapshot": |             case "snapshot": | ||||||
|               let parts = volume_content_source.snapshot.snapshot_id.split("/"); |               let parts = volume_content_source.snapshot.snapshot_id.split("/"); | ||||||
| 
 | 
 | ||||||
|               src_lun_id = parts[2]; |               src_lun_uuid = parts[2]; | ||||||
|               if (!src_lun_id) { |               if (!src_lun_uuid) { | ||||||
|                 throw new GrpcError( |                 throw new GrpcError( | ||||||
|                   grpc.status.NOT_FOUND, |                   grpc.status.NOT_FOUND, | ||||||
|                   `invalid snapshot_id: ${volume_content_source.snapshot.snapshot_id}` |                   `invalid snapshot_id: ${volume_content_source.snapshot.snapshot_id}` | ||||||
|  | @ -382,13 +480,17 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
|                 ); |                 ); | ||||||
|               } |               } | ||||||
| 
 | 
 | ||||||
|               let src_lun = await httpClient.GetLunByID(src_lun_id); |               // This is for backwards compatibility. Previous versions of this driver used the LUN ID instead of the
 | ||||||
|               src_lun_uuid = src_lun.uuid; |               // UUID. If this is the case we need to get the LUN UUID before we can proceed.
 | ||||||
|  |               if (!src_lun_uuid.includes("-")) { | ||||||
|  |                 src_lun_uuid = await httpClient.GetLunByID(src_lun_uuid).uuid; | ||||||
|  |               } | ||||||
| 
 | 
 | ||||||
|               let snapshot = await httpClient.GetSnapshotByLunIDAndSnapshotUUID( |               let snapshot = | ||||||
|                 src_lun_id, |                 await httpClient.GetSnapshotByLunUUIDAndSnapshotUUID( | ||||||
|                 snapshot_uuid |                   src_lun_uuid, | ||||||
|               ); |                   snapshot_uuid | ||||||
|  |                 ); | ||||||
|               if (!snapshot) { |               if (!snapshot) { | ||||||
|                 throw new GrpcError( |                 throw new GrpcError( | ||||||
|                   grpc.status.NOT_FOUND, |                   grpc.status.NOT_FOUND, | ||||||
|  | @ -401,7 +503,8 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
|                 await httpClient.CreateVolumeFromSnapshot( |                 await httpClient.CreateVolumeFromSnapshot( | ||||||
|                   src_lun_uuid, |                   src_lun_uuid, | ||||||
|                   snapshot_uuid, |                   snapshot_uuid, | ||||||
|                   iscsiName |                   iscsiName, | ||||||
|  |                   lunTemplate.description | ||||||
|                 ); |                 ); | ||||||
|               } |               } | ||||||
|               break; |               break; | ||||||
|  | @ -425,7 +528,12 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
|                     `invalid volume_id: ${volume_content_source.volume.volume_id}` |                     `invalid volume_id: ${volume_content_source.volume.volume_id}` | ||||||
|                   ); |                   ); | ||||||
|                 } |                 } | ||||||
|                 await httpClient.CreateClonedVolume(src_lun_uuid, iscsiName); |                 await httpClient.CreateClonedVolume( | ||||||
|  |                   src_lun_uuid, | ||||||
|  |                   iscsiName, | ||||||
|  |                   driver.getLocation(), | ||||||
|  |                   lunTemplate.description | ||||||
|  |                 ); | ||||||
|               } |               } | ||||||
|               break; |               break; | ||||||
|             default: |             default: | ||||||
|  | @ -444,20 +552,22 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
|           } |           } | ||||||
|         } else { |         } else { | ||||||
|           // create lun
 |           // create lun
 | ||||||
|           data = Object.assign({}, driver.options.iscsi.lunTemplate, { |           data = Object.assign({}, lunTemplate, { | ||||||
|             name: iscsiName, |             name: iscsiName, | ||||||
|             location: driver.options.synology.volume, |             location: driver.getLocation(), | ||||||
|             size: capacity_bytes, |             size: capacity_bytes, | ||||||
|           }); |           }); | ||||||
|  | 
 | ||||||
|           lun_uuid = await httpClient.CreateLun(data); |           lun_uuid = await httpClient.CreateLun(data); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // create target
 |         // create target
 | ||||||
|         let iqn = driver.options.iscsi.baseiqn + iscsiName; |         let iqn = driver.options.iscsi.baseiqn + iscsiName; | ||||||
|         data = Object.assign({}, driver.options.iscsi.targetTemplate, { |         data = Object.assign({}, targetTemplate, { | ||||||
|           name: iscsiName, |           name: iscsiName, | ||||||
|           iqn, |           iqn, | ||||||
|         }); |         }); | ||||||
|  | 
 | ||||||
|         let target_id = await httpClient.CreateTarget(data); |         let target_id = await httpClient.CreateTarget(data); | ||||||
|         //target = await httpClient.GetTargetByTargetID(target_id);
 |         //target = await httpClient.GetTargetByTargetID(target_id);
 | ||||||
|         target = await httpClient.GetTargetByIQN(iqn); |         target = await httpClient.GetTargetByIQN(iqn); | ||||||
|  | @ -609,12 +719,12 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
| 
 | 
 | ||||||
|             let waitTimeBetweenChecks = settleSeconds * 1000; |             let waitTimeBetweenChecks = settleSeconds * 1000; | ||||||
| 
 | 
 | ||||||
|             await sleep(waitTimeBetweenChecks); |             await GeneralUtils.sleep(waitTimeBetweenChecks); | ||||||
|             lun_uuid = await httpClient.GetLunUUIDByName(iscsiName); |             lun_uuid = await httpClient.GetLunUUIDByName(iscsiName); | ||||||
| 
 | 
 | ||||||
|             while (currentCheck <= settleMaxRetries && lun_uuid) { |             while (currentCheck <= settleMaxRetries && lun_uuid) { | ||||||
|               currentCheck++; |               currentCheck++; | ||||||
|               await sleep(waitTimeBetweenChecks); |               await GeneralUtils.sleep(waitTimeBetweenChecks); | ||||||
|               lun_uuid = await httpClient.GetLunUUIDByName(iscsiName); |               lun_uuid = await httpClient.GetLunUUIDByName(iscsiName); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | @ -737,8 +847,9 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
|   async GetCapacity(call) { |   async GetCapacity(call) { | ||||||
|     const driver = this; |     const driver = this; | ||||||
|     const httpClient = await driver.getHttpClient(); |     const httpClient = await driver.getHttpClient(); | ||||||
|  |     const location = driver.getLocation(); | ||||||
| 
 | 
 | ||||||
|     if (!driver.options.synology.volume) { |     if (!location) { | ||||||
|       throw new GrpcError( |       throw new GrpcError( | ||||||
|         grpc.status.FAILED_PRECONDITION, |         grpc.status.FAILED_PRECONDITION, | ||||||
|         `invalid configuration: missing volume` |         `invalid configuration: missing volume` | ||||||
|  | @ -753,9 +864,7 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let response = await httpClient.GetVolumeInfo( |     let response = await httpClient.GetVolumeInfo(location); | ||||||
|       driver.options.synology.volume |  | ||||||
|     ); |  | ||||||
|     return { available_capacity: response.body.data.volume.size_free_byte }; |     return { available_capacity: response.body.data.volume.size_free_byte }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -833,6 +942,24 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     const normalizedParameters = driver.getNormalizedParameters( | ||||||
|  |       call.request.parameters | ||||||
|  |     ); | ||||||
|  |     let lunSnapshotTemplate; | ||||||
|  | 
 | ||||||
|  |     lunSnapshotTemplate = Object.assign( | ||||||
|  |       {}, | ||||||
|  |       _.get(driver.options, "iscsi.lunSnapshotTemplate", {}), | ||||||
|  |       driver.parseParameterYamlData( | ||||||
|  |         _.get(normalizedParameters, "lunSnapshotTemplate", "{}"), | ||||||
|  |         "parameters.lunSnapshotTemplate" | ||||||
|  |       ), | ||||||
|  |       driver.parseParameterYamlData( | ||||||
|  |         _.get(call.request, "secrets.lunSnapshotTemplate", "{}"), | ||||||
|  |         "secrets.lunSnapshotTemplate" | ||||||
|  |       ) | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|     // check for other snapshopts with the same name on other volumes and fail as appropriate
 |     // check for other snapshopts with the same name on other volumes and fail as appropriate
 | ||||||
|     // TODO: technically this should only be checking lun/snapshots relevant to this specific install of the driver
 |     // TODO: technically this should only be checking lun/snapshots relevant to this specific install of the driver
 | ||||||
|     // but alas an isolation/namespacing mechanism does not exist in synology
 |     // but alas an isolation/namespacing mechanism does not exist in synology
 | ||||||
|  | @ -848,16 +975,16 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
| 
 | 
 | ||||||
|     // check for already exists
 |     // check for already exists
 | ||||||
|     let snapshot; |     let snapshot; | ||||||
|     snapshot = await httpClient.GetSnapshotByLunIDAndName(lun.lun_id, name); |     snapshot = await httpClient.GetSnapshotByLunUUIDAndName(lun.uuid, name); | ||||||
|     if (!snapshot) { |     if (!snapshot) { | ||||||
|       let data = Object.assign({}, driver.options.iscsi.lunSnapshotTemplate, { |       let data = Object.assign({}, lunSnapshotTemplate, { | ||||||
|         src_lun_uuid: lun.uuid, |         src_lun_uuid: lun.uuid, | ||||||
|         taken_by: "democratic-csi", |         taken_by: "democratic-csi", | ||||||
|         description: name, //check
 |         description: name, //check
 | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       await httpClient.CreateSnapshot(data); |       await httpClient.CreateSnapshot(data); | ||||||
|       snapshot = await httpClient.GetSnapshotByLunIDAndName(lun.lun_id, name); |       snapshot = await httpClient.GetSnapshotByLunUUIDAndName(lun.uuid, name); | ||||||
| 
 | 
 | ||||||
|       if (!snapshot) { |       if (!snapshot) { | ||||||
|         throw new Error(`failed to create snapshot`); |         throw new Error(`failed to create snapshot`); | ||||||
|  | @ -871,7 +998,7 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
|          * is needed to create a volume from this snapshot. |          * is needed to create a volume from this snapshot. | ||||||
|          */ |          */ | ||||||
|         size_bytes: snapshot.total_size, |         size_bytes: snapshot.total_size, | ||||||
|         snapshot_id: `/lun/${lun.lun_id}/${snapshot.uuid}`, // add shanpshot_uuid //fixme
 |         snapshot_id: `/lun/${lun.uuid}/${snapshot.uuid}`, | ||||||
|         source_volume_id: source_volume_id, |         source_volume_id: source_volume_id, | ||||||
|         //https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/timestamp.proto
 |         //https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/timestamp.proto
 | ||||||
|         creation_time: { |         creation_time: { | ||||||
|  | @ -908,8 +1035,8 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let parts = snapshot_id.split("/"); |     let parts = snapshot_id.split("/"); | ||||||
|     let lun_id = parts[2]; |     let lun_uuid = parts[2]; | ||||||
|     if (!lun_id) { |     if (!lun_uuid) { | ||||||
|       return {}; |       return {}; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -918,9 +1045,14 @@ class ControllerSynologyDriver extends CsiBaseDriver { | ||||||
|       return {}; |       return {}; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // TODO: delete snapshot
 |     // This is for backwards compatibility. Previous versions of this driver used the LUN ID instead of the UUID. If
 | ||||||
|     let snapshot = await httpClient.GetSnapshotByLunIDAndSnapshotUUID( |     // this is the case we need to get the LUN UUID before we can proceed.
 | ||||||
|       lun_id, |     if (!lun_uuid.includes("-")) { | ||||||
|  |       lun_uuid = await httpClient.GetLunByID(lun_uuid).uuid; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let snapshot = await httpClient.GetSnapshotByLunUUIDAndSnapshotUUID( | ||||||
|  |       lun_uuid, | ||||||
|       snapshot_uuid |       snapshot_uuid | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| const _ = require("lodash"); | const _ = require("lodash"); | ||||||
| const { ControllerZfsBaseDriver } = require("../controller-zfs"); | const { ControllerZfsBaseDriver } = require("../controller-zfs"); | ||||||
| const { GrpcError, grpc } = require("../../utils/grpc"); | const { GrpcError, grpc } = require("../../utils/grpc"); | ||||||
|  | const GeneralUtils = require("../../utils/general"); | ||||||
| const registry = require("../../utils/registry"); | const registry = require("../../utils/registry"); | ||||||
| const SshClient = require("../../utils/ssh").SshClient; | const SshClient = require("../../utils/ssh").SshClient; | ||||||
| const sleep = require("../../utils/general").sleep; |  | ||||||
| const { Zetabyte, ZfsSshProcessManager } = require("../../utils/zfs"); | const { Zetabyte, ZfsSshProcessManager } = require("../../utils/zfs"); | ||||||
| 
 | 
 | ||||||
| const Handlebars = require("handlebars"); | const Handlebars = require("handlebars"); | ||||||
|  | @ -52,6 +52,7 @@ class ControllerZfsGenericDriver extends ControllerZfsBaseDriver { | ||||||
|   getDriverZfsResourceType() { |   getDriverZfsResourceType() { | ||||||
|     switch (this.options.driver) { |     switch (this.options.driver) { | ||||||
|       case "zfs-generic-nfs": |       case "zfs-generic-nfs": | ||||||
|  |       case "zfs-generic-smb": | ||||||
|         return "filesystem"; |         return "filesystem"; | ||||||
|       case "zfs-generic-iscsi": |       case "zfs-generic-iscsi": | ||||||
|         return "volume"; |         return "volume"; | ||||||
|  | @ -60,6 +61,24 @@ class ControllerZfsGenericDriver extends ControllerZfsBaseDriver { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   generateSmbShareName(datasetName) { | ||||||
|  |     const driver = this; | ||||||
|  | 
 | ||||||
|  |     driver.ctx.logger.verbose( | ||||||
|  |       `generating smb share name for dataset: ${datasetName}` | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     let name = datasetName || ""; | ||||||
|  |     name = name.replaceAll("/", "_"); | ||||||
|  |     name = name.replaceAll("-", "_"); | ||||||
|  | 
 | ||||||
|  |     driver.ctx.logger.verbose( | ||||||
|  |       `generated smb share name for dataset (${datasetName}): ${name}` | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     return name; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   /** |   /** | ||||||
|    * should create any necessary share resources |    * should create any necessary share resources | ||||||
|    * should set the SHARE_VOLUME_CONTEXT_PROPERTY_NAME propery |    * should set the SHARE_VOLUME_CONTEXT_PROPERTY_NAME propery | ||||||
|  | @ -67,6 +86,7 @@ class ControllerZfsGenericDriver extends ControllerZfsBaseDriver { | ||||||
|    * @param {*} datasetName |    * @param {*} datasetName | ||||||
|    */ |    */ | ||||||
|   async createShare(call, datasetName) { |   async createShare(call, datasetName) { | ||||||
|  |     const driver = this; | ||||||
|     const zb = await this.getZetabyte(); |     const zb = await this.getZetabyte(); | ||||||
|     const execClient = this.getExecClient(); |     const execClient = this.getExecClient(); | ||||||
| 
 | 
 | ||||||
|  | @ -109,6 +129,41 @@ class ControllerZfsGenericDriver extends ControllerZfsBaseDriver { | ||||||
|         }; |         }; | ||||||
|         return volume_context; |         return volume_context; | ||||||
| 
 | 
 | ||||||
|  |       case "zfs-generic-smb": | ||||||
|  |         let share; | ||||||
|  |         switch (this.options.smb.shareStrategy) { | ||||||
|  |           case "setDatasetProperties": | ||||||
|  |             for (let key of ["share", "sharesmb"]) { | ||||||
|  |               if ( | ||||||
|  |                 this.options.smb.shareStrategySetDatasetProperties.properties[ | ||||||
|  |                   key | ||||||
|  |                 ] | ||||||
|  |               ) { | ||||||
|  |                 await zb.zfs.set(datasetName, { | ||||||
|  |                   [key]: | ||||||
|  |                     this.options.smb.shareStrategySetDatasetProperties | ||||||
|  |                       .properties[key], | ||||||
|  |                 }); | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             share = driver.generateSmbShareName(datasetName); | ||||||
|  |             break; | ||||||
|  |           default: | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         properties = await zb.zfs.get(datasetName, ["mountpoint"]); | ||||||
|  |         properties = properties[datasetName]; | ||||||
|  |         this.ctx.logger.debug("zfs props data: %j", properties); | ||||||
|  | 
 | ||||||
|  |         volume_context = { | ||||||
|  |           node_attach_driver: "smb", | ||||||
|  |           server: this.options.smb.shareHost, | ||||||
|  |           share, | ||||||
|  |         }; | ||||||
|  |         return volume_context; | ||||||
|  | 
 | ||||||
|       case "zfs-generic-iscsi": |       case "zfs-generic-iscsi": | ||||||
|         let basename; |         let basename; | ||||||
|         let iscsiName; |         let iscsiName; | ||||||
|  | @ -176,8 +231,12 @@ class ControllerZfsGenericDriver extends ControllerZfsBaseDriver { | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             response = await this.targetCliCommand( |             await GeneralUtils.retry( | ||||||
|               ` |               3, | ||||||
|  |               2000, | ||||||
|  |               async () => { | ||||||
|  |                 await this.targetCliCommand( | ||||||
|  |                   ` | ||||||
| # create target | # create target | ||||||
| cd /iscsi | cd /iscsi | ||||||
| create ${basename}:${iscsiName} | create ${basename}:${iscsiName} | ||||||
|  | @ -195,6 +254,16 @@ create ${iscsiName} /dev/${extentDiskName} | ||||||
| cd /iscsi/${basename}:${iscsiName}/tpg1/luns | cd /iscsi/${basename}:${iscsiName}/tpg1/luns | ||||||
| create /backstores/block/${iscsiName} | create /backstores/block/${iscsiName} | ||||||
| ` | ` | ||||||
|  |                 ); | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 retryCondition: (err) => { | ||||||
|  |                   if (err.stdout && err.stdout.includes("Ran out of input")) { | ||||||
|  |                     return true; | ||||||
|  |                   } | ||||||
|  |                   return false; | ||||||
|  |                 }, | ||||||
|  |               } | ||||||
|             ); |             ); | ||||||
|             break; |             break; | ||||||
|           default: |           default: | ||||||
|  | @ -258,7 +327,7 @@ create /backstores/block/${iscsiName} | ||||||
|                 } |                 } | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
|             await sleep(2000); // let things settle
 |             await GeneralUtils.sleep(2000); // let things settle
 | ||||||
|             break; |             break; | ||||||
|           default: |           default: | ||||||
|             throw new GrpcError( |             throw new GrpcError( | ||||||
|  | @ -268,6 +337,36 @@ create /backstores/block/${iscsiName} | ||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
| 
 | 
 | ||||||
|  |       case "zfs-generic-smb": | ||||||
|  |         switch (this.options.smb.shareStrategy) { | ||||||
|  |           case "setDatasetProperties": | ||||||
|  |             for (let key of ["share", "sharesmb"]) { | ||||||
|  |               if ( | ||||||
|  |                 this.options.smb.shareStrategySetDatasetProperties.properties[ | ||||||
|  |                   key | ||||||
|  |                 ] | ||||||
|  |               ) { | ||||||
|  |                 try { | ||||||
|  |                   await zb.zfs.inherit(datasetName, key); | ||||||
|  |                 } catch (err) { | ||||||
|  |                   if (err.toString().includes("dataset does not exist")) { | ||||||
|  |                     // do nothing
 | ||||||
|  |                   } else { | ||||||
|  |                     throw err; | ||||||
|  |                   } | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |             await GeneralUtils.sleep(2000); // let things settle
 | ||||||
|  |             break; | ||||||
|  |           default: | ||||||
|  |             throw new GrpcError( | ||||||
|  |               grpc.status.FAILED_PRECONDITION, | ||||||
|  |               `invalid configuration: unknown shareStrategy ${this.options.smb.shareStrategy}` | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  | 
 | ||||||
|       case "zfs-generic-iscsi": |       case "zfs-generic-iscsi": | ||||||
|         let basename; |         let basename; | ||||||
|         let iscsiName; |         let iscsiName; | ||||||
|  | @ -307,8 +406,12 @@ create /backstores/block/${iscsiName} | ||||||
|         switch (this.options.iscsi.shareStrategy) { |         switch (this.options.iscsi.shareStrategy) { | ||||||
|           case "targetCli": |           case "targetCli": | ||||||
|             basename = this.options.iscsi.shareStrategyTargetCli.basename; |             basename = this.options.iscsi.shareStrategyTargetCli.basename; | ||||||
|             response = await this.targetCliCommand( |             await GeneralUtils.retry( | ||||||
|               ` |               3, | ||||||
|  |               2000, | ||||||
|  |               async () => { | ||||||
|  |                 await this.targetCliCommand( | ||||||
|  |                   ` | ||||||
| # delete target | # delete target | ||||||
| cd /iscsi | cd /iscsi | ||||||
| delete ${basename}:${iscsiName} | delete ${basename}:${iscsiName} | ||||||
|  | @ -317,7 +420,18 @@ delete ${basename}:${iscsiName} | ||||||
| cd /backstores/block | cd /backstores/block | ||||||
| delete ${iscsiName} | delete ${iscsiName} | ||||||
| ` | ` | ||||||
|  |                 ); | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 retryCondition: (err) => { | ||||||
|  |                   if (err.stdout && err.stdout.includes("Ran out of input")) { | ||||||
|  |                     return true; | ||||||
|  |                   } | ||||||
|  |                   return false; | ||||||
|  |                 }, | ||||||
|  |               } | ||||||
|             ); |             ); | ||||||
|  | 
 | ||||||
|             break; |             break; | ||||||
|           default: |           default: | ||||||
|             break; |             break; | ||||||
|  | @ -362,19 +476,19 @@ delete ${iscsiName} | ||||||
| 
 | 
 | ||||||
|     let command = "sh"; |     let command = "sh"; | ||||||
|     let args = ["-c"]; |     let args = ["-c"]; | ||||||
|     let taregetCliCommand = []; |  | ||||||
|     taregetCliCommand.push(`echo "${data}"`.trim()); |  | ||||||
|     taregetCliCommand.push("|"); |  | ||||||
|     taregetCliCommand.push("targetcli"); |  | ||||||
| 
 | 
 | ||||||
|  |     let targetCliArgs = ["targetcli"]; | ||||||
|     if ( |     if ( | ||||||
|       _.get(this.options, "iscsi.shareStrategyTargetCli.sudoEnabled", false) |       _.get(this.options, "iscsi.shareStrategyTargetCli.sudoEnabled", false) | ||||||
|     ) { |     ) { | ||||||
|       command = "sudo"; |       targetCliArgs.unshift("sudo"); | ||||||
|       args.unshift("sh"); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     args.push("'" + taregetCliCommand.join(" ") + "'"); |     let targetCliCommand = []; | ||||||
|  |     targetCliCommand.push(`echo "${data}"`.trim()); | ||||||
|  |     targetCliCommand.push("|"); | ||||||
|  |     targetCliCommand.push(targetCliArgs.join(" ")); | ||||||
|  |     args.push("'" + targetCliCommand.join(" ") + "'"); | ||||||
| 
 | 
 | ||||||
|     let logCommandTmp = command + " " + args.join(" "); |     let logCommandTmp = command + " " + args.join(" "); | ||||||
|     let logCommand = ""; |     let logCommand = ""; | ||||||
|  | @ -405,12 +519,12 @@ delete ${iscsiName} | ||||||
|       execClient.buildCommand(command, args), |       execClient.buildCommand(command, args), | ||||||
|       options |       options | ||||||
|     ); |     ); | ||||||
|     if (response.code != 0) { |  | ||||||
|       throw new Error(JSON.stringify(response)); |  | ||||||
|     } |  | ||||||
|     driver.ctx.logger.verbose( |     driver.ctx.logger.verbose( | ||||||
|       "TargetCLI response: " + JSON.stringify(response) |       "TargetCLI response: " + JSON.stringify(response) | ||||||
|     ); |     ); | ||||||
|  |     if (response.code != 0) { | ||||||
|  |       throw response; | ||||||
|  |     } | ||||||
|     return response; |     return response; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| const _ = require("lodash"); | const _ = require("lodash"); | ||||||
| const { ControllerZfsBaseDriver } = require("../controller-zfs"); | const { ControllerZfsBaseDriver } = require("../controller-zfs"); | ||||||
| const { GrpcError, grpc } = require("../../utils/grpc"); | const { GrpcError, grpc } = require("../../utils/grpc"); | ||||||
|  | const GeneralUtils = require("../../utils/general"); | ||||||
| const LocalCliExecClient = require("./exec").LocalCliClient; | const LocalCliExecClient = require("./exec").LocalCliClient; | ||||||
| const registry = require("../../utils/registry"); | const registry = require("../../utils/registry"); | ||||||
| const { Zetabyte } = require("../../utils/zfs"); | const { Zetabyte } = require("../../utils/zfs"); | ||||||
|  | @ -95,7 +96,7 @@ class ControllerZfsLocalDriver extends ControllerZfsBaseDriver { | ||||||
|       case "filesystem": |       case "filesystem": | ||||||
|         return ["zfs"]; |         return ["zfs"]; | ||||||
|       case "volume": |       case "volume": | ||||||
|         return ["btrfs", "ext3", "ext4", "ext4dev", "xfs"]; |         return GeneralUtils.default_supported_block_filesystems(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| const _ = require("lodash"); | const _ = require("lodash"); | ||||||
| const { CsiBaseDriver } = require("../index"); | const { CsiBaseDriver } = require("../index"); | ||||||
| const { GrpcError, grpc } = require("../../utils/grpc"); | const { GrpcError, grpc } = require("../../utils/grpc"); | ||||||
| const sleep = require("../../utils/general").sleep; | const GeneralUtils = require("../../utils/general"); | ||||||
| const getLargestNumber = require("../../utils/general").getLargestNumber; | const getLargestNumber = require("../../utils/general").getLargestNumber; | ||||||
| 
 | 
 | ||||||
| const Handlebars = require("handlebars"); | const Handlebars = require("handlebars"); | ||||||
|  | @ -201,9 +201,9 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { | ||||||
|     const driverZfsResourceType = this.getDriverZfsResourceType(); |     const driverZfsResourceType = this.getDriverZfsResourceType(); | ||||||
|     switch (driverZfsResourceType) { |     switch (driverZfsResourceType) { | ||||||
|       case "filesystem": |       case "filesystem": | ||||||
|         return ["nfs", "cifs"]; |         return GeneralUtils.default_supported_file_filesystems(); | ||||||
|       case "volume": |       case "volume": | ||||||
|         return ["btrfs", "ext3", "ext4", "ext4dev", "xfs"]; |         return GeneralUtils.default_supported_block_filesystems(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -620,6 +620,7 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { | ||||||
|     let snapshotParentDatasetName = this.getDetachedSnapshotParentDatasetName(); |     let snapshotParentDatasetName = this.getDetachedSnapshotParentDatasetName(); | ||||||
|     let zvolBlocksize = this.options.zfs.zvolBlocksize || "16K"; |     let zvolBlocksize = this.options.zfs.zvolBlocksize || "16K"; | ||||||
|     let name = call.request.name; |     let name = call.request.name; | ||||||
|  |     let volume_id = await driver.getVolumeIdFromName(name); | ||||||
|     let volume_content_source = call.request.volume_content_source; |     let volume_content_source = call.request.volume_content_source; | ||||||
| 
 | 
 | ||||||
|     if (!datasetParentName) { |     if (!datasetParentName) { | ||||||
|  | @ -710,7 +711,7 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { | ||||||
|      * NOTE: avoid the urge to templatize this given the name length limits for zvols |      * NOTE: avoid the urge to templatize this given the name length limits for zvols | ||||||
|      * ie: namespace-name may quite easily exceed 58 chars |      * ie: namespace-name may quite easily exceed 58 chars | ||||||
|      */ |      */ | ||||||
|     const datasetName = datasetParentName + "/" + name; |     const datasetName = datasetParentName + "/" + volume_id; | ||||||
| 
 | 
 | ||||||
|     // ensure volumes with the same name being requested a 2nd time but with a different size fails
 |     // ensure volumes with the same name being requested a 2nd time but with a different size fails
 | ||||||
|     try { |     try { | ||||||
|  | @ -862,7 +863,7 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { | ||||||
|               volume_content_source_snapshot_id + |               volume_content_source_snapshot_id + | ||||||
|               "@" + |               "@" + | ||||||
|               VOLUME_SOURCE_CLONE_SNAPSHOT_PREFIX + |               VOLUME_SOURCE_CLONE_SNAPSHOT_PREFIX + | ||||||
|               name; |               volume_id; | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           driver.ctx.logger.debug("full snapshot name: %s", fullSnapshotName); |           driver.ctx.logger.debug("full snapshot name: %s", fullSnapshotName); | ||||||
|  | @ -909,6 +910,12 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { | ||||||
|             }); |             }); | ||||||
|           } else { |           } else { | ||||||
|             try { |             try { | ||||||
|  |               // remove readonly/undesired props
 | ||||||
|  |               let cloneProperties = volumeProperties; | ||||||
|  |               delete cloneProperties["aclmode"]; | ||||||
|  |               delete cloneProperties["aclinherit"]; | ||||||
|  |               delete cloneProperties["acltype"]; | ||||||
|  |               delete cloneProperties["casesensitivity"]; | ||||||
|               response = await zb.zfs.clone(fullSnapshotName, datasetName, { |               response = await zb.zfs.clone(fullSnapshotName, datasetName, { | ||||||
|                 properties: volumeProperties, |                 properties: volumeProperties, | ||||||
|               }); |               }); | ||||||
|  | @ -971,7 +978,7 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { | ||||||
|             volume_content_source_volume_id + |             volume_content_source_volume_id + | ||||||
|             "@" + |             "@" + | ||||||
|             VOLUME_SOURCE_CLONE_SNAPSHOT_PREFIX + |             VOLUME_SOURCE_CLONE_SNAPSHOT_PREFIX + | ||||||
|             name; |             volume_id; | ||||||
| 
 | 
 | ||||||
|           driver.ctx.logger.debug("full snapshot name: %s", fullSnapshotName); |           driver.ctx.logger.debug("full snapshot name: %s", fullSnapshotName); | ||||||
| 
 | 
 | ||||||
|  | @ -1024,9 +1031,15 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { | ||||||
|           } else { |           } else { | ||||||
|             // create clone
 |             // create clone
 | ||||||
|             // zfs origin property contains parent info, ie: pool0/k8s/test/PVC-111@clone-test
 |             // zfs origin property contains parent info, ie: pool0/k8s/test/PVC-111@clone-test
 | ||||||
|  |             // remove readonly/undesired props
 | ||||||
|  |             let cloneProperties = volumeProperties; | ||||||
|  |             delete cloneProperties["aclmode"]; | ||||||
|  |             delete cloneProperties["aclinherit"]; | ||||||
|  |             delete cloneProperties["acltype"]; | ||||||
|  |             delete cloneProperties["casesensitivity"]; | ||||||
|             try { |             try { | ||||||
|               response = await zb.zfs.clone(fullSnapshotName, datasetName, { |               response = await zb.zfs.clone(fullSnapshotName, datasetName, { | ||||||
|                 properties: volumeProperties, |                 properties: cloneProperties, | ||||||
|               }); |               }); | ||||||
|             } catch (err) { |             } catch (err) { | ||||||
|               if (err.toString().includes("dataset does not exist")) { |               if (err.toString().includes("dataset does not exist")) { | ||||||
|  | @ -1128,8 +1141,13 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { | ||||||
|         // TODO: this is unsfafe approach, make it better
 |         // TODO: this is unsfafe approach, make it better
 | ||||||
|         // probably could see if ^-.*\s and split and then shell escape
 |         // probably could see if ^-.*\s and split and then shell escape
 | ||||||
|         if (this.options.zfs.datasetPermissionsAcls) { |         if (this.options.zfs.datasetPermissionsAcls) { | ||||||
|  |           let aclBinary = _.get( | ||||||
|  |             driver.options, | ||||||
|  |             "zfs.datasetPermissionsAclsBinary", | ||||||
|  |             "setfacl" | ||||||
|  |           ); | ||||||
|           for (const acl of this.options.zfs.datasetPermissionsAcls) { |           for (const acl of this.options.zfs.datasetPermissionsAcls) { | ||||||
|             command = execClient.buildCommand("setfacl", [ |             command = execClient.buildCommand(aclBinary, [ | ||||||
|               acl, |               acl, | ||||||
|               properties.mountpoint.value, |               properties.mountpoint.value, | ||||||
|             ]); |             ]); | ||||||
|  | @ -1147,7 +1165,6 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         break; |         break; | ||||||
|       case "volume": |       case "volume": | ||||||
|         // set properties
 |         // set properties
 | ||||||
|  | @ -1191,7 +1208,7 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { | ||||||
| 
 | 
 | ||||||
|     const res = { |     const res = { | ||||||
|       volume: { |       volume: { | ||||||
|         volume_id: name, |         volume_id, | ||||||
|         //capacity_bytes: capacity_bytes, // kubernetes currently pukes if capacity is returned as 0
 |         //capacity_bytes: capacity_bytes, // kubernetes currently pukes if capacity is returned as 0
 | ||||||
|         capacity_bytes: |         capacity_bytes: | ||||||
|           this.options.zfs.datasetEnableQuotas || |           this.options.zfs.datasetEnableQuotas || | ||||||
|  | @ -1301,27 +1318,24 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { | ||||||
|     // NOTE: -R will recursively delete items + dependent filesets
 |     // NOTE: -R will recursively delete items + dependent filesets
 | ||||||
|     // delete dataset
 |     // delete dataset
 | ||||||
|     try { |     try { | ||||||
|       let max_tries = 5; |       await GeneralUtils.retry( | ||||||
|       let sleep_time = 3000; |         12, | ||||||
|       let current_try = 1; |         5000, | ||||||
|       let success = false; |         async () => { | ||||||
|       while (!success && current_try <= max_tries) { |  | ||||||
|         try { |  | ||||||
|           await zb.zfs.destroy(datasetName, { recurse: true, force: true }); |           await zb.zfs.destroy(datasetName, { recurse: true, force: true }); | ||||||
|           success = true; |         }, | ||||||
|         } catch (err) { |         { | ||||||
|           if (err.toString().includes("dataset is busy")) { |           retryCondition: (err) => { | ||||||
|             current_try++; |             if ( | ||||||
|             if (current_try > max_tries) { |               err.toString().includes("dataset is busy") || | ||||||
|               throw err; |               err.toString().includes("target is busy") | ||||||
|             } else { |             ) { | ||||||
|               await sleep(sleep_time); |               return true; | ||||||
|             } |             } | ||||||
|           } else { |             return false; | ||||||
|             throw err; |           }, | ||||||
|           } |  | ||||||
|         } |         } | ||||||
|       } |       ); | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       if (err.toString().includes("filesystem has dependent clones")) { |       if (err.toString().includes("filesystem has dependent clones")) { | ||||||
|         throw new GrpcError( |         throw new GrpcError( | ||||||
|  | @ -2190,7 +2204,7 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       // let things settle down
 |       // let things settle down
 | ||||||
|       //await sleep(3000);
 |       //await GneralUtils.sleep(3000);
 | ||||||
|     } else { |     } else { | ||||||
|       try { |       try { | ||||||
|         await zb.zfs.snapshot(fullSnapshotName, { |         await zb.zfs.snapshot(fullSnapshotName, { | ||||||
|  | @ -2198,7 +2212,7 @@ class ControllerZfsBaseDriver extends CsiBaseDriver { | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // let things settle down
 |         // let things settle down
 | ||||||
|         //await sleep(3000);
 |         //await GeneralUtils.sleep(3000);
 | ||||||
|       } catch (err) { |       } catch (err) { | ||||||
|         if (err.toString().includes("dataset does not exist")) { |         if (err.toString().includes("dataset does not exist")) { | ||||||
|           throw new GrpcError( |           throw new GrpcError( | ||||||
|  |  | ||||||
|  | @ -33,6 +33,7 @@ function factory(ctx, options) { | ||||||
|     case "synology-iscsi": |     case "synology-iscsi": | ||||||
|       return new ControllerSynologyDriver(ctx, options); |       return new ControllerSynologyDriver(ctx, options); | ||||||
|     case "zfs-generic-nfs": |     case "zfs-generic-nfs": | ||||||
|  |     case "zfs-generic-smb": | ||||||
|     case "zfs-generic-iscsi": |     case "zfs-generic-iscsi": | ||||||
|       return new ControllerZfsGenericDriver(ctx, options); |       return new ControllerZfsGenericDriver(ctx, options); | ||||||
|     case "zfs-local-dataset": |     case "zfs-local-dataset": | ||||||
|  |  | ||||||
|  | @ -4,9 +4,8 @@ const { CsiBaseDriver } = require("../index"); | ||||||
| const HttpClient = require("./http").Client; | const HttpClient = require("./http").Client; | ||||||
| const TrueNASApiClient = require("./http/api").Api; | const TrueNASApiClient = require("./http/api").Api; | ||||||
| const { Zetabyte } = require("../../utils/zfs"); | const { Zetabyte } = require("../../utils/zfs"); | ||||||
| const getLargestNumber = require("../../utils/general").getLargestNumber; |  | ||||||
| const registry = require("../../utils/registry"); | const registry = require("../../utils/registry"); | ||||||
| const sleep = require("../../utils/general").sleep; | const GeneralUtils = require("../../utils/general"); | ||||||
| 
 | 
 | ||||||
| const Handlebars = require("handlebars"); | const Handlebars = require("handlebars"); | ||||||
| const uuidv4 = require("uuid").v4; | const uuidv4 = require("uuid").v4; | ||||||
|  | @ -262,7 +261,27 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|                   break; |                   break; | ||||||
|               } |               } | ||||||
| 
 | 
 | ||||||
|               response = await httpClient.post("/sharing/nfs", share); |               response = await GeneralUtils.retry( | ||||||
|  |                 3, | ||||||
|  |                 1000, | ||||||
|  |                 async () => { | ||||||
|  |                   return await httpClient.post("/sharing/nfs", share); | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                   retryCondition: (err) => { | ||||||
|  |                     if (err.code == "ECONNRESET") { | ||||||
|  |                       return true; | ||||||
|  |                     } | ||||||
|  |                     if (err.code == "ECONNABORTED") { | ||||||
|  |                       return true; | ||||||
|  |                     } | ||||||
|  |                     if (err.response && err.response.statusCode == 504) { | ||||||
|  |                       return true; | ||||||
|  |                     } | ||||||
|  |                     return false; | ||||||
|  |                   }, | ||||||
|  |                 } | ||||||
|  |               ); | ||||||
| 
 | 
 | ||||||
|               /** |               /** | ||||||
|                * v1 = 201 |                * v1 = 201 | ||||||
|  | @ -483,7 +502,27 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|                   break; |                   break; | ||||||
|               } |               } | ||||||
| 
 | 
 | ||||||
|               response = await httpClient.post(endpoint, share); |               response = await GeneralUtils.retry( | ||||||
|  |                 3, | ||||||
|  |                 1000, | ||||||
|  |                 async () => { | ||||||
|  |                   return await httpClient.post(endpoint, share); | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                   retryCondition: (err) => { | ||||||
|  |                     if (err.code == "ECONNRESET") { | ||||||
|  |                       return true; | ||||||
|  |                     } | ||||||
|  |                     if (err.code == "ECONNABORTED") { | ||||||
|  |                       return true; | ||||||
|  |                     } | ||||||
|  |                     if (err.response && err.response.statusCode == 504) { | ||||||
|  |                       return true; | ||||||
|  |                     } | ||||||
|  |                     return false; | ||||||
|  |                   }, | ||||||
|  |                 } | ||||||
|  |               ); | ||||||
| 
 | 
 | ||||||
|               /** |               /** | ||||||
|                * v1 = 201 |                * v1 = 201 | ||||||
|  | @ -1363,7 +1402,27 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|                 if (deleteAsset) { |                 if (deleteAsset) { | ||||||
|                   response = await httpClient.delete(endpoint); |                   response = await GeneralUtils.retry( | ||||||
|  |                     3, | ||||||
|  |                     1000, | ||||||
|  |                     async () => { | ||||||
|  |                       return await httpClient.delete(endpoint); | ||||||
|  |                     }, | ||||||
|  |                     { | ||||||
|  |                       retryCondition: (err) => { | ||||||
|  |                         if (err.code == "ECONNRESET") { | ||||||
|  |                           return true; | ||||||
|  |                         } | ||||||
|  |                         if (err.code == "ECONNABORTED") { | ||||||
|  |                           return true; | ||||||
|  |                         } | ||||||
|  |                         if (err.response && err.response.statusCode == 504) { | ||||||
|  |                           return true; | ||||||
|  |                         } | ||||||
|  |                         return false; | ||||||
|  |                       }, | ||||||
|  |                     } | ||||||
|  |                   ); | ||||||
| 
 | 
 | ||||||
|                   // returns a 500 if does not exist
 |                   // returns a 500 if does not exist
 | ||||||
|                   // v1 = 204
 |                   // v1 = 204
 | ||||||
|  | @ -1444,12 +1503,35 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|                 if (deleteAsset) { |                 if (deleteAsset) { | ||||||
|                   response = await httpClient.delete(endpoint); |                   response = await GeneralUtils.retry( | ||||||
|  |                     3, | ||||||
|  |                     1000, | ||||||
|  |                     async () => { | ||||||
|  |                       return await httpClient.delete(endpoint); | ||||||
|  |                     }, | ||||||
|  |                     { | ||||||
|  |                       retryCondition: (err) => { | ||||||
|  |                         if (err.code == "ECONNRESET") { | ||||||
|  |                           return true; | ||||||
|  |                         } | ||||||
|  |                         if (err.code == "ECONNABORTED") { | ||||||
|  |                           return true; | ||||||
|  |                         } | ||||||
|  |                         if (err.response && err.response.statusCode == 504) { | ||||||
|  |                           return true; | ||||||
|  |                         } | ||||||
|  |                         return false; | ||||||
|  |                       }, | ||||||
|  |                     } | ||||||
|  |                   ); | ||||||
| 
 | 
 | ||||||
|                   // returns a 500 if does not exist
 |                   // returns a 500 if does not exist
 | ||||||
|                   // v1 = 204
 |                   // v1 = 204
 | ||||||
|                   // v2 = 200
 |                   // v2 = 200
 | ||||||
|                   if (![200, 204].includes(response.statusCode)) { |                   if ( | ||||||
|  |                     ![200, 204].includes(response.statusCode) && | ||||||
|  |                     !JSON.stringify(response.body).includes("does not exist") | ||||||
|  |                   ) { | ||||||
|                     throw new GrpcError( |                     throw new GrpcError( | ||||||
|                       grpc.status.UNKNOWN, |                       grpc.status.UNKNOWN, | ||||||
|                       `received error deleting smb share - share: ${shareId} code: ${ |                       `received error deleting smb share - share: ${shareId} code: ${ | ||||||
|  | @ -1477,7 +1559,7 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|         break; |         break; | ||||||
|       case "iscsi": |       case "iscsi": | ||||||
|         // Delete target
 |         // Delete target
 | ||||||
|         // NOTE: deletting a target inherently deletes associated targetgroup(s) and targettoextent(s)
 |         // NOTE: deleting a target inherently deletes associated targetgroup(s) and targettoextent(s)
 | ||||||
| 
 | 
 | ||||||
|         // Delete extent
 |         // Delete extent
 | ||||||
|         try { |         try { | ||||||
|  | @ -1565,7 +1647,7 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|                       targetId, |                       targetId, | ||||||
|                       retries |                       retries | ||||||
|                     ); |                     ); | ||||||
|                     await sleep(retryWait); |                     await GeneralUtils.sleep(retryWait); | ||||||
|                     response = await httpClient.delete(endpoint); |                     response = await httpClient.delete(endpoint); | ||||||
|                   } |                   } | ||||||
| 
 | 
 | ||||||
|  | @ -1958,7 +2040,7 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|           if (capability.access_type == "mount") { |           if (capability.access_type == "mount") { | ||||||
|             if ( |             if ( | ||||||
|               capability.mount.fs_type && |               capability.mount.fs_type && | ||||||
|               !["btrfs", "ext3", "ext4", "ext4dev", "xfs"].includes( |               !GeneralUtils.default_supported_block_filesystems().includes( | ||||||
|                 capability.mount.fs_type |                 capability.mount.fs_type | ||||||
|               ) |               ) | ||||||
|             ) { |             ) { | ||||||
|  | @ -2025,6 +2107,7 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|    */ |    */ | ||||||
|   async Probe(call) { |   async Probe(call) { | ||||||
|     const driver = this; |     const driver = this; | ||||||
|  |     const httpApiClient = await driver.getTrueNASHttpApiClient(); | ||||||
| 
 | 
 | ||||||
|     if (driver.ctx.args.csiMode.includes("controller")) { |     if (driver.ctx.args.csiMode.includes("controller")) { | ||||||
|       let datasetParentName = this.getVolumeParentDatasetName() + "/"; |       let datasetParentName = this.getVolumeParentDatasetName() + "/"; | ||||||
|  | @ -2039,6 +2122,14 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|           `datasetParentName and detachedSnapshotsDatasetParentName must not overlap` |           `datasetParentName and detachedSnapshotsDatasetParentName must not overlap` | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|  | 
 | ||||||
|  |       if (!(await httpApiClient.getIsScale())) { | ||||||
|  |         throw new GrpcError( | ||||||
|  |           grpc.status.FAILED_PRECONDITION, | ||||||
|  |           `driver is only availalbe with TrueNAS SCALE` | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       return { ready: { value: true } }; |       return { ready: { value: true } }; | ||||||
|     } else { |     } else { | ||||||
|       return { ready: { value: true } }; |       return { ready: { value: true } }; | ||||||
|  | @ -2066,6 +2157,7 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|     let snapshotParentDatasetName = this.getDetachedSnapshotParentDatasetName(); |     let snapshotParentDatasetName = this.getDetachedSnapshotParentDatasetName(); | ||||||
|     let zvolBlocksize = this.options.zfs.zvolBlocksize || "16K"; |     let zvolBlocksize = this.options.zfs.zvolBlocksize || "16K"; | ||||||
|     let name = call.request.name; |     let name = call.request.name; | ||||||
|  |     let volume_id = await driver.getVolumeIdFromName(name); | ||||||
|     let volume_content_source = call.request.volume_content_source; |     let volume_content_source = call.request.volume_content_source; | ||||||
|     let minimum_volume_size = await driver.getMinimumVolumeSize(); |     let minimum_volume_size = await driver.getMinimumVolumeSize(); | ||||||
|     let default_required_bytes = 1073741824; |     let default_required_bytes = 1073741824; | ||||||
|  | @ -2171,7 +2263,7 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|      * NOTE: avoid the urge to templatize this given the name length limits for zvols |      * NOTE: avoid the urge to templatize this given the name length limits for zvols | ||||||
|      * ie: namespace-name may quite easily exceed 58 chars |      * ie: namespace-name may quite easily exceed 58 chars | ||||||
|      */ |      */ | ||||||
|     const datasetName = datasetParentName + "/" + name; |     const datasetName = datasetParentName + "/" + volume_id; | ||||||
| 
 | 
 | ||||||
|     // ensure volumes with the same name being requested a 2nd time but with a different size fails
 |     // ensure volumes with the same name being requested a 2nd time but with a different size fails
 | ||||||
|     try { |     try { | ||||||
|  | @ -2326,7 +2418,7 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|               volume_content_source_snapshot_id + |               volume_content_source_snapshot_id + | ||||||
|               "@" + |               "@" + | ||||||
|               VOLUME_SOURCE_CLONE_SNAPSHOT_PREFIX + |               VOLUME_SOURCE_CLONE_SNAPSHOT_PREFIX + | ||||||
|               name; |               volume_id; | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           driver.ctx.logger.debug("full snapshot name: %s", fullSnapshotName); |           driver.ctx.logger.debug("full snapshot name: %s", fullSnapshotName); | ||||||
|  | @ -2378,7 +2470,7 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|               ) { |               ) { | ||||||
|                 job = await httpApiClient.CoreGetJobs({ id: job_id }); |                 job = await httpApiClient.CoreGetJobs({ id: job_id }); | ||||||
|                 job = job[0]; |                 job = job[0]; | ||||||
|                 await sleep(3000); |                 await GeneralUtils.sleep(3000); | ||||||
|               } |               } | ||||||
| 
 | 
 | ||||||
|               job.error = job.error || ""; |               job.error = job.error || ""; | ||||||
|  | @ -2488,7 +2580,7 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|             volume_content_source_volume_id + |             volume_content_source_volume_id + | ||||||
|             "@" + |             "@" + | ||||||
|             VOLUME_SOURCE_CLONE_SNAPSHOT_PREFIX + |             VOLUME_SOURCE_CLONE_SNAPSHOT_PREFIX + | ||||||
|             name; |             volume_id; | ||||||
| 
 | 
 | ||||||
|           driver.ctx.logger.debug("full snapshot name: %s", fullSnapshotName); |           driver.ctx.logger.debug("full snapshot name: %s", fullSnapshotName); | ||||||
| 
 | 
 | ||||||
|  | @ -2538,7 +2630,7 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|               ) { |               ) { | ||||||
|                 job = await httpApiClient.CoreGetJobs({ id: job_id }); |                 job = await httpApiClient.CoreGetJobs({ id: job_id }); | ||||||
|                 job = job[0]; |                 job = job[0]; | ||||||
|                 await sleep(3000); |                 await GeneralUtils.sleep(3000); | ||||||
|               } |               } | ||||||
| 
 | 
 | ||||||
|               job.error = job.error || ""; |               job.error = job.error || ""; | ||||||
|  | @ -2626,6 +2718,9 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|         volsize: driverZfsResourceType == "volume" ? capacity_bytes : undefined, |         volsize: driverZfsResourceType == "volume" ? capacity_bytes : undefined, | ||||||
|         sparse: driverZfsResourceType == "volume" ? sparse : undefined, |         sparse: driverZfsResourceType == "volume" ? sparse : undefined, | ||||||
|         create_ancestors: true, |         create_ancestors: true, | ||||||
|  |         share_type: driver.getDriverShareType().includes("smb") | ||||||
|  |           ? "SMB" | ||||||
|  |           : "GENERIC", | ||||||
|         user_properties: httpApiClient.getPropertiesKeyValueArray( |         user_properties: httpApiClient.getPropertiesKeyValueArray( | ||||||
|           httpApiClient.getUserProperties(volumeProperties) |           httpApiClient.getUserProperties(volumeProperties) | ||||||
|         ), |         ), | ||||||
|  | @ -2721,7 +2816,18 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (setPerms) { |         if (setPerms) { | ||||||
|           await httpApiClient.FilesystemSetperm(perms); |           response = await httpApiClient.FilesystemSetperm(perms); | ||||||
|  |           await httpApiClient.CoreWaitForJob(response, 30); | ||||||
|  |           // SetPerm does not alter ownership with extended ACLs
 | ||||||
|  |           // run this in addition just for good measure
 | ||||||
|  |           if (perms.uid || perms.gid) { | ||||||
|  |             response = await httpApiClient.FilesystemChown({ | ||||||
|  |               path: perms.path, | ||||||
|  |               uid: perms.uid, | ||||||
|  |               gid: perms.gid, | ||||||
|  |             }); | ||||||
|  |             await httpApiClient.CoreWaitForJob(response, 30); | ||||||
|  |           } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // set acls
 |         // set acls
 | ||||||
|  | @ -2777,7 +2883,7 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
| 
 | 
 | ||||||
|     const res = { |     const res = { | ||||||
|       volume: { |       volume: { | ||||||
|         volume_id: name, |         volume_id, | ||||||
|         //capacity_bytes: capacity_bytes, // kubernetes currently pukes if capacity is returned as 0
 |         //capacity_bytes: capacity_bytes, // kubernetes currently pukes if capacity is returned as 0
 | ||||||
|         capacity_bytes: |         capacity_bytes: | ||||||
|           this.options.zfs.datasetEnableQuotas || |           this.options.zfs.datasetEnableQuotas || | ||||||
|  | @ -3649,7 +3755,10 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|           // so we must be cognizant and use the highest possible value here
 |           // so we must be cognizant and use the highest possible value here
 | ||||||
|           // note that whatever value is returned here can/will essentially impact the refquota
 |           // note that whatever value is returned here can/will essentially impact the refquota
 | ||||||
|           // value of a derived volume
 |           // value of a derived volume
 | ||||||
|           size_bytes = getLargestNumber(row.referenced, row.logicalreferenced); |           size_bytes = GeneralUtils.getLargestNumber( | ||||||
|  |             row.referenced, | ||||||
|  |             row.logicalreferenced | ||||||
|  |           ); | ||||||
|         } else { |         } else { | ||||||
|           // get the size of the parent volume
 |           // get the size of the parent volume
 | ||||||
|           size_bytes = row.volsize; |           size_bytes = row.volsize; | ||||||
|  | @ -3930,7 +4039,7 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|         while (!job || !["SUCCESS", "ABORTED", "FAILED"].includes(job.state)) { |         while (!job || !["SUCCESS", "ABORTED", "FAILED"].includes(job.state)) { | ||||||
|           job = await httpApiClient.CoreGetJobs({ id: job_id }); |           job = await httpApiClient.CoreGetJobs({ id: job_id }); | ||||||
|           job = job[0]; |           job = job[0]; | ||||||
|           await sleep(3000); |           await GeneralUtils.sleep(3000); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         job.error = job.error || ""; |         job.error = job.error || ""; | ||||||
|  | @ -4045,7 +4154,7 @@ class FreeNASApiDriver extends CsiBaseDriver { | ||||||
|       // so we must be cognizant and use the highest possible value here
 |       // so we must be cognizant and use the highest possible value here
 | ||||||
|       // note that whatever value is returned here can/will essentially impact the refquota
 |       // note that whatever value is returned here can/will essentially impact the refquota
 | ||||||
|       // value of a derived volume
 |       // value of a derived volume
 | ||||||
|       size_bytes = getLargestNumber( |       size_bytes = GeneralUtils.getLargestNumber( | ||||||
|         properties.referenced.rawvalue, |         properties.referenced.rawvalue, | ||||||
|         properties.logicalreferenced.rawvalue |         properties.logicalreferenced.rawvalue | ||||||
|         // TODO: perhaps include minimum volume size here?
 |         // TODO: perhaps include minimum volume size here?
 | ||||||
|  |  | ||||||
|  | @ -681,7 +681,13 @@ class Api { | ||||||
|     throw new Error(JSON.stringify(response.body)); |     throw new Error(JSON.stringify(response.body)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async CoreWaitForJob(job_id, timeout = 0) { |   /** | ||||||
|  |    * | ||||||
|  |    * @param {*} job_id | ||||||
|  |    * @param {*} timeout in seconds | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|  |   async CoreWaitForJob(job_id, timeout = 0, check_interval = 3000) { | ||||||
|     if (!job_id) { |     if (!job_id) { | ||||||
|       throw new Error("invalid job_id"); |       throw new Error("invalid job_id"); | ||||||
|     } |     } | ||||||
|  | @ -692,16 +698,17 @@ class Api { | ||||||
|     let job; |     let job; | ||||||
| 
 | 
 | ||||||
|     // wait for job to finish
 |     // wait for job to finish
 | ||||||
|     while (!job || !["SUCCESS", "ABORTED", "FAILED"].includes(job.state)) { |     do { | ||||||
|  |       if (job) { | ||||||
|  |         await sleep(check_interval); | ||||||
|  |       } | ||||||
|       job = await this.CoreGetJobs({ id: job_id }); |       job = await this.CoreGetJobs({ id: job_id }); | ||||||
|       job = job[0]; |       job = job[0]; | ||||||
|       await sleep(3000); |  | ||||||
| 
 |  | ||||||
|       currentTime = Date.now() / 1000; |       currentTime = Date.now() / 1000; | ||||||
|       if (timeout > 0 && currentTime > startTime + timeout) { |       if (timeout > 0 && currentTime > startTime + timeout) { | ||||||
|         throw new Error("timeout waiting for job to complete"); |         throw new Error("timeout waiting for job to complete"); | ||||||
|       } |       } | ||||||
|     } |     } while (!["SUCCESS", "ABORTED", "FAILED"].includes(job.state)); | ||||||
| 
 | 
 | ||||||
|     return job; |     return job; | ||||||
|   } |   } | ||||||
|  | @ -754,7 +761,38 @@ class Api { | ||||||
|     response = await httpClient.post(endpoint, data); |     response = await httpClient.post(endpoint, data); | ||||||
| 
 | 
 | ||||||
|     if (response.statusCode == 200) { |     if (response.statusCode == 200) { | ||||||
|       return; |       return response.body; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     throw new Error(JSON.stringify(response.body)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * | ||||||
|  |    * @param {*} data | ||||||
|  |    */ | ||||||
|  |   async FilesystemChown(data) { | ||||||
|  |     /* | ||||||
|  |         { | ||||||
|  |           "path": "string", | ||||||
|  |           "uid": 0, | ||||||
|  |           "gid": 0, | ||||||
|  |           "options": { | ||||||
|  |             "recursive": false, | ||||||
|  |             "traverse": false | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       */ | ||||||
|  | 
 | ||||||
|  |     const httpClient = await this.getHttpClient(false); | ||||||
|  |     let response; | ||||||
|  |     let endpoint; | ||||||
|  | 
 | ||||||
|  |     endpoint = `/filesystem/chown`; | ||||||
|  |     response = await httpClient.post(endpoint, data); | ||||||
|  | 
 | ||||||
|  |     if (response.statusCode == 200) { | ||||||
|  |       return response.body; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     throw new Error(JSON.stringify(response.body)); |     throw new Error(JSON.stringify(response.body)); | ||||||
|  |  | ||||||
|  | @ -86,6 +86,12 @@ class Client { | ||||||
|       httpAgent: this.getHttpAgent(), |       httpAgent: this.getHttpAgent(), | ||||||
|       httpsAgent: this.getHttpsAgent(), |       httpsAgent: this.getHttpsAgent(), | ||||||
|       timeout: 60 * 1000, |       timeout: 60 * 1000, | ||||||
|  |       validateStatus: function (status) { | ||||||
|  |         if (status >= 500) { | ||||||
|  |           return false; | ||||||
|  |         } | ||||||
|  |         return true; | ||||||
|  |       }, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     if (client.options.apiKey) { |     if (client.options.apiKey) { | ||||||
|  | @ -122,10 +128,17 @@ class Client { | ||||||
|       _.set(options, prop, "redacted"); |       _.set(options, prop, "redacted"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     delete options.httpAgent; | ||||||
|  |     delete options.httpsAgent; | ||||||
|  | 
 | ||||||
|     this.logger.debug("FREENAS HTTP REQUEST: " + stringify(options)); |     this.logger.debug("FREENAS HTTP REQUEST: " + stringify(options)); | ||||||
|     this.logger.debug("FREENAS HTTP ERROR: " + error); |     this.logger.debug("FREENAS HTTP ERROR: " + error); | ||||||
|     this.logger.debug("FREENAS HTTP STATUS: " + response.statusCode); |     this.logger.debug( | ||||||
|     this.logger.debug("FREENAS HTTP HEADERS: " + stringify(response.headers)); |       "FREENAS HTTP STATUS: " + _.get(response, "statusCode", "") | ||||||
|  |     ); | ||||||
|  |     this.logger.debug( | ||||||
|  |       "FREENAS HTTP HEADERS: " + stringify(_.get(response, "headers", "")) | ||||||
|  |     ); | ||||||
|     this.logger.debug("FREENAS HTTP BODY: " + stringify(body)); |     this.logger.debug("FREENAS HTTP BODY: " + stringify(body)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,8 +4,9 @@ const { GrpcError, grpc } = require("../../utils/grpc"); | ||||||
| const registry = require("../../utils/registry"); | const registry = require("../../utils/registry"); | ||||||
| const SshClient = require("../../utils/ssh").SshClient; | const SshClient = require("../../utils/ssh").SshClient; | ||||||
| const HttpClient = require("./http").Client; | const HttpClient = require("./http").Client; | ||||||
|  | const TrueNASApiClient = require("./http/api").Api; | ||||||
| const { Zetabyte, ZfsSshProcessManager } = require("../../utils/zfs"); | const { Zetabyte, ZfsSshProcessManager } = require("../../utils/zfs"); | ||||||
| const { sleep, stringify } = require("../../utils/general"); | const GeneralUtils = require("../../utils/general"); | ||||||
| 
 | 
 | ||||||
| const Handlebars = require("handlebars"); | const Handlebars = require("handlebars"); | ||||||
| 
 | 
 | ||||||
|  | @ -112,6 +113,13 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver { | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   async getTrueNASHttpApiClient() { | ||||||
|  |     return registry.getAsync(`${__REGISTRY_NS__}:api_client`, async () => { | ||||||
|  |       const httpClient = await this.getHttpClient(); | ||||||
|  |       return new TrueNASApiClient(httpClient, this.ctx.cache); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   getDriverShareType() { |   getDriverShareType() { | ||||||
|     switch (this.options.driver) { |     switch (this.options.driver) { | ||||||
|       case "freenas-nfs": |       case "freenas-nfs": | ||||||
|  | @ -300,7 +308,27 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver { | ||||||
|                   break; |                   break; | ||||||
|               } |               } | ||||||
| 
 | 
 | ||||||
|               response = await httpClient.post("/sharing/nfs", share); |               response = await GeneralUtils.retry( | ||||||
|  |                 3, | ||||||
|  |                 1000, | ||||||
|  |                 async () => { | ||||||
|  |                   return await httpClient.post("/sharing/nfs", share); | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                   retryCondition: (err) => { | ||||||
|  |                     if (err.code == "ECONNRESET") { | ||||||
|  |                       return true; | ||||||
|  |                     } | ||||||
|  |                     if (err.code == "ECONNABORTED") { | ||||||
|  |                       return true; | ||||||
|  |                     } | ||||||
|  |                     if (err.response && err.response.statusCode == 504) { | ||||||
|  |                       return true; | ||||||
|  |                     } | ||||||
|  |                     return false; | ||||||
|  |                   }, | ||||||
|  |                 } | ||||||
|  |               ); | ||||||
| 
 | 
 | ||||||
|               /** |               /** | ||||||
|                * v1 = 201 |                * v1 = 201 | ||||||
|  | @ -521,7 +549,27 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver { | ||||||
|                   break; |                   break; | ||||||
|               } |               } | ||||||
| 
 | 
 | ||||||
|               response = await httpClient.post(endpoint, share); |               response = await GeneralUtils.retry( | ||||||
|  |                 3, | ||||||
|  |                 1000, | ||||||
|  |                 async () => { | ||||||
|  |                   return await httpClient.post(endpoint, share); | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                   retryCondition: (err) => { | ||||||
|  |                     if (err.code == "ECONNRESET") { | ||||||
|  |                       return true; | ||||||
|  |                     } | ||||||
|  |                     if (err.code == "ECONNABORTED") { | ||||||
|  |                       return true; | ||||||
|  |                     } | ||||||
|  |                     if (err.response && err.response.statusCode == 504) { | ||||||
|  |                       return true; | ||||||
|  |                     } | ||||||
|  |                     return false; | ||||||
|  |                   }, | ||||||
|  |                 } | ||||||
|  |               ); | ||||||
| 
 | 
 | ||||||
|               /** |               /** | ||||||
|                * v1 = 201 |                * v1 = 201 | ||||||
|  | @ -1402,7 +1450,27 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver { | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|                 if (deleteAsset) { |                 if (deleteAsset) { | ||||||
|                   response = await httpClient.delete(endpoint); |                   response = await GeneralUtils.retry( | ||||||
|  |                     3, | ||||||
|  |                     1000, | ||||||
|  |                     async () => { | ||||||
|  |                       return await httpClient.delete(endpoint); | ||||||
|  |                     }, | ||||||
|  |                     { | ||||||
|  |                       retryCondition: (err) => { | ||||||
|  |                         if (err.code == "ECONNRESET") { | ||||||
|  |                           return true; | ||||||
|  |                         } | ||||||
|  |                         if (err.code == "ECONNABORTED") { | ||||||
|  |                           return true; | ||||||
|  |                         } | ||||||
|  |                         if (err.response && err.response.statusCode == 504) { | ||||||
|  |                           return true; | ||||||
|  |                         } | ||||||
|  |                         return false; | ||||||
|  |                       }, | ||||||
|  |                     } | ||||||
|  |                   ); | ||||||
| 
 | 
 | ||||||
|                   // returns a 500 if does not exist
 |                   // returns a 500 if does not exist
 | ||||||
|                   // v1 = 204
 |                   // v1 = 204
 | ||||||
|  | @ -1484,12 +1552,35 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver { | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|                 if (deleteAsset) { |                 if (deleteAsset) { | ||||||
|                   response = await httpClient.delete(endpoint); |                   response = await GeneralUtils.retry( | ||||||
|  |                     3, | ||||||
|  |                     1000, | ||||||
|  |                     async () => { | ||||||
|  |                       return await httpClient.delete(endpoint); | ||||||
|  |                     }, | ||||||
|  |                     { | ||||||
|  |                       retryCondition: (err) => { | ||||||
|  |                         if (err.code == "ECONNRESET") { | ||||||
|  |                           return true; | ||||||
|  |                         } | ||||||
|  |                         if (err.code == "ECONNABORTED") { | ||||||
|  |                           return true; | ||||||
|  |                         } | ||||||
|  |                         if (err.response && err.response.statusCode == 504) { | ||||||
|  |                           return true; | ||||||
|  |                         } | ||||||
|  |                         return false; | ||||||
|  |                       }, | ||||||
|  |                     } | ||||||
|  |                   ); | ||||||
| 
 | 
 | ||||||
|                   // returns a 500 if does not exist
 |                   // returns a 500 if does not exist
 | ||||||
|                   // v1 = 204
 |                   // v1 = 204
 | ||||||
|                   // v2 = 200
 |                   // v2 = 200
 | ||||||
|                   if (![200, 204].includes(response.statusCode)) { |                   if ( | ||||||
|  |                     ![200, 204].includes(response.statusCode) && | ||||||
|  |                     !JSON.stringify(response.body).includes("does not exist") | ||||||
|  |                   ) { | ||||||
|                     throw new GrpcError( |                     throw new GrpcError( | ||||||
|                       grpc.status.UNKNOWN, |                       grpc.status.UNKNOWN, | ||||||
|                       `received error deleting smb share - share: ${shareId} code: ${ |                       `received error deleting smb share - share: ${shareId} code: ${ | ||||||
|  | @ -1606,7 +1697,7 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver { | ||||||
|                       targetId, |                       targetId, | ||||||
|                       retries |                       retries | ||||||
|                     ); |                     ); | ||||||
|                     await sleep(retryWait); |                     await GeneralUtils.sleep(retryWait); | ||||||
|                     response = await httpClient.delete(endpoint); |                     response = await httpClient.delete(endpoint); | ||||||
|                   } |                   } | ||||||
| 
 | 
 | ||||||
|  | @ -1716,6 +1807,7 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver { | ||||||
|   async setFilesystemMode(path, mode) { |   async setFilesystemMode(path, mode) { | ||||||
|     const httpClient = await this.getHttpClient(); |     const httpClient = await this.getHttpClient(); | ||||||
|     const apiVersion = httpClient.getApiVersion(); |     const apiVersion = httpClient.getApiVersion(); | ||||||
|  |     const httpApiClient = await this.getTrueNASHttpApiClient(); | ||||||
| 
 | 
 | ||||||
|     switch (apiVersion) { |     switch (apiVersion) { | ||||||
|       case 1: |       case 1: | ||||||
|  | @ -1747,6 +1839,7 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver { | ||||||
|         response = await httpClient.post(endpoint, perms); |         response = await httpClient.post(endpoint, perms); | ||||||
| 
 | 
 | ||||||
|         if (response.statusCode == 200) { |         if (response.statusCode == 200) { | ||||||
|  |           await httpApiClient.CoreWaitForJob(response.body, 30); | ||||||
|           return; |           return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -1764,6 +1857,7 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver { | ||||||
|   async setFilesystemOwnership(path, user = false, group = false) { |   async setFilesystemOwnership(path, user = false, group = false) { | ||||||
|     const httpClient = await this.getHttpClient(); |     const httpClient = await this.getHttpClient(); | ||||||
|     const apiVersion = httpClient.getApiVersion(); |     const apiVersion = httpClient.getApiVersion(); | ||||||
|  |     const httpApiClient = await this.getTrueNASHttpApiClient(); | ||||||
| 
 | 
 | ||||||
|     if (user === false || typeof user == "undefined" || user === null) { |     if (user === false || typeof user == "undefined" || user === null) { | ||||||
|       user = ""; |       user = ""; | ||||||
|  | @ -1832,6 +1926,7 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver { | ||||||
|         response = await httpClient.post(endpoint, perms); |         response = await httpClient.post(endpoint, perms); | ||||||
| 
 | 
 | ||||||
|         if (response.statusCode == 200) { |         if (response.statusCode == 200) { | ||||||
|  |           await httpApiClient.CoreWaitForJob(response.body, 30); | ||||||
|           return; |           return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -2122,7 +2217,7 @@ class FreeNASSshDriver extends ControllerZfsBaseDriver { | ||||||
|     // likely bad creds/url
 |     // likely bad creds/url
 | ||||||
|     throw new GrpcError( |     throw new GrpcError( | ||||||
|       grpc.status.UNKNOWN, |       grpc.status.UNKNOWN, | ||||||
|       `FreeNAS error getting system version info: ${stringify({ |       `FreeNAS error getting system version info: ${GeneralUtils.stringify({ | ||||||
|         errors: versionErrors, |         errors: versionErrors, | ||||||
|         responses: versionResponses, |         responses: versionResponses, | ||||||
|       })}` |       })}` | ||||||
|  |  | ||||||
							
								
								
									
										3584
									
								
								src/driver/index.js
								
								
								
								
							
							
						
						
									
										3584
									
								
								src/driver/index.js
								
								
								
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,269 @@ | ||||||
|  | const _ = require("lodash"); | ||||||
|  | const grpc = require("./grpc").grpc; | ||||||
|  | const path = require("path"); | ||||||
|  | const protoLoader = require("@grpc/proto-loader"); | ||||||
|  | 
 | ||||||
|  | const PROTO_BASE_PATH = | ||||||
|  |   path.dirname(path.dirname(__dirname)) + path.sep + "csi_proxy_proto"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * leave connection null as by default the named pipe is derrived | ||||||
|  |  */ | ||||||
|  | const DEFAULT_SERVICES = { | ||||||
|  |   filesystem: { version: "v1", connection: null }, | ||||||
|  |   disk: { version: "v1", connection: null }, | ||||||
|  |   volume: { version: "v1", connection: null }, | ||||||
|  |   smb: { version: "v1", connection: null }, | ||||||
|  |   system: { version: "v1alpha1", connection: null }, | ||||||
|  |   iscsi: { version: "v1alpha2", connection: null }, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | function capitalize(s) { | ||||||
|  |   return s && s[0].toUpperCase() + s.slice(1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class CsiProxyClient { | ||||||
|  |   constructor(options = {}) { | ||||||
|  |     this.clients = {}; | ||||||
|  | 
 | ||||||
|  |     // initialize all clients
 | ||||||
|  |     const services = Object.assign( | ||||||
|  |       {}, | ||||||
|  |       DEFAULT_SERVICES, | ||||||
|  |       options.services || {} | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     const pipePrefix = options.pipe_prefix || "csi-proxy"; | ||||||
|  | 
 | ||||||
|  |     for (const serviceName in services) { | ||||||
|  |       const service = services[serviceName]; | ||||||
|  |       const serviceVersion = | ||||||
|  |         service.version || DEFAULT_SERVICES[serviceName].version; | ||||||
|  |       const serviceConnection = | ||||||
|  |         // HANGS
 | ||||||
|  |         // Http2Session client (38) nghttp2 has 13 bytes to send directly
 | ||||||
|  |         // Http2Session client (38) wants read? 1
 | ||||||
|  |         // Then pipe closes after 60 seconds-ish
 | ||||||
|  |         service.connection || | ||||||
|  |         `unix:////./pipe/${pipePrefix}-${serviceName}-${serviceVersion}`; | ||||||
|  |       // EACCESS
 | ||||||
|  |       //service.connection ||
 | ||||||
|  |       //`unix:///csi/${pipePrefix}-${serviceName}-${serviceVersion}`;
 | ||||||
|  |       //service.connection ||
 | ||||||
|  |       //`unix:///csi/csi.sock.internal`;
 | ||||||
|  | 
 | ||||||
|  |       const PROTO_PATH = `${PROTO_BASE_PATH}\\${serviceName}\\${serviceVersion}\\api.proto`; | ||||||
|  | 
 | ||||||
|  |       const packageDefinition = protoLoader.loadSync(PROTO_PATH, { | ||||||
|  |         keepCase: true, | ||||||
|  |         longs: String, | ||||||
|  |         enums: String, | ||||||
|  |         defaults: true, | ||||||
|  |         oneofs: true, | ||||||
|  |         includeDirs: [__dirname + "/../csi_proxy_proto"], | ||||||
|  |       }); | ||||||
|  |       const protoDescriptor = grpc.loadPackageDefinition(packageDefinition); | ||||||
|  |       const serviceInstance = new protoDescriptor[serviceVersion][ | ||||||
|  |         capitalize(serviceName) | ||||||
|  |       ](serviceConnection, grpc.credentials.createInsecure()); | ||||||
|  |       this.clients[serviceName] = serviceInstance; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async executeRPC(serviceName, methodName, options = {}) { | ||||||
|  |     function rescursivePathFixer(obj) { | ||||||
|  |       for (const k in obj) { | ||||||
|  |         if (typeof obj[k] == "object" && obj[k] !== null) { | ||||||
|  |           rescursivePathFixer(obj[k]); | ||||||
|  |         } else { | ||||||
|  |           if (k.includes("path")) { | ||||||
|  |             obj[k] = obj[k].replaceAll("/", "\\"); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     rescursivePathFixer(options); | ||||||
|  | 
 | ||||||
|  |     const cleansedOptions = JSON.parse(JSON.stringify(options)); | ||||||
|  |     // This function handles arrays and objects
 | ||||||
|  |     function recursiveCleanse(obj) { | ||||||
|  |       for (const k in obj) { | ||||||
|  |         if (typeof obj[k] == "object" && obj[k] !== null) { | ||||||
|  |           recursiveCleanse(obj[k]); | ||||||
|  |         } else { | ||||||
|  |           if ( | ||||||
|  |             k.includes("secret") || | ||||||
|  |             k.includes("username") || | ||||||
|  |             k.includes("password") | ||||||
|  |           ) { | ||||||
|  |             obj[k] = "redacted"; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     recursiveCleanse(cleansedOptions); | ||||||
|  | 
 | ||||||
|  |     console.log( | ||||||
|  |       "csi-proxy request %s/%s - data: %j", | ||||||
|  |       capitalize(serviceName), | ||||||
|  |       methodName, | ||||||
|  |       cleansedOptions | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     return new Promise((resolve, reject) => { | ||||||
|  |       const functionRef = this.clients[serviceName.toLowerCase()][methodName]; | ||||||
|  |       if (!functionRef) { | ||||||
|  |         reject( | ||||||
|  |           new Error( | ||||||
|  |             `missing method ${methodName} on service ${capitalize(serviceName)}` | ||||||
|  |           ) | ||||||
|  |         ); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       this.clients[serviceName.toLowerCase()][methodName]( | ||||||
|  |         options, | ||||||
|  |         (error, data) => { | ||||||
|  |           console.log( | ||||||
|  |             "csi-proxy response %s/%s - error: %j, data: %j", | ||||||
|  |             capitalize(serviceName), | ||||||
|  |             methodName, | ||||||
|  |             error, | ||||||
|  |             data | ||||||
|  |           ); | ||||||
|  | 
 | ||||||
|  |           if (error) { | ||||||
|  |             reject(error); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           resolve(data); | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Returns a disk_number if the target has 0 or 1 disks | ||||||
|  |    * | ||||||
|  |    * @param {*} target_portal | ||||||
|  |    * @param {*} iqn | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|  |   async getDiskNumberFromIscsiTarget(target_portal, iqn) { | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     if (typeof target_portal != "object") { | ||||||
|  |       target_portal = { | ||||||
|  |         target_address: target_portal.split(":")[0], | ||||||
|  |         target_port: target_portal.split(":")[1] || 3260, | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // get device
 | ||||||
|  |     try { | ||||||
|  |       result = await this.executeRPC("iscsi", "GetTargetDisks", { | ||||||
|  |         target_portal, | ||||||
|  |         iqn, | ||||||
|  |       }); | ||||||
|  |     } catch (e) { | ||||||
|  |       let details = _.get(e, "details", ""); | ||||||
|  |       if (!details.includes("ObjectNotFound")) { | ||||||
|  |         throw e; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let diskIds = _.get(result, "diskIDs", []); | ||||||
|  |     if (diskIds.length > 1) { | ||||||
|  |       throw new Error( | ||||||
|  |         `${diskIds.length} disks on the target, no way to know which is the relevant disk` | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return diskIds[0]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Returns a volume_id if the disk has 0 or 1 volumes | ||||||
|  |    * | ||||||
|  |    * @param {*} disk_number | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|  |   async getVolumeIdFromDiskNumber(disk_number) { | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     if (disk_number == 0 || disk_number > 0) { | ||||||
|  |       result = await this.executeRPC("volume", "ListVolumesOnDisk", { | ||||||
|  |         disk_number, | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       let volume_ids = _.get(result, "volume_ids", []); | ||||||
|  |       /** | ||||||
|  |        * the 1st partition is a sort of system partion and is "" | ||||||
|  |        * usually around 15MB in size | ||||||
|  |        */ | ||||||
|  |       volume_ids = volume_ids.filter((item) => { | ||||||
|  |         return Boolean(item); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       if (volume_ids.length > 1) { | ||||||
|  |         throw new Error( | ||||||
|  |           `${volume_ids.length} volumes on the disk, no way to know which is the relevant volume` | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // ok of null/undefined
 | ||||||
|  |       return volume_ids[0]; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Return a volume_id if the target and disk both have 0 or 1 entries | ||||||
|  |    * | ||||||
|  |    * @param {*} target_portal | ||||||
|  |    * @param {*} iqn | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|  |   async getVolumeIdFromIscsiTarget(target_portal, iqn) { | ||||||
|  |     const disk_number = await this.getDiskNumberFromIscsiTarget(...arguments); | ||||||
|  |     return await this.getVolumeIdFromDiskNumber(disk_number); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async FilesystemPathExists(path) { | ||||||
|  |     let result; | ||||||
|  |     try { | ||||||
|  |       result = await this.executeRPC("filesystem", "PathExists", { | ||||||
|  |         path, | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       return result.exists; | ||||||
|  |     } catch (e) { | ||||||
|  |       let details = _.get(e, "details", ""); | ||||||
|  |       if (details.includes("not an absolute Windows path")) { | ||||||
|  |         return false; | ||||||
|  |       } else { | ||||||
|  |         throw e; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async FilesystemIsSymlink(path) { | ||||||
|  |     let result; | ||||||
|  |     try { | ||||||
|  |       result = await this.executeRPC("filesystem", "IsSymlink", { | ||||||
|  |         path, | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       return result.is_symlink; | ||||||
|  |     } catch (e) { | ||||||
|  |       let details = _.get(e, "details", ""); | ||||||
|  |       if (details.includes("not an absolute Windows path")) { | ||||||
|  |         return false; | ||||||
|  |       } else { | ||||||
|  |         throw e; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports.CsiProxyClient = CsiProxyClient; | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| const cp = require("child_process"); | const cp = require("child_process"); | ||||||
| const fs = require("fs"); | const fs = require("fs"); | ||||||
|  | const GeneralUtils = require("./general"); | ||||||
|  | const path = require("path"); | ||||||
| 
 | 
 | ||||||
| const DEFAULT_TIMEOUT = process.env.FILESYSTEM_DEFAULT_TIMEOUT || 30000; | const DEFAULT_TIMEOUT = process.env.FILESYSTEM_DEFAULT_TIMEOUT || 30000; | ||||||
| 
 | 
 | ||||||
|  | @ -25,6 +27,10 @@ class Filesystem { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   covertUnixSeparatorToWindowsSeparator(p) { | ||||||
|  |     return p.replaceAll(path.posix.sep, path.win32.sep); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   /** |   /** | ||||||
|    * Attempt to discover if device is a block device |    * Attempt to discover if device is a block device | ||||||
|    * |    * | ||||||
|  | @ -223,8 +229,12 @@ class Filesystem { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   async isSymbolicLink(path) { | ||||||
|  |     return fs.lstatSync(path).isSymbolicLink(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   /** |   /** | ||||||
|    * create symlink |    * remove file | ||||||
|    * |    * | ||||||
|    * @param {*} device |    * @param {*} device | ||||||
|    */ |    */ | ||||||
|  | @ -298,7 +308,7 @@ class Filesystem { | ||||||
|   async getBlockDevice(device) { |   async getBlockDevice(device) { | ||||||
|     const filesystem = this; |     const filesystem = this; | ||||||
|     device = await filesystem.realpath(device); |     device = await filesystem.realpath(device); | ||||||
|     let args = ["-a", "-b", "-l", "-J", "-O"]; |     let args = ["-a", "-b", "-J", "-O"]; | ||||||
|     args.push(device); |     args.push(device); | ||||||
|     let result; |     let result; | ||||||
| 
 | 
 | ||||||
|  | @ -312,30 +322,214 @@ class Filesystem { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * blkid -p -o export <device> |    * | ||||||
|  |    * @param {*} device | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|  |   async getBlockDeviceLargestPartition(device) { | ||||||
|  |     const filesystem = this; | ||||||
|  |     let block_device_info = await filesystem.getBlockDevice(device); | ||||||
|  |     if (block_device_info.children) { | ||||||
|  |       let child; | ||||||
|  |       for (const child_i of block_device_info.children) { | ||||||
|  |         if (child_i.type == "part") { | ||||||
|  |           if (!child) { | ||||||
|  |             child = child_i; | ||||||
|  |           } else { | ||||||
|  |             if (child_i.size > child.size) { | ||||||
|  |               child = child_i; | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       return `${child.path}`; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * | ||||||
|  |    * @param {*} device | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|  |   async getBlockDeviceLastPartition(device) { | ||||||
|  |     const filesystem = this; | ||||||
|  |     let block_device_info = await filesystem.getBlockDevice(device); | ||||||
|  |     if (block_device_info.children) { | ||||||
|  |       let child; | ||||||
|  |       for (const child_i of block_device_info.children) { | ||||||
|  |         if (child_i.type == "part") { | ||||||
|  |           if (!child) { | ||||||
|  |             child = child_i; | ||||||
|  |           } else { | ||||||
|  |             let minor = child["maj:min"].split(":")[1]; | ||||||
|  |             let minor_i = child_i["maj:min"].split(":")[1]; | ||||||
|  |             if (minor_i > minor) { | ||||||
|  |               child = child_i; | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       return `${child.path}`; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * | ||||||
|  |    * @param {*} device | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|  |   async getBlockDevicePartitionCount(device) { | ||||||
|  |     const filesystem = this; | ||||||
|  |     let count = 0; | ||||||
|  |     let block_device_info = await filesystem.getBlockDevice(device); | ||||||
|  |     if (block_device_info.children) { | ||||||
|  |       for (const child_i of block_device_info.children) { | ||||||
|  |         if (child_i.type == "part") { | ||||||
|  |           count++; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return count; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async getBlockDeviceHasParitionTable(device) { | ||||||
|  |     const filesystem = this; | ||||||
|  |     let block_device_info = await filesystem.getBlockDevice(device); | ||||||
|  | 
 | ||||||
|  |     return block_device_info.pttype ? true : false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * DOS | ||||||
|  |    * - type=83 = Linux | ||||||
|  |    * - type=07 = HPFS/NTFS/exFAT | ||||||
|  |    * | ||||||
|  |    * GPT | ||||||
|  |    * - type=0FC63DAF-8483-4772-8E79-3D69D8477DE4 = linux | ||||||
|  |    * - type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 = ntfs | ||||||
|  |    * - type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B = EFI | ||||||
|  |    * | ||||||
|  |    * @param {*} device | ||||||
|  |    * @param {*} label | ||||||
|  |    * @param {*} type | ||||||
|  |    */ | ||||||
|  |   async partitionDevice( | ||||||
|  |     device, | ||||||
|  |     label = "gpt", | ||||||
|  |     type = "0FC63DAF-8483-4772-8E79-3D69D8477DE4" | ||||||
|  |   ) { | ||||||
|  |     const filesystem = this; | ||||||
|  |     let args = [device]; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |       result = await filesystem.exec("sfdisk", args, { | ||||||
|  |         stdin: `label: ${label}\n`, | ||||||
|  |       }); | ||||||
|  |       result = await filesystem.exec("sfdisk", args, { | ||||||
|  |         stdin: `type=${type}\n`, | ||||||
|  |       }); | ||||||
|  |     } catch (err) { | ||||||
|  |       throw err; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * mimic the behavior of partitioning a new data drive in windows directly | ||||||
|  |    * | ||||||
|  |    * https://en.wikipedia.org/wiki/Microsoft_Reserved_Partition
 | ||||||
|  |    * | ||||||
|  |    * @param {*} device | ||||||
|  |    */ | ||||||
|  |   async partitionDeviceWindows(device) { | ||||||
|  |     const filesystem = this; | ||||||
|  |     let args = [device]; | ||||||
|  |     let result; | ||||||
|  |     let block_device_info = await filesystem.getBlockDevice(device); | ||||||
|  | 
 | ||||||
|  |     //let sixteen_megabytes = 16777216;
 | ||||||
|  |     //let thirtytwo_megabytes = 33554432;
 | ||||||
|  |     //let onehundredtwentyeight_megabytes = 134217728;
 | ||||||
|  | 
 | ||||||
|  |     let msr_partition_size = "16M"; | ||||||
|  |     let label = "gpt"; | ||||||
|  |     let msr_guid = "E3C9E316-0B5C-4DB8-817D-F92DF00215AE"; | ||||||
|  |     let ntfs_guid = "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"; | ||||||
|  | 
 | ||||||
|  |     if (block_device_info.type != "disk") { | ||||||
|  |       throw new Error( | ||||||
|  |         `cannot partition device of type: ${block_device_info.type}` | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * On drives less than 16GB in size, the MSR is 32MB. | ||||||
|  |      * On drives greater than or equal two 16GB, the MSR is 128 MB. | ||||||
|  |      * It is only 128 MB for Win 7/8 ( On drives less than 16GB in size, the MSR is 32MB ) & 16 MB for win 10! | ||||||
|  |      */ | ||||||
|  |     let msr_partition_size_break = 17179869184; // 16GB
 | ||||||
|  | 
 | ||||||
|  |     // TODO: this size may be sectors so not really disk size in terms of GB
 | ||||||
|  |     if (block_device_info.size >= msr_partition_size_break) { | ||||||
|  |       // ignoring for now, appears windows 10+ use 16MB always
 | ||||||
|  |       //msr_partition_size = "128M";
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |       result = await filesystem.exec("sfdisk", args, { | ||||||
|  |         stdin: `label: ${label}\n`, | ||||||
|  |       }); | ||||||
|  |       // must send ALL partitions at once (newline separated), cannot send them 1 at a time
 | ||||||
|  |       result = await filesystem.exec("sfdisk", args, { | ||||||
|  |         stdin: `size=${msr_partition_size},type=${msr_guid}\ntype=${ntfs_guid}\n`, | ||||||
|  |       }); | ||||||
|  |     } catch (err) { | ||||||
|  |       throw err; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|    * |    * | ||||||
|    * @param {*} device |    * @param {*} device | ||||||
|    */ |    */ | ||||||
|   async deviceIsFormatted(device) { |   async deviceIsFormatted(device) { | ||||||
|     const filesystem = this; |     const filesystem = this; | ||||||
|     let args = ["-p", "-o", "export", device]; |  | ||||||
|     let result; |     let result; | ||||||
| 
 | 
 | ||||||
|     try { |     try { | ||||||
|       result = await filesystem.exec("blkid", args); |       result = await filesystem.getBlockDevice(device); | ||||||
|  |       return result.fstype ? true : false; | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       if (err.code == 2 && err.stderr.includes("No such device or address")) { |  | ||||||
|         throw err; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       if (err.code == 2) { |  | ||||||
|         return false; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       throw err; |       throw err; | ||||||
|     } |     } | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|     return true; |   async deviceIsIscsi(device) { | ||||||
|  |     const filesystem = this; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |       if (result) { | ||||||
|  |         device = `/dev/${result.pkname}`; | ||||||
|  |       } | ||||||
|  |       result = await filesystem.getBlockDevice(device); | ||||||
|  |     } while (result.pkname); | ||||||
|  | 
 | ||||||
|  |     return result && result.tran == "iscsi"; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async getBlockDeviceParent(device) { | ||||||
|  |     const filesystem = this; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |       if (result) { | ||||||
|  |         device = `/dev/${result.pkname}`; | ||||||
|  |       } | ||||||
|  |       result = await filesystem.getBlockDevice(device); | ||||||
|  |     } while (result.pkname); | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|  | @ -438,6 +632,31 @@ class Filesystem { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   async expandPartition(device) { | ||||||
|  |     const filesystem = this; | ||||||
|  |     const command = "growpart"; | ||||||
|  |     const args = []; | ||||||
|  | 
 | ||||||
|  |     let block_device_info = await filesystem.getBlockDevice(device); | ||||||
|  |     let device_fs_info = await filesystem.getDeviceFilesystemInfo(device); | ||||||
|  |     let growpart_partition = device_fs_info["part_entry_number"]; | ||||||
|  |     let parent_block_device = await filesystem.getBlockDeviceParent(device); | ||||||
|  | 
 | ||||||
|  |     args.push(parent_block_device.path, growpart_partition); | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |       await filesystem.exec(command, args); | ||||||
|  |     } catch (err) { | ||||||
|  |       if ( | ||||||
|  |         err.code == 1 && | ||||||
|  |         err.stdout && | ||||||
|  |         err.stdout.includes("could only be grown by") | ||||||
|  |       ) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   /** |   /** | ||||||
|    * expand a given filesystem |    * expand a given filesystem | ||||||
|    * |    * | ||||||
|  | @ -458,6 +677,9 @@ class Filesystem { | ||||||
|         args = args.concat(["filesystem", "resize", "max"]); |         args = args.concat(["filesystem", "resize", "max"]); | ||||||
|         args.push(device); // in this case should be a mounted path
 |         args.push(device); // in this case should be a mounted path
 | ||||||
|         break; |         break; | ||||||
|  |       case "exfat": | ||||||
|  |         // https://github.com/exfatprogs/exfatprogs/issues/134
 | ||||||
|  |         return; | ||||||
|       case "ext4": |       case "ext4": | ||||||
|       case "ext3": |       case "ext3": | ||||||
|       case "ext4dev": |       case "ext4dev": | ||||||
|  | @ -465,6 +687,16 @@ class Filesystem { | ||||||
|         args = args.concat(options); |         args = args.concat(options); | ||||||
|         args.push(device); |         args.push(device); | ||||||
|         break; |         break; | ||||||
|  |       case "ntfs": | ||||||
|  |         // must be unmounted
 | ||||||
|  |         command = "ntfsresize"; | ||||||
|  |         await filesystem.exec(command, ["-c", device]); | ||||||
|  |         await filesystem.exec(command, ["-n", device]); | ||||||
|  |         args = args.concat("-P", "-f"); | ||||||
|  |         args = args.concat(options); | ||||||
|  |         //args = args.concat(["-s", "max"]);
 | ||||||
|  |         args.push(device); | ||||||
|  |         break; | ||||||
|       case "xfs": |       case "xfs": | ||||||
|         command = "xfs_growfs"; |         command = "xfs_growfs"; | ||||||
|         args = args.concat(options); |         args = args.concat(options); | ||||||
|  | @ -481,6 +713,10 @@ class Filesystem { | ||||||
| 
 | 
 | ||||||
|     try { |     try { | ||||||
|       result = await filesystem.exec(command, args); |       result = await filesystem.exec(command, args); | ||||||
|  |       // must clear the dirty bit after resize
 | ||||||
|  |       if (fstype.toLowerCase() == "ntfs") { | ||||||
|  |         await filesystem.exec("ntfsfix", ["-d", device]); | ||||||
|  |       } | ||||||
|       return result; |       return result; | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       throw err; |       throw err; | ||||||
|  | @ -521,6 +757,15 @@ class Filesystem { | ||||||
|         args.push("-f"); |         args.push("-f"); | ||||||
|         args.push("-p"); |         args.push("-p"); | ||||||
|         break; |         break; | ||||||
|  |       case "ntfs": | ||||||
|  |         /** | ||||||
|  |          * -b, --clear-bad-sectors Clear the bad sector list | ||||||
|  |          * -d, --clear-dirty       Clear the volume dirty flag | ||||||
|  |          */ | ||||||
|  |         command = "ntfsfix"; | ||||||
|  |         args.puuh("-d"); | ||||||
|  |         args.push(device); | ||||||
|  |         break; | ||||||
|       case "xfs": |       case "xfs": | ||||||
|         command = "xfs_repair"; |         command = "xfs_repair"; | ||||||
|         args = args.concat(["-o", "force_geometry"]); |         args = args.concat(["-o", "force_geometry"]); | ||||||
|  | @ -589,16 +834,31 @@ class Filesystem { | ||||||
|    * @param {*} path |    * @param {*} path | ||||||
|    */ |    */ | ||||||
|   async pathExists(path) { |   async pathExists(path) { | ||||||
|     const filesystem = this; |     let result = false; | ||||||
|     let args = []; |  | ||||||
|     args.push(path); |  | ||||||
| 
 |  | ||||||
|     try { |     try { | ||||||
|       await filesystem.exec("stat", args); |       await GeneralUtils.retry( | ||||||
|  |         10, | ||||||
|  |         200, | ||||||
|  |         () => { | ||||||
|  |           fs.statSync(path); | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           retryCondition: (err) => { | ||||||
|  |             if (err.code == "UNKNOWN") { | ||||||
|  |               return true; | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |           }, | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  |       result = true; | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       return false; |       if (err.code !== "ENOENT") { | ||||||
|  |         throw err; | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|     return true; | 
 | ||||||
|  |     return result; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   exec(command, args, options = {}) { |   exec(command, args, options = {}) { | ||||||
|  | @ -607,6 +867,12 @@ class Filesystem { | ||||||
|       //options.timeout = DEFAULT_TIMEOUT;
 |       //options.timeout = DEFAULT_TIMEOUT;
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     let stdin; | ||||||
|  |     if (options.stdin) { | ||||||
|  |       stdin = options.stdin; | ||||||
|  |       delete options.stdin; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     const filesystem = this; |     const filesystem = this; | ||||||
|     args = args || []; |     args = args || []; | ||||||
| 
 | 
 | ||||||
|  | @ -614,13 +880,27 @@ class Filesystem { | ||||||
|       args.unshift(command); |       args.unshift(command); | ||||||
|       command = filesystem.options.paths.sudo; |       command = filesystem.options.paths.sudo; | ||||||
|     } |     } | ||||||
|     console.log("executing filesystem command: %s %s", command, args.join(" ")); |     let command_log = `${command} ${args.join(" ")}`.trim(); | ||||||
|      |     if (stdin) { | ||||||
|  |       command_log = `echo '${stdin}' | ${command_log}` | ||||||
|  |         .trim() | ||||||
|  |         .replace(/\n/, "\\n"); | ||||||
|  |     } | ||||||
|  |     console.log("executing filesystem command: %s", command_log); | ||||||
|  | 
 | ||||||
|     return new Promise((resolve, reject) => { |     return new Promise((resolve, reject) => { | ||||||
|       const child = filesystem.options.executor.spawn(command, args, options); |       const child = filesystem.options.executor.spawn(command, args, options); | ||||||
|       let stdout = ""; |       let stdout = ""; | ||||||
|       let stderr = ""; |       let stderr = ""; | ||||||
|    | 
 | ||||||
|  |       child.on("spawn", function () { | ||||||
|  |         if (stdin) { | ||||||
|  |           child.stdin.setEncoding("utf-8"); | ||||||
|  |           child.stdin.write(stdin); | ||||||
|  |           child.stdin.end(); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|       child.stdout.on("data", function (data) { |       child.stdout.on("data", function (data) { | ||||||
|         stdout = stdout + data; |         stdout = stdout + data; | ||||||
|       }); |       }); | ||||||
|  |  | ||||||
|  | @ -1,4 +1,6 @@ | ||||||
|  | const _ = require("lodash"); | ||||||
| const axios = require("axios"); | const axios = require("axios"); | ||||||
|  | const crypto = require("crypto"); | ||||||
| 
 | 
 | ||||||
| function sleep(ms) { | function sleep(ms) { | ||||||
|   return new Promise((resolve) => { |   return new Promise((resolve) => { | ||||||
|  | @ -6,6 +8,64 @@ function sleep(ms) { | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function md5(val) { | ||||||
|  |   return crypto.createHash("md5").update(val).digest("hex"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function crc32(val) { | ||||||
|  |   for (var a, o = [], c = 0; c < 256; c++) { | ||||||
|  |     a = c; | ||||||
|  |     for (var f = 0; f < 8; f++) a = 1 & a ? 3988292384 ^ (a >>> 1) : a >>> 1; | ||||||
|  |     o[c] = a; | ||||||
|  |   } | ||||||
|  |   for (var n = -1, t = 0; t < val.length; t++) | ||||||
|  |     n = (n >>> 8) ^ o[255 & (n ^ val.charCodeAt(t))]; | ||||||
|  |   return (-1 ^ n) >>> 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const crctab16 = new Uint16Array([ | ||||||
|  |   0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, | ||||||
|  |   0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108, | ||||||
|  |   0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb, | ||||||
|  |   0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399, | ||||||
|  |   0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, | ||||||
|  |   0xfae7, 0xc87c, 0xd9f5, 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, | ||||||
|  |   0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, | ||||||
|  |   0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, | ||||||
|  |   0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285, | ||||||
|  |   0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, | ||||||
|  |   0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 0x6306, 0x728f, 0x4014, | ||||||
|  |   0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5, | ||||||
|  |   0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, | ||||||
|  |   0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, | ||||||
|  |   0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, | ||||||
|  |   0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, | ||||||
|  |   0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, | ||||||
|  |   0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, | ||||||
|  |   0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50, | ||||||
|  |   0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710, | ||||||
|  |   0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, | ||||||
|  |   0x6e6e, 0x5cf5, 0x4d7c, 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, | ||||||
|  |   0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, | ||||||
|  |   0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, | ||||||
|  |   0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e, | ||||||
|  |   0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, | ||||||
|  |   0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 0xf78f, 0xe606, 0xd49d, | ||||||
|  |   0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, | ||||||
|  |   0x3de3, 0x2c6a, 0x1ef1, 0x0f78, | ||||||
|  | ]); | ||||||
|  | 
 | ||||||
|  | // calculate the 16-bit CRC of data with predetermined length.
 | ||||||
|  | function crc16(data) { | ||||||
|  |   var res = 0x0ffff; | ||||||
|  | 
 | ||||||
|  |   for (let b of data) { | ||||||
|  |     res = ((res >> 8) & 0x0ff) ^ crctab16[(res ^ b) & 0xff]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return ~res & 0x0ffff; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| function lockKeysFromRequest(call, serviceMethodName) { | function lockKeysFromRequest(call, serviceMethodName) { | ||||||
|   switch (serviceMethodName) { |   switch (serviceMethodName) { | ||||||
|     // controller
 |     // controller
 | ||||||
|  | @ -53,11 +113,19 @@ function getLargestNumber() { | ||||||
|   return number; |   return number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function stripWindowsDriveLetter(path) { | ||||||
|  |   return path.replace(/^[a-zA-Z]:/, ""); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function hasWindowsDriveLetter(path) { | ||||||
|  |   return /^[a-zA-Z]:/i.test(path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * transition function to replicate `request` style requests using axios |  * transition function to replicate `request` style requests using axios | ||||||
|  *  |  * | ||||||
|  * @param {*} options  |  * @param {*} options | ||||||
|  * @param {*} callback  |  * @param {*} callback | ||||||
|  */ |  */ | ||||||
| function axios_request(options, callback = function () {}) { | function axios_request(options, callback = function () {}) { | ||||||
|   function prep_response(res) { |   function prep_response(res) { | ||||||
|  | @ -80,7 +148,14 @@ function axios_request(options, callback = function () {}) { | ||||||
|         // The request was made and the server responded with a status code
 |         // The request was made and the server responded with a status code
 | ||||||
|         // that falls out of the range of 2xx
 |         // that falls out of the range of 2xx
 | ||||||
|         let res = prep_response(err.response); |         let res = prep_response(err.response); | ||||||
|         callback(null, res, res.body); |         let senderr = false; | ||||||
|  |         if ( | ||||||
|  |           options.validateStatus && | ||||||
|  |           typeof options.validateStatus == "function" | ||||||
|  |         ) { | ||||||
|  |           senderr = true; | ||||||
|  |         } | ||||||
|  |         callback(senderr ? err : null, res, res.body); | ||||||
|       } else if (err.request) { |       } else if (err.request) { | ||||||
|         // The request was made but no response was received
 |         // The request was made but no response was received
 | ||||||
|         // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
 |         // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
 | ||||||
|  | @ -110,8 +185,82 @@ function stringify(value) { | ||||||
|   return JSON.stringify(value, getCircularReplacer()); |   return JSON.stringify(value, getCircularReplacer()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function default_supported_block_filesystems() { | ||||||
|  |   return ["btrfs", "exfat", "ext3", "ext4", "ext4dev", "ntfs", "vfat", "xfs"]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function default_supported_file_filesystems() { | ||||||
|  |   return ["nfs", "cifs"]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function retry(retries, retriesDelay, code, options = {}) { | ||||||
|  |   let current_try = 0; | ||||||
|  |   let maxwait = _.get(options, "maxwait"); | ||||||
|  |   let logerrors = _.get(options, "logerrors", false); | ||||||
|  |   let retryCondition = options.retryCondition; | ||||||
|  |   let executeStartTime; | ||||||
|  | 
 | ||||||
|  |   do { | ||||||
|  |     current_try++; | ||||||
|  |     try { | ||||||
|  |       executeStartTime = Date.now(); | ||||||
|  |       return await code(); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (current_try >= retries) { | ||||||
|  |         throw err; | ||||||
|  |       } | ||||||
|  |       if (retryCondition) { | ||||||
|  |         let retry = retryCondition(err); | ||||||
|  |         if (!retry) { | ||||||
|  |           console.log(`retry - failed condition, not trying again`); | ||||||
|  |           throw err; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       if (logerrors === true) { | ||||||
|  |         console.log(`retry - err:`, err); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // handle minExecutionTime
 | ||||||
|  |     if (options.minExecutionTime > 0) { | ||||||
|  |       let executionElapsedTIme = Date.now() - executeStartTime; | ||||||
|  |       let minExecutionDelayTime = | ||||||
|  |         options.minExecutionTime - executionElapsedTIme; | ||||||
|  |       if (minExecutionDelayTime > 0) { | ||||||
|  |         await sleep(minExecutionDelayTime); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // handle delay
 | ||||||
|  |     let sleep_time = retriesDelay; | ||||||
|  |     if (_.get(options, "exponential", false) === true) { | ||||||
|  |       sleep_time = retriesDelay * current_try; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (maxwait) { | ||||||
|  |       if (sleep_time > maxwait) { | ||||||
|  |         sleep_time = maxwait; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (sleep_time > 0) { | ||||||
|  |       console.log(`retry - waiting ${sleep_time}ms before trying again`); | ||||||
|  |       await sleep(sleep_time); | ||||||
|  |     } | ||||||
|  |   } while (true); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| module.exports.sleep = sleep; | module.exports.sleep = sleep; | ||||||
|  | module.exports.md5 = md5; | ||||||
|  | module.exports.crc32 = crc32; | ||||||
|  | module.exports.crc16 = crc16; | ||||||
| module.exports.lockKeysFromRequest = lockKeysFromRequest; | module.exports.lockKeysFromRequest = lockKeysFromRequest; | ||||||
| module.exports.getLargestNumber = getLargestNumber; | module.exports.getLargestNumber = getLargestNumber; | ||||||
| module.exports.stringify = stringify; | module.exports.stringify = stringify; | ||||||
|  | module.exports.stripWindowsDriveLetter = stripWindowsDriveLetter; | ||||||
|  | module.exports.hasWindowsDriveLetter = hasWindowsDriveLetter; | ||||||
| module.exports.axios_request = axios_request; | module.exports.axios_request = axios_request; | ||||||
|  | module.exports.default_supported_block_filesystems = | ||||||
|  |   default_supported_block_filesystems; | ||||||
|  | module.exports.default_supported_file_filesystems = | ||||||
|  |   default_supported_file_filesystems; | ||||||
|  | module.exports.retry = retry; | ||||||
|  |  | ||||||
|  | @ -298,7 +298,7 @@ class Mount { | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|     const mount_info = await mount.getMountDetails(path); |     const mount_info = await mount.getMountDetails(path); | ||||||
|     const is_block = filesystem.isBlockDevice(path); |     const is_block = await filesystem.isBlockDevice(path); | ||||||
|     if (mount_info.fstype == "devtmpfs" && is_block) { |     if (mount_info.fstype == "devtmpfs" && is_block) { | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | if (typeof String.prototype.replaceAll == "undefined") { | ||||||
|  |   String.prototype.replaceAll = function (match, replace) { | ||||||
|  |     return this.replace(new RegExp(match, "g"), () => replace); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,85 @@ | ||||||
|  | const cp = require("child_process"); | ||||||
|  | 
 | ||||||
|  | class Powershell { | ||||||
|  |     async exec(command, options = {}) { | ||||||
|  |         if (!options.hasOwnProperty("timeout")) { | ||||||
|  |             // TODO: cannot use this as fsck etc are too risky to kill
 | ||||||
|  |             //options.timeout = DEFAULT_TIMEOUT;
 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         //cmd := exec.Command("powershell", "-Mta", "-NoProfile", "-Command", command)
 | ||||||
|  | 
 | ||||||
|  |         let stdin; | ||||||
|  |         if (options.stdin) { | ||||||
|  |             stdin = options.stdin; | ||||||
|  |             delete options.stdin; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // https://github.com/kubernetes-csi/csi-proxy/blob/master/pkg/utils/utils.go
 | ||||||
|  |         const _command = "powershell"; | ||||||
|  |         const args = [ | ||||||
|  |             "-Mta", | ||||||
|  |             "-NoProfile", | ||||||
|  |             "-Command", | ||||||
|  |             command | ||||||
|  |         ]; | ||||||
|  | 
 | ||||||
|  |         let command_log = `${_command} ${args.join(" ")}`.trim(); | ||||||
|  |         if (stdin) { | ||||||
|  |             command_log = `echo '${stdin}' | ${command_log}` | ||||||
|  |                 .trim() | ||||||
|  |                 .replace(/\n/, "\\n"); | ||||||
|  |         } | ||||||
|  |         console.log("executing powershell command: %s", command_log); | ||||||
|  | 
 | ||||||
|  |         return new Promise((resolve, reject) => { | ||||||
|  |             const child = cp.spawn(_command, args, options); | ||||||
|  |             let stdout = ""; | ||||||
|  |             let stderr = ""; | ||||||
|  | 
 | ||||||
|  |             child.on("spawn", function () { | ||||||
|  |                 if (stdin) { | ||||||
|  |                     child.stdin.setEncoding("utf-8"); | ||||||
|  |                     child.stdin.write(stdin); | ||||||
|  |                     child.stdin.end(); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             child.stdout.on("data", function (data) { | ||||||
|  |                 stdout = stdout + data; | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             child.stderr.on("data", function (data) { | ||||||
|  |                 stderr = stderr + data; | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             child.on("close", function (code) { | ||||||
|  |                 const result = { code, stdout, stderr, timeout: false }; | ||||||
|  | 
 | ||||||
|  |                 // timeout scenario
 | ||||||
|  |                 if (code === null) { | ||||||
|  |                     result.timeout = true; | ||||||
|  |                     reject(result); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if (code) { | ||||||
|  |                     console.log( | ||||||
|  |                         "failed to execute powershell command: %s, response: %j", | ||||||
|  |                         command_log, | ||||||
|  |                         result | ||||||
|  |                     ); | ||||||
|  |                     reject(result); | ||||||
|  |                 } else { | ||||||
|  |                     try { | ||||||
|  |                         result.parsed = JSON.parse(result.stdout); | ||||||
|  |                     } catch (err) { }; | ||||||
|  |                     resolve(result); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | module.exports.Powershell = Powershell; | ||||||
							
								
								
									
										163
									
								
								src/utils/ssh.js
								
								
								
								
							
							
						
						
									
										163
									
								
								src/utils/ssh.js
								
								
								
								
							|  | @ -1,4 +1,6 @@ | ||||||
| var Client = require("ssh2").Client; | const Client = require("ssh2").Client; | ||||||
|  | const { E_CANCELED, Mutex } = require("async-mutex"); | ||||||
|  | const GeneralUtils = require("./general"); | ||||||
| 
 | 
 | ||||||
| class SshClient { | class SshClient { | ||||||
|   constructor(options = {}) { |   constructor(options = {}) { | ||||||
|  | @ -8,7 +10,47 @@ class SshClient { | ||||||
|       this.logger = this.options.logger; |       this.logger = this.options.logger; | ||||||
|     } else { |     } else { | ||||||
|       this.logger = console; |       this.logger = console; | ||||||
|  |       console.silly = console.debug; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     if (!this.options.connection.hasOwnProperty("keepaliveInterval")) { | ||||||
|  |       this.options.connection.keepaliveInterval = 10000; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (this.options.connection.debug == true) { | ||||||
|  |       this.options.connection.debug = function (msg) { | ||||||
|  |         this.debug(msg); | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this.conn_mutex = new Mutex(); | ||||||
|  |     this.conn_state; | ||||||
|  |     this.conn_err; | ||||||
|  |     this.ready_event_count = 0; | ||||||
|  |     this.error_event_count = 0; | ||||||
|  | 
 | ||||||
|  |     this.conn = new Client(); | ||||||
|  |     // invoked before close
 | ||||||
|  |     this.conn.on("end", () => { | ||||||
|  |       this.conn_state = "ended"; | ||||||
|  |       this.debug("Client :: end"); | ||||||
|  |     }); | ||||||
|  |     // invoked after end
 | ||||||
|  |     this.conn.on("close", () => { | ||||||
|  |       this.conn_state = "closed"; | ||||||
|  |       this.debug("Client :: close"); | ||||||
|  |     }); | ||||||
|  |     this.conn.on("error", (err) => { | ||||||
|  |       this.conn_state = "error"; | ||||||
|  |       this.conn_err = err; | ||||||
|  |       this.error_event_count++; | ||||||
|  |       this.debug("Client :: error"); | ||||||
|  |     }); | ||||||
|  |     this.conn.on("ready", () => { | ||||||
|  |       this.conn_state = "ready"; | ||||||
|  |       this.ready_event_count++; | ||||||
|  |       this.debug("Client :: ready"); | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|  | @ -27,17 +69,119 @@ class SshClient { | ||||||
|     this.logger.silly(...arguments); |     this.logger.silly(...arguments); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   async _connect() { | ||||||
|  |     const start_ready_event_count = this.ready_event_count; | ||||||
|  |     const start_error_event_count = this.error_event_count; | ||||||
|  |     try { | ||||||
|  |       await this.conn_mutex.runExclusive(async () => { | ||||||
|  |         this.conn.connect(this.options.connection); | ||||||
|  |         do { | ||||||
|  |           if (start_error_event_count != this.error_event_count) { | ||||||
|  |             throw this.conn_err; | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           if (start_ready_event_count != this.ready_event_count) { | ||||||
|  |             break; | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           await GeneralUtils.sleep(100); | ||||||
|  |         } while (true); | ||||||
|  |       }); | ||||||
|  |     } catch (err) { | ||||||
|  |       if (err === E_CANCELED) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       throw err; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async connect() { | ||||||
|  |     if (this.conn_state == "ready") { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return this._connect(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   async exec(command, options = {}, stream_proxy = null) { |   async exec(command, options = {}, stream_proxy = null) { | ||||||
|  |     // default is to reuse
 | ||||||
|  |     if (process.env.SSH_REUSE_CONNECTION == "0") { | ||||||
|  |       return this._nexec(...arguments); | ||||||
|  |     } else { | ||||||
|  |       return this._rexec(...arguments); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async _rexec(command, options = {}, stream_proxy = null) { | ||||||
|  |     const client = this; | ||||||
|  |     const conn = this.conn; | ||||||
|  | 
 | ||||||
|  |     return new Promise(async (resolve, reject) => { | ||||||
|  |       do { | ||||||
|  |         try { | ||||||
|  |           await this.connect(); | ||||||
|  |           conn.exec(command, options, function (err, stream) { | ||||||
|  |             if (err) { | ||||||
|  |               reject(err); | ||||||
|  |               return; | ||||||
|  |             } | ||||||
|  |             let stderr; | ||||||
|  |             let stdout; | ||||||
|  | 
 | ||||||
|  |             if (stream_proxy) { | ||||||
|  |               stream_proxy.on("kill", (signal) => { | ||||||
|  |                 stream.destroy(); | ||||||
|  |               }); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             stream | ||||||
|  |               .on("close", function (code, signal) { | ||||||
|  |                 client.debug( | ||||||
|  |                   "Stream :: close :: code: " + code + ", signal: " + signal | ||||||
|  |                 ); | ||||||
|  |                 if (stream_proxy) { | ||||||
|  |                   stream_proxy.emit("close", ...arguments); | ||||||
|  |                 } | ||||||
|  |                 resolve({ stderr, stdout, code, signal }); | ||||||
|  |                 //conn.end();
 | ||||||
|  |               }) | ||||||
|  |               .on("data", function (data) { | ||||||
|  |                 client.debug("STDOUT: " + data); | ||||||
|  |                 if (stream_proxy) { | ||||||
|  |                   stream_proxy.stdout.emit("data", ...arguments); | ||||||
|  |                 } | ||||||
|  |                 if (stdout == undefined) { | ||||||
|  |                   stdout = ""; | ||||||
|  |                 } | ||||||
|  |                 stdout = stdout.concat(data); | ||||||
|  |               }) | ||||||
|  |               .stderr.on("data", function (data) { | ||||||
|  |                 client.debug("STDERR: " + data); | ||||||
|  |                 if (stream_proxy) { | ||||||
|  |                   stream_proxy.stderr.emit("data", ...arguments); | ||||||
|  |                 } | ||||||
|  |                 if (stderr == undefined) { | ||||||
|  |                   stderr = ""; | ||||||
|  |                 } | ||||||
|  |                 stderr = stderr.concat(data); | ||||||
|  |               }); | ||||||
|  |           }); | ||||||
|  |           break; | ||||||
|  |         } catch (err) { | ||||||
|  |           if (err.message && !err.message.includes("Not connected")) { | ||||||
|  |             throw err; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         await GeneralUtils.sleep(1000); | ||||||
|  |       } while (true); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async _nexec(command, options = {}, stream_proxy = null) { | ||||||
|     const client = this; |     const client = this; | ||||||
|     return new Promise((resolve, reject) => { |     return new Promise((resolve, reject) => { | ||||||
|       var conn = new Client(); |       var conn = new Client(); | ||||||
| 
 | 
 | ||||||
|       if (client.options.connection.debug == true) { |  | ||||||
|         client.options.connection.debug = function (msg) { |  | ||||||
|           client.debug(msg); |  | ||||||
|         }; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       conn |       conn | ||||||
|         .on("error", function (err) { |         .on("error", function (err) { | ||||||
|           client.debug("Client :: error"); |           client.debug("Client :: error"); | ||||||
|  | @ -50,7 +194,10 @@ class SshClient { | ||||||
|           //  TERM: "",
 |           //  TERM: "",
 | ||||||
|           //};
 |           //};
 | ||||||
|           conn.exec(command, options, function (err, stream) { |           conn.exec(command, options, function (err, stream) { | ||||||
|             if (err) reject(err); |             if (err) { | ||||||
|  |               reject(err); | ||||||
|  |               return; | ||||||
|  |             } | ||||||
|             let stderr; |             let stderr; | ||||||
|             let stdout; |             let stdout; | ||||||
|             stream |             stream | ||||||
|  |  | ||||||
|  | @ -0,0 +1,783 @@ | ||||||
|  | const _ = require("lodash"); | ||||||
|  | const GeneralUtils = require("./general"); | ||||||
|  | const Powershell = require("./powershell").Powershell; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * https://kubernetes.io/blog/2021/08/16/windows-hostprocess-containers/
 | ||||||
|  |  * https://github.com/kubernetes-csi/csi-proxy/tree/master/pkg/os
 | ||||||
|  |  * | ||||||
|  |  * multipath notes: | ||||||
|  |  * - http://scst.sourceforge.net/mc_s.html
 | ||||||
|  |  * - https://github.com/kubernetes-csi/csi-proxy/pull/99
 | ||||||
|  |  * - https://docs.microsoft.com/en-us/azure/storsimple/storsimple-8000-configure-mpio-windows-server
 | ||||||
|  |  * - https://support.purestorage.com/Legacy_Documentation/Setting_the_MPIO_Policy
 | ||||||
|  |  * - https://docs.microsoft.com/en-us/powershell/module/mpio/?view=windowsserver2022-ps
 | ||||||
|  |  * | ||||||
|  |  * Get-WindowsFeature -Name 'Multipath-IO' | ||||||
|  |  * Add-WindowsFeature -Name 'Multipath-IO' | ||||||
|  |  * | ||||||
|  |  * Enable-MSDSMAutomaticClaim -BusType "iSCSI" | ||||||
|  |  * Disable-MSDSMAutomaticClaim -BusType "iSCSI" | ||||||
|  |  * | ||||||
|  |  * Get-MSDSMGlobalDefaultLoadBalancePolicy | ||||||
|  |  * Set-MSDSMGlobalLoadBalancePolicy -Policy RR | ||||||
|  |  * | ||||||
|  |  * synology woes: | ||||||
|  |  * - https://community.spiceworks.com/topic/2279882-synology-iscsi-will-not-disconnect-using-powershell-commands
 | ||||||
|  |  * - https://support.hpe.com/hpesc/public/docDisplay?docId=c01880810&docLocale=en_US
 | ||||||
|  |  * - https://askubuntu.com/questions/1159103/why-is-iscsi-trying-to-connect-on-ipv6-at-boot
 | ||||||
|  |  */ | ||||||
|  | class Windows { | ||||||
|  |   constructor() { | ||||||
|  |     this.ps = new Powershell(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   resultToArray(result) { | ||||||
|  |     if (!result.parsed) { | ||||||
|  |       result.parsed = []; | ||||||
|  |     } | ||||||
|  |     if (!Array.isArray(result.parsed)) { | ||||||
|  |       result.parsed = [result.parsed]; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   uncPathToShare(path) { | ||||||
|  |     // UNC\<server>\<share>[\<path>\]
 | ||||||
|  |     if (path.startsWith("UNC")) { | ||||||
|  |       path = path.replace("UNC", "\\"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!path.startsWith("\\\\")) { | ||||||
|  |       path = `\\\\${path}`; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let parts = path.split("\\"); | ||||||
|  |     return `\\\\${parts[2]}\\${parts[3]}`; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetRealTarget(path) { | ||||||
|  |     let item; | ||||||
|  |     let target; | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |       item = await this.GetItem(path); | ||||||
|  |       path = null; | ||||||
|  | 
 | ||||||
|  |       target = _.get(item, "Target.[0]", ""); | ||||||
|  |       if (target.startsWith("UNC")) { | ||||||
|  |         let parts = target.split("\\", 3); | ||||||
|  |         return `\\\\${parts[1]}\\${parts[2]}`; | ||||||
|  |       } else if (target.startsWith("Volume")) { | ||||||
|  |         return `\\\\?\\${target}`; | ||||||
|  |       } else { | ||||||
|  |         path = target; | ||||||
|  |       } | ||||||
|  |     } while (path); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetItem(localPath) { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  |     command = 'Get-Item "$Env:localpath" | ConvertTo-Json'; | ||||||
|  |     try { | ||||||
|  |       result = await this.ps.exec(command, { | ||||||
|  |         env: { | ||||||
|  |           localpath: localPath, | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |       return result.parsed; | ||||||
|  |     } catch (err) {} | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetSmbGlobalMapping(remotePath) { | ||||||
|  |     let command; | ||||||
|  |     // cannot have trailing slash nor a path
 | ||||||
|  |     // must be \\<server>\<share>
 | ||||||
|  |     remotePath = this.uncPathToShare(remotePath); | ||||||
|  |     command = | ||||||
|  |       "Get-SmbGlobalMapping -RemotePath $Env:smbremotepath | ConvertTo-Json"; | ||||||
|  |     try { | ||||||
|  |       return await this.ps.exec(command, { | ||||||
|  |         env: { | ||||||
|  |           smbremotepath: remotePath, | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |     } catch (err) {} | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Global in this context is allowed access by all users | ||||||
|  |    * | ||||||
|  |    * @param {*} remotePath | ||||||
|  |    * @param {*} username | ||||||
|  |    * @param {*} password | ||||||
|  |    */ | ||||||
|  |   async NewSmbGlobalMapping(remotePath, username, password) { | ||||||
|  |     let result; | ||||||
|  |     let command; | ||||||
|  |     // -UseWriteThrough $true
 | ||||||
|  |     // cannot have trailing slash nor a path
 | ||||||
|  |     // must be \\<server>\<share>
 | ||||||
|  |     remotePath = this.uncPathToShare(remotePath); | ||||||
|  |     command = | ||||||
|  |       "$PWord = ConvertTo-SecureString -String $Env:smbpassword -AsPlainText -Force;$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Env:smbuser, $PWord;New-SmbGlobalMapping -RemotePath $Env:smbremotepath -Credential $Credential -RequirePrivacy $true"; | ||||||
|  | 
 | ||||||
|  |     result = await this.GetSmbGlobalMapping(remotePath); | ||||||
|  |     if (!result) { | ||||||
|  |       await this.ps.exec(command, { | ||||||
|  |         env: { | ||||||
|  |           smbuser: username, | ||||||
|  |           smbpassword: password, | ||||||
|  |           smbremotepath: remotePath, | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async RemoveSmbGlobalMapping(remotePath) { | ||||||
|  |     let result; | ||||||
|  |     let command; | ||||||
|  |     // cannot have trailing slash nor a path
 | ||||||
|  |     // must be \\<server>\<share>
 | ||||||
|  |     remotePath = this.uncPathToShare(remotePath); | ||||||
|  |     command = "Remove-SmbGlobalMapping -RemotePath $Env:smbremotepath -Force"; | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |       result = await this.GetSmbGlobalMapping(remotePath); | ||||||
|  |       if (result) { | ||||||
|  |         await this.ps.exec(command, { | ||||||
|  |           env: { | ||||||
|  |             smbremotepath: remotePath, | ||||||
|  |           }, | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } while (result); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async NewSmbLink(remotePath, localPath) { | ||||||
|  |     let command; | ||||||
|  |     // trailing slash required
 | ||||||
|  |     // may include subdirectories on the share if desired
 | ||||||
|  |     if (!remotePath.endsWith("\\")) { | ||||||
|  |       remotePath = `${remotePath}\\`; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     command = | ||||||
|  |       "New-Item -ItemType SymbolicLink $Env:smblocalPath -Target $Env:smbremotepath"; | ||||||
|  |     await this.ps.exec(command, { | ||||||
|  |       env: { | ||||||
|  |         smblocalpath: localPath, | ||||||
|  |         smbremotepath: remotePath, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async NewIscsiTargetPortal(address, port) { | ||||||
|  |     let command; | ||||||
|  |     command = | ||||||
|  |       "New-IscsiTargetPortal -TargetPortalAddress ${Env:iscsi_tp_address} -TargetPortalPortNumber ${Env:iscsi_tp_port}"; | ||||||
|  |     await this.ps.exec(command, { | ||||||
|  |       env: { | ||||||
|  |         iscsi_tp_address: address, | ||||||
|  |         iscsi_tp_port: port, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async RemoveIscsiTargetPortalByTargetPortalAddress(targetPortalAddress) { | ||||||
|  |     let command; | ||||||
|  |     command = `Remove-IscsiTargetPortal -TargetPortalAddress ${targetPortalAddress} -Confirm:$false`; | ||||||
|  |     await this.ps.exec(command); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async RemoveIscsiTargetPortalByTargetPortalAddressTargetPortalPort( | ||||||
|  |     targetPortalAddress, | ||||||
|  |     targetPortalPort | ||||||
|  |   ) { | ||||||
|  |     let command; | ||||||
|  |     command = `Get-IscsiTargetPortal -TargetPortalAddress ${targetPortalAddress} -TargetPortalPortNumber ${targetPortalPort} | Remove-IscsiTargetPortal -Confirm:$false`; | ||||||
|  |     await this.ps.exec(command); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async IscsiTargetIsConnectedByPortalAddressPortalPort(address, port, iqn) { | ||||||
|  |     let sessions = await this.GetIscsiSessionsByTargetNodeAddress(iqn); | ||||||
|  |     for (let session of sessions) { | ||||||
|  |       let connections = await this.GetIscsiConnectionsByIscsiSessionIdentifier( | ||||||
|  |         session.SessionIdentifier | ||||||
|  |       ); | ||||||
|  |       for (let connection of connections) { | ||||||
|  |         if ( | ||||||
|  |           connection.TargetAddress == address && | ||||||
|  |           connection.TargetPortNumber == port | ||||||
|  |         ) { | ||||||
|  |           return true; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     //process.exit(1);
 | ||||||
|  | 
 | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * -IsMultipathEnabled | ||||||
|  |    * | ||||||
|  |    * @param {*} address | ||||||
|  |    * @param {*} port | ||||||
|  |    * @param {*} iqn | ||||||
|  |    * @param {*} authType | ||||||
|  |    * @param {*} chapUser | ||||||
|  |    * @param {*} chapSecret | ||||||
|  |    */ | ||||||
|  |   async ConnectIscsiTarget( | ||||||
|  |     address, | ||||||
|  |     port, | ||||||
|  |     iqn, | ||||||
|  |     authType, | ||||||
|  |     chapUser, | ||||||
|  |     chapSecret, | ||||||
|  |     multipath = false | ||||||
|  |   ) { | ||||||
|  |     let is_connected = | ||||||
|  |       await this.IscsiTargetIsConnectedByPortalAddressPortalPort( | ||||||
|  |         address, | ||||||
|  |         port, | ||||||
|  |         iqn | ||||||
|  |       ); | ||||||
|  |     if (is_connected) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let command; | ||||||
|  |     // -IsMultipathEnabled $([System.Convert]::ToBoolean(${Env:iscsi_is_multipath}))
 | ||||||
|  |     // -InitiatorPortalAddress
 | ||||||
|  |     command = | ||||||
|  |       "Connect-IscsiTarget -TargetPortalAddress ${Env:iscsi_tp_address} -TargetPortalPortNumber ${Env:iscsi_tp_port} -NodeAddress ${Env:iscsi_target_iqn} -AuthenticationType ${Env:iscsi_auth_type}"; | ||||||
|  | 
 | ||||||
|  |     if (chapUser) { | ||||||
|  |       command += " -ChapUsername ${Env:iscsi_chap_user}"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (chapSecret) { | ||||||
|  |       command += " -ChapSecret ${Env:iscsi_chap_secret}"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (multipath) { | ||||||
|  |       command += | ||||||
|  |         " -IsMultipathEnabled $([System.Convert]::ToBoolean(${Env:iscsi_is_multipath}))"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |       await this.ps.exec(command, { | ||||||
|  |         env: { | ||||||
|  |           iscsi_tp_address: address, | ||||||
|  |           iscsi_tp_port: port, | ||||||
|  |           iscsi_target_iqn: iqn, | ||||||
|  |           iscsi_auth_type: authType, | ||||||
|  |           iscsi_chap_user: chapUser, | ||||||
|  |           iscsi_chap_secret: chapSecret, | ||||||
|  |           iscsi_is_multipath: String(multipath), | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |     } catch (err) { | ||||||
|  |       let details = _.get(err, "stderr", ""); | ||||||
|  |       if ( | ||||||
|  |         !details.includes( | ||||||
|  |           "The target has already been logged in via an iSCSI session" | ||||||
|  |         ) | ||||||
|  |       ) { | ||||||
|  |         throw err; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetIscsiTargetsByTargetPortalAddressTargetPortalPort(address, port) { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = | ||||||
|  |       "Get-IscsiTargetPortal -TargetPortalAddress ${Env:iscsi_tp_address} -TargetPortalPortNumber ${Env:iscsi_tp_port} | Get-IscsiTarget | ConvertTo-Json"; | ||||||
|  |     result = await this.ps.exec(command, { | ||||||
|  |       env: { | ||||||
|  |         iscsi_tp_address: address, | ||||||
|  |         iscsi_tp_port: port, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  |     this.resultToArray(result); | ||||||
|  | 
 | ||||||
|  |     return result.parsed; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * This disconnects *all* sessions from the target | ||||||
|  |    * | ||||||
|  |    * @param {*} nodeAddress | ||||||
|  |    */ | ||||||
|  |   async DisconnectIscsiTargetByNodeAddress(nodeAddress) { | ||||||
|  |     let command; | ||||||
|  | 
 | ||||||
|  |     // https://github.com/PowerShell/PowerShell/issues/17306
 | ||||||
|  |     command = `Disconnect-IscsiTarget -NodeAddress ${nodeAddress.toLowerCase()} -Confirm:$false`; | ||||||
|  |     await this.ps.exec(command); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetIscsiConnectionsByIscsiSessionIdentifier(iscsiSessionIdentifier) { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = `Get-IscsiSession -SessionIdentifier ${iscsiSessionIdentifier} | Get-IscsiConnection | ConvertTo-Json`; | ||||||
|  |     result = await this.ps.exec(command); | ||||||
|  |     this.resultToArray(result); | ||||||
|  | 
 | ||||||
|  |     return result.parsed; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetIscsiSessions() { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = `Get-IscsiSession | ConvertTo-Json`; | ||||||
|  |     result = await this.ps.exec(command); | ||||||
|  |     this.resultToArray(result); | ||||||
|  | 
 | ||||||
|  |     return result.parsed; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetIscsiSessionsByDiskNumber(diskNumber) { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = `Get-Disk -Number ${diskNumber} | Get-IscsiSession | ConvertTo-Json`; | ||||||
|  |     result = await this.ps.exec(command); | ||||||
|  |     this.resultToArray(result); | ||||||
|  | 
 | ||||||
|  |     return result.parsed; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetIscsiSessionsByVolumeId(volumeId) { | ||||||
|  |     let sessions = []; | ||||||
|  |     let disks = await this.GetDisksByVolumeId(volumeId); | ||||||
|  |     for (let disk of disks) { | ||||||
|  |       let i_sessions = await this.GetIscsiSessionsByDiskNumber(disk.DiskNumber); | ||||||
|  |       sessions.push(...i_sessions); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return sessions; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetIscsiSessionsByTargetNodeAddress(targetNodeAddress) { | ||||||
|  |     let sessions = await this.GetIscsiSessions(); | ||||||
|  |     let r_sessions = []; | ||||||
|  |     // Where-Object { $_.TargetNodeAddress -eq ${targetNodeAddress} }
 | ||||||
|  |     for (let session of sessions) { | ||||||
|  |       if (session.TargetNodeAddress == targetNodeAddress) { | ||||||
|  |         r_sessions.push(session); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return r_sessions; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetIscsiSessionByIscsiConnectionIdentifier(iscsiConnectionIdentifier) { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = `Get-IscsiConnection -ConnectionIdentifier ${iscsiConnectionIdentifier} | Get-IscsiSession | ConvertTo-Json`; | ||||||
|  |     result = await this.ps.exec(command); | ||||||
|  | 
 | ||||||
|  |     return result.parsed; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetIscsiTargetPortalBySessionId(sessionId) { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = `Get-IscsiSession -SessionIdentifier ${sessionId} | Get-IscsiTargetPortal | ConvertTo-Json`; | ||||||
|  |     result = await this.ps.exec(command); | ||||||
|  | 
 | ||||||
|  |     return result.parsed; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async UpdateHostStorageCache() { | ||||||
|  |     let command; | ||||||
|  |     command = "Update-HostStorageCache"; | ||||||
|  |     await this.ps.exec(command); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetIscsiDisks() { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = "Get-iSCSISession | Get-Disk | ConvertTo-Json"; | ||||||
|  |     result = await this.ps.exec(command); | ||||||
|  |     this.resultToArray(result); | ||||||
|  | 
 | ||||||
|  |     return result.parsed; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetWin32DiskDrives() { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = "Get-WmiObject Win32_DiskDrive | ConvertTo-Json"; | ||||||
|  |     result = await this.ps.exec(command); | ||||||
|  |     this.resultToArray(result); | ||||||
|  | 
 | ||||||
|  |     return result.parsed; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetDiskLunByDiskNumber(diskNumber) { | ||||||
|  |     let result; | ||||||
|  |     result = await this.GetWin32DiskDrives(); | ||||||
|  |     for (let drive of result) { | ||||||
|  |       if (drive.Index == diskNumber) { | ||||||
|  |         return drive.SCSILogicalUnit; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetTargetDisks(address, port, iqn) { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     // this fails for synology for some reason
 | ||||||
|  |     //command =
 | ||||||
|  |     //  '$ErrorActionPreference = "Stop"; $tp = Get-IscsiTargetPortal -TargetPortalAddress ${Env:iscsi_tp_address} -TargetPortalPortNumber ${Env:iscsi_tp_port}; $t = $tp | Get-IscsiTarget | Where-Object { $_.NodeAddress -eq ${Env:iscsi_target_iqn} }; $s = Get-iSCSISession -IscsiTarget $t; $s | Get-Disk | ConvertTo-Json';
 | ||||||
|  | 
 | ||||||
|  |     command = | ||||||
|  |       '$ErrorActionPreference = "Stop"; $s = Get-iSCSISession | Where-Object { $_.TargetNodeAddress -eq ${Env:iscsi_target_iqn} }; $s | Get-Disk | ConvertTo-Json'; | ||||||
|  | 
 | ||||||
|  |     result = await this.ps.exec(command, { | ||||||
|  |       env: { | ||||||
|  |         iscsi_tp_address: address, | ||||||
|  |         iscsi_tp_port: port, | ||||||
|  |         iscsi_target_iqn: iqn, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  |     this.resultToArray(result); | ||||||
|  | 
 | ||||||
|  |     return result.parsed; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetTargetDisksByIqn(iqn) { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = | ||||||
|  |       '$ErrorActionPreference = "Stop"; $s = Get-iSCSISession | Where-Object { $_.TargetNodeAddress -eq ${Env:iscsi_target_iqn} }; $s | Get-Disk | ConvertTo-Json'; | ||||||
|  | 
 | ||||||
|  |     result = await this.ps.exec(command, { | ||||||
|  |       env: { | ||||||
|  |         iscsi_target_iqn: iqn, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  |     this.resultToArray(result); | ||||||
|  | 
 | ||||||
|  |     return result.parsed; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * This can be multiple when mpio is not configured properly and each | ||||||
|  |    * session creates a new disk | ||||||
|  |    * | ||||||
|  |    * @param {*} iqn | ||||||
|  |    * @param {*} lun | ||||||
|  |    * @returns | ||||||
|  |    */ | ||||||
|  |   async GetTargetDisksByIqnLun(iqn, lun) { | ||||||
|  |     let result; | ||||||
|  |     let dlun; | ||||||
|  |     let disks = []; | ||||||
|  | 
 | ||||||
|  |     result = await this.GetTargetDisksByIqn(iqn); | ||||||
|  |     for (let disk of result) { | ||||||
|  |       dlun = await this.GetDiskLunByDiskNumber(disk.DiskNumber); | ||||||
|  |       if (dlun == lun) { | ||||||
|  |         disks.push(disk); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return disks; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetDiskByDiskNumber(diskNumber) { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = `Get-Disk -Number ${diskNumber} | ConvertTo-Json`; | ||||||
|  |     result = await this.ps.exec(command); | ||||||
|  | 
 | ||||||
|  |     return result.parsed; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetDisks() { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = "Get-Disk | ConvertTo-Json"; | ||||||
|  |     result = await this.ps.exec(command); | ||||||
|  |     this.resultToArray(result); | ||||||
|  | 
 | ||||||
|  |     return result.parsed; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetPartitions() { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = "Get-Partition | ConvertTo-Json"; | ||||||
|  |     result = await this.ps.exec(command); | ||||||
|  |     this.resultToArray(result); | ||||||
|  | 
 | ||||||
|  |     return result.parsed; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetPartitionsByDiskNumber(diskNumber) { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = `Get-Disk -Number ${diskNumber} | Get-Partition | ConvertTo-Json`; | ||||||
|  |     result = await this.ps.exec(command); | ||||||
|  |     this.resultToArray(result); | ||||||
|  | 
 | ||||||
|  |     return result.parsed; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async DiskIsInitialized(diskNumber) { | ||||||
|  |     let disk = await this.GetDiskByDiskNumber(diskNumber); | ||||||
|  | 
 | ||||||
|  |     return disk.PartitionStyle != "RAW"; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async InitializeDisk(diskNumber) { | ||||||
|  |     let command; | ||||||
|  | 
 | ||||||
|  |     command = `Initialize-Disk -Number ${diskNumber} -PartitionStyle GPT`; | ||||||
|  |     await this.ps.exec(command); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async DiskHasBasicPartition(diskNumber) { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = `Get-Partition | Where DiskNumber -eq ${diskNumber} | Where Type -ne Reserved | ConvertTo-Json`; | ||||||
|  |     result = await this.ps.exec(command); | ||||||
|  |     this.resultToArray(result); | ||||||
|  | 
 | ||||||
|  |     return result.parsed.length > 0; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async NewPartition(diskNumber) { | ||||||
|  |     let command; | ||||||
|  | 
 | ||||||
|  |     command = `New-Partition -DiskNumber ${diskNumber} -UseMaximumSize`; | ||||||
|  |     await this.ps.exec(command); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async PartitionDisk(diskNumber) { | ||||||
|  |     let is_intialized; | ||||||
|  |     let has_basic_partition; | ||||||
|  | 
 | ||||||
|  |     is_intialized = await this.DiskIsInitialized(diskNumber); | ||||||
|  |     if (!is_intialized) { | ||||||
|  |       await this.InitializeDisk(diskNumber); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     has_basic_partition = await this.DiskHasBasicPartition(diskNumber); | ||||||
|  |     if (!has_basic_partition) { | ||||||
|  |       await this.NewPartition(diskNumber); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetLastPartitionByDiskNumber(diskNumber) { | ||||||
|  |     let partitions = await this.GetPartitionsByDiskNumber(diskNumber); | ||||||
|  |     let p; | ||||||
|  |     for (let partition of partitions) { | ||||||
|  |       if (!p) { | ||||||
|  |         p = partition; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (partition.PartitionNumber > p.PartitionNumber) { | ||||||
|  |         p = partition; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return p; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetVolumesByDiskNumber(diskNumber) { | ||||||
|  |     let command; | ||||||
|  |     command = `Get-Disk -Number ${diskNumber} | Get-Partition | Get-Volume | ConvertTo-Json`; | ||||||
|  |     result = await this.ps.exec(command); | ||||||
|  |     this.resultToArray(result); | ||||||
|  | 
 | ||||||
|  |     return result.parsed; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetVolumeByDiskNumberPartitionNumber(diskNumber, partitionNumber) { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = `Get-Disk -Number ${diskNumber} | Get-Partition -PartitionNumber ${partitionNumber} | Get-Volume | ConvertTo-Json`; | ||||||
|  |     result = await this.ps.exec(command); | ||||||
|  | 
 | ||||||
|  |     return result.parsed; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetVolumeByVolumeId(volumeId) { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = `Get-Volume -UniqueId \"${volumeId}\" -ErrorAction Stop | ConvertTo-Json`; | ||||||
|  |     result = await this.ps.exec(command); | ||||||
|  | 
 | ||||||
|  |     return result.parsed; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetPartitionsByVolumeId(volumeId) { | ||||||
|  |     let partitions = await this.GetPartitions(); | ||||||
|  |     let p = []; | ||||||
|  |     for (let partition of partitions) { | ||||||
|  |       let paths = _.get(partition, "AccessPaths", []); | ||||||
|  |       if (paths === null) { | ||||||
|  |         paths = []; | ||||||
|  |       } | ||||||
|  |       if (!Array.isArray(paths)) { | ||||||
|  |         paths = []; | ||||||
|  |       } | ||||||
|  |       if (paths.includes(volumeId)) { | ||||||
|  |         p.push(partition); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return p; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetDisksByVolumeId(volumeId) { | ||||||
|  |     let partitions = await this.GetPartitionsByVolumeId(volumeId); | ||||||
|  |     let diskNumbers = new Set(); | ||||||
|  |     for (let parition of partitions) { | ||||||
|  |       diskNumbers.add(parition.DiskNumber); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let disks = []; | ||||||
|  |     let disk; | ||||||
|  |     for (let diskNumber of diskNumbers) { | ||||||
|  |       disk = await this.GetDiskByDiskNumber(diskNumber); | ||||||
|  |       if (disk) { | ||||||
|  |         disks.push(disk); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return disks; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async VolumeIsFormatted(volumeId) { | ||||||
|  |     let volume = await this.GetVolumeByVolumeId(volumeId); | ||||||
|  |     let type = volume.FileSystemType || ""; | ||||||
|  |     type = type.toLowerCase().trim(); | ||||||
|  |     if (!type || type == "unknown") { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async VolumeIsIscsi(volumeId) { | ||||||
|  |     let disks = await this.GetDisksByVolumeId(volumeId); | ||||||
|  |     for (let disk of disks) { | ||||||
|  |       if (_.get(disk, "BusType", "").toLowerCase() == "iscsi") { | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async FormatVolume(volumeId) { | ||||||
|  |     let command; | ||||||
|  |     command = `Get-Volume -UniqueId \"${volumeId}\" | Format-Volume -FileSystem ntfs -Confirm:$false`; | ||||||
|  |     await this.ps.exec(command); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async ResizeVolume(volumeId, size = 0) { | ||||||
|  |     let command; | ||||||
|  |     let final_size; | ||||||
|  | 
 | ||||||
|  |     if (!size) { | ||||||
|  |       final_size = await this.GetVolumeMaxSize(volumeId); | ||||||
|  |     } else { | ||||||
|  |       final_size = size; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let current_size = await this.GetVolumeSize(volumeId); | ||||||
|  |     if (current_size >= final_size) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     command = `Get-Volume -UniqueId \"${volumeId}\" | Get-Partition | Resize-Partition -Size ${final_size}`; | ||||||
|  |     try { | ||||||
|  |       await this.ps.exec(command); | ||||||
|  |     } catch (err) { | ||||||
|  |       let details = _.get(err, "stderr", ""); | ||||||
|  |       if ( | ||||||
|  |         !details.includes( | ||||||
|  |           "The size of the extent is less than the minimum of 1MB" | ||||||
|  |         ) | ||||||
|  |       ) { | ||||||
|  |         throw err; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async GetVolumeMaxSize(volumeId) { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = `Get-Volume -UniqueId \"${volumeId}\" | Get-partition | Get-PartitionSupportedSize | Select SizeMax | ConvertTo-Json`; | ||||||
|  |     result = await this.ps.exec(command); | ||||||
|  |     return result.parsed.SizeMax; | ||||||
|  |   } | ||||||
|  |   async GetVolumeSize(volumeId) { | ||||||
|  |     let command; | ||||||
|  |     let result; | ||||||
|  | 
 | ||||||
|  |     command = `Get-Volume -UniqueId \"${volumeId}\" | Get-partition | ConvertTo-Json`; | ||||||
|  |     result = await this.ps.exec(command); | ||||||
|  | 
 | ||||||
|  |     return result.parsed.Size; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async MountVolume(volumeId, path) { | ||||||
|  |     let command; | ||||||
|  |     command = `Get-Volume -UniqueId \"${volumeId}\" | Get-Partition | Add-PartitionAccessPath -AccessPath ${path}`; | ||||||
|  | 
 | ||||||
|  |     await this.ps.exec(command); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async UnmountVolume(volumeId, path) { | ||||||
|  |     let command; | ||||||
|  | 
 | ||||||
|  |     // this errors if it does not have a drive letter
 | ||||||
|  |     if (!GeneralUtils.hasWindowsDriveLetter(path)) { | ||||||
|  |       let item = await this.GetItem(path); | ||||||
|  |       if (!item) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       path = item.FullName; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     command = `Get-Volume -UniqueId \"${volumeId}\" | Get-Partition | Remove-PartitionAccessPath -AccessPath ${path}`; | ||||||
|  | 
 | ||||||
|  |     await this.ps.exec(command); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async WriteVolumeCache(volumeId) { | ||||||
|  |     let command; | ||||||
|  |     command = `Get-Volume -UniqueId \"${volumeId}\" | Write-Volumecache`; | ||||||
|  | 
 | ||||||
|  |     await this.ps.exec(command); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports.Windows = Windows; | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue