Repository: kubernetes-csi/external-provisioner
Pull request #630 (2021-05-08 03:10:23)

Map PV access modes to CSI access modes based on driver capability
**What type of PR is this?**
/kind feature

**What this PR does / why we need it**:

See [KEP#2485](https://github.com/kubernetes/enhancements/pull/2489) for more details.

This PR introduces mappings between Kubernetes PV access modes and CSI access modes. If the CSI driver supports the `SINGLE_NODE_MULTI_WRITER` controller capability, map ReadWriteOnce to `SINGLE_NODE_MULTI_WRITER` and ReadWriteOncePod to `SINGLE_NODE_SINGLE_WRITER`. If the CSI driver does not support this controller capability, map ReadWriteOnce to `SINGLE_NODE_WRITER` (current behavior preserved for backwards compatibility).

**Which issue(s) this PR fixes**:
Fixes #

**Special notes for your reviewer**:

The CSI spec change [PR#476 is here](https://github.com/container-storage-interface/spec/pull/476) and just requires merging. Until then, I have a placeholder commit using my forked version of the CSI spec.

Similarly for K8s, the API change and feature addition [PR#102028 is here](https://github.com/kubernetes/kubernetes/pull/102028) and still is in the early stages of review. Until then, I have a placeholder commit using my forked version of the K8s API.

**Does this PR introduce a user-facing change?**:
```release-note
Adds mappings for PV access modes to new CSI access modes: `SINGLE_NODE_SINGLE_WRITER` and `SINGLE_NODE_MULTI_WRITER`.
```


k8s-ci-robot: Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with `/test all`
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-csi/external-provisioner/pull/630#" title="Author self-approved">chrishenzie</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **saad-ali** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @saad-ali` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-csi%2Fexternal-provisioner).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-csi/external-provisioner/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["saad-ali"]} -->
chrishenzie: /assign @msau42 
/assign @jingxu97 
k8s-ci-robot: @chrishenzie: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-kubernetes-csi-external-provisioner-1-21-on-kubernetes-1-21 | 1a185803bca1363472c8504c41a099bd58120ac6 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-csi_external-provisioner/630/pull-kubernetes-csi-external-provisioner-1-21-on-kubernetes-1-21/1393633527722938368) | `/test pull-kubernetes-csi-external-provisioner-1-21-on-kubernetes-1-21`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-csi&repo=external-provisioner&pr=630). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Achrishenzie). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-csi/external-provisioner/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-csi/external-provisioner/pull/630#" title="Author self-approved">chrishenzie</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please ask for approval from **msau42** after the PR has been reviewed.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-csi%2Fexternal-provisioner).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-csi/external-provisioner/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["msau42"]} -->
k8s-ci-robot: @chrishenzie: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-kubernetes-csi-external-provisioner-1-21-on-kubernetes-1-21 | 6b90a3d009aceee22a349e3db8c9d5b747122f49 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-csi_external-provisioner/630/pull-kubernetes-csi-external-provisioner-1-21-on-kubernetes-1-21/1394014139155746816) | `/test pull-kubernetes-csi-external-provisioner-1-21-on-kubernetes-1-21`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-csi&repo=external-provisioner&pr=630). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Achrishenzie). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-csi/external-provisioner/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @chrishenzie: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-kubernetes-csi-external-provisioner-1-21-on-kubernetes-1-21 | 6b90a3d009aceee22a349e3db8c9d5b747122f49 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-csi_external-provisioner/630/pull-kubernetes-csi-external-provisioner-1-21-on-kubernetes-1-21/1394014139155746816) | `/test pull-kubernetes-csi-external-provisioner-1-21-on-kubernetes-1-21`
pull-kubernetes-csi-external-provisioner-distributed-on-kubernetes-1-21 | 6b90a3d009aceee22a349e3db8c9d5b747122f49 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-csi_external-provisioner/630/pull-kubernetes-csi-external-provisioner-distributed-on-kubernetes-1-21/1397209256456884224) | `/test pull-kubernetes-csi-external-provisioner-distributed-on-kubernetes-1-21`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-csi&repo=external-provisioner&pr=630). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Achrishenzie). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-csi/external-provisioner/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
humblec: @chrishenzie ( just as a suggestion) it may be easy to have `table/matrix` representation of `ToCSIAccessMode` logic in source code comment or in the PR to make sure we are covering all the scenario.. 
k8s-ci-robot: @chrishenzie: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
chrishenzie: @humblec I've added the tables to https://github.com/kubernetes-csi/csi-lib-utils/pull/89, PTAL, I plan on depending on this library across our sidecars.
humblec: > @humblec I've added the tables to [kubernetes-csi/csi-lib-utils#89](https://github.com/kubernetes-csi/csi-lib-utils/pull/89), PTAL, I plan on depending on this library across our sidecars, I just need to update this one to do that.

