Repository: kubernetes-sigs/gcp-compute-persistent-disk-csi-driver
Pull request #1254 (2023-06-07 06:59:18)

Add force attach to storage class
/kind feature

**What this PR does / why we need it**:
This adds an alpha-level storage class parameter to always perform a force attach. This is only valid for RePD disks (although this is not validated --- using this parameter with zonal disks will cause attaches to fail).

This parameter is not guaranteed to have continued support or to be stable in any way. In addition, using this can cause data loss.

```release-note
Add alpha-level force attach storage class parameter.
```


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 **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/gcp-compute-persistent-disk-csi-driver/pull/1254#" title="Author self-approved">mattcary</a>*

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

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

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

- ~~[OWNERS](https://github.com/kubernetes-sigs/gcp-compute-persistent-disk-csi-driver/blob/master/OWNERS)~~ [mattcary]

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":[]} -->
mattcary: /test all
mattcary: /test all
k8s-ci-robot: @mattcary: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-gcp-compute-persistent-disk-csi-driver-e2e | 6302b0e51123584206065348e24983d8d86fe06e | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-e2e/1666342546126671872/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-compute-persistent-disk-csi-driver&pr=1254). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amattcary). 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-sigs/gcp-compute-persistent-disk-csi-driver/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: @mattcary: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-gcp-compute-persistent-disk-csi-driver-e2e | 6302b0e51123584206065348e24983d8d86fe06e | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-e2e/1666342546126671872/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-e2e`
pull-gcp-compute-persistent-disk-csi-driver-kubernetes-integration | 6302b0e51123584206065348e24983d8d86fe06e | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-kubernetes-integration/1666342546332192768/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-kubernetes-integration`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-compute-persistent-disk-csi-driver&pr=1254). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amattcary). 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-sigs/gcp-compute-persistent-disk-csi-driver/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 -->
mattcary: /test all
k8s-ci-robot: @mattcary: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-gcp-compute-persistent-disk-csi-driver-kubernetes-integration | 6302b0e51123584206065348e24983d8d86fe06e | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-kubernetes-integration/1666342546332192768/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-kubernetes-integration`
pull-gcp-compute-persistent-disk-csi-driver-e2e | 6302b0e51123584206065348e24983d8d86fe06e | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-e2e/1666495670162296832/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-compute-persistent-disk-csi-driver&pr=1254). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amattcary). 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-sigs/gcp-compute-persistent-disk-csi-driver/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: @mattcary: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-gcp-compute-persistent-disk-csi-driver-kubernetes-integration | 6302b0e51123584206065348e24983d8d86fe06e | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-kubernetes-integration/1666342546332192768/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-kubernetes-integration`
pull-gcp-compute-persistent-disk-csi-driver-e2e | 6302b0e51123584206065348e24983d8d86fe06e | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-e2e/1666495670162296832/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-e2e`
pull-gcp-compute-persistent-disk-csi-driver-verify | d6917fef9b99f77057bcce6e5b111f77ec4028e4 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-verify/1666570777463361536/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-compute-persistent-disk-csi-driver&pr=1254). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amattcary). 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-sigs/gcp-compute-persistent-disk-csi-driver/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 -->
msau42: What's the plan for detach? Should repd always use force attach regardless?
mattcary: > What's the plan for detach? Should repd always use force attach regardless?

We don't know. For the PoC of an availability controller we're building we will be doing force attach unconditionally, because as far as we can tell it takes too long to figure out if it's necessary or not (this is what cloud sql does for instance).

But we have to evaluate. This is why I have the scary alpha/unsafe name, I think there's a good chance well figure out a better solution after we work through real-world options. I expect that this parameter will be removed & replaced with a better one.
msau42: I'm wondering for the purpose of POC you can just have a feature gate, instead of an opt-in storageclass parameter
mattcary: > I'm wondering for the purpose of POC you can just have a feature gate, instead of an opt-in storageclass parameter that ends up as an immutable field on the PV object

Do you mean a command-line flag that would enable this for the driver per-cluster? We need to enable this only on certain volumes, we'll be testing it in clusters where we only want some workloads to use this as we don't want to risk data loss by force-attaching cavalierly. 
k8s-ci-robot: @mattcary: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-gcp-compute-persistent-disk-csi-driver-unit | f6494ffedefecd4bfb83ad55ab7c7b15f0c37348 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-unit/1666887078475993088/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-unit`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-compute-persistent-disk-csi-driver&pr=1254). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amattcary). 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-sigs/gcp-compute-persistent-disk-csi-driver/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: @mattcary: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-gcp-compute-persistent-disk-csi-driver-unit | f6494ffedefecd4bfb83ad55ab7c7b15f0c37348 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-unit/1666887078475993088/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-unit`
pull-gcp-compute-persistent-disk-csi-driver-sanity | f6494ffedefecd4bfb83ad55ab7c7b15f0c37348 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-sanity/1666887078438244352/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-sanity`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-compute-persistent-disk-csi-driver&pr=1254). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amattcary). 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-sigs/gcp-compute-persistent-disk-csi-driver/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: @mattcary: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-gcp-compute-persistent-disk-csi-driver-unit | f6494ffedefecd4bfb83ad55ab7c7b15f0c37348 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-unit/1666887078475993088/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-unit`
pull-gcp-compute-persistent-disk-csi-driver-sanity | f6494ffedefecd4bfb83ad55ab7c7b15f0c37348 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-sanity/1666887078438244352/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-sanity`
pull-gcp-compute-persistent-disk-csi-driver-verify | f6494ffedefecd4bfb83ad55ab7c7b15f0c37348 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-verify/1666887078534713344/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-compute-persistent-disk-csi-driver&pr=1254). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amattcary). 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-sigs/gcp-compute-persistent-disk-csi-driver/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: @mattcary: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-gcp-compute-persistent-disk-csi-driver-unit | f6494ffedefecd4bfb83ad55ab7c7b15f0c37348 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-unit/1666887078475993088/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-unit`
pull-gcp-compute-persistent-disk-csi-driver-sanity | f6494ffedefecd4bfb83ad55ab7c7b15f0c37348 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-sanity/1666887078438244352/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-sanity`
pull-gcp-compute-persistent-disk-csi-driver-verify | f6494ffedefecd4bfb83ad55ab7c7b15f0c37348 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-verify/1666887078534713344/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-verify`
pull-gcp-compute-persistent-disk-csi-driver-e2e | f6494ffedefecd4bfb83ad55ab7c7b15f0c37348 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-e2e/1666887078375329792/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-compute-persistent-disk-csi-driver&pr=1254). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amattcary). 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-sigs/gcp-compute-persistent-disk-csi-driver/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: @mattcary: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-gcp-compute-persistent-disk-csi-driver-unit | f6494ffedefecd4bfb83ad55ab7c7b15f0c37348 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-unit/1666887078475993088/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-unit`
pull-gcp-compute-persistent-disk-csi-driver-sanity | f6494ffedefecd4bfb83ad55ab7c7b15f0c37348 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-sanity/1666887078438244352/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-sanity`
pull-gcp-compute-persistent-disk-csi-driver-verify | f6494ffedefecd4bfb83ad55ab7c7b15f0c37348 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-verify/1666887078534713344/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-verify`
pull-gcp-compute-persistent-disk-csi-driver-e2e | f6494ffedefecd4bfb83ad55ab7c7b15f0c37348 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-e2e/1666887078375329792/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-e2e`
pull-gcp-compute-persistent-disk-csi-driver-kubernetes-integration | f6494ffedefecd4bfb83ad55ab7c7b15f0c37348 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-kubernetes-integration/1666887078589239296/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-kubernetes-integration`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-compute-persistent-disk-csi-driver&pr=1254). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amattcary). 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-sigs/gcp-compute-persistent-disk-csi-driver/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: @mattcary: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-gcp-compute-persistent-disk-csi-driver-kubernetes-integration | f6494ffedefecd4bfb83ad55ab7c7b15f0c37348 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-kubernetes-integration/1666887078589239296/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-kubernetes-integration`
pull-gcp-compute-persistent-disk-csi-driver-e2e | 6e7feb7352282334aa5891a1c74c8f81e92644fc | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-e2e/1666892019735203840/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-compute-persistent-disk-csi-driver&pr=1254). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amattcary). 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-sigs/gcp-compute-persistent-disk-csi-driver/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 -->
mattcary: Hmm, the e2e test succeeded locally after fixing the request-vs-query param problem. I'll have to dig.
k8s-ci-robot: @mattcary: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-gcp-compute-persistent-disk-csi-driver-e2e | 6e7feb7352282334aa5891a1c74c8f81e92644fc | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-e2e/1666892019735203840/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-e2e`
pull-gcp-compute-persistent-disk-csi-driver-verify | 4574209cb891a4fa356a644bb24577b55e808578 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-compute-persistent-disk-csi-driver/1254/pull-gcp-compute-persistent-disk-csi-driver-verify/1668057349526392832/) | true | `/test pull-gcp-compute-persistent-disk-csi-driver-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-compute-persistent-disk-csi-driver&pr=1254). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amattcary). 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-sigs/gcp-compute-persistent-disk-csi-driver/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 -->

Patch:
diff --git a/pkg/common/parameters.go b/pkg/common/parameters.go
index e7f0104d..f91d915e 100644
--- a/pkg/common/parameters.go
+++ b/pkg/common/parameters.go
@@ -29,6 +29,7 @@ const (
 	ParameterKeyLabels                        = "labels"
 	ParameterKeyProvisionedIOPSOnCreate       = "provisioned-iops-on-create"
 	ParameterKeyProvisionedThroughputOnCreate = "provisioned-throughput-on-create"
+	ParameterAvailabilityClass                = "availability-class"
 
 	// Parameters for VolumeSnapshotClass
 	ParameterKeyStorageLocations = "storage-locations"
@@ -38,6 +39,10 @@ const (
 	DiskImageType                = "images"
 	replicationTypeNone          = "none"
 
+	// Parameters for AvailabilityClass
+	ParameterNoAvailabilityClass       = "none"
+	ParameterRegionalHardFailoverClass = "regional-hard-failover"
+
 	// Keys for PV and PVC parameters as reported by external-provisioner
 	ParameterKeyPVCName      = "csi.storage.k8s.io/pvc/name"
 	ParameterKeyPVCNamespace = "csi.storage.k8s.io/pvc/namespace"
@@ -83,6 +88,8 @@ type DiskParameters struct {
 	// Values: {int64}
 	// Default: none
 	ProvisionedThroughputOnCreate int64
+	// Default: false
+	ForceAttach bool
 }
 
 // SnapshotParameters contains normalized and defaulted parameters for snapshots
@@ -155,6 +162,14 @@ func ExtractAndDefaultParameters(parameters map[string]string, driverName string
 				return p, fmt.Errorf("parameters contain invalid provisionedThroughputOnCreate parameter: %w", err)
 			}
 			p.ProvisionedThroughputOnCreate = paramProvisionedThroughputOnCreate
+		case ParameterAvailabilityClass:
+			paramAvailabilityClass, err := ConvertStringToAvailabilityClass(v)
+			if err != nil {
+				return p, fmt.Errorf("parameters contain invalid availability class parameter: %w", err)
+			}
+			if paramAvailabilityClass == ParameterRegionalHardFailoverClass {
+				p.ForceAttach = true
+			}
 		default:
 			return p, fmt.Errorf("parameters contains invalid option %q", k)
 		}
diff --git a/pkg/common/utils.go b/pkg/common/utils.go
index af4e5d21..26ecb5a8 100644
--- a/pkg/common/utils.go
+++ b/pkg/common/utils.go
@@ -284,6 +284,28 @@ func ConvertMiStringToInt64(str string) (int64, error) {
 	return volumehelpers.RoundUpToMiB(quantity)
 }
 
+// ConvertStringToBool converts a string to a boolean.
+func ConvertStringToBool(str string) (bool, error) {
+	switch strings.ToLower(str) {
+	case "true":
+		return true, nil
+	case "false":
+		return false, nil
+	}
+	return false, fmt.Errorf("Unexpected boolean string %s", str)
+}
+
+// ConvertStringToAvailabilityClass converts a string to an availability class string.
+func ConvertStringToAvailabilityClass(str string) (string, error) {
+	switch strings.ToLower(str) {
+	case ParameterNoAvailabilityClass:
+		return ParameterNoAvailabilityClass, nil
+	case ParameterRegionalHardFailoverClass:
+		return ParameterRegionalHardFailoverClass, nil
+	}
+	return "", fmt.Errorf("Unexpected boolean string %s", str)
+}
+
 // ParseMachineType returns an extracted machineType from a URL, or empty if not found.
 // machineTypeUrl: Full or partial URL of the machine type resource, in the format:
 //
diff --git a/pkg/gce-cloud-provider/compute/fake-gce.go b/pkg/gce-cloud-provider/compute/fake-gce.go
index e6731140..82a5e18f 100644
--- a/pkg/gce-cloud-provider/compute/fake-gce.go
+++ b/pkg/gce-cloud-provider/compute/fake-gce.go
@@ -246,15 +246,16 @@ func (cloud *FakeCloudProvider) DeleteDisk(ctx context.Context, project string,
 	return nil
 }
 
-func (cloud *FakeCloudProvider) AttachDisk(ctx context.Context, project string, volKey *meta.Key, readWrite, diskType, instanceZone, instanceName string) error {
+func (cloud *FakeCloudProvider) AttachDisk(ctx context.Context, project string, volKey *meta.Key, readWrite, diskType, instanceZone, instanceName string, forceAttach bool) error {
 	source := cloud.GetDiskSourceURI(project, volKey)
 
 	attachedDiskV1 := &computev1.AttachedDisk{
-		DeviceName: volKey.Name,
-		Kind:       diskKind,
-		Mode:       readWrite,
-		Source:     source,
-		Type:       diskType,
+		DeviceName:  volKey.Name,
+		Kind:        diskKind,
+		Mode:        readWrite,
+		Source:      source,
+		Type:        diskType,
+		ForceAttach: forceAttach,
 	}
 	instance, ok := cloud.instances[instanceName]
 	if !ok {
@@ -538,14 +539,14 @@ func (cloud *FakeBlockingCloudProvider) DetachDisk(ctx context.Context, project,
 	return cloud.FakeCloudProvider.DetachDisk(ctx, project, deviceName, instanceZone, instanceName)
 }
 
-func (cloud *FakeBlockingCloudProvider) AttachDisk(ctx context.Context, project string, volKey *meta.Key, readWrite, diskType, instanceZone, instanceName string) error {
+func (cloud *FakeBlockingCloudProvider) AttachDisk(ctx context.Context, project string, volKey *meta.Key, readWrite, diskType, instanceZone, instanceName string, forceAttach bool) error {
 	execute := make(chan Signal)
 	cloud.ReadyToExecute <- execute
 	val := <-execute
 	if val.ReportError {
 		return fmt.Errorf("force mock error for AttachDisk: volkey %s", volKey)
 	}
-	return cloud.FakeCloudProvider.AttachDisk(ctx, project, volKey, readWrite, diskType, instanceZone, instanceName)
+	return cloud.FakeCloudProvider.AttachDisk(ctx, project, volKey, readWrite, diskType, instanceZone, instanceName, forceAttach)
 }
 
 func notFoundError() *googleapi.Error {
diff --git a/pkg/gce-cloud-provider/compute/gce-compute.go b/pkg/gce-cloud-provider/compute/gce-compute.go
index 6631855a..82f15431 100644
--- a/pkg/gce-cloud-provider/compute/gce-compute.go
+++ b/pkg/gce-cloud-provider/compute/gce-compute.go
@@ -92,7 +92,7 @@ type GCECompute interface {
 	ValidateExistingDisk(ctx context.Context, disk *CloudDisk, params common.DiskParameters, reqBytes, limBytes int64, multiWriter bool) error
 	InsertDisk(ctx context.Context, project string, volKey *meta.Key, params common.DiskParameters, capBytes int64, capacityRange *csi.CapacityRange, replicaZones []string, snapshotID string, volumeContentSourceVolumeID string, multiWriter bool) error
 	DeleteDisk(ctx context.Context, project string, volumeKey *meta.Key) error
-	AttachDisk(ctx context.Context, project string, volKey *meta.Key, readWrite, diskType, instanceZone, instanceName string) error
+	AttachDisk(ctx context.Context, project string, volKey *meta.Key, readWrite, diskType, instanceZone, instanceName string, forceAttach bool) error
 	DetachDisk(ctx context.Context, project, deviceName, instanceZone, instanceName string) error
 	GetDiskSourceURI(project string, volKey *meta.Key) string
 	GetDiskTypeURI(project string, volKey *meta.Key, diskType string) string
@@ -700,7 +700,7 @@ func (cloud *CloudProvider) deleteRegionalDisk(ctx context.Context, project, reg
 	return nil
 }
 
-func (cloud *CloudProvider) AttachDisk(ctx context.Context, project string, volKey *meta.Key, readWrite, diskType, instanceZone, instanceName string) error {
+func (cloud *CloudProvider) AttachDisk(ctx context.Context, project string, volKey *meta.Key, readWrite, diskType, instanceZone, instanceName string, forceAttach bool) error {
 	klog.V(5).Infof("Attaching disk %v to %s", volKey, instanceName)
 	source := cloud.GetDiskSourceURI(project, volKey)
 
@@ -714,9 +714,13 @@ func (cloud *CloudProvider) AttachDisk(ctx context.Context, project string, volK
 		Mode:       readWrite,
 		Source:     source,
 		Type:       diskType,
+		// This parameter is ignored in the call, the ForceAttach decorator
+		// (query parameter) is the important one. We'll set it in both places
+		// in case that behavior changes.
+		ForceAttach: forceAttach,
 	}
 
-	op, err := cloud.service.Instances.AttachDisk(project, instanceZone, instanceName, attachedDiskV1).Context(ctx).Do()
+	op, err := cloud.service.Instances.AttachDisk(project, instanceZone, instanceName, attachedDiskV1).Context(ctx).ForceAttach(forceAttach).Do()
 	if err != nil {
 		return fmt.Errorf("failed cloud service attach disk call: %w", err)
 	}
diff --git a/pkg/gce-pd-csi-driver/controller.go b/pkg/gce-pd-csi-driver/controller.go
index c2e40a69..ff7a0858 100644
--- a/pkg/gce-pd-csi-driver/controller.go
+++ b/pkg/gce-pd-csi-driver/controller.go
@@ -111,6 +111,11 @@ type locationRequirements struct {
 	cloneReplicationType string
 }
 
+// PDCSIContext is the extracted VolumeContext from controller requests.
+type PDCSIContext struct {
+	ForceAttach bool
+}
+
 var _ csi.ControllerServer = &GCEControllerServer{}
 
 const (
@@ -130,6 +135,9 @@ const (
 	// but 500 is a good proxy (gives ~8KB of data per ListVolumesResponse#Entry)
 	// See https://github.com/grpc/grpc/blob/master/include/grpc/impl/codegen/grpc_types.h#L503)
 	maxListVolumesResponseEntries = 500
+
+	// Keys in the volume context.
+	contextForceAttach = "force-attach"
 )
 
 func isDiskReady(disk *gce.CloudDisk) (bool, error) {
@@ -236,6 +244,11 @@ func (gceCS *GCEControllerServer) CreateVolume(ctx context.Context, req *csi.Cre
 		gceAPIVersion = gce.GCEAPIVersionBeta
 	}
 
+	// Verify that the regional availability class is only used on regional disks.
+	if params.ForceAttach && params.ReplicationType != replicationTypeRegionalPD {
+		return nil, status.Errorf(codes.InvalidArgument, "invalid availabilty class for zonal disk")
+	}
+
 	var locationTopReq *locationRequirements
 	if useVolumeCloning(req) {
 		locationTopReq, err = cloningLocationRequirements(req, params.ReplicationType)
@@ -309,7 +322,7 @@ func (gceCS *GCEControllerServer) CreateVolume(ctx context.Context, req *csi.Cre
 
 		// If there is no validation error, immediately return success
 		klog.V(4).Infof("CreateVolume succeeded for disk %v, it already exists and was compatible", volKey)
-		return generateCreateVolumeResponse(existingDisk, zones), nil
+		return generateCreateVolumeResponse(existingDisk, zones, params), nil
 	}
 
 	snapshotID := ""
@@ -424,7 +437,7 @@ func (gceCS *GCEControllerServer) CreateVolume(ctx context.Context, req *csi.Cre
 	}
 
 	klog.V(4).Infof("CreateVolume succeeded for disk %v", volKey)
-	return generateCreateVolumeResponse(disk, zones), nil
+	return generateCreateVolumeResponse(disk, zones, params), nil
 
 }
 
@@ -483,7 +496,7 @@ func (gceCS *GCEControllerServer) ControllerPublishVolume(ctx context.Context, r
 		}
 	}()
 	// Only valid requests will be accepted
-	_, _, err = gceCS.validateControllerPublishVolumeRequest(ctx, req)
+	_, _, _, err = gceCS.validateControllerPublishVolumeRequest(ctx, req)
 	if err != nil {
 		return nil, err
 	}
@@ -504,32 +517,37 @@ func (gceCS *GCEControllerServer) ControllerPublishVolume(ctx context.Context, r
 	return resp, err
 }
 
-func (gceCS *GCEControllerServer) validateControllerPublishVolumeRequest(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (string, *meta.Key, error) {
+func (gceCS *GCEControllerServer) validateControllerPublishVolumeRequest(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (string, *meta.Key, *PDCSIContext, error) {
 	// Validate arguments
 	volumeID := req.GetVolumeId()
 	nodeID := req.GetNodeId()
 	volumeCapability := req.GetVolumeCapability()
 	if len(volumeID) == 0 {
-		return "", nil, status.Error(codes.InvalidArgument, "ControllerPublishVolume Volume ID must be provided")
+		return "", nil, nil, status.Error(codes.InvalidArgument, "ControllerPublishVolume Volume ID must be provided")
 	}
 	if len(nodeID) == 0 {
-		return "", nil, status.Error(codes.InvalidArgument, "ControllerPublishVolume Node ID must be provided")
+		return "", nil, nil, status.Error(codes.InvalidArgument, "ControllerPublishVolume Node ID must be provided")
 	}
 	if volumeCapability == nil {
-		return "", nil, status.Error(codes.InvalidArgument, "ControllerPublishVolume Volume capability must be provided")
+		return "", nil, nil, status.Error(codes.InvalidArgument, "ControllerPublishVolume Volume capability must be provided")
 	}
 
 	project, volKey, err := common.VolumeIDToKey(volumeID)
 	if err != nil {
-		return "", nil, status.Errorf(codes.InvalidArgument, "ControllerPublishVolume volume ID is invalid: %v", err.Error())
+		return "", nil, nil, status.Errorf(codes.InvalidArgument, "ControllerPublishVolume volume ID is invalid: %v", err.Error())
 	}
 
 	// TODO(#253): Check volume capability matches for ALREADY_EXISTS
 	if err = validateVolumeCapability(volumeCapability); err != nil {
-		return "", nil, status.Errorf(codes.InvalidArgument, "VolumeCapabilities is invalid: %v", err.Error())
+		return "", nil, nil, status.Errorf(codes.InvalidArgument, "VolumeCapabilities is invalid: %v", err.Error())
 	}
 
-	return project, volKey, nil
+	var pdcsiContext *PDCSIContext
+	if pdcsiContext, err = extractVolumeContext(req.VolumeContext); err != nil {
+		return "", nil, nil, status.Errorf(codes.InvalidArgument, "Invalid volume context: %v", err.Error())
+	}
+
+	return project, volKey, pdcsiContext, nil
 }
 
 func parseMachineType(machineTypeUrl string) string {
@@ -543,7 +561,7 @@ func parseMachineType(machineTypeUrl string) string {
 
 func (gceCS *GCEControllerServer) executeControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error, string) {
 	diskType := ""
-	project, volKey, err := gceCS.validateControllerPublishVolumeRequest(ctx, req)
+	project, volKey, pdcsiContext, err := gceCS.validateControllerPublishVolumeRequest(ctx, req)
 	if err != nil {
 		return nil, err, diskType
 	}
@@ -578,7 +596,7 @@ func (gceCS *GCEControllerServer) executeControllerPublishVolume(ctx context.Con
 		if gce.IsGCENotFoundError(err) {
 			return nil, status.Errorf(codes.NotFound, "Could not find disk %v: %v", volKey.String(), err.Error()), diskType
 		}
-		return nil, common.LoggedError("Failed to getDisk: ", err), diskType
+		return nil, status.Errorf(codes.Internal, "Failed to getDisk: %v", err.Error()), diskType
 	}
 	instanceZone, instanceName, err := common.NodeIDToZoneAndName(nodeID)
 	if err != nil {
@@ -615,7 +633,7 @@ func (gceCS *GCEControllerServer) executeControllerPublishVolume(ctx context.Con
 	if err != nil {
 		return nil, status.Errorf(codes.InvalidArgument, "could not split nodeID: %v", err.Error()), diskType
 	}
-	err = gceCS.CloudProvider.AttachDisk(ctx, project, volKey, readWrite, attachableDiskTypePersistent, instanceZone, instanceName)
+	err = gceCS.CloudProvider.AttachDisk(ctx, project, volKey, readWrite, attachableDiskTypePersistent, instanceZone, instanceName, pdcsiContext.ForceAttach)
 	if err != nil {
 		var udErr *gce.UnsupportedDiskError
 		if errors.As(err, &udErr) {
@@ -788,11 +806,6 @@ func (gceCS *GCEControllerServer) ValidateVolumeCapabilities(ctx context.Context
 		return nil, common.LoggedError("Failed to getDisk: ", err)
 	}
 
-	// Check Volume Context is Empty
-	if len(req.GetVolumeContext()) != 0 {
-		return generateFailedValidationMessage("VolumeContext expected to be empty but got %v", req.GetVolumeContext()), nil
-	}
-
 	// Check volume capabilities supported by PD. These are the same for any PD
 	if err := validateVolumeCapabilities(req.GetVolumeCapabilities()); err != nil {
 		return generateFailedValidationMessage("VolumeCapabilities not valid: %v", err.Error()), nil
@@ -1667,7 +1680,35 @@ func getDefaultZonesInRegion(ctx context.Context, gceCS *GCEControllerServer, ex
 	return ret, nil
 }
 
-func generateCreateVolumeResponse(disk *gce.CloudDisk, zones []string) *csi.CreateVolumeResponse {
+func paramsToVolumeContext(params common.DiskParameters) map[string]string {
+	context := map[string]string{}
+	if params.ForceAttach {
+		context[contextForceAttach] = "true"
+	}
+	if len(context) > 0 {
+		return context
+	}
+	return nil
+}
+
+func extractVolumeContext(context map[string]string) (*PDCSIContext, error) {
+	info := &PDCSIContext{}
+	// Note that sidecars may inject values in the context (eg,
+	// csiProvisionerIdentity). So we don't validate that all keys are known.
+	for key, val := range context {
+		switch key {
+		case contextForceAttach:
+			b, err := common.ConvertStringToBool(val)
+			if err != nil {
+				return nil, fmt.Errorf("Bad volume context force attach: %v", err)
+			}
+			info.ForceAttach = b
+		}
+	}
+	return info, nil
+}
+
+func generateCreateVolumeResponse(disk *gce.CloudDisk, zones []string, params common.DiskParameters) *csi.CreateVolumeResponse {
 	tops := []*csi.Topology{}
 	for _, zone := range zones {
 		tops = append(tops, &csi.Topology{
@@ -1679,7 +1720,7 @@ func generateCreateVolumeResponse(disk *gce.CloudDisk, zones []string) *csi.Crea
 		Volume: &csi.Volume{
 			CapacityBytes:      realDiskSizeBytes,
 			VolumeId:           cleanSelfLink(disk.GetSelfLink()),
-			VolumeContext:      nil,
+			VolumeContext:      paramsToVolumeContext(params),
 			AccessibleTopology: tops,
 		},
 	}


Test patch:
diff --git a/pkg/common/parameters_test.go b/pkg/common/parameters_test.go
index 2d1a4b6b..acffe5a5 100644
--- a/pkg/common/parameters_test.go
+++ b/pkg/common/parameters_test.go
@@ -178,6 +178,27 @@ func TestExtractAndDefaultParameters(t *testing.T) {
 				Labels:               map[string]string{"key1": "value1", "label-1": "value-a", "label-2": "label-value-2"},
 			},
 		},
+		{
+			name:       "availability class parameters",
+			parameters: map[string]string{ParameterAvailabilityClass: ParameterRegionalHardFailoverClass},
+			expectParams: DiskParameters{
+				DiskType:        "pd-standard",
+				ReplicationType: "none",
+				ForceAttach:     true,
+				Tags:            map[string]string{},
+				Labels:          map[string]string{},
+			},
+		},
+		{
+			name:       "no force attach parameters",
+			parameters: map[string]string{ParameterAvailabilityClass: ParameterNoAvailabilityClass},
+			expectParams: DiskParameters{
+				DiskType:        "pd-standard",
+				ReplicationType: "none",
+				Tags:            map[string]string{},
+				Labels:          map[string]string{},
+			},
+		},
 	}
 
 	for _, tc := range tests {
diff --git a/pkg/common/utils_test.go b/pkg/common/utils_test.go
index edf49e28..0fbb5a0b 100644
--- a/pkg/common/utils_test.go
+++ b/pkg/common/utils_test.go
@@ -806,6 +806,114 @@ func TestConvertMiStringToInt64(t *testing.T) {
 	}
 }
 
+func TestConvertStringToBool(t *testing.T) {
+	tests := []struct {
+		desc        string
+		inputStr    string
+		expected    bool
+		expectError bool
+	}{
+		{
+			desc:        "valid true",
+			inputStr:    "true",
+			expected:    true,
+			expectError: false,
+		},
+		{
+			desc:        "valid mixed case true",
+			inputStr:    "True",
+			expected:    true,
+			expectError: false,
+		},
+		{
+			desc:        "valid false",
+			inputStr:    "false",
+			expected:    false,
+			expectError: false,
+		},
+		{
+			desc:        "valid mixed case false",
+			inputStr:    "False",
+			expected:    false,
+			expectError: false,
+		},
+		{
+			desc:        "invalid",
+			inputStr:    "yes",
+			expected:    false,
+			expectError: true,
+		},
+	}
+	for _, tc := range tests {
+		t.Run(tc.desc, func(t *testing.T) {
+			got, err := ConvertStringToBool(tc.inputStr)
+			if err != nil && !tc.expectError {
+				t.Errorf("Got error %v converting string to bool %s; expect no error", err, tc.inputStr)
+			}
+			if err == nil && tc.expectError {
+				t.Errorf("Got no error converting string to bool %s; expect an error", tc.inputStr)
+			}
+			if err == nil && got != tc.expected {
+				t.Errorf("Got %v for converting string to bool; expect %v", got, tc.expected)
+			}
+		})
+	}
+}
+
+func TestConvertStringToAvailabilityClass(t *testing.T) {
+	tests := []struct {
+		desc        string
+		inputStr    string
+		expected    string
+		expectError bool
+	}{
+		{
+			desc:        "valid none",
+			inputStr:    "none",
+			expected:    ParameterNoAvailabilityClass,
+			expectError: false,
+		},
+		{
+			desc:        "valid mixed case none",
+			inputStr:    "None",
+			expected:    ParameterNoAvailabilityClass,
+			expectError: false,
+		},
+		{
+			desc:        "valid failover",
+			inputStr:    "regional-hard-failover",
+			expected:    ParameterRegionalHardFailoverClass,
+			expectError: false,
+		},
+		{
+			desc:        "valid mixed case failover",
+			inputStr:    "Regional-Hard-Failover",
+			expected:    ParameterRegionalHardFailoverClass,
+			expectError: false,
+		},
+		{
+			desc:        "invalid",
+			inputStr:    "yes",
+			expected:    "",
+			expectError: true,
+		},
+	}
+	for _, tc := range tests {
+		t.Run(tc.desc, func(t *testing.T) {
+			got, err := ConvertStringToAvailabilityClass(tc.inputStr)
+			if err != nil && !tc.expectError {
+				t.Errorf("Got error %v converting string to availablity class %s; expect no error", err, tc.inputStr)
+			}
+			if err == nil && tc.expectError {
+				t.Errorf("Got no error converting string to availablity class %s; expect an error", tc.inputStr)
+			}
+			if err == nil && got != tc.expected {
+				t.Errorf("Got %v for converting string to availablity class; expect %v", got, tc.expected)
+			}
+		})
+	}
+}
+
 func TestParseMachineType(t *testing.T) {
 	tests := []struct {
 		desc                string
diff --git a/pkg/gce-pd-csi-driver/controller_test.go b/pkg/gce-pd-csi-driver/controller_test.go
index d49809c7..d97815f1 100644
--- a/pkg/gce-pd-csi-driver/controller_test.go
+++ b/pkg/gce-pd-csi-driver/controller_test.go
@@ -3117,9 +3117,11 @@ func TestControllerUnpublishSucceedsIfNotFound(t *testing.T) {
 
 func TestControllerPublishBackoff(t *testing.T) {
 	for desc, tc := range map[string]struct {
-		config *backoffDriverConfig
+		config      *backoffDriverConfig
+		forceAttach bool
 	}{
-		"success": {},
+		"success":      {},
+		"force attach": {forceAttach: true},
 		"missing instance": {
 			config: &backoffDriverConfig{
 				mockMissingInstance: true,
@@ -3148,6 +3150,12 @@ func TestControllerPublishBackoff(t *testing.T) {
 				t.Errorf("expected no error on different unpublish, got %v", err)
 			}
 
+			var volumeContext map[string]string
+			if tc.forceAttach {
+				volumeContext = map[string]string{
+					contextForceAttach: "true",
+				}
+			}
 			pubreq := &csi.ControllerPublishVolumeRequest{
 				VolumeId: testVolumeID,
 				NodeId:   testNodeID,
@@ -3159,6 +3167,7 @@ func TestControllerPublishBackoff(t *testing.T) {
 						Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
 					},
 				},
+				VolumeContext: volumeContext,
 			}
 			// For the first 199 ms, the backoff condition is true. All controller publish request will be denied with 'Unavailable' error code.
 			for i := 0; i < 199; i++ {
@@ -3222,6 +3231,18 @@ func TestControllerPublishBackoff(t *testing.T) {
 				t.Errorf("unexpected error")
 			}
 
+			if tc.forceAttach {
+				instance, err := driver.cs.CloudProvider.GetInstanceOrError(context.Background(), zone, node)
+				if err != nil {
+					t.Fatalf("%s instance not found: %v", node, err)
+				}
+				for _, disk := range instance.Disks {
+					if !disk.ForceAttach {
+						t.Errorf("Expected %s to be force attached", disk.DeviceName)
+					}
+				}
+			}
+
 			// Driver is expected to remove the node key from the backoff map.
 			t1 := driver.cs.errorBackoff.backoff.Get(string(backoffId))
 			if t1 != 0 {
diff --git a/test/e2e/tests/multi_zone_e2e_test.go b/test/e2e/tests/multi_zone_e2e_test.go
index 5149c4d8..bb54356c 100644
--- a/test/e2e/tests/multi_zone_e2e_test.go
+++ b/test/e2e/tests/multi_zone_e2e_test.go
@@ -31,6 +31,14 @@ import (
 	remote "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/test/remote"
 )
 
+type verifyArgs struct {
+	publishDir, stageDir string
+}
+
+type verifyFunc func(*verifyArgs) error
+
+type detacherFunc func()
+
 var _ = Describe("GCE PD CSI Driver Multi-Zone", func() {
 	BeforeEach(func() {
 		Expect(len(testContexts)).To(BeNumerically(">", 1))
@@ -77,7 +85,7 @@ var _ = Describe("GCE PD CSI Driver Multi-Zone", func() {
 			}
 		}
 
-		Expect(len(zoneToContext)).To(Equal(2), "Must have instances in exactly 2 zones")
+		Expect(len(zoneToContext)).To(Equal(2), "Must have instances in 2 zones")
 
 		controllerContext := zoneToContext[zones[0]]
 		controllerClient := controllerContext.Client
@@ -90,7 +98,7 @@ var _ = Describe("GCE PD CSI Driver Multi-Zone", func() {
 
 		// Create Disk
 		volName := testNamePrefix + string(uuid.NewUUID())
-		volID, err := controllerClient.CreateVolume(volName, map[string]string{
+		volume, err := controllerClient.CreateVolume(volName, map[string]string{
 			common.ParameterKeyReplicationType: "regional-pd",
 		}, defaultRepdSizeGb, &csi.TopologyRequirement{
 			Requisite: []*csi.Topology{
@@ -121,7 +129,7 @@ var _ = Describe("GCE PD CSI Driver Multi-Zone", func() {
 
 		defer func() {
 			// Delete Disk
-			controllerClient.DeleteVolume(volID)
+			controllerClient.DeleteVolume(volume.VolumeId)
 			Expect(err).To(BeNil(), "DeleteVolume failed")
 
 			// Validate Disk Deleted
@@ -136,22 +144,121 @@ var _ = Describe("GCE PD CSI Driver Multi-Zone", func() {
 			if i >= 1 {
 				readOnly = true
 			}
-			err = testAttachWriteReadDetach(volID, volName, testContext.Instance, testContext.Client, readOnly)
+			err = testAttachWriteReadDetach(volume.VolumeId, volName, testContext.Instance, testContext.Client, readOnly)
 			Expect(err).To(BeNil(), "failed volume lifecycle checks")
 			i = i + 1
 		}
 	})
-})
 
-type verifyArgs struct {
-	publishDir string
-}
+	It("Should create a RePD instance, write to it, force-attach it to another instance, and read the same data", func() {
+		Expect(testContexts).NotTo(BeEmpty())
+
+		zoneToContext := map[string]*remote.TestContext{}
+		zones := []string{}
+
+		for _, tc := range testContexts {
+			_, z, _ := tc.Instance.GetIdentity()
+			// Zone hasn't been seen before
+			if _, ok := zoneToContext[z]; !ok {
+				zoneToContext[z] = tc
+				zones = append(zones, z)
+			}
+			if len(zoneToContext) == 2 {
+				break
+			}
+		}
+
+		Expect(len(zoneToContext)).To(Equal(2), "Must have instances in 2 zones")
+
+		controllerContext := zoneToContext[zones[0]]
+		controllerClient := controllerContext.Client
+		controllerInstance := controllerContext.Instance
 
-type verifyFunc func(verifyArgs) error
+		p, _, _ := controllerInstance.GetIdentity()
+
+		region, err := common.GetRegionFromZones(zones)
+		Expect(err).To(BeNil(), "Failed to get region from zones")
+
+		// Create Disk
+		volName := testNamePrefix + string(uuid.NewUUID())
+		volume, err := controllerClient.CreateVolume(volName, map[string]string{
+			common.ParameterKeyReplicationType: "regional-pd",
+			common.ParameterAvailabilityClass:  common.ParameterRegionalHardFailoverClass,
+		}, defaultRepdSizeGb, &csi.TopologyRequirement{
+			Requisite: []*csi.Topology{
+				{
+					Segments: map[string]string{common.TopologyKeyZone: zones[0]},
+				},
+				{
+					Segments: map[string]string{common.TopologyKeyZone: zones[1]},
+				},
+			},
+		}, nil)
+		Expect(err).To(BeNil(), "CreateVolume failed with error: %v", err)
+
+		// Validate Disk Created
+		cloudDisk, err := computeService.RegionDisks.Get(p, region, volName).Do()
+		Expect(err).To(BeNil(), "Could not get disk from cloud directly")
+		Expect(cloudDisk.Type).To(ContainSubstring(standardDiskType))
+		Expect(cloudDisk.Status).To(Equal(readyState))
+		Expect(cloudDisk.SizeGb).To(Equal(defaultRepdSizeGb))
+		Expect(cloudDisk.Name).To(Equal(volName))
+		Expect(len(cloudDisk.ReplicaZones)).To(Equal(2))
+		zonesSet := sets.NewString(zones...)
+		for _, replicaZone := range cloudDisk.ReplicaZones {
+			tokens := strings.Split(replicaZone, "/")
+			actualZone := tokens[len(tokens)-1]
+			Expect(zonesSet.Has(actualZone)).To(BeTrue(), "Expected zone %v to exist in zone set %v", actualZone, zones)
+		}
+		Expect(volume.VolumeContext).To(HaveKeyWithValue("force-attach", "true"))
+
+		detachers := []detacherFunc{}
+
+		defer func() {
+			// Perform any detaches
+			for _, fn := range detachers {
+				fn()
+			}
+
+			// Delete Disk
+			controllerClient.DeleteVolume(volume.VolumeId)
+			Expect(err).To(BeNil(), "DeleteVolume failed")
+
+			// Validate Disk Deleted
+			_, err = computeService.RegionDisks.Get(p, region, volName).Do()
+			Expect(gce.IsGCEError(err, "notFound")).To(BeTrue(), "Expected disk to not be found")
+		}()
+
+		// Attach disk to instance in the first zone.
+		tc0 := zoneToContext[zones[0]]
+		err, detacher, args := testAttachAndMount(volume.VolumeId, volName, tc0.Instance, tc0.Client, false /* useBlock */, false /* forceAttach */)
+		detachers = append(detachers, detacher)
+		Expect(err).To(BeNil(), "failed attach in zone 0")
+		testFileName := filepath.Join(args.publishDir, "force-attach-test")
+		testFileContents := "force attach test"
+		err = testutils.WriteFile(tc0.Instance, testFileName, testFileContents)
+		Expect(err).To(BeNil(), "failed write in zone 0")
+		_, err = tc0.Instance.SSH("sync") // Sync so force detach doesn't lose data.
+		Expect(err).To(BeNil(), "failed sync")
+
+		readContents, err := testutils.ReadFile(tc0.Instance, testFileName)
+		Expect(err).To(BeNil(), "failed read in zone 0")
+		Expect(strings.TrimSpace(string(readContents))).To(BeIdenticalTo(testFileContents), "content mismatch in zone 0")
+
+		// Now force attach to the second instance without detaching.
+		tc1 := zoneToContext[zones[1]]
+		err, detacher, args = testAttachAndMount(volume.VolumeId, volName, tc1.Instance, tc1.Client, false /* useBlock */, true /* forceAttach */)
+		detachers = append(detachers, detacher)
+		Expect(err).To(BeNil(), "failed force attach in zone 1")
+		readContents, err = testutils.ReadFile(tc1.Instance, testFileName)
+		Expect(err).To(BeNil(), "failed read in zone 1")
+		Expect(strings.TrimSpace(string(readContents))).To(BeIdenticalTo(testFileContents), "content mismatch in zone 1")
+	})
+})
 
 func testAttachWriteReadDetach(volID string, volName string, instance *remote.InstanceInfo, client *remote.CsiClient, readOnly bool) error {
 	var testFileContents = "test"
-	writeFile := func(a verifyArgs) error {
+	writeFile := func(a *verifyArgs) error {
 		if !readOnly {
 			// Write a file
 			testFile := filepath.Join(a.publishDir, "testfile")
@@ -163,7 +270,7 @@ func testAttachWriteReadDetach(volID string, volName string, instance *remote.In
 		return nil
 	}
 
-	verifyReadFile := func(a verifyArgs) error {
+	verifyReadFile := func(a *verifyArgs) error {
 		// Read File
 		secondTestFile := filepath.Join(a.publishDir, "testfile")
 		readContents, err := testutils.ReadFile(instance, secondTestFile)
@@ -178,23 +285,20 @@ func testAttachWriteReadDetach(volID string, volName string, instance *remote.In
 	return testLifecycleWithVerify(volID, volName, instance, client, readOnly, false /* fs */, writeFile, verifyReadFile)
 }
 
-func testLifecycleWithVerify(volID string, volName string, instance *remote.InstanceInfo, client *remote.CsiClient, readOnly, useBlock bool, firstMountVerify, secondMountVerify verifyFunc) error {
-	var err error
-	klog.Infof("Starting testAttachWriteReadDetach with volume %v node %v with readonly %v\n", volID, instance.GetNodeID(), readOnly)
+func testAttachAndMount(volID string, volName string, instance *remote.InstanceInfo, client *remote.CsiClient, useBlock, forceAttach bool) (error, func(), *verifyArgs) {
 	// Attach Disk
-	err = client.ControllerPublishVolume(volID, instance.GetNodeID())
+	err := client.ControllerPublishVolume(volID, instance.GetNodeID(), forceAttach)
 	if err != nil {
-		return fmt.Errorf("ControllerPublishVolume failed with error for disk %v on node %v: %v", volID, instance.GetNodeID(), err.Error())
+		return fmt.Errorf("ControllerPublishVolume failed with error for disk %v on node %v: %v", volID, instance.GetNodeID(), err.Error()), nil, nil
 	}
 
-	defer func() {
+	detach := func() {
 		// Detach Disk
 		err = client.ControllerUnpublishVolume(volID, instance.GetNodeID())
 		if err != nil {
 			klog.Errorf("Failed to detach disk: %w", err)
 		}
-
-	}()
+	}
 
 	// Stage Disk
 	stageDir := filepath.Join("/tmp/", volName, "stage")
@@ -204,12 +308,12 @@ func testLifecycleWithVerify(volID string, volName string, instance *remote.Inst
 		err = client.NodeStageExt4Volume(volID, stageDir)
 	}
 
-	//err = client.NodeStageExt4Volume(volID, stageDir)
 	if err != nil {
-		return fmt.Errorf("NodeStageExt4Volume failed with error: %w", err)
+		detach()
+		return fmt.Errorf("NodeStageExt4Volume failed with error: %w", err), nil, nil
 	}
 
-	defer func() {
+	unstageAndDetach := func() {
 		// Unstage Disk
 		err = client.NodeUnstageVolume(volID, stageDir)
 		if err != nil {
@@ -220,7 +324,9 @@ func testLifecycleWithVerify(volID string, volName string, instance *remote.Inst
 		if err != nil {
 			klog.Errorf("Failed to rm file path %s: %w", fp, err)
 		}
-	}()
+
+		detach()
+	}
 
 	// Mount Disk
 	publishDir := filepath.Join("/tmp/", volName, "mount")
@@ -232,24 +338,38 @@ func testLifecycleWithVerify(volID string, volName string, instance *remote.Inst
 	}
 
 	if err != nil {
-		return fmt.Errorf("NodePublishVolume failed with error: %v", err.Error())
+		unstageAndDetach()
+		return fmt.Errorf("NodePublishVolume failed with error: %v", err.Error()), nil, nil
 	}
 	err = testutils.ForceChmod(instance, filepath.Join("/tmp/", volName), "777")
 	if err != nil {
-		return fmt.Errorf("Chmod failed with error: %v", err.Error())
+		unstageAndDetach()
+		return fmt.Errorf("Chmod failed with error: %v", err.Error()), nil, nil
 	}
 
-	a := verifyArgs{
+	args := &verifyArgs{
 		publishDir: publishDir,
+		stageDir:   stageDir,
+	}
+
+	return nil, unstageAndDetach, args
+}
+
+func testLifecycleWithVerify(volID string, volName string, instance *remote.InstanceInfo, client *remote.CsiClient, readOnly, useBlock bool, firstMountVerify, secondMountVerify verifyFunc) error {
+	klog.Infof("Starting testAttachWriteReadDetach with volume %v node %v with readonly %v\n", volID, instance.GetNodeID(), readOnly)
+	err, detacher, args := testAttachAndMount(volID, volName, instance, client, useBlock, false /* forceAttach */)
+	if err != nil {
+		return fmt.Errorf("failed to attach and mount: %w", err)
 	}
+	defer detacher()
 
-	err = firstMountVerify(a)
+	err = firstMountVerify(args)
 	if err != nil {
-		return fmt.Errorf("failed to verify after first mount to %s: %v", publishDir, err)
+		return fmt.Errorf("failed to verify after first mount to %s: %w", args.publishDir, err)
 	}
 
 	// Unmount Disk
-	err = client.NodeUnpublishVolume(volID, publishDir)
+	err = client.NodeUnpublishVolume(volID, args.publishDir)
 	if err != nil {
 		return fmt.Errorf("NodeUnpublishVolume failed with error: %v", err.Error())
 	}
@@ -258,9 +378,9 @@ func testLifecycleWithVerify(volID string, volName string, instance *remote.Inst
 		// Mount disk somewhere else
 		secondPublishDir := filepath.Join("/tmp/", volName, "secondmount")
 		if useBlock {
-			err = client.NodePublishBlockVolume(volID, stageDir, secondPublishDir)
+			err = client.NodePublishBlockVolume(volID, args.stageDir, secondPublishDir)
 		} else {
-			err = client.NodePublishVolume(volID, stageDir, secondPublishDir)
+			err = client.NodePublishVolume(volID, args.stageDir, secondPublishDir)
 		}
 		if err != nil {
 			return fmt.Errorf("NodePublishVolume failed with error: %v", err.Error())
@@ -273,9 +393,9 @@ func testLifecycleWithVerify(volID string, volName string, instance *remote.Inst
 		b := verifyArgs{
 			publishDir: secondPublishDir,
 		}
-		err = secondMountVerify(b)
+		err = secondMountVerify(&b)
 		if err != nil {
-			return fmt.Errorf("failed to verify after second mount to %s: %v", publishDir, err.Error())
+			return fmt.Errorf("failed to verify after second mount to %s: %v", args.publishDir, err.Error())
 		}
 
 		// Unmount Disk
diff --git a/test/e2e/tests/resize_e2e_test.go b/test/e2e/tests/resize_e2e_test.go
index f6126475..2b04047d 100644
--- a/test/e2e/tests/resize_e2e_test.go
+++ b/test/e2e/tests/resize_e2e_test.go
@@ -38,7 +38,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Create Disk
 		volName := testNamePrefix + string(uuid.NewUUID())
-		volID, err := client.CreateVolume(volName, nil, defaultSizeGb,
+		volume, err := client.CreateVolume(volName, nil, defaultSizeGb,
 			&csi.TopologyRequirement{
 				Requisite: []*csi.Topology{
 					{
@@ -58,7 +58,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		defer func() {
 			// Delete Disk
-			client.DeleteVolume(volID)
+			client.DeleteVolume(volume.VolumeId)
 			Expect(err).To(BeNil(), "DeleteVolume failed")
 
 			// Validate Disk Deleted
@@ -67,12 +67,12 @@ var _ = Describe("GCE PD CSI Driver", func() {
 		}()
 
 		// Attach Disk
-		err = client.ControllerPublishVolume(volID, instance.GetNodeID())
+		err = client.ControllerPublishVolume(volume.VolumeId, instance.GetNodeID(), false /* forceAttach */)
 		Expect(err).To(BeNil(), "Controller publish volume failed")
 
 		defer func() {
 			// Detach Disk
-			err = client.ControllerUnpublishVolume(volID, instance.GetNodeID())
+			err = client.ControllerUnpublishVolume(volume.VolumeId, instance.GetNodeID())
 			if err != nil {
 				klog.Errorf("Failed to detach disk: %w", err)
 			}
@@ -80,12 +80,12 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Stage Disk
 		stageDir := filepath.Join("/tmp/", volName, "stage")
-		err = client.NodeStageExt4Volume(volID, stageDir)
+		err = client.NodeStageExt4Volume(volume.VolumeId, stageDir)
 		Expect(err).To(BeNil(), "Node Stage volume failed")
 
 		defer func() {
 			// Unstage Disk
-			err = client.NodeUnstageVolume(volID, stageDir)
+			err = client.NodeUnstageVolume(volume.VolumeId, stageDir)
 			if err != nil {
 				klog.Errorf("Failed to unstage volume: %w", err)
 			}
@@ -98,12 +98,12 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Mount Disk
 		publishDir := filepath.Join("/tmp/", volName, "mount")
-		err = client.NodePublishVolume(volID, stageDir, publishDir)
+		err = client.NodePublishVolume(volume.VolumeId, stageDir, publishDir)
 		Expect(err).To(BeNil(), "Node publish volume failed")
 
 		defer func() {
 			// Unmount Disk
-			err = client.NodeUnpublishVolume(volID, publishDir)
+			err = client.NodeUnpublishVolume(volume.VolumeId, publishDir)
 			if err != nil {
 				klog.Errorf("NodeUnpublishVolume failed with error: %w", err)
 			}
@@ -116,7 +116,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Resize controller
 		var newSizeGb int64 = 10
-		err = client.ControllerExpandVolume(volID, newSizeGb)
+		err = client.ControllerExpandVolume(volume.VolumeId, newSizeGb)
 
 		Expect(err).To(BeNil(), "Controller expand volume failed")
 
@@ -126,7 +126,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 		Expect(cloudDisk.SizeGb).To(Equal(newSizeGb))
 
 		// Resize node
-		_, err = client.NodeExpandVolume(volID, publishDir, newSizeGb)
+		_, err = client.NodeExpandVolume(volume.VolumeId, publishDir, newSizeGb)
 		Expect(err).To(BeNil(), "Node expand volume failed")
 
 		// Verify disk size
@@ -145,7 +145,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Create Disk
 		volName := testNamePrefix + string(uuid.NewUUID())
-		volID, err := client.CreateVolume(volName, nil, defaultSizeGb,
+		volume, err := client.CreateVolume(volName, nil, defaultSizeGb,
 			&csi.TopologyRequirement{
 				Requisite: []*csi.Topology{
 					{
@@ -165,7 +165,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		defer func() {
 			// Delete Disk
-			client.DeleteVolume(volID)
+			client.DeleteVolume(volume.VolumeId)
 			Expect(err).To(BeNil(), "DeleteVolume failed")
 
 			// Validate Disk Deleted
@@ -174,12 +174,12 @@ var _ = Describe("GCE PD CSI Driver", func() {
 		}()
 
 		// Volume should be attached/formatted/mounted/unmounted/detached
-		err = testAttachWriteReadDetach(volID, volName, instance, client, false /* readOnly */)
+		err = testAttachWriteReadDetach(volume.VolumeId, volName, instance, client, false /* readOnly */)
 		Expect(err).To(BeNil(), "Failed to go through volume lifecycle")
 
 		// Resize controller
 		var newSizeGb int64 = 10
-		err = client.ControllerExpandVolume(volID, newSizeGb)
+		err = client.ControllerExpandVolume(volume.VolumeId, newSizeGb)
 
 		Expect(err).To(BeNil(), "Controller expand volume failed")
 
@@ -189,12 +189,12 @@ var _ = Describe("GCE PD CSI Driver", func() {
 		Expect(cloudDisk.SizeGb).To(Equal(newSizeGb))
 
 		// Attach and mount again
-		err = client.ControllerPublishVolume(volID, instance.GetNodeID())
+		err = client.ControllerPublishVolume(volume.VolumeId, instance.GetNodeID(), false /* forceAttach */)
 		Expect(err).To(BeNil(), "Controller publish volume failed")
 
 		defer func() {
 			// Detach Disk
-			err = client.ControllerUnpublishVolume(volID, instance.GetNodeID())
+			err = client.ControllerUnpublishVolume(volume.VolumeId, instance.GetNodeID())
 			if err != nil {
 				klog.Errorf("Failed to detach disk: %w", err)
 			}
@@ -203,12 +203,12 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Stage Disk
 		stageDir := filepath.Join("/tmp/", volName, "stage")
-		err = client.NodeStageExt4Volume(volID, stageDir)
+		err = client.NodeStageExt4Volume(volume.VolumeId, stageDir)
 		Expect(err).To(BeNil(), "Node Stage volume failed")
 
 		defer func() {
 			// Unstage Disk
-			err = client.NodeUnstageVolume(volID, stageDir)
+			err = client.NodeUnstageVolume(volume.VolumeId, stageDir)
 			if err != nil {
 				klog.Errorf("Failed to unstage volume: %w", err)
 			}
@@ -221,19 +221,19 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Mount Disk
 		publishDir := filepath.Join("/tmp/", volName, "mount")
-		err = client.NodePublishVolume(volID, stageDir, publishDir)
+		err = client.NodePublishVolume(volume.VolumeId, stageDir, publishDir)
 		Expect(err).To(BeNil(), "Node publish volume failed")
 
 		defer func() {
 			// Unmount Disk
-			err = client.NodeUnpublishVolume(volID, publishDir)
+			err = client.NodeUnpublishVolume(volume.VolumeId, publishDir)
 			if err != nil {
 				klog.Errorf("NodeUnpublishVolume failed with error: %w", err)
 			}
 		}()
 
 		// Resize node
-		_, err = client.NodeExpandVolume(volID, publishDir, newSizeGb)
+		_, err = client.NodeExpandVolume(volume.VolumeId, publishDir, newSizeGb)
 		Expect(err).To(BeNil(), "Node expand volume failed")
 
 		// Verify disk size
@@ -252,7 +252,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Create Disk
 		volName := testNamePrefix + string(uuid.NewUUID())
-		volID, err := client.CreateVolume(volName, nil, defaultSizeGb,
+		volume, err := client.CreateVolume(volName, nil, defaultSizeGb,
 			&csi.TopologyRequirement{
 				Requisite: []*csi.Topology{
 					{
@@ -272,7 +272,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		defer func() {
 			// Delete Disk
-			client.DeleteVolume(volID)
+			client.DeleteVolume(volume.VolumeId)
 			Expect(err).To(BeNil(), "DeleteVolume failed")
 
 			// Validate Disk Deleted
@@ -281,12 +281,12 @@ var _ = Describe("GCE PD CSI Driver", func() {
 		}()
 
 		// Attach Disk
-		err = client.ControllerPublishVolume(volID, instance.GetNodeID())
+		err = client.ControllerPublishVolume(volume.VolumeId, instance.GetNodeID(), false /* forceAttach */)
 		Expect(err).To(BeNil(), "Controller publish volume failed")
 
 		defer func() {
 			// Detach Disk
-			err = client.ControllerUnpublishVolume(volID, instance.GetNodeID())
+			err = client.ControllerUnpublishVolume(volume.VolumeId, instance.GetNodeID())
 			if err != nil {
 				klog.Errorf("Failed to detach disk: %w", err)
 			}
@@ -295,12 +295,12 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Stage Disk
 		stageDir := filepath.Join("/tmp/", volName, "stage")
-		err = client.NodeStageBlockVolume(volID, stageDir)
+		err = client.NodeStageBlockVolume(volume.VolumeId, stageDir)
 		Expect(err).To(BeNil(), "Node Stage volume failed")
 
 		defer func() {
 			// Unstage Disk
-			err = client.NodeUnstageVolume(volID, stageDir)
+			err = client.NodeUnstageVolume(volume.VolumeId, stageDir)
 			if err != nil {
 				klog.Errorf("Failed to unstage volume: %w", err)
 			}
@@ -313,12 +313,12 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Mount Disk
 		publishDir := filepath.Join("/tmp/", volName, "mount")
-		err = client.NodePublishBlockVolume(volID, stageDir, publishDir)
+		err = client.NodePublishBlockVolume(volume.VolumeId, stageDir, publishDir)
 		Expect(err).To(BeNil(), "Node publish volume failed")
 
 		defer func() {
 			// Unmount Disk
-			err = client.NodeUnpublishVolume(volID, publishDir)
+			err = client.NodeUnpublishVolume(volume.VolumeId, publishDir)
 			if err != nil {
 				klog.Errorf("NodeUnpublishVolume failed with error: %w", err)
 			}
@@ -331,7 +331,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Resize controller
 		var newSizeGb int64 = 10
-		err = client.ControllerExpandVolume(volID, newSizeGb)
+		err = client.ControllerExpandVolume(volume.VolumeId, newSizeGb)
 
 		Expect(err).To(BeNil(), "Controller expand volume failed")
 
@@ -341,7 +341,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 		Expect(cloudDisk.SizeGb).To(Equal(newSizeGb))
 
 		// Resize node
-		resp, err := client.NodeExpandVolume(volID, publishDir, newSizeGb)
+		resp, err := client.NodeExpandVolume(volume.VolumeId, publishDir, newSizeGb)
 		Expect(err).To(BeNil(), "Node expand volume failed")
 		Expect(resp.CapacityBytes).To(Equal(common.GbToBytes(newSizeGb)), "Node expand should not do anything")
 
diff --git a/test/e2e/tests/single_zone_e2e_test.go b/test/e2e/tests/single_zone_e2e_test.go
index 96075799..c115d3e1 100644
--- a/test/e2e/tests/single_zone_e2e_test.go
+++ b/test/e2e/tests/single_zone_e2e_test.go
@@ -113,7 +113,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 		}()
 
 		// Attach Disk
-		err := client.ControllerPublishVolume(volID, instance.GetNodeID())
+		err := client.ControllerPublishVolume(volID, instance.GetNodeID(), false /* forceAttach */)
 		Expect(err).To(BeNil(), "ControllerPublishVolume failed with error for disk %v on node %v: %v", volID, instance.GetNodeID())
 
 		defer func() {
@@ -185,7 +185,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 		}()
 
 		// Attach Disk
-		err := client.ControllerPublishVolume(volID, instance.GetNodeID())
+		err := client.ControllerPublishVolume(volID, instance.GetNodeID(), false /* forceAttach */)
 		Expect(err).To(BeNil(), "ControllerPublishVolume failed with error for disk %v on node %v: %v", volID, instance.GetNodeID())
 
 		defer func() {
@@ -251,10 +251,10 @@ var _ = Describe("GCE PD CSI Driver", func() {
 					},
 				},
 			}
-			volID, err := testContext.Client.CreateVolume(volName, nil, defaultSizeGb, topReq, nil)
+			volume, err := testContext.Client.CreateVolume(volName, nil, defaultSizeGb, topReq, nil)
 			Expect(err).To(BeNil(), "Failed to create volume")
 			defer func() {
-				err = testContext.Client.DeleteVolume(volID)
+				err = testContext.Client.DeleteVolume(volume.VolumeId)
 				Expect(err).To(BeNil(), "Failed to delete volume")
 			}()
 
@@ -324,7 +324,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 			}()
 
 			// Attach Disk
-			err := client.ControllerPublishVolume(underSpecifiedID, instance.GetNodeID())
+			err := client.ControllerPublishVolume(underSpecifiedID, instance.GetNodeID(), false /* forceAttach */)
 			Expect(err).To(BeNil(), "ControllerPublishVolume failed")
 		},
 		Entry("on pd-standard", standardDiskType),
@@ -345,7 +345,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Create Disk
 		volName := testNamePrefix + string(uuid.NewUUID())
-		volID, err := controllerClient.CreateVolume(volName, map[string]string{
+		volume, err := controllerClient.CreateVolume(volName, map[string]string{
 			common.ParameterKeyReplicationType: "regional-pd",
 		}, defaultRepdSizeGb, nil, nil)
 		Expect(err).To(BeNil(), "CreateVolume failed with error: %v", err)
@@ -366,7 +366,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 		}
 		defer func() {
 			// Delete Disk
-			controllerClient.DeleteVolume(volID)
+			controllerClient.DeleteVolume(volume.VolumeId)
 			Expect(err).To(BeNil(), "DeleteVolume failed")
 
 			// Validate Disk Deleted
@@ -386,7 +386,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 			// Create Disk
 			disk := typeToDisk[diskType]
 			volName := testNamePrefix + string(uuid.NewUUID())
-			volID, err := client.CreateVolume(volName, disk.params, defaultSizeGb, nil, nil)
+			volume, err := client.CreateVolume(volName, disk.params, defaultSizeGb, nil, nil)
 			Expect(err).To(BeNil(), "CreateVolume failed with error: %v", err)
 
 			// Validate Disk Created
@@ -399,7 +399,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 			defer func() {
 				// Delete Disk
-				client.DeleteVolume(volID)
+				client.DeleteVolume(volume.VolumeId)
 				Expect(err).To(BeNil(), "DeleteVolume failed")
 
 				// Validate Disk Deleted
@@ -425,7 +425,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 			params := merge(disk.params, map[string]string{
 				common.ParameterKeyLabels: "key1=value1,key2=value2",
 			})
-			volID, err := client.CreateVolume(volName, params, defaultSizeGb, nil, nil)
+			volume, err := client.CreateVolume(volName, params, defaultSizeGb, nil, nil)
 			Expect(err).To(BeNil(), "CreateVolume failed with error: %v", err)
 
 			// Validate Disk Created
@@ -444,7 +444,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 			defer func() {
 				// Delete Disk
-				err := client.DeleteVolume(volID)
+				err := client.DeleteVolume(volume.VolumeId)
 				Expect(err).To(BeNil(), "DeleteVolume failed")
 
 				// Validate Disk Deleted
@@ -547,7 +547,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 					},
 				},
 			}
-			volID, err := controllerClient.CreateVolume(volName, params, defaultSizeGb, topology, nil)
+			volume, err := controllerClient.CreateVolume(volName, params, defaultSizeGb, topology, nil)
 			Expect(err).To(BeNil(), "CreateVolume failed with error: %v", err)
 
 			// Validate Disk Created
@@ -560,7 +560,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 			defer func() {
 				// Delete Disk
-				err = controllerClient.DeleteVolume(volID)
+				err = controllerClient.DeleteVolume(volume.VolumeId)
 				Expect(err).To(BeNil(), "DeleteVolume failed")
 
 				// Validate Disk Deleted
@@ -569,7 +569,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 			}()
 
 			// Test disk works
-			err = testAttachWriteReadDetach(volID, volName, controllerInstance, controllerClient, false /* readOnly */)
+			err = testAttachWriteReadDetach(volume.VolumeId, volName, controllerInstance, controllerClient, false /* readOnly */)
 			Expect(err).To(BeNil(), "Failed to go through volume lifecycle before revoking CMEK key")
 
 			// Revoke CMEK key
@@ -590,7 +590,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 			}
 
 			// Make sure attach of PD fails
-			err = testAttachWriteReadDetach(volID, volName, controllerInstance, controllerClient, false /* readOnly */)
+			err = testAttachWriteReadDetach(volume.VolumeId, volName, controllerInstance, controllerClient, false /* readOnly */)
 			Expect(err).ToNot(BeNil(), "Volume lifecycle should have failed, but succeeded")
 
 			// Restore CMEK key
@@ -611,7 +611,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 			// The controller publish failure in above step would set a backoff condition on the node. Wait suffcient amount of time for the driver to accept new controller publish requests.
 			time.Sleep(time.Second)
 			// Make sure attach of PD succeeds
-			err = testAttachWriteReadDetach(volID, volName, controllerInstance, controllerClient, false /* readOnly */)
+			err = testAttachWriteReadDetach(volume.VolumeId, volName, controllerInstance, controllerClient, false /* readOnly */)
 			Expect(err).To(BeNil(), "Failed to go through volume lifecycle after restoring CMEK key")
 		},
 		Entry("on pd-standard", standardDiskType),
@@ -634,7 +634,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 		defer deleteVolumeOrError(client, secondVolID)
 
 		// Attach volID to current instance
-		err := client.ControllerPublishVolume(volID, nodeID)
+		err := client.ControllerPublishVolume(volID, nodeID, false /* forceAttach */)
 		Expect(err).To(BeNil(), "Failed ControllerPublishVolume")
 		defer client.ControllerUnpublishVolume(volID, nodeID)
 
@@ -662,7 +662,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Create Disk
 		volName := testNamePrefix + string(uuid.NewUUID())
-		volID, err := controllerClient.CreateVolume(volName, map[string]string{
+		volume, err := controllerClient.CreateVolume(volName, map[string]string{
 			common.ParameterKeyReplicationType: "regional-pd",
 		}, defaultRepdSizeGb, nil, nil)
 		Expect(err).To(BeNil(), "CreateVolume failed with error: %v", err)
@@ -684,7 +684,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Create Snapshot
 		snapshotName := testNamePrefix + string(uuid.NewUUID())
-		snapshotID, err := controllerClient.CreateSnapshot(snapshotName, volID, nil)
+		snapshotID, err := controllerClient.CreateSnapshot(snapshotName, volume.VolumeId, nil)
 		Expect(err).To(BeNil(), "CreateSnapshot failed with error: %v", err)
 
 		// Validate Snapshot Created
@@ -704,7 +704,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		defer func() {
 			// Delete Disk
-			err := controllerClient.DeleteVolume(volID)
+			err := controllerClient.DeleteVolume(volume.VolumeId)
 			Expect(err).To(BeNil(), "DeleteVolume failed")
 
 			// Validate Disk Deleted
@@ -740,7 +740,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 			Expect(gce.IsGCEError(err, "notFound")).To(BeTrue(), "Expected disk to not be found")
 		}()
 
-		verifyVolumeStats := func(a verifyArgs) error {
+		verifyVolumeStats := func(a *verifyArgs) error {
 			available, capacity, used, inodesFree, inodes, inodesUsed, err := client.NodeGetVolumeStats(volID, a.publishDir)
 			if err != nil {
 				return fmt.Errorf("failed to get node volume stats: %v", err.Error())
@@ -777,7 +777,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 			Expect(gce.IsGCEError(err, "notFound")).To(BeTrue(), "Expected disk to not be found")
 		}()
 
-		verifyVolumeStats := func(a verifyArgs) error {
+		verifyVolumeStats := func(a *verifyArgs) error {
 			available, capacity, used, inodesFree, inodes, inodesUsed, err := client.NodeGetVolumeStats(volID, a.publishDir)
 			if err != nil {
 				return fmt.Errorf("failed to get node volume stats: %v", err.Error())
@@ -843,14 +843,14 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Attach Disk
 		testFileContents := "test"
-		writeFunc := func(a verifyArgs) error {
+		writeFunc := func(a *verifyArgs) error {
 			err := testutils.WriteBlock(instance, a.publishDir, testFileContents)
 			if err != nil {
 				return fmt.Errorf("Failed to write file: %v", err.Error())
 			}
 			return nil
 		}
-		verifyReadFunc := func(a verifyArgs) error {
+		verifyReadFunc := func(a *verifyArgs) error {
 			readContents, err := testutils.ReadBlock(instance, a.publishDir, len(testFileContents))
 			if err != nil {
 				return fmt.Errorf("ReadFile failed with error: %v", err.Error())
@@ -882,7 +882,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 				common.ParameterKeyPVCNamespace: "test-pvc-namespace",
 				common.ParameterKeyPVName:       "test-pv-name",
 			})
-			volID, err := controllerClient.CreateVolume(volName, params, defaultSizeGb, nil /* topReq */, nil)
+			volume, err := controllerClient.CreateVolume(volName, params, defaultSizeGb, nil /* topReq */, nil)
 			Expect(err).To(BeNil(), "CreateVolume failed with error: %v", err)
 
 			// Validate Disk Created
@@ -896,7 +896,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 			defer func() {
 				// Delete Disk
-				controllerClient.DeleteVolume(volID)
+				controllerClient.DeleteVolume(volume.VolumeId)
 				Expect(err).To(BeNil(), "DeleteVolume failed")
 
 				// Validate Disk Deleted
@@ -1042,7 +1042,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Create Disk
 		volName := testNamePrefix + string(uuid.NewUUID())
-		volID, err := controllerClient.CreateVolume(volName, map[string]string{
+		volume, err := controllerClient.CreateVolume(volName, map[string]string{
 			common.ParameterKeyReplicationType: "none",
 		}, defaultSizeGb,
 			&csi.TopologyRequirement{
@@ -1075,7 +1075,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 		Expect(zoneFromURL(cloudDisk.Zone)).To(Equal(srcKey.Zone))
 		defer func() {
 			// Delete Disk
-			controllerClient.DeleteVolume(volID)
+			controllerClient.DeleteVolume(volume.VolumeId)
 			Expect(err).To(BeNil(), "DeleteVolume failed")
 
 			// Validate Disk Deleted
@@ -1098,18 +1098,18 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Create Source Disk
 		srcVolName := testNamePrefix + string(uuid.NewUUID())
-		srcVolID, err := controllerClient.CreateVolume(srcVolName, map[string]string{
+		srcVolume, err := controllerClient.CreateVolume(srcVolName, map[string]string{
 			common.ParameterKeyReplicationType: "none",
 		}, defaultRepdSizeGb, nil, nil)
 		// Create Disk
 		volName := testNamePrefix + string(uuid.NewUUID())
-		volID, err := controllerClient.CreateVolume(volName, map[string]string{
+		volume, err := controllerClient.CreateVolume(volName, map[string]string{
 			common.ParameterKeyReplicationType: "regional-pd",
 		}, defaultRepdSizeGb, nil,
 			&csi.VolumeContentSource{
 				Type: &csi.VolumeContentSource_Volume{
 					Volume: &csi.VolumeContentSource_VolumeSource{
-						VolumeId: srcVolID,
+						VolumeId: srcVolume.VolumeId,
 					},
 				},
 			})
@@ -1125,7 +1125,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 		Expect(cloudDisk.Name).To(Equal(volName))
 		Expect(len(cloudDisk.ReplicaZones)).To(Equal(2))
 		replicaZonesCompatible := false
-		_, srcKey, err := common.VolumeIDToKey(srcVolID)
+		_, srcKey, err := common.VolumeIDToKey(srcVolume.VolumeId)
 		Expect(err).To(BeNil(), "Could not get source volume key from id")
 		for _, replicaZone := range cloudDisk.ReplicaZones {
 			actualZone := zoneFromURL(replicaZone)
@@ -1140,7 +1140,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 		Expect(replicaZonesCompatible).To(Equal(true))
 		defer func() {
 			// Delete Disk
-			controllerClient.DeleteVolume(volID)
+			controllerClient.DeleteVolume(volume.VolumeId)
 			Expect(err).To(BeNil(), "DeleteVolume failed")
 
 			// Validate Disk Deleted
@@ -1163,18 +1163,18 @@ var _ = Describe("GCE PD CSI Driver", func() {
 
 		// Create Source Disk
 		srcVolName := testNamePrefix + string(uuid.NewUUID())
-		srcVolID, err := controllerClient.CreateVolume(srcVolName, map[string]string{
+		srcVolume, err := controllerClient.CreateVolume(srcVolName, map[string]string{
 			common.ParameterKeyReplicationType: "regional-pd",
 		}, defaultRepdSizeGb, nil, nil)
 		// Create Disk
 		volName := testNamePrefix + string(uuid.NewUUID())
-		volID, err := controllerClient.CreateVolume(volName, map[string]string{
+		volume, err := controllerClient.CreateVolume(volName, map[string]string{
 			common.ParameterKeyReplicationType: "regional-pd",
 		}, defaultRepdSizeGb, nil,
 			&csi.VolumeContentSource{
 				Type: &csi.VolumeContentSource_Volume{
 					Volume: &csi.VolumeContentSource_VolumeSource{
-						VolumeId: srcVolID,
+						VolumeId: srcVolume.VolumeId,
 					},
 				},
 			})
@@ -1201,7 +1201,7 @@ var _ = Describe("GCE PD CSI Driver", func() {
 		}
 		defer func() {
 			// Delete Disk
-			controllerClient.DeleteVolume(volID)
+			controllerClient.DeleteVolume(volume.VolumeId)
 			Expect(err).To(BeNil(), "DeleteVolume failed")
 
 			// Validate Disk Deleted
@@ -1256,7 +1256,7 @@ func createAndValidateUniqueZonalDisk(client *remote.CsiClient, project, zone st
 	// Create Disk
 	disk := typeToDisk[diskType]
 	volName := testNamePrefix + string(uuid.NewUUID())
-	volID, err := client.CreateVolume(volName, disk.params, defaultSizeGb,
+	volume, err := client.CreateVolume(volName, disk.params, defaultSizeGb,
 		&csi.TopologyRequirement{
 			Requisite: []*csi.Topology{
 				{
@@ -1274,7 +1274,7 @@ func createAndValidateUniqueZonalDisk(client *remote.CsiClient, project, zone st
 	Expect(cloudDisk.Name).To(Equal(volName))
 	disk.validate(cloudDisk)
 
-	return volName, volID
+	return volName, volume.VolumeId
 }
 
 func deleteVolumeOrError(client *remote.CsiClient, volID string) {
@@ -1293,7 +1293,7 @@ func createAndValidateUniqueZonalMultiWriterDisk(client *remote.CsiClient, proje
 	// Create Disk
 	disk := typeToDisk[diskType]
 	volName := testNamePrefix + string(uuid.NewUUID())
-	volID, err := client.CreateVolumeWithCaps(volName, disk.params, defaultMwSizeGb,
+	volume, err := client.CreateVolumeWithCaps(volName, disk.params, defaultMwSizeGb,
 		&csi.TopologyRequirement{
 			Requisite: []*csi.Topology{
 				{
@@ -1325,7 +1325,7 @@ func createAndValidateUniqueZonalMultiWriterDisk(client *remote.CsiClient, proje
 	Expect(err).To(BeNil(), "Failed to get cloud disk using alpha API")
 	Expect(alphaDisk.MultiWriter).To(Equal(true))
 
-	return volName, volID
+	return volName, volume.VolumeId
 }
 
 func cleanSelfLink(selfLink string) string {
diff --git a/test/e2e/utils/utils.go b/test/e2e/utils/utils.go
index 8a80501e..f57c09d9 100644
--- a/test/e2e/utils/utils.go
+++ b/test/e2e/utils/utils.go
@@ -59,7 +59,9 @@ func GCEClientAndDriverSetup(instance *remote.InstanceInfo, computeEndpoint stri
 	}
 
 	workspace := remote.NewWorkspaceDir("gce-pd-e2e-")
-	driverRunCmd := fmt.Sprintf("sh -c '/usr/bin/nohup %s/gce-pd-csi-driver -v=4 --endpoint=%s %s --extra-labels=%s=%s 2> %s/prog.out < /dev/null > /dev/null &'",
+	// Log at V(6) as the compute API calls are emitted at that level and it's
+	// useful to see what's happening when debugging tests.
+	driverRunCmd := fmt.Sprintf("sh -c '/usr/bin/nohup %s/gce-pd-csi-driver -v=6 --endpoint=%s %s --extra-labels=%s=%s 2> %s/prog.out < /dev/null > /dev/null &'",
 		workspace, endpoint, computeFlag, DiskLabelKey, DiskLabelValue, workspace)
 
 	config := &remote.ClientConfig{
diff --git a/test/remote/client-wrappers.go b/test/remote/client-wrappers.go
index 748c0918..7c86e657 100644
--- a/test/remote/client-wrappers.go
+++ b/test/remote/client-wrappers.go
@@ -99,7 +99,7 @@ func (c *CsiClient) CloseConn() error {
 	return c.conn.Close()
 }
 
-func (c *CsiClient) CreateVolumeWithCaps(volName string, params map[string]string, sizeInGb int64, topReq *csipb.TopologyRequirement, caps []*csipb.VolumeCapability, volContentSrc *csipb.VolumeContentSource) (string, error) {
+func (c *CsiClient) CreateVolumeWithCaps(volName string, params map[string]string, sizeInGb int64, topReq *csipb.TopologyRequirement, caps []*csipb.VolumeCapability, volContentSrc *csipb.VolumeContentSource) (*csipb.Volume, error) {
 	capRange := &csipb.CapacityRange{
 		RequiredBytes: common.GbToBytes(sizeInGb),
 	}
@@ -117,12 +117,12 @@ func (c *CsiClient) CreateVolumeWithCaps(volName string, params map[string]strin
 	}
 	cresp, err := c.ctrlClient.CreateVolume(context.Background(), cvr)
 	if err != nil {
-		return "", err
+		return nil, err
 	}
-	return cresp.GetVolume().GetVolumeId(), nil
+	return cresp.Volume, nil
 }
 
-func (c *CsiClient) CreateVolume(volName string, params map[string]string, sizeInGb int64, topReq *csipb.TopologyRequirement, volContentSrc *csipb.VolumeContentSource) (string, error) {
+func (c *CsiClient) CreateVolume(volName string, params map[string]string, sizeInGb int64, topReq *csipb.TopologyRequirement, volContentSrc *csipb.VolumeContentSource) (*csipb.Volume, error) {
 	return c.CreateVolumeWithCaps(volName, params, sizeInGb, topReq, stdVolCaps, volContentSrc)
 }
 
@@ -134,13 +134,18 @@ func (c *CsiClient) DeleteVolume(volId string) error {
 	return err
 }
 
-func (c *CsiClient) ControllerPublishVolume(volId, nodeId string) error {
+func (c *CsiClient) ControllerPublishVolume(volId, nodeId string, forceAttach bool) error {
 	cpreq := &csipb.ControllerPublishVolumeRequest{
 		VolumeId:         volId,
 		NodeId:           nodeId,
 		VolumeCapability: stdVolCap,
 		Readonly:         false,
 	}
+	if forceAttach {
+		cpreq.VolumeContext = map[string]string{
+			"force-attach": "true",
+		}
+	}
 	_, err := c.ctrlClient.ControllerPublishVolume(context.Background(), cpreq)
 	return err
 }
diff --git a/test/run-e2e-local.sh b/test/run-e2e-local.sh
index be0455a1..6e60ccc8 100755
--- a/test/run-e2e-local.sh
+++ b/test/run-e2e-local.sh
@@ -2,7 +2,13 @@
 
 set -o nounset
 set -o errexit
+set -x
+
+echo Using GOPATH $GOPATH
 
 readonly PKGDIR=sigs.k8s.io/gcp-compute-persistent-disk-csi-driver
 
-ginkgo --v "test/e2e/tests" -- --project "${PROJECT}" --service-account "${IAM_NAME}" --v=4 --logtostderr
+# This requires application default credentials to be set up, eg by
+# `gcloud auth application-default login`
+
+ginkgo --v "test/e2e/tests" -- --project "${PROJECT}" --service-account "${IAM_NAME}" --v=6 --logtostderr