@chrishenzie Thanks :+1: , it really helps !
chrishenzie: /hold

Rebased on #656 which is blocked on release of v1.22.
k8s-ci-robot: @chrishenzie: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
chrishenzie: /hold cancel

Patch:
diff --git a/go.mod b/go.mod
index 3ea4a987b..e1dab65db 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@ require (
 	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/google/uuid v1.2.0 // indirect
 	github.com/imdario/mergo v0.3.12 // indirect
-	github.com/kubernetes-csi/csi-lib-utils v0.9.1
+	github.com/kubernetes-csi/csi-lib-utils v0.10.0
 	github.com/kubernetes-csi/csi-test/v4 v4.0.2
 	github.com/kubernetes-csi/external-snapshotter/client/v3 v3.0.0
 	github.com/miekg/dns v1.1.40 // indirect
diff --git a/go.sum b/go.sum
index d11ac8765..4ea2ca255 100644
--- a/go.sum
+++ b/go.sum
@@ -85,7 +85,6 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
-github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
 github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
 github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -105,7 +104,6 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z
 github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
 github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
 github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
-github.com/container-storage-interface/spec v1.2.0/go.mod h1:6URME8mwIBbpVyZV93Ce5St17xBiQJQY67NDsuohiy4=
 github.com/container-storage-interface/spec v1.3.0/go.mod h1:6URME8mwIBbpVyZV93Ce5St17xBiQJQY67NDsuohiy4=
 github.com/container-storage-interface/spec v1.5.0 h1:lvKxe3uLgqQeVQcrnL2CPQKISoKjTJxojEs9cBk+HXo=
 github.com/container-storage-interface/spec v1.5.0/go.mod h1:8K96oQNkJ7pFcC2R9Z1ynGGBB1I93kcS6PGg3SsOk8s=
@@ -133,7 +131,6 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
-github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
 github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
 github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -191,7 +188,6 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
-github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -337,8 +333,8 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/kubernetes-csi/csi-lib-utils v0.9.1 h1:sGq6ifVujfMSkfTsMZip44Ttv8SDXvsBlFk9GdYl/b8=
-github.com/kubernetes-csi/csi-lib-utils v0.9.1/go.mod h1:8E2jVUX9j3QgspwHXa6LwyN7IHQDjW9jX3kwoWnSC+M=
+github.com/kubernetes-csi/csi-lib-utils v0.10.0 h1:Aqm8X81eCzzfH/bvIEqSWtcbK9HF9NbFk4d+le1snVA=
+github.com/kubernetes-csi/csi-lib-utils v0.10.0/go.mod h1:BmGZZB16L18+9+Lgg9YWwBKfNEHIDdgGfAyuW6p2NV0=
 github.com/kubernetes-csi/csi-test/v4 v4.0.2 h1:MNj94SFHOGK6lOy+yDgxI+zlFWaPcgByqBH3JZZGyZI=
 github.com/kubernetes-csi/csi-test/v4 v4.0.2/go.mod h1:z3FYigjLFAuzmFzKdHQr8gUPm5Xr4Du2twKcxfys0eI=
 github.com/kubernetes-csi/external-snapshotter/client/v3 v3.0.0 h1:OYDCOjVcx/5wNzlZ/At8otRibUlw0T6R0xOD31f32bw=
@@ -421,7 +417,6 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@@ -864,7 +859,6 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
-google.golang.org/grpc v1.29.0/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
 google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
 google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
@@ -953,7 +947,6 @@ k8s.io/client-go v0.22.0 h1:sD6o9O6tCwUKCENw8v+HFsuAbq2jCu8cWC61/ydwA50=
 k8s.io/client-go v0.22.0/go.mod h1:GUjIuXR5PiEv/RVK5OODUsm6eZk7wtSWZSaSJbpFdGg=
 k8s.io/code-generator v0.19.0/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk=
 k8s.io/code-generator v0.20.1/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg=
-k8s.io/component-base v0.19.0/go.mod h1:dKsY8BxkA+9dZIAh2aWJLL/UdASFDNtGYTCItL4LM7Y=
 k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk=
 k8s.io/component-base v0.20.2/go.mod h1:pzFtCiwe/ASD0iV7ySMu8SYVJjCapNM9bjvk7ptpKh0=
 k8s.io/component-base v0.22.0 h1:ZTmX8hUqH9T9gc0mM42O+KDgtwTYbVTt2MwmLP0eK8A=
diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go
index dfbb9d071..dd683bce6 100644
--- a/pkg/controller/controller.go
+++ b/pkg/controller/controller.go
@@ -26,6 +26,7 @@ import (
 	"time"
 
 	"github.com/container-storage-interface/spec/lib/go/csi"
+	"github.com/kubernetes-csi/csi-lib-utils/accessmodes"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
@@ -451,42 +452,53 @@ func getAccessTypeMount(fsType string, mountFlags []string) *csi.VolumeCapabilit
 	}
 }
 
-func getAccessMode(pvcAccessMode v1.PersistentVolumeAccessMode) *csi.VolumeCapability_AccessMode {
-	switch pvcAccessMode {
-	case v1.ReadWriteOnce:
-		return &csi.VolumeCapability_AccessMode{
-			Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
-		}
-	case v1.ReadWriteMany:
-		return &csi.VolumeCapability_AccessMode{
-			Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
-		}
-	case v1.ReadOnlyMany:
-		return &csi.VolumeCapability_AccessMode{
-			Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
-		}
-	default:
-		return nil
-	}
-}
-
 func getVolumeCapability(
 	claim *v1.PersistentVolumeClaim,
 	sc *storagev1.StorageClass,
 	pvcAccessMode v1.PersistentVolumeAccessMode,
 	fsType string,
-) *csi.VolumeCapability {
+	supportsSingleNodeMultiWriter bool,
+) (*csi.VolumeCapability, error) {
+	accessMode, err := accessmodes.ToCSIAccessMode([]v1.PersistentVolumeAccessMode{pvcAccessMode}, supportsSingleNodeMultiWriter)
+	if err != nil {
+		return nil, err
+	}
+
 	if util.CheckPersistentVolumeClaimModeBlock(claim) {
 		return &csi.VolumeCapability{
 			AccessType: getAccessTypeBlock(),
-			AccessMode: getAccessMode(pvcAccessMode),
-		}
+			AccessMode: &csi.VolumeCapability_AccessMode{
+				Mode: accessMode,
+			},
+		}, nil
 	}
 	return &csi.VolumeCapability{
 		AccessType: getAccessTypeMount(fsType, sc.MountOptions),
-		AccessMode: getAccessMode(pvcAccessMode),
+		AccessMode: &csi.VolumeCapability_AccessMode{
+			Mode: accessMode,
+		},
+	}, nil
+}
+
+func (p *csiProvisioner) getVolumeCapabilities(
+	claim *v1.PersistentVolumeClaim,
+	sc *storagev1.StorageClass,
+	fsType string,
+) ([]*csi.VolumeCapability, error) {
+	supportsSingleNodeMultiWriter := false
+	if p.controllerCapabilities[csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER] {
+		supportsSingleNodeMultiWriter = true
 	}
 
+	volumeCaps := make([]*csi.VolumeCapability, 0)
+	for _, pvcAccessMode := range claim.Spec.AccessModes {
+		volumeCap, err := getVolumeCapability(claim, sc, pvcAccessMode, fsType, supportsSingleNodeMultiWriter)
+		if err != nil {
+			return []*csi.VolumeCapability{}, err
+		}
+		volumeCaps = append(volumeCaps, volumeCap)
+	}
+	return volumeCaps, nil
 }
 
 type prepareProvisionResult struct {
@@ -581,10 +593,9 @@ func (p *csiProvisioner) prepareProvision(ctx context.Context, claim *v1.Persist
 	capacity := claim.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
 	volSizeBytes := capacity.Value()
 
-	// Get access mode
-	volumeCaps := make([]*csi.VolumeCapability, 0)
-	for _, pvcAccessMode := range claim.Spec.AccessModes {
-		volumeCaps = append(volumeCaps, getVolumeCapability(claim, sc, pvcAccessMode, fsType))
+	volumeCaps, err := p.getVolumeCapabilities(claim, sc, fsType)
+	if err != nil {
+		return nil, controller.ProvisioningFinished, err
 	}
 
 	// Create a CSI CreateVolumeRequest and Response
diff --git a/vendor/github.com/kubernetes-csi/csi-lib-utils/accessmodes/access_modes.go b/vendor/github.com/kubernetes-csi/csi-lib-utils/accessmodes/access_modes.go
new file mode 100644
index 000000000..beb91b2a1
--- /dev/null
+++ b/vendor/github.com/kubernetes-csi/csi-lib-utils/accessmodes/access_modes.go
@@ -0,0 +1,128 @@
+/*
+Copyright 2021 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package accessmodes
+
+import (
+	"fmt"
+
+	"github.com/container-storage-interface/spec/lib/go/csi"
+	v1 "k8s.io/api/core/v1"
+)
+
+// ToCSIAccessMode maps PersistentVolume access modes in Kubernetes to CSI
+// access modes. Which mapping is used depends if the driver supports the
+// SINGLE_NODE_MULTI_WRITER capability.
+func ToCSIAccessMode(pvAccessModes []v1.PersistentVolumeAccessMode, supportsSingleNodeMultiWriter bool) (csi.VolumeCapability_AccessMode_Mode, error) {
+	if supportsSingleNodeMultiWriter {
+		return toSingleNodeMultiWriterCapableCSIAccessMode(pvAccessModes)
+	}
+	return toCSIAccessMode(pvAccessModes)
+}
+
+// toCSIAccessMode maps PersistentVolume access modes in Kubernetes to CSI
+// access modes.
+//
+// +------------------+-------------------------+----------------------------------------+
+// |  K8s AccessMode  |     CSI AccessMode      |           Additional Details           |
+// +------------------+-------------------------+----------------------------------------+
+// | ReadWriteMany    | MULTI_NODE_MULTI_WRITER |                                        |
+// | ReadOnlyMany     | MULTI_NODE_READER_ONLY  | Cannot be combined with ReadWriteOnce  |
+// | ReadWriteOnce    | SINGLE_NODE_WRITER      | Cannot be combined with ReadOnlyMany   |
+// | ReadWriteOncePod | SINGLE_NODE_WRITER      | Cannot be combined with any AccessMode |
+// +------------------+-------------------------+----------------------------------------+
+func toCSIAccessMode(pvAccessModes []v1.PersistentVolumeAccessMode) (csi.VolumeCapability_AccessMode_Mode, error) {
+	m := uniqueAccessModes(pvAccessModes)
+
+	switch {
+	// This mapping exists to enable CSI drivers that lack the
+	// SINGLE_NODE_MULTI_WRITER capability to work with the
+	// ReadWriteOncePod access mode.
+	case m[v1.ReadWriteOncePod]:
+		if len(m) > 1 {
+			return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("Kubernetes does not support use of ReadWriteOncePod with other access modes on the same PersistentVolume")
+		}
+		return csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, nil
+
+	case m[v1.ReadWriteMany]:
+		// ReadWriteMany takes precedence, regardless of what other
+		// modes are set.
+		return csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, nil
+
+	case m[v1.ReadOnlyMany] && m[v1.ReadWriteOnce]:
+		// This is not possible in the CSI spec.
+		return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("CSI does not support ReadOnlyMany and ReadWriteOnce on the same PersistentVolume")
+
+	case m[v1.ReadOnlyMany]:
+		return csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY, nil
+
+	case m[v1.ReadWriteOnce]:
+		return csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, nil
+
+	default:
+		return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("unsupported AccessMode combination: %+v", pvAccessModes)
+	}
+}
+
+// toSingleNodeMultiWriterCapableCSIAccessMode maps PersistentVolume access
+// modes in Kubernetes to CSI access modes for drivers that support the
+// SINGLE_NODE_MULTI_WRITER capability.
+//
+// +------------------+---------------------------+----------------------------------------+
+// |  K8s AccessMode  |      CSI AccessMode       |           Additional Details           |
+// +------------------+---------------------------+----------------------------------------+
+// | ReadWriteMany    | MULTI_NODE_MULTI_WRITER   |                                        |
+// | ReadOnlyMany     | MULTI_NODE_READER_ONLY    | Cannot be combined with ReadWriteOnce  |
+// | ReadWriteOnce    | SINGLE_NODE_MULTI_WRITER  | Cannot be combined with ReadOnlyMany   |
+// | ReadWriteOncePod | SINGLE_NODE_SINGLE_WRITER | Cannot be combined with any AccessMode |
+// +------------------+---------------------------+----------------------------------------+
+func toSingleNodeMultiWriterCapableCSIAccessMode(pvAccessModes []v1.PersistentVolumeAccessMode) (csi.VolumeCapability_AccessMode_Mode, error) {
+	m := uniqueAccessModes(pvAccessModes)
+
+	switch {
+	case m[v1.ReadWriteOncePod]:
+		if len(m) > 1 {
+			return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("Kubernetes does not support use of ReadWriteOncePod with other access modes on the same PersistentVolume")
+		}
+		return csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER, nil
+
+	case m[v1.ReadWriteMany]:
+		// ReadWriteMany trumps everything, regardless of what other
+		// modes are set.
+		return csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, nil
+
+	case m[v1.ReadOnlyMany] && m[v1.ReadWriteOnce]:
+		// This is not possible in the CSI spec.
+		return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("CSI does not support ReadOnlyMany and ReadWriteOnce on the same PersistentVolume")
+
+	case m[v1.ReadOnlyMany]:
+		return csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY, nil
+
+	case m[v1.ReadWriteOnce]:
+		return csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER, nil
+
+	default:
+		return csi.VolumeCapability_AccessMode_UNKNOWN, fmt.Errorf("unsupported AccessMode combination: %+v", pvAccessModes)
+	}
+}
+
+func uniqueAccessModes(pvAccessModes []v1.PersistentVolumeAccessMode) map[v1.PersistentVolumeAccessMode]bool {
+	m := map[v1.PersistentVolumeAccessMode]bool{}
+	for _, mode := range pvAccessModes {
+		m[mode] = true
+	}
+	return m
+}
diff --git a/vendor/github.com/kubernetes-csi/csi-lib-utils/connection/connection.go b/vendor/github.com/kubernetes-csi/csi-lib-utils/connection/connection.go
index fbd8d37b5..ad37321e8 100644
--- a/vendor/github.com/kubernetes-csi/csi-lib-utils/connection/connection.go
+++ b/vendor/github.com/kubernetes-csi/csi-lib-utils/connection/connection.go
@@ -84,7 +84,8 @@ func ExitOnConnectionLoss() func() bool {
 		if err := ioutil.WriteFile(terminationLogPath, []byte(terminationMsg), 0644); err != nil {
 			klog.Errorf("%s: %s", terminationLogPath, err)
 		}
-		klog.Fatalf(terminationMsg)
+		klog.Exit(terminationMsg)
+		// Not reached.
 		return false
 	}
 }
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 7e6d5ca5a..31cc739a6 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -55,8 +55,9 @@ github.com/imdario/mergo
 github.com/inconshreveable/mousetrap
 # github.com/json-iterator/go v1.1.11
 github.com/json-iterator/go
-# github.com/kubernetes-csi/csi-lib-utils v0.9.1
+# github.com/kubernetes-csi/csi-lib-utils v0.10.0
 ## explicit
+github.com/kubernetes-csi/csi-lib-utils/accessmodes
 github.com/kubernetes-csi/csi-lib-utils/connection
 github.com/kubernetes-csi/csi-lib-utils/leaderelection
 github.com/kubernetes-csi/csi-lib-utils/metrics


Test patch:
diff --git a/pkg/controller/controller_test.go b/pkg/controller/controller_test.go
index d0479e222..751ac0aad 100644
--- a/pkg/controller/controller_test.go
+++ b/pkg/controller/controller_test.go
@@ -462,6 +462,15 @@ func provisionCapabilities() (rpc.PluginCapabilitySet, rpc.ControllerCapabilityS
 		}
 }
 
+func provisionWithSingleNodeMultiWriterCapabilities() (rpc.PluginCapabilitySet, rpc.ControllerCapabilitySet) {
+	return rpc.PluginCapabilitySet{
+			csi.PluginCapability_Service_CONTROLLER_SERVICE: true,
+		}, rpc.ControllerCapabilitySet{
+			csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME:     true,
+			csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER: true,
+		}
+}
+
 func provisionFromSnapshotCapabilities() (rpc.PluginCapabilitySet, rpc.ControllerCapabilitySet) {
 	return rpc.PluginCapabilitySet{
 			csi.PluginCapability_Service_CONTROLLER_SERVICE: true,
@@ -805,26 +814,27 @@ func TestGetSecretReference(t *testing.T) {
 }
 
 type provisioningTestcase struct {
-	capacity           int64 // if zero, default capacity, otherwise available bytes
-	volOpts            controller.ProvisionOptions
-	notNilSelector     bool
-	makeVolumeNameErr  bool
-	getSecretRefErr    bool
-	getCredentialsErr  bool
-	volWithLessCap     bool
-	volWithZeroCap     bool
-	expectedPVSpec     *pvSpec
-	clientSetObjects   []runtime.Object
-	createVolumeError  error
-	expectErr          bool
-	expectState        controller.ProvisioningState
-	expectCreateVolDo  func(t *testing.T, ctx context.Context, req *csi.CreateVolumeRequest)
-	withExtraMetadata  bool
-	skipCreateVolume   bool
-	deploymentNode     string // fake distributed provisioning with this node as host
-	immediateBinding   bool   // enable immediate binding support for distributed provisioning
-	expectSelectedNode string // a specific selected-node of the PVC in the apiserver after the test, same as before if empty
-	expectNoProvision  bool   // if true, then ShouldProvision should return false
+	capacity                      int64 // if zero, default capacity, otherwise available bytes
+	volOpts                       controller.ProvisionOptions
+	notNilSelector                bool
+	makeVolumeNameErr             bool
+	getSecretRefErr               bool
+	getCredentialsErr             bool
+	volWithLessCap                bool
+	volWithZeroCap                bool
+	expectedPVSpec                *pvSpec
+	clientSetObjects              []runtime.Object
+	createVolumeError             error
+	expectErr                     bool
+	expectState                   controller.ProvisioningState
+	expectCreateVolDo             func(t *testing.T, ctx context.Context, req *csi.CreateVolumeRequest)
+	withExtraMetadata             bool
+	skipCreateVolume              bool
+	deploymentNode                string // fake distributed provisioning with this node as host
+	immediateBinding              bool   // enable immediate binding support for distributed provisioning
+	expectSelectedNode            string // a specific selected-node of the PVC in the apiserver after the test, same as before if empty
+	expectNoProvision             bool   // if true, then ShouldProvision should return false
+	supportsSingleNodeMultiWriter bool   // if true, then provision with single node multi writer capabilities
 }
 
 type provisioningFSTypeTestcase struct {
@@ -1258,7 +1268,7 @@ func provisionTestcases() (int64, map[string]provisioningTestcase) {
 			},
 			expectState: controller.ProvisioningFinished,
 		},
-		"provision with access mode single writer": {
+		"provision with access mode single node writer": {
 			volOpts: controller.ProvisionOptions{
 				StorageClass: &storagev1.StorageClass{
 					ReclaimPolicy: &deletePolicy,
@@ -1310,6 +1320,112 @@ func provisionTestcases() (int64, map[string]provisioningTestcase) {
 			},
 			expectState: controller.ProvisioningFinished,
 		},
+		"provision with access mode single node writer and single node multi writer capability": {
+			supportsSingleNodeMultiWriter: true,
+			volOpts: controller.ProvisionOptions{
+				StorageClass: &storagev1.StorageClass{
+					ReclaimPolicy: &deletePolicy,
+					Parameters:    map[string]string{},
+				},
+				PVName: "test-name",
+				PVC: &v1.PersistentVolumeClaim{
+					ObjectMeta: metav1.ObjectMeta{
+						UID:         "testid",
+						Annotations: driverNameAnnotation,
+					},
+					Spec: v1.PersistentVolumeClaimSpec{
+						Selector: nil,
+						Resources: v1.ResourceRequirements{
+							Requests: v1.ResourceList{
+								v1.ResourceName(v1.ResourceStorage): resource.MustParse(strconv.FormatInt(requestedBytes, 10)),
+							},
+						},
+						AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
+					},
+				},
+			},
+			expectedPVSpec: &pvSpec{
+				Name:          "test-testi",
+				ReclaimPolicy: v1.PersistentVolumeReclaimDelete,
+				AccessModes:   []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
+				Capacity: v1.ResourceList{
+					v1.ResourceName(v1.ResourceStorage): bytesToQuantity(requestedBytes),
+				},
+				CSIPVS: &v1.CSIPersistentVolumeSource{
+					Driver:       "test-driver",
+					VolumeHandle: "test-volume-id",
+					FSType:       "ext4",
+					VolumeAttributes: map[string]string{
+						"storage.kubernetes.io/csiProvisionerIdentity": "test-provisioner",
+					},
+				},
+			},
+			expectCreateVolDo: func(t *testing.T, ctx context.Context, req *csi.CreateVolumeRequest) {
+				if len(req.GetVolumeCapabilities()) != 1 {
+					t.Errorf("Incorrect length in volume capabilities")
+				}
+				if req.GetVolumeCapabilities()[0].GetAccessMode() == nil {
+					t.Errorf("Expected access mode to be set")
+				}
+				if req.GetVolumeCapabilities()[0].GetAccessMode().GetMode() != csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER {
+					t.Errorf("Expected single_node_multi_writer")
+				}
+			},
+			expectState: controller.ProvisioningFinished,
+		},
+		"provision with access mode single node single writer and single node multi writer capability": {
+			supportsSingleNodeMultiWriter: true,
+			volOpts: controller.ProvisionOptions{
+				StorageClass: &storagev1.StorageClass{
+					ReclaimPolicy: &deletePolicy,
+					Parameters:    map[string]string{},
+				},
+				PVName: "test-name",
+				PVC: &v1.PersistentVolumeClaim{
+					ObjectMeta: metav1.ObjectMeta{
+						UID:         "testid",
+						Annotations: driverNameAnnotation,
+					},
+					Spec: v1.PersistentVolumeClaimSpec{
+						Selector: nil,
+						Resources: v1.ResourceRequirements{
+							Requests: v1.ResourceList{
+								v1.ResourceName(v1.ResourceStorage): resource.MustParse(strconv.FormatInt(requestedBytes, 10)),
+							},
+						},
+						AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod},
+					},
+				},
+			},
+			expectedPVSpec: &pvSpec{
+				Name:          "test-testi",
+				ReclaimPolicy: v1.PersistentVolumeReclaimDelete,
+				AccessModes:   []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod},
+				Capacity: v1.ResourceList{
+					v1.ResourceName(v1.ResourceStorage): bytesToQuantity(requestedBytes),
+				},
+				CSIPVS: &v1.CSIPersistentVolumeSource{
+					Driver:       "test-driver",
+					VolumeHandle: "test-volume-id",
+					FSType:       "ext4",
+					VolumeAttributes: map[string]string{
+						"storage.kubernetes.io/csiProvisionerIdentity": "test-provisioner",
+					},
+				},
+			},
+			expectCreateVolDo: func(t *testing.T, ctx context.Context, req *csi.CreateVolumeRequest) {
+				if len(req.GetVolumeCapabilities()) != 1 {
+					t.Errorf("Incorrect length in volume capabilities")
+				}
+				if req.GetVolumeCapabilities()[0].GetAccessMode() == nil {
+					t.Errorf("Expected access mode to be set")
+				}
+				if req.GetVolumeCapabilities()[0].GetAccessMode().GetMode() != csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER {
+					t.Errorf("Expected single_node_multi_writer")
+				}
+			},
+			expectState: controller.ProvisioningFinished,
+		},
 		"provision with multiple access modes": {
 			volOpts: controller.ProvisionOptions{
 				StorageClass: &storagev1.StorageClass{
@@ -2299,7 +2415,14 @@ func runProvisionTest(t *testing.T, tc provisioningTestcase, requestedBytes int6
 		}
 	}
 
-	pluginCaps, controllerCaps := provisionCapabilities()
+	var pluginCaps rpc.PluginCapabilitySet
+	var controllerCaps rpc.ControllerCapabilitySet
+	if tc.supportsSingleNodeMultiWriter {
+		pluginCaps, controllerCaps = provisionWithSingleNodeMultiWriterCapabilities()
+	} else {
+		pluginCaps, controllerCaps = provisionCapabilities()
+	}
+
 	csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn,
 		nil, provisionDriverName, pluginCaps, controllerCaps, supportsMigrationFromInTreePluginName, false, true, csitrans.New(), scInformer.Lister(), csiNodeInformer.Lister(), nodeInformer.Lister(), nil, nil, tc.withExtraMetadata, defaultfsType, nodeDeployment)