Repository: kubernetes-sigs/cluster-api
Pull request #3336 (2020-07-14 20:31:54)

🐛 Ensure that etcd health check errors are logged in KCP
**What this PR does / why we need it**:
The initial error returned for an etcd health check gets overwritten and is never logged. 


k8s-ci-robot: Hi @Arvinderpal. Thanks for your PR.

I'm waiting for a [kubernetes-sigs](https://github.com/orgs/kubernetes-sigs/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcluster-api).

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

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **davidewatson**
You can assign the PR to them by writing `/assign @davidewatson` 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-sigs%2Fcluster-api).

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

- **[OWNERS](https://github.com/kubernetes-sigs/cluster-api/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":["davidewatson"]} -->
Arvinderpal: @sedefsavas 
sedefsavas: /ok-to-test
k8s-ci-robot: @Arvinderpal: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-make | 3dea741c6ad17aa3dd96857077454bb3b9f2efcc | [link](https://prow.k8s.io/view/gcs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-make/1283163359184687107) | `/test pull-cluster-api-make`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **cecilerobertmichon**
You can assign the PR to them by writing `/assign @cecilerobertmichon` 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-sigs%2Fcluster-api).

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

- **[OWNERS](https://github.com/kubernetes-sigs/cluster-api/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":["cecilerobertmichon"]} -->
k8s-ci-robot: @Arvinderpal: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-make | 100f630ab8f2b9fba137035c71783032ddfed438 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-make/1283447673436770304) | `/test pull-cluster-api-make`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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 -->
sedefsavas: @Arvinderpal make fails due to lint check: https://storage.googleapis.com/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-make/1283447673436770304/build-log.txt
k8s-ci-robot: @Arvinderpal: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-test | 2afe147eb07f761c7cc553d6b10e037b57792357 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-test/1285955644142129152) | `/test pull-cluster-api-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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: @Arvinderpal: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-test | 2afe147eb07f761c7cc553d6b10e037b57792357 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-test/1285955644142129152) | `/test pull-cluster-api-test`
pull-cluster-api-e2e | 2afe147eb07f761c7cc553d6b10e037b57792357 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-e2e/1285955644142129155) | `/test pull-cluster-api-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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: @Arvinderpal: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-test | 2afe147eb07f761c7cc553d6b10e037b57792357 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-test/1285955644142129152) | `/test pull-cluster-api-test`
pull-cluster-api-e2e | 2afe147eb07f761c7cc553d6b10e037b57792357 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-e2e/1285955644142129155) | `/test pull-cluster-api-e2e`
pull-cluster-api-build | 299a98cb10d9599bffb0775094748bb3196e8369 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-build/1286379321040048129) | `/test pull-cluster-api-build`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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: @Arvinderpal: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-test | 2afe147eb07f761c7cc553d6b10e037b57792357 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-test/1285955644142129152) | `/test pull-cluster-api-test`
pull-cluster-api-build | 299a98cb10d9599bffb0775094748bb3196e8369 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-build/1286379321040048129) | `/test pull-cluster-api-build`
pull-cluster-api-e2e | 299a98cb10d9599bffb0775094748bb3196e8369 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-e2e/1286379321040048132) | `/test pull-cluster-api-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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: @Arvinderpal: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-build | 299a98cb10d9599bffb0775094748bb3196e8369 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-build/1286379321040048129) | `/test pull-cluster-api-build`
pull-cluster-api-e2e | 299a98cb10d9599bffb0775094748bb3196e8369 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-e2e/1286379321040048132) | `/test pull-cluster-api-e2e`
pull-cluster-api-test | 299a98cb10d9599bffb0775094748bb3196e8369 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-test/1286379321044242432) | `/test pull-cluster-api-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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: @Arvinderpal: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-build | 299a98cb10d9599bffb0775094748bb3196e8369 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-build/1286379321040048129) | `/test pull-cluster-api-build`
pull-cluster-api-e2e | 299a98cb10d9599bffb0775094748bb3196e8369 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-e2e/1286379321040048132) | `/test pull-cluster-api-e2e`
pull-cluster-api-test | 299a98cb10d9599bffb0775094748bb3196e8369 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-test/1286379321044242432) | `/test pull-cluster-api-test`
pull-cluster-api-make | 299a98cb10d9599bffb0775094748bb3196e8369 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-make/1286379321040048131) | `/test pull-cluster-api-make`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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: @Arvinderpal: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-make | 299a98cb10d9599bffb0775094748bb3196e8369 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-make/1286379321040048131) | `/test pull-cluster-api-make`
pull-cluster-api-test | b1f5fba5635d04ca989df865182bd9fd2d7d3dd3 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-test/1286383851324575744) | `/test pull-cluster-api-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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: @Arvinderpal: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-test | d6b40cc1974ec3f8f1044ef90903ddef2f216aee | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-test/1286390898002759680) | `/test pull-cluster-api-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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 -->
Arvinderpal: @sedefsavas @vincepri Tests are clean. 
k8s-ci-robot: @Arvinderpal: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-make | 07e758901d73f6d2a605dde1ea1236c79207fb64 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-make/1288181348300754950) | `/test pull-cluster-api-make`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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 -->
Arvinderpal: /retest
k8s-ci-robot: @Arvinderpal: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-e2e | 93797cb19776375057b6ba69a10a42a712103a78 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-e2e/1288527499940073475) | `/test pull-cluster-api-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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: @Arvinderpal: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-verify | 95e17749bf700450e856848b817e87dc7a587e4c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-verify/1290340562565599238) | `/test pull-cluster-api-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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 -->
Arvinderpal: /retest
k8s-ci-robot: @Arvinderpal: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-e2e | 95e17749bf700450e856848b817e87dc7a587e4c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-e2e/1290648825454661632) | `/test pull-cluster-api-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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 -->
Arvinderpal: `/retest`
vincepri: /test pull-cluster-api-e2e
Arvinderpal: Ready to merge? 
Arvinderpal: Do you think we can merge this for the next release? It's a trivial change.
@vincepri @sedefsavas 


k8s-ci-robot: @Arvinderpal: 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>
vincepri: Yeah, let's rebase it and I'll take another look later today

/milestone v0.3.9
vincepri: /assign @fabriziopandini 
k8s-ci-robot: @Arvinderpal: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-test | 75e1a307a46f1771d6863ebbbee3ae61ef1d5dab | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-test/1299049250239287299) | `/test pull-cluster-api-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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 -->
fabriziopandini: /test pull-cluster-api-test
/lgtm
k8s-ci-robot: @Arvinderpal: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-e2e | 75e1a307a46f1771d6863ebbbee3ae61ef1d5dab | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-e2e/1299049250239287296) | `/test pull-cluster-api-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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: New changes are detected. LGTM label has been removed.
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **fabriziopandini**
You can assign the PR to them by writing `/assign @fabriziopandini` 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-sigs%2Fcluster-api).

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

- **[OWNERS](https://github.com/kubernetes-sigs/cluster-api/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":["fabriziopandini"]} -->
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/cluster-api/pull/3336#pullrequestreview-477080848" title="Approved">vincepri</a>*

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

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/cluster-api/blob/master/OWNERS)~~ [vincepri]

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-e2e | 75e1a307a46f1771d6863ebbbee3ae61ef1d5dab | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-e2e/1299049250239287296) | `/test pull-cluster-api-e2e`
pull-cluster-api-test | f1abb542748dc42bb481e6db84bcd0664310e0a6 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-test/1299090670929055744) | `/test pull-cluster-api-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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: @Arvinderpal: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-test | f1abb542748dc42bb481e6db84bcd0664310e0a6 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-test/1299090670929055744) | `/test pull-cluster-api-test`
pull-cluster-api-e2e | f1abb542748dc42bb481e6db84bcd0664310e0a6 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-e2e/1299090670924861440) | `/test pull-cluster-api-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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: @Arvinderpal: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-e2e | f1abb542748dc42bb481e6db84bcd0664310e0a6 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-e2e/1299090670924861440) | `/test pull-cluster-api-e2e`
pull-cluster-api-test | 1f4d5e4346afb9c23bedd8410caf6543b2542abc | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-test/1299121748284805120) | `/test pull-cluster-api-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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: @Arvinderpal: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-test | 1f4d5e4346afb9c23bedd8410caf6543b2542abc | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-test/1299121748284805120) | `/test pull-cluster-api-test`
pull-cluster-api-e2e | 1f4d5e4346afb9c23bedd8410caf6543b2542abc | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-e2e/1299121748280610816) | `/test pull-cluster-api-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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 -->
fabriziopandini: /retest
k8s-ci-robot: @Arvinderpal: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-e2e | 1f4d5e4346afb9c23bedd8410caf6543b2542abc | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-e2e/1299121748280610816) | `/test pull-cluster-api-e2e`
pull-cluster-api-test | 1f4d5e4346afb9c23bedd8410caf6543b2542abc | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-test/1299275763958157312) | `/test pull-cluster-api-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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 -->
vincepri: /test pull-cluster-api-test
k8s-ci-robot: @Arvinderpal: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-test | 1f4d5e4346afb9c23bedd8410caf6543b2542abc | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-test/1299346228319883264) | `/test pull-cluster-api-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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 -->
fabriziopandini: /test pull-cluster-api-test
k8s-ci-robot: @Arvinderpal: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-test | 1f4d5e4346afb9c23bedd8410caf6543b2542abc | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3336/pull-cluster-api-test/1299349374362456064) | `/test pull-cluster-api-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3336). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arvinderpal). 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/cluster-api/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 -->
vincepri: Seems there are two other tests under KCP that are now failing
vincepri: @Arvinderpal can you take a look?
CecileRobertMichon: lgtm pending tests

Patch:
diff --git a/Makefile b/Makefile
index 486499b7c..338cd44fb 100644
--- a/Makefile
+++ b/Makefile
@@ -89,6 +89,8 @@ TAG ?= dev
 ARCH ?= amd64
 ALL_ARCH = amd64 arm arm64 ppc64le s390x
 
+GINKGO_NODES ?= 1
+
 # Allow overriding the imagePullPolicy
 PULL_POLICY ?= Always
 
@@ -112,7 +114,7 @@ help:  ## Display this help
 ## --------------------------------------
 
 .PHONY: test
-test: ## Run tests. 
+test: ## Run tests
 	source ./scripts/fetch_ext_bins.sh; fetch_tools; setup_envs; go test -v ./... $(TEST_ARGS)
 
 .PHONY: test-cover
@@ -599,4 +601,3 @@ diagrams: ## Build proposal diagrams
 .PHONY: serve-book
 serve-book: ## Build and serve the book with live-reloading enabled
 	$(MAKE) -C docs/book serve
-
diff --git a/api/v1alpha3/condition_consts.go b/api/v1alpha3/condition_consts.go
index 62f13a28f..a19a6e39d 100644
--- a/api/v1alpha3/condition_consts.go
+++ b/api/v1alpha3/condition_consts.go
@@ -24,15 +24,6 @@ const (
 	ReadyCondition ConditionType = "Ready"
 )
 
-// Common ConditionReason used by Cluster API objects.
-const (
-	// DeletingReason (Severity=Info) documents an condition not in Status=True because the underlying object it is currently being deleted.
-	DeletingReason = "Deleting"
-
-	// DeletedReason (Severity=Info) documents an condition not in Status=True because the underlying object was deleted.
-	DeletedReason = "Deleted"
-)
-
 const (
 	// InfrastructureReadyCondition reports a summary of current status of the infrastructure object defined for this cluster/machine.
 	// This condition is mirrored from the Ready condition in the infrastructure ref object, and
@@ -76,25 +67,6 @@ const (
 	// to be available.
 	// NOTE: This reason is used only as a fallback when the bootstrap object is not reporting its own ready condition.
 	WaitingForDataSecretFallbackReason = "WaitingForDataSecret"
-
-	// DrainingSucceededCondition provide evidence of the status of the node drain operation which happens during the machine
-	// deletion process.
-	DrainingSucceededCondition ConditionType = "DrainingSucceeded"
-
-	// DrainingReason (Severity=Info) documents a machine node being drained.
-	DrainingReason = "Draining"
-
-	// DrainingFailedReason (Severity=Warning) documents a machine node drain operation failed.
-	DrainingFailedReason = "DrainingFailed"
-
-	// PreDrainDeleteHookSucceededCondition reports a machine waiting for a PreDrainDeleteHook before being delete.
-	PreDrainDeleteHookSucceededCondition ConditionType = "PreDrainDeleteHookSucceeded"
-
-	// PreTerminateDeleteHookSucceededCondition reports a machine waiting for a PreDrainDeleteHook before being delete.
-	PreTerminateDeleteHookSucceededCondition ConditionType = "PreTerminateDeleteHookSucceeded"
-
-	// WaitingExternalHookReason (Severity=Info) provide evidence that we are waiting for an external hook to complete.
-	WaitingExternalHookReason = "WaitingExternalHook"
 )
 
 const (
diff --git a/api/v1alpha3/machine_types.go b/api/v1alpha3/machine_types.go
index 97bd17378..32fe0dc43 100644
--- a/api/v1alpha3/machine_types.go
+++ b/api/v1alpha3/machine_types.go
@@ -37,18 +37,6 @@ const (
 
 	// MachineDeploymentLabelName is the label set on machines if they're controlled by MachineDeployment
 	MachineDeploymentLabelName = "cluster.x-k8s.io/deployment-name"
-
-	// PreDrainDeleteHookAnnotationPrefix annotation specifies the prefix we
-	// search each annotation for during the pre-drain.delete lifecycle hook
-	// to pause reconciliation of deletion. These hooks will prevent removal of
-	// draining the associated node until all are removed.
-	PreDrainDeleteHookAnnotationPrefix = "pre-drain.delete.hook.machine.cluster.x-k8s.io"
-
-	// PreTerminateDeleteHookAnnotationPrefix annotation specifies the prefix we
-	// search each annotation for during the pre-terminate.delete lifecycle hook
-	// to pause reconciliation of deletion. These hooks will prevent removal of
-	// an instance from an infrastructure provider until all are removed.
-	PreTerminateDeleteHookAnnotationPrefix = "pre-terminate.delete.hook.machine.cluster.x-k8s.io"
 )
 
 // ANCHOR: MachineSpec
diff --git a/api/v1alpha3/machinehealthcheck_webhook.go b/api/v1alpha3/machinehealthcheck_webhook.go
index 9f378ef42..4aad8ea22 100644
--- a/api/v1alpha3/machinehealthcheck_webhook.go
+++ b/api/v1alpha3/machinehealthcheck_webhook.go
@@ -36,19 +36,8 @@ var (
 	// 10 minutes should allow the instance to start and the node to join the
 	// cluster on most providers.
 	defaultNodeStartupTimeout = metav1.Duration{Duration: 10 * time.Minute}
-	// Minimum time allowed for a node to start up
-	minNodeStartupTimeout = metav1.Duration{Duration: 30 * time.Second}
 )
 
-// SetMinNodeStartupTimeout allows users to optionally set a custom timeout
-// for the validation webhook.
-//
-// This function is mostly used within envtest (integration tests), and should
-// never be used in a production environment.
-func SetMinNodeStartupTimeout(d metav1.Duration) {
-	minNodeStartupTimeout = d
-}
-
 func (m *MachineHealthCheck) SetupWebhookWithManager(mgr ctrl.Manager) error {
 	return ctrl.NewWebhookManagedBy(mgr).
 		For(m).
@@ -130,10 +119,10 @@ func (m *MachineHealthCheck) validate(old *MachineHealthCheck) error {
 		)
 	}
 
-	if m.Spec.NodeStartupTimeout != nil && m.Spec.NodeStartupTimeout.Seconds() < minNodeStartupTimeout.Seconds() {
+	if m.Spec.NodeStartupTimeout != nil && m.Spec.NodeStartupTimeout.Seconds() < 30 {
 		allErrs = append(
 			allErrs,
-			field.Invalid(field.NewPath("spec", "nodeStartupTimeout"), m.Spec.NodeStartupTimeout.Seconds(), "must be at least 30s"),
+			field.Invalid(field.NewPath("spec", "nodeStartupTimeout"), m.Spec.NodeStartupTimeout, "must be at least 30s"),
 		)
 	}
 
@@ -156,5 +145,6 @@ func (m *MachineHealthCheck) validate(old *MachineHealthCheck) error {
 	if len(allErrs) == 0 {
 		return nil
 	}
+
 	return apierrors.NewInvalid(GroupVersion.WithKind("MachineHealthCheck").GroupKind(), m.Name, allErrs)
 }
diff --git a/cmd/clusterctl/cmd/root.go b/cmd/clusterctl/cmd/root.go
index 9d40ac637..a107f53f3 100644
--- a/cmd/clusterctl/cmd/root.go
+++ b/cmd/clusterctl/cmd/root.go
@@ -46,23 +46,6 @@ var RootCmd = &cobra.Command{
 	Long: LongDesc(`
 		Get started with Cluster API using clusterctl to create a management cluster,
 		install providers, and create templates for your workload cluster.`),
-	PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
-		// Check if clusterctl needs an upgrade "AFTER" running each command
-		// and sub-command.
-		configClient, err := config.New(cfgFile)
-		if err != nil {
-			return err
-		}
-		output, err := newVersionChecker(configClient.Variables()).Check()
-		if err != nil {
-			return errors.Wrap(err, "unable to verify clusterctl version")
-		}
-		if len(output) != 0 {
-			// Print the output in yellow so it is more visible.
-			fmt.Fprintf(os.Stderr, "\033[33m%s\033[0m", output)
-		}
-		return nil
-	},
 }
 
 func Execute() {
diff --git a/cmd/clusterctl/cmd/version_checker.go b/cmd/clusterctl/cmd/version_checker.go
deleted file mode 100644
index 19ac458a3..000000000
--- a/cmd/clusterctl/cmd/version_checker.go
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
-Copyright 2020 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 cmd
-
-import (
-	"context"
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"regexp"
-	"strings"
-	"time"
-
-	"github.com/blang/semver"
-	"github.com/google/go-github/github"
-	"github.com/pkg/errors"
-	"golang.org/x/oauth2"
-	"k8s.io/client-go/util/homedir"
-	"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
-	logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
-	"sigs.k8s.io/cluster-api/cmd/version"
-	"sigs.k8s.io/yaml"
-)
-
-var (
-	// gitVersionRegEx matches git versions of style 0.3.7-45-c1aeccb679cd56
-	// see ./hack/version.sh for more info.
-	gitVersionRegEx = regexp.MustCompile(`(.*)-(\d+)-([0-9,a-f]{14})`)
-)
-
-type versionChecker struct {
-	versionFilePath string
-	cliVersion      func() version.Info
-	githubClient    *github.Client
-}
-
-// newVersionChecker returns a versionChecker. Its behavior has been inspired
-// by https://github.com/cli/cli.
-func newVersionChecker(vc config.VariablesClient) *versionChecker {
-	var client *github.Client
-	token, err := vc.Get("GITHUB_TOKEN")
-	if err == nil {
-		ts := oauth2.StaticTokenSource(
-			&oauth2.Token{AccessToken: token},
-		)
-		tc := oauth2.NewClient(context.TODO(), ts)
-		client = github.NewClient(tc)
-	} else {
-		client = github.NewClient(nil)
-	}
-
-	return &versionChecker{
-		versionFilePath: filepath.Join(homedir.HomeDir(), ".cluster-api", "version.yaml"),
-		cliVersion:      version.Get,
-		githubClient:    client,
-	}
-}
-
-// ReleaseInfo stores information about the release.
-type ReleaseInfo struct {
-	Version string
-	URL     string
-}
-
-// VersionState stores the release info and the last time it was updated.
-type VersionState struct {
-	LastCheck     time.Time
-	LatestRelease ReleaseInfo
-}
-
-// Check returns a message if the current clusterctl version is less than the
-// latest available release for CAPI
-// (https://github.com/kubernetes-sigs/cluster-api). It gets the latest
-// release from github at most once during a 24 hour period and caches the
-// state by default in $HOME/.cluster-api/state.yaml. If the clusterctl
-// version is the same or greater it returns nothing.
-func (v *versionChecker) Check() (string, error) {
-	log := logf.Log
-	cliVer, err := semver.ParseTolerant(v.cliVersion().GitVersion)
-	if err != nil {
-		return "", errors.Wrap(err, "unable to semver parse clusterctl GitVersion")
-	}
-
-	release, err := v.getLatestRelease()
-	if err != nil {
-		return "", err
-	}
-	if release == nil {
-		return "", nil
-	}
-	latestVersion, err := semver.ParseTolerant(release.Version)
-	if err != nil {
-		return "", errors.Wrap(err, "unable to semver parse latest release version")
-	}
-
-	// if we are using a dirty dev build, just log it out
-	if strings.HasSuffix(cliVer.String(), "-dirty") {
-		log.V(1).Info("⚠️  Using a development build of clusterctl.", "CLIVersion", cliVer.String(), "LatestGithubRelease", release.Version)
-		return "", nil
-	}
-
-	// if the cli version is a dev build off of the latest available release,
-	// the just log it out as informational.
-	if strings.HasPrefix(cliVer.String(), latestVersion.String()) && gitVersionRegEx.MatchString(cliVer.String()) {
-		log.V(1).Info("⚠️  Using a development build of clusterctl.", "CLIVersion", cliVer.String(), "LatestGithubRelease", release.Version)
-		return "", nil
-	}
-
-	if cliVer.Major == latestVersion.Major &&
-		cliVer.Minor == latestVersion.Minor &&
-		latestVersion.GT(cliVer) {
-		return fmt.Sprintf(`
-New clusterctl version available: v%s -> v%s
-%s
-`, cliVer, latestVersion.String(), release.URL), nil
-	}
-	return "", nil
-}
-
-func (v *versionChecker) getLatestRelease() (*ReleaseInfo, error) {
-	log := logf.Log
-	vs, err := readStateFile(v.versionFilePath)
-	if err != nil {
-		return nil, errors.Wrap(err, "unable to read version state file")
-	}
-
-	// if there is no release info in the state file, pull latest release from github
-	if vs == nil {
-		release, _, err := v.githubClient.Repositories.GetLatestRelease(context.TODO(), "kubernetes-sigs", "cluster-api")
-		if err != nil {
-			log.V(1).Info("⚠️ Unable to get latest github release for clusterctl")
-			// failing silently here so we don't error out in air-gapped
-			// environments.
-			return nil, nil
-		}
-
-		vs = &VersionState{
-			LastCheck: time.Now(),
-			LatestRelease: ReleaseInfo{
-				Version: release.GetTagName(),
-				URL:     release.GetHTMLURL(),
-			},
-		}
-	}
-
-	if err := writeStateFile(v.versionFilePath, vs); err != nil {
-		return nil, errors.Wrap(err, "unable to write version state file")
-	}
-
-	return &vs.LatestRelease, nil
-
-}
-
-func writeStateFile(filepath string, vs *VersionState) error {
-	vsb, err := yaml.Marshal(vs)
-	if err != nil {
-		return err
-	}
-	err = ioutil.WriteFile(filepath, vsb, 0600)
-	if err != nil {
-		return err
-	}
-	return nil
-
-}
-
-func readStateFile(filepath string) (*VersionState, error) {
-	b, err := ioutil.ReadFile(filepath)
-	if err != nil {
-		if os.IsNotExist(err) {
-			// if the file doesn't exist yet, don't error
-			return nil, nil
-		}
-		return nil, err
-	}
-
-	vs := new(VersionState)
-	if err := yaml.Unmarshal(b, vs); err != nil {
-		return nil, err
-	}
-
-	// If the last check is more than 24 hours ago, then don't return the
-	// version state of the file.
-	if time.Since(vs.LastCheck).Hours() > 24 {
-		return nil, nil
-	}
-
-	return vs, nil
-}
diff --git a/controllers/cluster_controller.go b/controllers/cluster_controller.go
index 7f8c03706..fa6a5148c 100644
--- a/controllers/cluster_controller.go
+++ b/controllers/cluster_controller.go
@@ -124,6 +124,14 @@ func (r *ClusterReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, reterr e
 	}
 
 	defer func() {
+		// Always update the readyCondition with the summary of the cluster conditions.
+		conditions.SetSummary(cluster,
+			conditions.WithConditions(
+				clusterv1.ControlPlaneReadyCondition,
+				clusterv1.InfrastructureReadyCondition,
+			),
+		)
+
 		// Always reconcile the Status.Phase field.
 		r.reconcilePhase(ctx, cluster)
 
@@ -133,7 +141,7 @@ func (r *ClusterReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, reterr e
 		if reterr == nil {
 			patchOpts = append(patchOpts, patch.WithStatusObservedGeneration{})
 		}
-		if err := patchCluster(ctx, patchHelper, cluster, patchOpts...); err != nil {
+		if err := patchHelper.Patch(ctx, cluster, patchOpts...); err != nil {
 			reterr = kerrors.NewAggregate([]error{reterr, err})
 		}
 	}()
@@ -153,28 +161,6 @@ func (r *ClusterReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, reterr e
 	return r.reconcile(ctx, cluster)
 }
 
-func patchCluster(ctx context.Context, patchHelper *patch.Helper, cluster *clusterv1.Cluster, options ...patch.Option) error {
-	// Always update the readyCondition by summarizing the state of other conditions.
-	conditions.SetSummary(cluster,
-		conditions.WithConditions(
-			clusterv1.ControlPlaneReadyCondition,
-			clusterv1.InfrastructureReadyCondition,
-		),
-	)
-
-	// Patch the object, ignoring conflicts on the conditions owned by this controller.
-	// Also, if requested, we are adding additional options like e.g. Patch ObservedGeneration when issuing the
-	// patch at the end of the reconcile loop.
-	options = append(options,
-		patch.WithOwnedConditions{Conditions: []clusterv1.ConditionType{
-			clusterv1.ReadyCondition,
-			clusterv1.ControlPlaneReadyCondition,
-			clusterv1.InfrastructureReadyCondition,
-		}},
-	)
-	return patchHelper.Patch(ctx, cluster, options...)
-}
-
 // reconcile handles cluster reconciliation.
 func (r *ClusterReconciler) reconcile(ctx context.Context, cluster *clusterv1.Cluster) (ctrl.Result, error) {
 	phases := []func(context.Context, *clusterv1.Cluster) (ctrl.Result, error){
@@ -260,18 +246,11 @@ func (r *ClusterReconciler) reconcileDelete(ctx context.Context, cluster *cluste
 		switch {
 		case apierrors.IsNotFound(errors.Cause(err)):
 			// All good - the control plane resource has been deleted
-			conditions.MarkFalse(cluster, clusterv1.ControlPlaneReadyCondition, clusterv1.DeletedReason, clusterv1.ConditionSeverityInfo, "")
 		case err != nil:
 			return reconcile.Result{}, errors.Wrapf(err, "failed to get %s %q for Cluster %s/%s",
 				path.Join(cluster.Spec.ControlPlaneRef.APIVersion, cluster.Spec.ControlPlaneRef.Kind),
 				cluster.Spec.ControlPlaneRef.Name, cluster.Namespace, cluster.Name)
 		default:
-			// Report a summary of current status of the control plane object defined for this cluster.
-			conditions.SetMirror(cluster, clusterv1.ControlPlaneReadyCondition,
-				conditions.UnstructuredGetter(obj),
-				conditions.WithFallbackValue(false, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, ""),
-			)
-
 			// Issue a deletion request for the control plane object.
 			// Once it's been deleted, the cluster will get processed again.
 			if err := r.Client.Delete(ctx, obj); err != nil {
@@ -291,18 +270,11 @@ func (r *ClusterReconciler) reconcileDelete(ctx context.Context, cluster *cluste
 		switch {
 		case apierrors.IsNotFound(errors.Cause(err)):
 			// All good - the infra resource has been deleted
-			conditions.MarkFalse(cluster, clusterv1.InfrastructureReadyCondition, clusterv1.DeletedReason, clusterv1.ConditionSeverityInfo, "")
 		case err != nil:
 			return ctrl.Result{}, errors.Wrapf(err, "failed to get %s %q for Cluster %s/%s",
 				path.Join(cluster.Spec.InfrastructureRef.APIVersion, cluster.Spec.InfrastructureRef.Kind),
 				cluster.Spec.InfrastructureRef.Name, cluster.Namespace, cluster.Name)
 		default:
-			// Report a summary of current status of the infrastructure object defined for this cluster.
-			conditions.SetMirror(cluster, clusterv1.InfrastructureReadyCondition,
-				conditions.UnstructuredGetter(obj),
-				conditions.WithFallbackValue(false, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, ""),
-			)
-
 			// Issue a deletion request for the infrastructure object.
 			// Once it's been deleted, the cluster will get processed again.
 			if err := r.Client.Delete(ctx, obj); err != nil {
diff --git a/controllers/machine_controller.go b/controllers/machine_controller.go
index e490328c4..1ef2c0954 100644
--- a/controllers/machine_controller.go
+++ b/controllers/machine_controller.go
@@ -167,6 +167,18 @@ func (r *MachineReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, reterr e
 	}
 
 	defer func() {
+		// Always update the readyCondition with the summary of the machine conditions.
+		conditions.SetSummary(m,
+			conditions.WithConditions(
+				clusterv1.BootstrapReadyCondition,
+				clusterv1.InfrastructureReadyCondition,
+				// TODO: add MHC conditions here
+			),
+			conditions.WithStepCounterIfOnly(
+				clusterv1.BootstrapReadyCondition,
+				clusterv1.InfrastructureReadyCondition,
+			),
+		)
 
 		r.reconcilePhase(ctx, m)
 
@@ -176,7 +188,7 @@ func (r *MachineReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, reterr e
 		if reterr == nil {
 			patchOpts = append(patchOpts, patch.WithStatusObservedGeneration{})
 		}
-		if err := patchMachine(ctx, patchHelper, m, patchOpts...); err != nil {
+		if err := patchHelper.Patch(ctx, m, patchOpts...); err != nil {
 			reterr = kerrors.NewAggregate([]error{reterr, err})
 		}
 	}()
@@ -202,37 +214,6 @@ func (r *MachineReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, reterr e
 	return r.reconcile(ctx, cluster, m)
 }
 
-func patchMachine(ctx context.Context, patchHelper *patch.Helper, machine *clusterv1.Machine, options ...patch.Option) error {
-	// Always update the readyCondition by summarizing the state of other conditions.
-	// A step counter is added to represent progress during the provisioning process (instead we are hiding it
-	// after provisioning - e.g. when a MHC condition exists - or during the deletion process).
-	conditions.SetSummary(machine,
-		conditions.WithConditions(
-			clusterv1.BootstrapReadyCondition,
-			clusterv1.InfrastructureReadyCondition,
-			// TODO: add MHC conditions here
-		),
-		conditions.WithStepCounterIf(machine.ObjectMeta.DeletionTimestamp.IsZero()),
-		conditions.WithStepCounterIfOnly(
-			clusterv1.BootstrapReadyCondition,
-			clusterv1.InfrastructureReadyCondition,
-		),
-	)
-
-	// Patch the object, ignoring conflicts on the conditions owned by this controller.
-	// Also, if requested, we are adding additional options like e.g. Patch ObservedGeneration when issuing the
-	// patch at the end of the reconcile loop.
-	options = append(options,
-		patch.WithOwnedConditions{Conditions: []clusterv1.ConditionType{
-			clusterv1.ReadyCondition,
-			clusterv1.BootstrapReadyCondition,
-			clusterv1.InfrastructureReadyCondition,
-		}},
-	)
-
-	return patchHelper.Patch(ctx, machine, options...)
-}
-
 func (r *MachineReconciler) reconcile(ctx context.Context, cluster *clusterv1.Cluster, m *clusterv1.Machine) (ctrl.Result, error) {
 	logger := r.Log.WithValues("machine", m.Name, "namespace", m.Namespace)
 	logger = logger.WithValues("cluster", cluster.Name)
@@ -289,54 +270,20 @@ func (r *MachineReconciler) reconcileDelete(ctx context.Context, cluster *cluste
 	}
 
 	if isDeleteNodeAllowed {
-		// pre-drain.delete lifecycle hook
-		// Return early without error, will requeue if/when the hook owner removes the annotation.
-		if annotations.HasWithPrefix(clusterv1.PreDrainDeleteHookAnnotationPrefix, m.ObjectMeta.Annotations) {
-			conditions.MarkFalse(m, clusterv1.PreDrainDeleteHookSucceededCondition, clusterv1.WaitingExternalHookReason, clusterv1.ConditionSeverityInfo, "")
-			return ctrl.Result{}, nil
-		}
-		conditions.MarkTrue(m, clusterv1.PreDrainDeleteHookSucceededCondition)
-
-		// Drain node before deletion and issue a patch in order to make this operation visible to the users.
+		// Drain node before deletion.
 		if _, exists := m.ObjectMeta.Annotations[clusterv1.ExcludeNodeDrainingAnnotation]; !exists {
-			patchHelper, err := patch.NewHelper(m, r.Client)
-			if err != nil {
-				return ctrl.Result{}, err
-			}
-
 			logger.Info("Draining node", "node", m.Status.NodeRef.Name)
-			conditions.MarkFalse(m, clusterv1.DrainingSucceededCondition, clusterv1.DrainingReason, clusterv1.ConditionSeverityInfo, "Draining the node before deletion")
-			if err := patchMachine(ctx, patchHelper, m); err != nil {
-				return ctrl.Result{}, errors.Wrap(err, "failed to patch Machine")
-			}
-
 			if err := r.drainNode(ctx, cluster, m.Status.NodeRef.Name, m.Name); err != nil {
-				conditions.MarkFalse(m, clusterv1.DrainingSucceededCondition, clusterv1.DrainingFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
 				r.recorder.Eventf(m, corev1.EventTypeWarning, "FailedDrainNode", "error draining Machine's node %q: %v", m.Status.NodeRef.Name, err)
 				return ctrl.Result{}, err
 			}
-
-			conditions.MarkTrue(m, clusterv1.DrainingSucceededCondition)
 			r.recorder.Eventf(m, corev1.EventTypeNormal, "SuccessfulDrainNode", "success draining Machine's node %q", m.Status.NodeRef.Name)
 		}
 	}
 
-	// pre-term.delete lifecycle hook
-	// Return early without error, will requeue if/when the hook owner removes the annotation.
-	if annotations.HasWithPrefix(clusterv1.PreTerminateDeleteHookAnnotationPrefix, m.ObjectMeta.Annotations) {
-		conditions.MarkFalse(m, clusterv1.PreTerminateDeleteHookSucceededCondition, clusterv1.WaitingExternalHookReason, clusterv1.ConditionSeverityInfo, "")
-		return ctrl.Result{}, nil
-	}
-	conditions.MarkTrue(m, clusterv1.PreTerminateDeleteHookSucceededCondition)
-
-	// Return early and don't remove the finalizer if we got an error or
-	// the external reconciliation deletion isn't ready.
-
-	if ok, err := r.reconcileDeleteInfrastructure(ctx, m); !ok || err != nil {
-		return ctrl.Result{}, err
-	}
-
-	if ok, err := r.reconcileDeleteBootstrap(ctx, m); !ok || err != nil {
+	if ok, err := r.reconcileDeleteExternal(ctx, m); !ok || err != nil {
+		// Return early and don't remove the finalizer if we got an error or
+		// the external reconciliation deletion isn't ready.
 		return ctrl.Result{}, err
 	}
 
@@ -483,70 +430,41 @@ func (r *MachineReconciler) deleteNode(ctx context.Context, cluster *clusterv1.C
 	return nil
 }
 
-func (r *MachineReconciler) reconcileDeleteBootstrap(ctx context.Context, m *clusterv1.Machine) (bool, error) {
-	obj, err := r.reconcileDeleteExternal(ctx, m, m.Spec.Bootstrap.ConfigRef)
-	if err != nil {
-		return false, err
-	}
-
-	if obj == nil {
-		// Marks the bootstrap as deleted
-		conditions.MarkFalse(m, clusterv1.BootstrapReadyCondition, clusterv1.DeletedReason, clusterv1.ConditionSeverityInfo, "")
-		return true, nil
-	}
-
-	// Report a summary of current status of the bootstrap object defined for this machine.
-	conditions.SetMirror(m, clusterv1.BootstrapReadyCondition,
-		conditions.UnstructuredGetter(obj),
-		conditions.WithFallbackValue(false, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, ""),
-	)
-	return false, nil
-}
-
-func (r *MachineReconciler) reconcileDeleteInfrastructure(ctx context.Context, m *clusterv1.Machine) (bool, error) {
-	obj, err := r.reconcileDeleteExternal(ctx, m, &m.Spec.InfrastructureRef)
-	if err != nil {
-		return false, err
-	}
-
-	if obj == nil {
-		// Marks the infrastructure as deleted
-		conditions.MarkFalse(m, clusterv1.InfrastructureReadyCondition, clusterv1.DeletedReason, clusterv1.ConditionSeverityInfo, "")
-		return true, nil
+// reconcileDeleteExternal tries to delete external references, returning true if it cannot find any.
+func (r *MachineReconciler) reconcileDeleteExternal(ctx context.Context, m *clusterv1.Machine) (bool, error) {
+	objects := []*unstructured.Unstructured{}
+	references := []*corev1.ObjectReference{
+		m.Spec.Bootstrap.ConfigRef,
+		&m.Spec.InfrastructureRef,
 	}
 
-	// Report a summary of current status of the bootstrap object defined for this machine.
-	conditions.SetMirror(m, clusterv1.InfrastructureReadyCondition,
-		conditions.UnstructuredGetter(obj),
-		conditions.WithFallbackValue(false, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, ""),
-	)
-	return false, nil
-}
-
-// reconcileDeleteExternal tries to delete external references.
-func (r *MachineReconciler) reconcileDeleteExternal(ctx context.Context, m *clusterv1.Machine, ref *corev1.ObjectReference) (*unstructured.Unstructured, error) {
-	if ref == nil {
-		return nil, nil
-	}
+	// Loop over the references and try to retrieve it with the client.
+	for _, ref := range references {
+		if ref == nil {
+			continue
+		}
 
-	// get the external object
-	obj, err := external.Get(ctx, r.Client, ref, m.Namespace)
-	if err != nil && !apierrors.IsNotFound(errors.Cause(err)) {
-		return nil, errors.Wrapf(err, "failed to get %s %q for Machine %q in namespace %q",
-			ref.GroupVersionKind(), ref.Name, m.Name, m.Namespace)
+		obj, err := external.Get(ctx, r.Client, ref, m.Namespace)
+		if err != nil && !apierrors.IsNotFound(errors.Cause(err)) {
+			return false, errors.Wrapf(err, "failed to get %s %q for Machine %q in namespace %q",
+				ref.GroupVersionKind(), ref.Name, m.Name, m.Namespace)
+		}
+		if obj != nil {
+			objects = append(objects, obj)
+		}
 	}
 
-	if obj != nil {
-		// Issue a delete request.
+	// Issue a delete request for any object that has been found.
+	for _, obj := range objects {
 		if err := r.Client.Delete(ctx, obj); err != nil && !apierrors.IsNotFound(err) {
-			return obj, errors.Wrapf(err,
+			return false, errors.Wrapf(err,
 				"failed to delete %v %q for Machine %q in namespace %q",
 				obj.GroupVersionKind(), obj.GetName(), m.Name, m.Namespace)
 		}
 	}
 
 	// Return true if there are no more external objects.
-	return obj, nil
+	return len(objects) == 0, nil
 }
 
 func (r *MachineReconciler) shouldAdopt(m *clusterv1.Machine) bool {
diff --git a/controlplane/kubeadm/controllers/controller.go b/controlplane/kubeadm/controllers/controller.go
index 08933d0db..73ed73c36 100644
--- a/controlplane/kubeadm/controllers/controller.go
+++ b/controlplane/kubeadm/controllers/controller.go
@@ -187,8 +187,19 @@ func (r *KubeadmControlPlaneReconciler) Reconcile(req ctrl.Request) (res ctrl.Re
 			}
 		}
 
+		// Always update the readyCondition.
+		conditions.SetSummary(kcp,
+			conditions.WithConditions(
+				controlplanev1.MachinesSpecUpToDateCondition,
+				controlplanev1.ResizedCondition,
+				controlplanev1.MachinesReadyCondition,
+				controlplanev1.AvailableCondition,
+				controlplanev1.CertificatesAvailableCondition,
+			),
+		)
+
 		// Always attempt to Patch the KubeadmControlPlane object and status after each reconciliation.
-		if err := patchKubeadmControlPlane(ctx, patchHelper, kcp); err != nil {
+		if err := patchHelper.Patch(ctx, kcp); err != nil {
 			logger.Error(err, "Failed to patch KubeadmControlPlane")
 			reterr = kerrors.NewAggregate([]error{reterr, err})
 		}
@@ -212,33 +223,6 @@ func (r *KubeadmControlPlaneReconciler) Reconcile(req ctrl.Request) (res ctrl.Re
 	return r.reconcile(ctx, cluster, kcp)
 }
 
-func patchKubeadmControlPlane(ctx context.Context, patchHelper *patch.Helper, kcp *controlplanev1.KubeadmControlPlane) error {
-	// Always update the readyCondition by summarizing the state of other conditions.
-	conditions.SetSummary(kcp,
-		conditions.WithConditions(
-			controlplanev1.MachinesSpecUpToDateCondition,
-			controlplanev1.ResizedCondition,
-			controlplanev1.MachinesReadyCondition,
-			controlplanev1.AvailableCondition,
-			controlplanev1.CertificatesAvailableCondition,
-		),
-	)
-
-	// Patch the object, ignoring conflicts on the conditions owned by this controller.
-	return patchHelper.Patch(
-		ctx,
-		kcp,
-		patch.WithOwnedConditions{Conditions: []clusterv1.ConditionType{
-			clusterv1.ReadyCondition,
-			controlplanev1.MachinesSpecUpToDateCondition,
-			controlplanev1.ResizedCondition,
-			controlplanev1.MachinesReadyCondition,
-			controlplanev1.AvailableCondition,
-			controlplanev1.CertificatesAvailableCondition,
-		}},
-	)
-}
-
 // reconcile handles KubeadmControlPlane reconciliation.
 func (r *KubeadmControlPlaneReconciler) reconcile(ctx context.Context, cluster *clusterv1.Cluster, kcp *controlplanev1.KubeadmControlPlane) (res ctrl.Result, reterr error) {
 	logger := r.Log.WithValues("namespace", kcp.Namespace, "kubeadmControlPlane", kcp.Name, "cluster", cluster.Name)
@@ -391,16 +375,9 @@ func (r *KubeadmControlPlaneReconciler) reconcileDelete(ctx context.Context, clu
 		return ctrl.Result{}, nil
 	}
 
-	// Aggregate the operational state of all the machines; while aggregating we are adding the
-	// source ref (reason@machine/name) so the problem can be easily tracked down to its source machine.
-	// However, during delete we are hiding the counter (1 of x) because it does not make sense given that
-	// all the machines are deleted in parallel.
-	conditions.SetAggregate(kcp, controlplanev1.MachinesReadyCondition, ownedMachines.ConditionGetters(), conditions.AddSourceRef(), conditions.WithStepCounterIf(false))
-
 	// Verify that only control plane machines remain
 	if len(allMachines) != len(ownedMachines) {
-		logger.Info("Waiting for worker nodes to be deleted first")
-		conditions.MarkFalse(kcp, controlplanev1.ResizedCondition, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "Waiting for worker nodes to be deleted first")
+		logger.V(2).Info("Waiting for worker nodes to be deleted first")
 		return ctrl.Result{RequeueAfter: deleteRequeueAfter}, nil
 	}
 
@@ -421,7 +398,6 @@ func (r *KubeadmControlPlaneReconciler) reconcileDelete(ctx context.Context, clu
 			"Failed to delete control plane Machines for cluster %s/%s control plane: %v", cluster.Namespace, cluster.Name, err)
 		return ctrl.Result{}, err
 	}
-	conditions.MarkFalse(kcp, controlplanev1.ResizedCondition, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "")
 	return ctrl.Result{RequeueAfter: deleteRequeueAfter}, nil
 }
 
@@ -446,11 +422,9 @@ func (r *KubeadmControlPlaneReconciler) ClusterToKubeadmControlPlane(o handler.M
 // It removes any etcd members that do not have a corresponding node.
 // Also, as a final step, checks if there is any machines that is being deleted.
 func (r *KubeadmControlPlaneReconciler) reconcileHealth(ctx context.Context, cluster *clusterv1.Cluster, kcp *controlplanev1.KubeadmControlPlane, controlPlane *internal.ControlPlane) (ctrl.Result, error) {
-	logger := controlPlane.Logger()
 
 	// Do a health check of the Control Plane components
 	if err := r.managementCluster.TargetClusterControlPlaneIsHealthy(ctx, util.ObjectKey(cluster)); err != nil {
-		logger.V(2).Info("Waiting for control plane to pass control plane health check to continue reconciliation", "cause", err)
 		r.recorder.Eventf(kcp, corev1.EventTypeWarning, "ControlPlaneUnhealthy",
 			"Waiting for control plane to pass control plane health check to continue reconciliation: %v", err)
 		return ctrl.Result{RequeueAfter: healthCheckFailedRequeueAfter}, nil
@@ -459,20 +433,18 @@ func (r *KubeadmControlPlaneReconciler) reconcileHealth(ctx context.Context, clu
 	// If KCP should manage etcd, ensure etcd is healthy.
 	if controlPlane.IsEtcdManaged() {
 		if err := r.managementCluster.TargetClusterEtcdIsHealthy(ctx, util.ObjectKey(cluster)); err != nil {
+			errList := []error{errors.Wrap(err, "failed to pass etcd health check")}
+			r.recorder.Eventf(kcp, corev1.EventTypeWarning, "ControlPlaneUnhealthy",
+				"Waiting for control plane to pass etcd health check to continue reconciliation: %v", err)
 			// If there are any etcd members that do not have corresponding nodes, remove them from etcd and from the kubeadm configmap.
 			// This will solve issues related to manual control-plane machine deletion.
 			workloadCluster, err := r.managementCluster.GetWorkloadCluster(ctx, util.ObjectKey(cluster))
 			if err != nil {
-				return ctrl.Result{}, err
-			}
-			if err := workloadCluster.ReconcileEtcdMembers(ctx); err != nil {
-				logger.V(2).Info("Failed attempt to remove potential hanging etcd members to pass etcd health check to continue reconciliation", "cause", err)
+				errList = append(errList, errors.Wrap(err, "cannot get remote client to workload cluster"))
+			} else if err := workloadCluster.ReconcileEtcdMembers(ctx); err != nil {
+				errList = append(errList, errors.Wrap(err, "failed attempt to remove potential hanging etcd members to pass etcd health check to continue reconciliation"))
 			}
-
-			logger.V(2).Info("Waiting for control plane to pass etcd health check to continue reconciliation", "cause", err)
-			r.recorder.Eventf(kcp, corev1.EventTypeWarning, "ControlPlaneUnhealthy",
-				"Waiting for control plane to pass etcd health check to continue reconciliation: %v", err)
-			return ctrl.Result{RequeueAfter: healthCheckFailedRequeueAfter}, nil
+			return ctrl.Result{}, kerrors.NewAggregate(errList)
 		}
 	}
 
diff --git a/controlplane/kubeadm/controllers/status.go b/controlplane/kubeadm/controllers/status.go
index 0c6b0ceac..edf9ddd37 100644
--- a/controlplane/kubeadm/controllers/status.go
+++ b/controlplane/kubeadm/controllers/status.go
@@ -57,19 +57,13 @@ func (r *KubeadmControlPlaneReconciler) updateStatus(ctx context.Context, kcp *c
 	kcp.Status.ReadyReplicas = 0
 	kcp.Status.UnavailableReplicas = replicas
 
-	// Return early if the deletion timestamp is set, because we don't want to try to connect to the workload cluster
-	// and we don't want to report resize condition (because it is set to deleting into reconcile delete).
-	if !kcp.DeletionTimestamp.IsZero() {
-		return nil
-	}
-
 	switch {
 	// We are scaling up
 	case replicas < desiredReplicas:
-		conditions.MarkFalse(kcp, controlplanev1.ResizedCondition, controlplanev1.ScalingUpReason, clusterv1.ConditionSeverityWarning, "Scaling up control plane to %d replicas (actual %d)", desiredReplicas, replicas)
+		conditions.MarkFalse(kcp, controlplanev1.ResizedCondition, controlplanev1.ScalingUpReason, clusterv1.ConditionSeverityWarning, "Scaling up to %d replicas (actual %d)", desiredReplicas, replicas)
 	// We are scaling down
 	case replicas > desiredReplicas:
-		conditions.MarkFalse(kcp, controlplanev1.ResizedCondition, controlplanev1.ScalingDownReason, clusterv1.ConditionSeverityWarning, "Scaling down control plane to %d replicas (actual %d)", desiredReplicas, replicas)
+		conditions.MarkFalse(kcp, controlplanev1.ResizedCondition, controlplanev1.ScalingDownReason, clusterv1.ConditionSeverityWarning, "Scaling down to %d replicas (actual %d)", desiredReplicas, replicas)
 	default:
 		// make sure last resize operation is marked as completed.
 		// NOTE: we are checking the number of machines ready so we report resize completed only when the machines
@@ -80,6 +74,11 @@ func (r *KubeadmControlPlaneReconciler) updateStatus(ctx context.Context, kcp *c
 		}
 	}
 
+	// Return early if the deletion timestamp is set, we don't want to try to connect to the workload cluster.
+	if !kcp.DeletionTimestamp.IsZero() {
+		return nil
+	}
+
 	workloadCluster, err := r.managementCluster.GetWorkloadCluster(ctx, util.ObjectKey(cluster))
 	if err != nil {
 		return errors.Wrap(err, "failed to create remote cluster client")
diff --git a/controlplane/kubeadm/internal/etcd/fake/client.go b/controlplane/kubeadm/internal/etcd/fake/client.go
index 98ce83c9a..91c1b0568 100644
--- a/controlplane/kubeadm/internal/etcd/fake/client.go
+++ b/controlplane/kubeadm/internal/etcd/fake/client.go
@@ -32,7 +32,6 @@ type FakeEtcdClient struct {
 	StatusResponse       *clientv3.StatusResponse
 	ErrorResponse        error
 	MovedLeader          uint64
-	RemovedMember        uint64
 }
 
 func (c *FakeEtcdClient) Endpoints() []string {
@@ -55,8 +54,7 @@ func (c *FakeEtcdClient) AlarmList(_ context.Context) (*clientv3.AlarmResponse,
 func (c *FakeEtcdClient) MemberList(_ context.Context) (*clientv3.MemberListResponse, error) {
 	return c.MemberListResponse, c.ErrorResponse
 }
-func (c *FakeEtcdClient) MemberRemove(_ context.Context, i uint64) (*clientv3.MemberRemoveResponse, error) {
-	c.RemovedMember = i
+func (c *FakeEtcdClient) MemberRemove(_ context.Context, _ uint64) (*clientv3.MemberRemoveResponse, error) {
 	return c.MemberRemoveResponse, c.ErrorResponse
 }
 func (c *FakeEtcdClient) MemberUpdate(_ context.Context, _ uint64, _ []string) (*clientv3.MemberUpdateResponse, error) {
diff --git a/controlplane/kubeadm/internal/workload_cluster.go b/controlplane/kubeadm/internal/workload_cluster.go
index d0db1fe68..2a1a55a73 100644
--- a/controlplane/kubeadm/internal/workload_cluster.go
+++ b/controlplane/kubeadm/internal/workload_cluster.go
@@ -237,23 +237,19 @@ func (w *Workload) RemoveMachineFromKubeadmConfigMap(ctx context.Context, machin
 
 // RemoveNodeFromKubeadmConfigMap removes the entry for the node from the kubeadm configmap.
 func (w *Workload) RemoveNodeFromKubeadmConfigMap(ctx context.Context, name string) error {
-	return util.Retry(func() (bool, error) {
-		configMapKey := ctrlclient.ObjectKey{Name: kubeadmConfigKey, Namespace: metav1.NamespaceSystem}
-		kubeadmConfigMap, err := w.getConfigMap(ctx, configMapKey)
-		if err != nil {
-			Log.Error(err, "unable to get kubeadmConfigMap")
-			return false, nil
-		}
-		config := &kubeadmConfig{ConfigMap: kubeadmConfigMap}
-		if err := config.RemoveAPIEndpoint(name); err != nil {
-			return false, err
-		}
-		if err := w.Client.Update(ctx, config.ConfigMap); err != nil {
-			Log.Error(err, "error updating kubeadm ConfigMap")
-			return false, nil
-		}
-		return true, nil
-	}, 5)
+	configMapKey := ctrlclient.ObjectKey{Name: kubeadmConfigKey, Namespace: metav1.NamespaceSystem}
+	kubeadmConfigMap, err := w.getConfigMap(ctx, configMapKey)
+	if err != nil {
+		return err
+	}
+	config := &kubeadmConfig{ConfigMap: kubeadmConfigMap}
+	if err := config.RemoveAPIEndpoint(name); err != nil {
+		return err
+	}
+	if err := w.Client.Update(ctx, config.ConfigMap); err != nil {
+		return errors.Wrap(err, "error updating kubeadm ConfigMap")
+	}
+	return nil
 }
 
 // ClusterStatus holds stats information about the cluster.
diff --git a/docs/book/src/user/quick-start.md b/docs/book/src/user/quick-start.md
index a21d4ba64..289062ba0 100644
--- a/docs/book/src/user/quick-start.md
+++ b/docs/book/src/user/quick-start.md
@@ -632,7 +632,7 @@ See the [clusterctl] documentation for more detail about clusterctl supported ac
 [clusterctl config cluster]: ../clusterctl/commands/config-cluster.md
 [clusterctl]: ../clusterctl/overview.md
 [Docker]: https://www.docker.com/
-[docker-provider]: ../clusterctl/developers.md#additional-steps-for-the-docker-provider
+[docker-provider]: ../clusterctl/developers.md#additional-steps-in-order-to-use-the-docker-provider
 [GCP provider]: https://github.com/kubernetes-sigs/cluster-api-provider-gcp
 [infrastructure provider]: ../reference/glossary.md#infrastructure-provider
 [kind]: https://kind.sigs.k8s.io/
diff --git a/docs/proposals/20200602-machine-deletion-phase-hooks.md b/docs/proposals/20200602-machine-deletion-phase-hooks.md
index 279ad7e40..6aa9438b8 100644
--- a/docs/proposals/20200602-machine-deletion-phase-hooks.md
+++ b/docs/proposals/20200602-machine-deletion-phase-hooks.md
@@ -8,8 +8,8 @@ reviewers:
   - "@detiber"
   - "@ncdc"
 creation-date: 2020-06-02
-last-updated: 2020-08-07
-status: implemented
+last-updated: 2020-06-02
+status: implementable
 ---
 
 # Machine Deletion Phase Hooks
diff --git a/go.mod b/go.mod
index 57f049bdb..2cd39f0e9 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,6 @@ require (
 	github.com/evanphx/json-patch v4.5.0+incompatible
 	github.com/go-logr/logr v0.1.0
 	github.com/gogo/protobuf v1.3.1 // indirect
-	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
 	github.com/google/go-cmp v0.4.1
 	github.com/google/go-github v17.0.0+incompatible
 	github.com/google/go-querystring v1.0.0 // indirect
@@ -26,9 +25,7 @@ require (
 	github.com/spf13/pflag v1.0.5
 	github.com/spf13/viper v1.6.2
 	go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738
-	golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 // indirect
 	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
-	golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
 	google.golang.org/appengine v1.6.6 // indirect
 	google.golang.org/grpc v1.26.0
 	k8s.io/api v0.17.8
diff --git a/go.sum b/go.sum
index eb70ab23b..3b5625408 100644
--- a/go.sum
+++ b/go.sum
@@ -90,7 +90,6 @@ github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BU
 github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg=
 github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
@@ -180,8 +179,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -457,8 +454,6 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
-golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -536,8 +531,6 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -615,7 +608,6 @@ gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 h1:Xe2gvTZUJpsvOWUnvmL/tmhVBZUmHSvLbMjRj6NUUKo=
 gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/util/annotations/helpers.go b/util/annotations/paused.go
similarity index 86%
rename from util/annotations/helpers.go
rename to util/annotations/paused.go
index fbccc929f..acb817a05 100644
--- a/util/annotations/helpers.go
+++ b/util/annotations/paused.go
@@ -17,8 +17,6 @@ limitations under the License.
 package annotations
 
 import (
-	"strings"
-
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
 )
@@ -40,12 +38,3 @@ func HasPausedAnnotation(o metav1.Object) bool {
 	_, ok := annotations[clusterv1.PausedAnnotation]
 	return ok
 }
-
-func HasWithPrefix(prefix string, annotations map[string]string) bool {
-	for key := range annotations {
-		if strings.HasPrefix(key, prefix) {
-			return true
-		}
-	}
-	return false
-}
diff --git a/util/conditions/getter.go b/util/conditions/getter.go
index 64dc8d21a..8be83a84a 100644
--- a/util/conditions/getter.go
+++ b/util/conditions/getter.go
@@ -155,6 +155,7 @@ func summary(from Getter, options ...MergeOption) *clusterv1.Condition {
 	// If it is required to add a step counter only if a subset of condition exists, check if the conditions
 	// in scope are included in this subset.
 	if mergeOpt.addStepCounterIfOnlyConditionTypes != nil {
+		mergeOpt.addStepCounter = true
 		for _, c := range conditionsInScope {
 			for _, t := range mergeOpt.addStepCounterIfOnlyConditionTypes {
 				if c.Type != t {
diff --git a/util/conditions/merge_strategies.go b/util/conditions/merge_strategies.go
index bd8bbc00e..e84512f15 100644
--- a/util/conditions/merge_strategies.go
+++ b/util/conditions/merge_strategies.go
@@ -58,22 +58,11 @@ func WithStepCounter() MergeOption {
 	}
 }
 
-// WithStepCounterIf adds a step counter if the value is true.
-// This can be used e.g. to add a step counter only if the object is not being deleted.
-//
-// IMPORTANT: This options works only while generating the Summary condition.
-func WithStepCounterIf(value bool) MergeOption {
-	return func(c *mergeOptions) {
-		c.addStepCounter = value
-	}
-}
-
-// WithStepCounterIfOnly ensure a step counter is show only if a subset of condition exists.
-// This applies for example on Machines, where we want to use
+// If it is required to add a step counter only if a subset of condition exists, check if the conditions
+// in scope are included in this subset. This applies for example on Machines, where we want to use
 // the step counter notation while provisioning the machine, but then we want to move away from this notation
 // as soon as the machine is provisioned and e.g. a Machine health check condition is generated
 //
-// IMPORTANT: This options requires WithStepCounter or WithStepCounterIf to be set.
 // IMPORTANT: This options works only while generating the Summary condition.
 func WithStepCounterIfOnly(t ...clusterv1.ConditionType) MergeOption {
 	return func(c *mergeOptions) {
@@ -112,7 +101,7 @@ func localizeReason(reason string, from Getter) string {
 	if strings.Contains(reason, "@") {
 		return reason
 	}
-	return fmt.Sprintf("%s @ %s/%s", reason, from.GetObjectKind().GroupVersionKind().Kind, from.GetName())
+	return fmt.Sprintf("%s@%s/%s", reason, from.GetObjectKind().GroupVersionKind().Kind, from.GetName())
 }
 
 // getMessage returns the message to be applied to the condition resulting by merging a set of condition groups.


Test patch:
diff --git a/cmd/clusterctl/cmd/version_checker_test.go b/cmd/clusterctl/cmd/version_checker_test.go
deleted file mode 100644
index 2e9c995cc..000000000
--- a/cmd/clusterctl/cmd/version_checker_test.go
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
-Copyright 2020 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 cmd
-
-import (
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"os"
-	"path/filepath"
-	"testing"
-	"time"
-
-	. "github.com/onsi/gomega"
-	"k8s.io/client-go/util/homedir"
-	"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test"
-	"sigs.k8s.io/cluster-api/cmd/version"
-	"sigs.k8s.io/yaml"
-)
-
-func TestVersionChecker_newVersionChecker(t *testing.T) {
-	g := NewWithT(t)
-
-	versionChecker := newVersionChecker(test.NewFakeVariableClient())
-
-	expectedStateFilePath := filepath.Join(homedir.HomeDir(), ".cluster-api", "version.yaml")
-	g.Expect(versionChecker.versionFilePath).To(Equal(expectedStateFilePath))
-	g.Expect(versionChecker.cliVersion).ToNot(BeNil())
-	g.Expect(versionChecker.githubClient).ToNot(BeNil())
-}
-
-func TestVersionChecker(t *testing.T) {
-
-	tests := []struct {
-		name           string
-		cliVersion     func() version.Info
-		githubResponse string
-		expectErr      bool
-		expectedOutput string
-	}{
-		{
-			name: "returns error if unable to parse cli version",
-			cliVersion: func() version.Info {
-				return version.Info{
-					GitVersion: "bla-version",
-				}
-			},
-			expectErr: true,
-		},
-		{
-			name: "returns error if unable to parse latest version",
-			cliVersion: func() version.Info {
-				return version.Info{
-					GitVersion: "v0.3.8",
-				}
-			},
-			githubResponse: `{"tag_name": "bla-version"}`,
-			expectErr:      true,
-		},
-		{
-			name: "returns error if github request fails",
-			cliVersion: func() version.Info {
-				return version.Info{
-					GitVersion: "v0.3.8",
-				}
-			},
-			githubResponse: `bad-response`,
-			expectErr:      false,
-			// here we are printing out a log instead
-			expectedOutput: "",
-		},
-		{
-			name: "returns message if cli version is less than latest available release within minor version only",
-			cliVersion: func() version.Info {
-				return version.Info{
-					GitVersion: "v0.3.8",
-				}
-			},
-			githubResponse: `{"tag_name": "v0.3.10", "html_url": "https://github.com/foo/bar/releases/v0.3.10"}`,
-			expectErr:      false,
-			expectedOutput: `
-New clusterctl version available: v0.3.8 -> v0.3.10
-https://github.com/foo/bar/releases/v0.3.10
-`,
-		},
-		{
-			name: "returns message if cli version is a dev build deriving from latest-1 available release",
-			cliVersion: func() version.Info {
-				return version.Info{
-					GitVersion: "v0.3.7-45-c1aeccb679cd56",
-				}
-			},
-			githubResponse: `{"tag_name": "v0.3.8", "html_url": "https://github.com/foo/bar/releases/v0.3.8"}`,
-			expectErr:      false,
-			expectedOutput: `
-New clusterctl version available: v0.3.7-45-c1aeccb679cd56 -> v0.3.8
-https://github.com/foo/bar/releases/v0.3.8
-`,
-		},
-		{
-			name: "returns message if latest available release is a pre-release",
-			cliVersion: func() version.Info {
-				return version.Info{
-					GitVersion: "v0.3.8",
-				}
-			},
-			githubResponse: `{"tag_name": "v0.3.9-alpha.0", "html_url": "https://github.com/foo/bar/releases/v0.3.9-alpha.0"}`,
-			expectErr:      false,
-			expectedOutput: `
-New clusterctl version available: v0.3.8 -> v0.3.9-alpha.0
-https://github.com/foo/bar/releases/v0.3.9-alpha.0
-`,
-		},
-		{
-			name: "returns message if cli version is a pre-release of the latest available release",
-			cliVersion: func() version.Info {
-				return version.Info{
-					GitVersion: "v0.3.9-alpha.0",
-				}
-			},
-			githubResponse: `{"tag_name": "v0.3.9", "html_url": "https://github.com/foo/bar/releases/v0.3.9"}`,
-			expectErr:      false,
-			expectedOutput: `
-New clusterctl version available: v0.3.9-alpha.0 -> v0.3.9
-https://github.com/foo/bar/releases/v0.3.9
-`,
-		},
-		{
-			name: "returns message if cli version is a pre-release and latest available release is the next pre-release",
-			cliVersion: func() version.Info {
-				return version.Info{
-					GitVersion: "v0.3.8-alpha.0",
-				}
-			},
-			githubResponse: `{"tag_name": "v0.3.8-alpha.1", "html_url": "https://github.com/foo/bar/releases/v0.3.8-alpha.1"}`,
-			expectErr:      false,
-			expectedOutput: `
-New clusterctl version available: v0.3.8-alpha.0 -> v0.3.8-alpha.1
-https://github.com/foo/bar/releases/v0.3.8-alpha.1
-`,
-		},
-		{
-			name: "does not return message if cli version is a dirty build deriving from latest available release",
-			cliVersion: func() version.Info {
-				return version.Info{
-					GitVersion: "v0.3.8-45-c1aeccb679cd56-dirty",
-				}
-			},
-			githubResponse: `{"tag_name": "v0.3.8", "html_url": "https://github.com/foo/bar/releases/v0.3.8"}`,
-			expectErr:      false,
-			// here we are printing out a log instead
-			expectedOutput: "",
-		},
-		{
-			name: "does not return message if cli version is a dev build deriving from latest available release",
-			cliVersion: func() version.Info {
-				return version.Info{
-					GitVersion: "v0.3.8-45-c1aeccb679cd56",
-				}
-			},
-			githubResponse: `{"tag_name": "v0.3.8", "html_url": "https://github.com/foo/bar/releases/v0.3.8"}`,
-			expectErr:      false,
-			// here we are printing out a log instead
-			expectedOutput: "",
-		},
-		{
-			name: "does not return message if latest available version has a higher minor version than the cli version",
-			cliVersion: func() version.Info {
-				return version.Info{
-					GitVersion: "v0.3.8",
-				}
-			},
-			githubResponse: `{"tag_name": "v0.4.0", "html_url": "https://github.com/foo/bar/releases/v0.4.0"}`,
-			expectErr:      false,
-			expectedOutput: "",
-		},
-		{
-			name: "does not return message if latest available version has a higher major version and same minor version than the cli version",
-			cliVersion: func() version.Info {
-				return version.Info{
-					GitVersion: "v0.3.8",
-				}
-			},
-			githubResponse: `{"tag_name": "v100.3.9", "html_url": "https://github.com/foo/bar/releases/v100.3.9"}`,
-			expectErr:      false,
-			// we are only prompting upgrade within minor versions of the cli
-			// version
-			expectedOutput: "",
-		},
-		{
-			name: "does not return message if cli version is greater than latest available release",
-			cliVersion: func() version.Info {
-				return version.Info{
-					GitVersion: "v0.3.8",
-				}
-			},
-			githubResponse: `{"tag_name": "v0.3.7"}`,
-			expectErr:      false,
-			expectedOutput: "",
-		},
-		{
-			name: "does not return message if cli version is equal to latest available release",
-			cliVersion: func() version.Info {
-				return version.Info{
-					GitVersion: "v0.3.8",
-				}
-			},
-			githubResponse: `{"tag_name": "v0.3.8"}`,
-			expectErr:      false,
-			expectedOutput: "",
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			g := NewWithT(t)
-			tmpVersionFile, cleanDir := generateTempVersionFilePath(g)
-			defer cleanDir()
-
-			fakeGithubClient, mux, cleanup := test.NewFakeGitHub()
-			// test.NewFakeGitHub and handler for returning a fake release
-			mux.HandleFunc(
-				"/repos/kubernetes-sigs/cluster-api/releases/latest",
-				func(w http.ResponseWriter, r *http.Request) {
-					g.Expect(r.Method).To(Equal("GET"))
-					fmt.Fprint(w, tt.githubResponse)
-				},
-			)
-			defer cleanup()
-			versionChecker := newVersionChecker(test.NewFakeVariableClient())
-			versionChecker.cliVersion = tt.cliVersion
-			versionChecker.githubClient = fakeGithubClient
-			versionChecker.versionFilePath = tmpVersionFile
-
-			output, err := versionChecker.Check()
-
-			if tt.expectErr {
-				g.Expect(err).To(HaveOccurred())
-				return
-			}
-			g.Expect(err).ToNot(HaveOccurred())
-			g.Expect(output).To(Equal(tt.expectedOutput))
-		})
-	}
-}
-
-func TestVersionChecker_WriteStateFile(t *testing.T) {
-	g := NewWithT(t)
-	fakeGithubClient, mux, cleanup := test.NewFakeGitHub()
-	mux.HandleFunc(
-		"/repos/kubernetes-sigs/cluster-api/releases/latest",
-		func(w http.ResponseWriter, r *http.Request) {
-			g.Expect(r.Method).To(Equal("GET"))
-			fmt.Fprint(w, `{"tag_name": "v0.3.8", "html_url": "https://github.com/foo/bar/releases/v0.3.8"}`)
-		},
-	)
-	defer cleanup()
-
-	tmpVersionFile, cleanDir := generateTempVersionFilePath(g)
-	defer cleanDir()
-
-	versionChecker := newVersionChecker(test.NewFakeVariableClient())
-	versionChecker.versionFilePath = tmpVersionFile
-	versionChecker.githubClient = fakeGithubClient
-
-	release, err := versionChecker.getLatestRelease()
-
-	g.Expect(err).ToNot(HaveOccurred())
-	// ensure that the state file has been created
-	g.Expect(tmpVersionFile).Should(BeARegularFile())
-	fb, err := ioutil.ReadFile(tmpVersionFile)
-	g.Expect(err).ToNot(HaveOccurred())
-	var actualVersionState VersionState
-	g.Expect(yaml.Unmarshal(fb, &actualVersionState)).To(Succeed())
-	g.Expect(actualVersionState.LatestRelease).To(Equal(*release))
-}
-
-func TestVersionChecker_ReadFromStateFile(t *testing.T) {
-	g := NewWithT(t)
-
-	tmpVersionFile, cleanDir := generateTempVersionFilePath(g)
-	defer cleanDir()
-
-	fakeGithubClient1, mux1, cleanup1 := test.NewFakeGitHub()
-	mux1.HandleFunc(
-		"/repos/kubernetes-sigs/cluster-api/releases/latest",
-		func(w http.ResponseWriter, r *http.Request) {
-			g.Expect(r.Method).To(Equal("GET"))
-			fmt.Fprint(w, `{"tag_name": "v0.3.8", "html_url": "https://github.com/foo/bar/releases/v0.3.8"}`)
-		},
-	)
-	defer cleanup1()
-	versionChecker := newVersionChecker(test.NewFakeVariableClient())
-	versionChecker.versionFilePath = tmpVersionFile
-	versionChecker.githubClient = fakeGithubClient1
-
-	// this call to getLatestRelease will pull from our fakeGithubClient1 and
-	// store the information including timestamp into the state file.
-	_, err := versionChecker.getLatestRelease()
-	g.Expect(err).ToNot(HaveOccurred())
-
-	// override the github client with response to a new version v0.3.99
-	var githubCalled bool = false
-	fakeGithubClient2, mux2, cleanup2 := test.NewFakeGitHub()
-	mux2.HandleFunc(
-		"/repos/kubernetes-sigs/cluster-api/releases/latest",
-		func(w http.ResponseWriter, r *http.Request) {
-			githubCalled = true
-			fmt.Fprint(w, `{"tag_name": "v0.3.99", "html_url": "https://github.com/foo/bar/releases/v0.3.99"}`)
-		},
-	)
-	defer cleanup2()
-	versionChecker.githubClient = fakeGithubClient2
-
-	// now instead of making another call to github, we want to read from the
-	// file. This will avoid unnecessary calls to github.
-	release, err := versionChecker.getLatestRelease()
-	g.Expect(err).ToNot(HaveOccurred())
-	g.Expect(release.Version).To(Equal("v0.3.8"))
-	g.Expect(release.URL).To(Equal("https://github.com/foo/bar/releases/v0.3.8"))
-	g.Expect(githubCalled).To(BeFalse())
-}
-
-func TestVersionChecker_ReadFromStateFileWithin24Hrs(t *testing.T) {
-	g := NewWithT(t)
-
-	tmpVersionFile, cleanDir := generateTempVersionFilePath(g)
-	defer cleanDir()
-
-	// this state file was generated more than 24 hours ago.
-	reallyOldVersionState := &VersionState{
-		LastCheck: time.Now().Add(-25 * time.Hour),
-		LatestRelease: ReleaseInfo{
-			Version: "v0.3.8",
-			URL:     "https://github.com/foo/bar/releases/v0.3.8",
-		},
-	}
-	b, err := yaml.Marshal(reallyOldVersionState)
-	g.Expect(err).ToNot(HaveOccurred())
-	g.Expect(ioutil.WriteFile(tmpVersionFile, b, 0600)).To(Succeed())
-
-	fakeGithubClient1, mux1, cleanup1 := test.NewFakeGitHub()
-	mux1.HandleFunc(
-		"/repos/kubernetes-sigs/cluster-api/releases/latest",
-		func(w http.ResponseWriter, r *http.Request) {
-			g.Expect(r.Method).To(Equal("GET"))
-			fmt.Fprint(w, `{"tag_name": "v0.3.10", "html_url": "https://github.com/foo/bar/releases/v0.3.10"}`)
-		},
-	)
-	defer cleanup1()
-	versionChecker := newVersionChecker(test.NewFakeVariableClient())
-	versionChecker.versionFilePath = tmpVersionFile
-	versionChecker.githubClient = fakeGithubClient1
-
-	_, err = versionChecker.getLatestRelease()
-	g.Expect(err).ToNot(HaveOccurred())
-
-	// Since the state file is more that 24 hours old we want to retrieve the
-	// latest release from github.
-	release, err := versionChecker.getLatestRelease()
-	g.Expect(err).ToNot(HaveOccurred())
-	g.Expect(release.Version).To(Equal("v0.3.10"))
-	g.Expect(release.URL).To(Equal("https://github.com/foo/bar/releases/v0.3.10"))
-}
-
-func generateTempVersionFilePath(g *WithT) (string, func()) {
-	dir, err := ioutil.TempDir("", "clusterctl")
-	g.Expect(err).NotTo(HaveOccurred())
-	// don't create the state file, just have a path to the file
-	tmpVersionFile := filepath.Join(dir, "state.yaml")
-
-	return tmpVersionFile, func() {
-		os.RemoveAll(dir)
-	}
-}
diff --git a/controllers/cluster_controller_test.go b/controllers/cluster_controller_test.go
index 12cf19544..1f433f16c 100644
--- a/controllers/cluster_controller_test.go
+++ b/controllers/cluster_controller_test.go
@@ -296,7 +296,7 @@ var _ = Describe("Cluster Reconciler", func() {
 				},
 			},
 		}
-		machine.Spec.Bootstrap.DataSecretName = pointer.StringPtr("test6-bootstrapdata")
+
 		Expect(testEnv.Create(ctx, machine)).To(BeNil())
 		key = client.ObjectKey{Name: machine.Name, Namespace: machine.Namespace}
 		defer func() {
diff --git a/controllers/machine_controller_test.go b/controllers/machine_controller_test.go
index ef52bb8a9..d6a345884 100644
--- a/controllers/machine_controller_test.go
+++ b/controllers/machine_controller_test.go
@@ -471,6 +471,17 @@ func TestReconcileDeleteExternal(t *testing.T) {
 		},
 	}
 
+	infraConfig := &unstructured.Unstructured{
+		Object: map[string]interface{}{
+			"kind":       "InfrastructureMachine",
+			"apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3",
+			"metadata": map[string]interface{}{
+				"name":      "delete-infra",
+				"namespace": "default",
+			},
+		},
+	}
+
 	machine := &clusterv1.Machine{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      "delete",
@@ -478,6 +489,11 @@ func TestReconcileDeleteExternal(t *testing.T) {
 		},
 		Spec: clusterv1.MachineSpec{
 			ClusterName: "test-cluster",
+			InfrastructureRef: corev1.ObjectReference{
+				APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3",
+				Kind:       "InfrastructureMachine",
+				Name:       "delete-infra",
+			},
 			Bootstrap: clusterv1.Bootstrap{
 				ConfigRef: &corev1.ObjectReference{
 					APIVersion: "bootstrap.cluster.x-k8s.io/v1alpha3",
@@ -491,29 +507,36 @@ func TestReconcileDeleteExternal(t *testing.T) {
 	testCases := []struct {
 		name            string
 		bootstrapExists bool
+		infraExists     bool
+		expected        bool
 		expectError     bool
-		expected        *unstructured.Unstructured
 	}{
 		{
-			name:            "should continue to reconcile delete of external refs if exists",
+			name:            "should continue to reconcile delete of external refs since both refs exists",
 			bootstrapExists: true,
-			expected: &unstructured.Unstructured{
-				Object: map[string]interface{}{
-					"apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3",
-					"kind":       "BootstrapConfig",
-					"metadata": map[string]interface{}{
-						"name":            "delete-bootstrap",
-						"namespace":       "default",
-						"resourceVersion": "1",
-					},
-				},
-			},
-			expectError: false,
+			infraExists:     true,
+			expected:        false,
+			expectError:     false,
 		},
 		{
-			name:            "should no longer reconcile deletion of external refs since it doesn't exist",
+			name:            "should continue to reconcile delete of external refs since infra ref exist",
 			bootstrapExists: false,
-			expected:        nil,
+			infraExists:     true,
+			expected:        false,
+			expectError:     false,
+		},
+		{
+			name:            "should continue to reconcile delete of external refs since bootstrap ref exist",
+			bootstrapExists: true,
+			infraExists:     false,
+			expected:        false,
+			expectError:     false,
+		},
+		{
+			name:            "should no longer reconcile deletion of external refs since both don't exist",
+			bootstrapExists: false,
+			infraExists:     false,
+			expected:        true,
 			expectError:     false,
 		},
 	}
@@ -528,14 +551,18 @@ func TestReconcileDeleteExternal(t *testing.T) {
 				objs = append(objs, bootstrapConfig)
 			}
 
+			if tc.infraExists {
+				objs = append(objs, infraConfig)
+			}
+
 			r := &MachineReconciler{
 				Client: helpers.NewFakeClientWithScheme(scheme.Scheme, objs...),
 				Log:    log.Log,
 				scheme: scheme.Scheme,
 			}
 
-			obj, err := r.reconcileDeleteExternal(ctx, machine, machine.Spec.Bootstrap.ConfigRef)
-			g.Expect(obj).To(Equal(tc.expected))
+			ok, err := r.reconcileDeleteExternal(ctx, machine)
+			g.Expect(ok).To(Equal(tc.expected))
 			if tc.expectError {
 				g.Expect(err).To(HaveOccurred())
 			} else {
diff --git a/controllers/machinedeployment_controller_test.go b/controllers/machinedeployment_controller_test.go
index 29fbe6f31..1c30ad890 100644
--- a/controllers/machinedeployment_controller_test.go
+++ b/controllers/machinedeployment_controller_test.go
@@ -105,7 +105,7 @@ var _ = Describe("MachineDeployment Reconciler", func() {
 							Name:       "md-template",
 						},
 						Bootstrap: clusterv1.Bootstrap{
-							DataSecretName: pointer.StringPtr("data-secret-name"),
+							Data: pointer.StringPtr("data"),
 						},
 					},
 				},
diff --git a/controllers/machinehealthcheck_controller_test.go b/controllers/machinehealthcheck_controller_test.go
index f522aef3d..04e588efb 100644
--- a/controllers/machinehealthcheck_controller_test.go
+++ b/controllers/machinehealthcheck_controller_test.go
@@ -46,71 +46,6 @@ import (
 
 const defaultNamespaceName = "default"
 
-func newMachine(mhc *clusterv1.MachineHealthCheck, cluster *clusterv1.Cluster) clusterv1.Machine {
-	return clusterv1.Machine{
-		TypeMeta: metav1.TypeMeta{
-			APIVersion: clusterv1.GroupVersion.String(),
-			Kind:       "Machine",
-		},
-		ObjectMeta: metav1.ObjectMeta{
-			GenerateName: "test-mhc-machine-",
-			Namespace:    mhc.Namespace,
-			Labels:       mhc.Spec.Selector.MatchLabels,
-		},
-		Spec: clusterv1.MachineSpec{
-			ClusterName: cluster.Name,
-			Bootstrap: clusterv1.Bootstrap{
-				DataSecretName: pointer.StringPtr("data-secret-name"),
-			},
-		},
-		Status: clusterv1.MachineStatus{
-			InfrastructureReady: true,
-			BootstrapReady:      true,
-			Phase:               string(clusterv1.MachinePhaseRunning),
-			ObservedGeneration:  1,
-		},
-	}
-}
-
-func newNode() corev1.Node {
-	node := corev1.Node{
-		ObjectMeta: metav1.ObjectMeta{
-			GenerateName: "test-mhc-node-",
-		},
-		Status: corev1.NodeStatus{
-			Conditions: []corev1.NodeCondition{
-				{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
-			},
-		},
-	}
-	return node
-}
-
-func setNodeUnhealthy(node *corev1.Node) {
-	node.Status.Conditions[0] = corev1.NodeCondition{
-		Type:               corev1.NodeReady,
-		Status:             corev1.ConditionUnknown,
-		LastTransitionTime: metav1.NewTime(time.Now().Add(-10 * time.Minute)),
-	}
-}
-
-func newInfraMachine(machine *clusterv1.Machine) (*unstructured.Unstructured, string) {
-	providerID := fmt.Sprintf("test:////%v", uuid.NewUUID())
-	return &unstructured.Unstructured{
-		Object: map[string]interface{}{
-			"apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3",
-			"kind":       "InfrastructureMachine",
-			"metadata": map[string]interface{}{
-				"generateName": "test-mhc-machine-infra-",
-				"namespace":    machine.Namespace,
-			},
-			"spec": map[string]interface{}{
-				"providerID": providerID,
-			},
-		},
-	}, providerID
-}
-
 var _ = Describe("MachineHealthCheck", func() {
 	Context("on reconciliation", func() {
 		var (
@@ -122,38 +57,87 @@ var _ = Describe("MachineHealthCheck", func() {
 			nodes             []*corev1.Node
 			machines          []*clusterv1.Machine
 			fakeNodesMachines = func(n int, healthy bool, nodeRef bool) {
+				machine := clusterv1.Machine{
+					TypeMeta: metav1.TypeMeta{
+						APIVersion: clusterv1.GroupVersion.String(),
+						Kind:       "Machine",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						GenerateName: "test-mhc-machine-",
+						Namespace:    mhc.Namespace,
+						Labels:       mhc.Spec.Selector.MatchLabels,
+					},
+					Spec: clusterv1.MachineSpec{
+						ClusterName: cluster.Name,
+						Bootstrap: clusterv1.Bootstrap{
+							Data: pointer.StringPtr("data"),
+						},
+					},
+					Status: clusterv1.MachineStatus{
+						InfrastructureReady: true,
+						BootstrapReady:      true,
+						Phase:               string(clusterv1.MachinePhaseRunning),
+						ObservedGeneration:  1,
+					},
+				}
+
+				node := corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						GenerateName: "test-mhc-node-",
+					},
+					Status: corev1.NodeStatus{
+						Conditions: []corev1.NodeCondition{
+							{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
+						},
+					},
+				}
+				if !healthy {
+					node.Status.Conditions[0] = corev1.NodeCondition{
+						Type:               corev1.NodeReady,
+						Status:             corev1.ConditionUnknown,
+						LastTransitionTime: metav1.NewTime(time.Now().Add(-10 * time.Minute)),
+					}
+				}
 
 				for i := 0; i < n; i++ {
-					machine := newMachine(mhc, cluster)
-					infraMachine, providerID := newInfraMachine(&machine)
-					node := newNode()
-					if !healthy {
-						setNodeUnhealthy(&node)
+					providerID := fmt.Sprintf("test:////%v", uuid.NewUUID())
+
+					infraMachine := &unstructured.Unstructured{
+						Object: map[string]interface{}{
+							"apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3",
+							"kind":       "InfrastructureMachine",
+							"metadata": map[string]interface{}{
+								"generateName": "test-mhc-machine-infra-",
+								"namespace":    machine.Namespace,
+							},
+							"spec": map[string]interface{}{
+								"providerID": providerID,
+							},
+						},
 					}
 					Expect(testEnv.Create(ctx, infraMachine)).To(Succeed())
-					fmt.Fprintf(GinkgoWriter, "inframachine created: %s\n", infraMachine.GetName())
+					infraMachinePatch := client.MergeFrom(infraMachine.DeepCopy())
+					Expect(unstructured.SetNestedField(infraMachine.Object, true, "status", "ready")).To(Succeed())
+					Expect(testEnv.Status().Patch(ctx, infraMachine, infraMachinePatch)).To(Succeed())
+
+					machine := machine.DeepCopy()
 					machine.Spec.InfrastructureRef = corev1.ObjectReference{
 						APIVersion: infraMachine.GetAPIVersion(),
 						Kind:       infraMachine.GetKind(),
 						Name:       infraMachine.GetName(),
 					}
-					infraMachinePatch := client.MergeFrom(infraMachine.DeepCopy())
-					Expect(unstructured.SetNestedField(infraMachine.Object, true, "status", "ready")).To(Succeed())
-					Expect(testEnv.Status().Patch(ctx, infraMachine, infraMachinePatch)).To(Succeed())
-
 					machineStatus := machine.Status
-					Expect(testEnv.Create(ctx, &machine)).To(Succeed())
-					fmt.Fprintf(GinkgoWriter, "machine created: %s\n", machine.GetName())
+					Expect(testEnv.Create(ctx, machine)).To(Succeed())
 
 					if nodeRef {
+						node := node.DeepCopy()
 						node.Spec.ProviderID = providerID
 						nodeStatus := node.Status
-						Expect(testEnv.Create(ctx, &node)).To(Succeed())
-						fmt.Fprintf(GinkgoWriter, "node created: %s\n", node.GetName())
-						nodePatch := client.MergeFrom(&node)
+						Expect(testEnv.Create(ctx, node)).To(Succeed())
+						nodePatch := client.MergeFrom(node.DeepCopy())
 						node.Status = nodeStatus
-						Expect(testEnv.Status().Patch(ctx, &node, nodePatch)).To(Succeed())
-						nodes = append(nodes, &node)
+						Expect(testEnv.Status().Patch(ctx, node, nodePatch)).To(Succeed())
+						nodes = append(nodes, node)
 
 						machinePatch := client.MergeFrom(machine.DeepCopy())
 						machine.Status = machineStatus
@@ -163,10 +147,10 @@ var _ = Describe("MachineHealthCheck", func() {
 
 						now := metav1.NewTime(time.Now())
 						machine.Status.LastUpdated = &now
-						Expect(testEnv.Status().Patch(ctx, &machine, machinePatch)).To(Succeed())
+						Expect(testEnv.Status().Patch(ctx, machine, machinePatch)).To(Succeed())
 					}
 
-					machines = append(machines, &machine)
+					machines = append(machines, machine)
 				}
 			}
 		)
@@ -194,7 +178,6 @@ var _ = Describe("MachineHealthCheck", func() {
 							"selector": string(uuid.NewUUID()),
 						},
 					},
-					NodeStartupTimeout: &metav1.Duration{Duration: 1 * time.Millisecond},
 					UnhealthyConditions: []clusterv1.UnhealthyCondition{
 						{
 							Type:    corev1.NodeReady,
@@ -204,12 +187,10 @@ var _ = Describe("MachineHealthCheck", func() {
 					},
 				},
 			}
+			mhc.Default()
 		})
 
-		// delete all the nodes and mhc in the cluster, if the delete get issues, it does not stop
-		// Need to wait for the delete
 		AfterEach(func() {
-			fmt.Fprintln(GinkgoWriter, "Tearing down test")
 			for _, node := range nodes {
 				if err := testEnv.Delete(ctx, node); !apierrors.IsNotFound(err) {
 					Expect(err).NotTo(HaveOccurred())
@@ -226,7 +207,6 @@ var _ = Describe("MachineHealthCheck", func() {
 			Specify("with no existing labels exist", func() {
 				mhc.Spec.ClusterName = cluster.Name
 				mhc.Labels = map[string]string{}
-				mhc.SetGenerateName("correct-cluster-name-label-with-no-existing-labels-")
 				Expect(testEnv.Create(ctx, mhc)).To(Succeed())
 
 				Eventually(func() map[string]string {
@@ -243,7 +223,6 @@ var _ = Describe("MachineHealthCheck", func() {
 				mhc.Labels = map[string]string{
 					clusterv1.ClusterLabelName: "wrong-cluster",
 				}
-				mhc.SetGenerateName("correct-cluster-name-label-when-has-wrong-cluster-label-")
 				Expect(testEnv.Create(ctx, mhc)).To(Succeed())
 
 				Eventually(func() map[string]string {
@@ -260,7 +239,6 @@ var _ = Describe("MachineHealthCheck", func() {
 				mhc.Labels = map[string]string{
 					"extra-label": "1",
 				}
-				mhc.SetGenerateName("correct-cluster-name-label-when-other-labels-present-")
 				Expect(testEnv.Create(ctx, mhc)).To(Succeed())
 
 				Eventually(func() map[string]string {
@@ -282,16 +260,15 @@ var _ = Describe("MachineHealthCheck", func() {
 			Specify("when no existing ones exist", func() {
 				mhc.Spec.ClusterName = cluster.Name
 				mhc.OwnerReferences = nil
-				mhc.SetGenerateName("owner-reference-when-no-existing-ones-exist-")
 				Expect(testEnv.Create(ctx, mhc)).To(Succeed())
+
 				Eventually(func() []metav1.OwnerReference {
 					err := testEnv.Get(ctx, util.ObjectKey(mhc), mhc)
 					if err != nil {
-						fmt.Fprintf(GinkgoWriter, "error cannot retrieve mhc in ctx: %s\n", err)
 						return nil
 					}
 					return mhc.GetOwnerReferences()
-				}, timeout, time.Second).Should(And(
+				}).Should(And(
 					ContainElement(ownerReferenceForCluster(ctx, cluster)),
 					HaveLen(1),
 				))
@@ -302,7 +279,6 @@ var _ = Describe("MachineHealthCheck", func() {
 				mhc.OwnerReferences = []metav1.OwnerReference{
 					{Kind: "Foo", APIVersion: "foo.bar.baz/v1", Name: "Bar", UID: "12345"},
 				}
-				mhc.SetGenerateName("owner-reference-when-modifying-existing-ones-")
 				Expect(testEnv.Create(ctx, mhc)).To(Succeed())
 
 				Eventually(func() []metav1.OwnerReference {
@@ -311,7 +287,7 @@ var _ = Describe("MachineHealthCheck", func() {
 						return nil
 					}
 					return mhc.GetOwnerReferences()
-				}, timeout, time.Second).Should(And(
+				}).Should(And(
 					ContainElements(
 						metav1.OwnerReference{Kind: "Foo", APIVersion: "foo.bar.baz/v1", Name: "Bar", UID: "12345"},
 						ownerReferenceForCluster(ctx, cluster)),
@@ -323,7 +299,6 @@ var _ = Describe("MachineHealthCheck", func() {
 		Context("it marks unhealthy machines for remediation", func() {
 			Specify("when all Machines are healthy", func() {
 				mhc.Spec.ClusterName = cluster.Name
-				mhc.SetGenerateName("all-machines-healthy-")
 				Expect(testEnv.Create(ctx, mhc)).To(Succeed())
 
 				// Healthy nodes and machines.
@@ -349,7 +324,6 @@ var _ = Describe("MachineHealthCheck", func() {
 
 			Specify("when there is one unhealthy Machine", func() {
 				mhc.Spec.ClusterName = cluster.Name
-				mhc.SetGenerateName("one-unhealthy-machine-")
 				Expect(testEnv.Create(ctx, mhc)).To(Succeed())
 
 				// Healthy nodes and machines.
@@ -380,7 +354,6 @@ var _ = Describe("MachineHealthCheck", func() {
 				mhc.Spec.ClusterName = cluster.Name
 				maxUnhealthy := intstr.Parse("40%")
 				mhc.Spec.MaxUnhealthy = &maxUnhealthy
-				mhc.SetGenerateName("unhealthy-machines-exceed-maxunhealthy-")
 				Expect(testEnv.Create(ctx, mhc)).To(Succeed())
 
 				// Healthy nodes and machines.
@@ -446,8 +419,6 @@ var _ = Describe("MachineHealthCheck", func() {
 
 		Specify("when a Machine has no Node ref for less than the NodeStartupTimeout", func() {
 			mhc.Spec.ClusterName = cluster.Name
-			mhc.SetGenerateName("no-noderef-for-less-than-the-nodestartuptimeout-")
-			mhc.Spec.NodeStartupTimeout = &metav1.Duration{Duration: 5 * time.Hour}
 			Expect(testEnv.Create(ctx, mhc)).To(Succeed())
 
 			// Healthy nodes and machines.
@@ -512,8 +483,7 @@ var _ = Describe("MachineHealthCheck", func() {
 
 		Specify("when a Machine has no Node ref for longer than the NodeStartupTimeout", func() {
 			mhc.Spec.ClusterName = cluster.Name
-			mhc.Spec.NodeStartupTimeout = &metav1.Duration{Duration: time.Second}
-			mhc.SetGenerateName("no-noderef-for-longer-than-the-nodestartuptimeout-")
+			mhc.Spec.NodeStartupTimeout = &metav1.Duration{Duration: 5 * time.Second}
 			Expect(testEnv.Create(ctx, mhc)).To(Succeed())
 
 			// Healthy nodes and machines.
@@ -529,11 +499,10 @@ var _ = Describe("MachineHealthCheck", func() {
 			Eventually(func() *clusterv1.MachineHealthCheckStatus {
 				err := testEnv.Get(ctx, util.ObjectKey(mhc), mhc)
 				if err != nil {
-					fmt.Fprintf(GinkgoWriter, "error retrieving mhc: %s", err)
 					return nil
 				}
 				return &mhc.Status
-			}, timeout).Should(MatchMachineHealthCheckStatus(&clusterv1.MachineHealthCheckStatus{
+			}).Should(MatchMachineHealthCheckStatus(&clusterv1.MachineHealthCheckStatus{
 				ExpectedMachines:   3,
 				CurrentHealthy:     2,
 				ObservedGeneration: 1,
@@ -556,7 +525,7 @@ var _ = Describe("MachineHealthCheck", func() {
 					}
 				}
 				return
-			}, timeout, time.Second).Should(Equal(1))
+			}).Should(Equal(1))
 
 			// Calculate how many Machines have been remediated.
 			Eventually(func() (remediated int) {
@@ -574,12 +543,11 @@ var _ = Describe("MachineHealthCheck", func() {
 					}
 				}
 				return
-			}, timeout, time.Second).Should(Equal(1))
+			}).Should(Equal(1))
 		})
 
 		Specify("when a Machine's Node has gone away", func() {
 			mhc.Spec.ClusterName = cluster.Name
-			mhc.SetGenerateName("node-gone-away-")
 			Expect(testEnv.Create(ctx, mhc)).To(Succeed())
 
 			// Healthy nodes and machines.
@@ -651,7 +619,6 @@ var _ = Describe("MachineHealthCheck", func() {
 
 		Specify("should react when a Node transitions to unhealthy", func() {
 			mhc.Spec.ClusterName = cluster.Name
-			mhc.SetGenerateName("reach-node-transition-unhealthy-")
 			Expect(testEnv.Create(ctx, mhc)).To(Succeed())
 
 			// Healthy nodes and machines.
@@ -778,7 +745,7 @@ var _ = Describe("MachineHealthCheck", func() {
 						Spec: clusterv1.MachineSpec{
 							ClusterName: cluster.Name,
 							Bootstrap: clusterv1.Bootstrap{
-								DataSecretName: pointer.StringPtr("test-data-secret-name"),
+								Data: pointer.StringPtr("test"),
 							},
 							InfrastructureRef: corev1.ObjectReference{
 								APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3",
@@ -807,7 +774,6 @@ var _ = Describe("MachineHealthCheck", func() {
 			// Create the MachineHealthCheck instance.
 			mhc.Spec.ClusterName = cluster.Name
 			mhc.Spec.NodeStartupTimeout = &metav1.Duration{Duration: 1 * time.Second}
-			mhc.SetGenerateName("unhealthy-machines-deleted-")
 			Expect(testEnv.Create(ctx, mhc)).To(Succeed())
 
 			// Pause the MachineSet reconciler.
@@ -1062,8 +1028,7 @@ func newTestMachineHealthCheck(name, namespace, cluster string, labels map[strin
 			Selector: metav1.LabelSelector{
 				MatchLabels: l,
 			},
-			NodeStartupTimeout: &metav1.Duration{Duration: 1 * time.Millisecond},
-			ClusterName:        cluster,
+			ClusterName: cluster,
 			UnhealthyConditions: []clusterv1.UnhealthyCondition{
 				{
 					Type:    corev1.NodeReady,
@@ -1433,8 +1398,7 @@ func TestIsAllowedRemediation(t *testing.T) {
 
 			mhc := &clusterv1.MachineHealthCheck{
 				Spec: clusterv1.MachineHealthCheckSpec{
-					MaxUnhealthy:       tc.maxUnhealthy,
-					NodeStartupTimeout: &metav1.Duration{Duration: 1 * time.Millisecond},
+					MaxUnhealthy: tc.maxUnhealthy,
 				},
 				Status: clusterv1.MachineHealthCheckStatus{
 					ExpectedMachines:   tc.expectedMachines,
diff --git a/controllers/suite_test.go b/controllers/suite_test.go
index 8238d9b62..3a7615561 100644
--- a/controllers/suite_test.go
+++ b/controllers/suite_test.go
@@ -114,10 +114,8 @@ var _ = BeforeSuite(func(done Done) {
 		Expect(testEnv.StartManager()).To(Succeed())
 	}()
 
-	// wait for webhook port to be open prior to running tests
-	testEnv.WaitForWebhooks()
 	close(done)
-}, 80)
+}, 60)
 
 var _ = AfterSuite(func() {
 	if testEnv != nil {
diff --git a/controlplane/kubeadm/controllers/scale_test.go b/controlplane/kubeadm/controllers/scale_test.go
index dd29817a1..ceed678ae 100644
--- a/controlplane/kubeadm/controllers/scale_test.go
+++ b/controlplane/kubeadm/controllers/scale_test.go
@@ -137,14 +137,20 @@ func TestKubeadmControlPlaneReconciler_scaleUpControlPlane(t *testing.T) {
 			name                  string
 			etcdUnHealthy         bool
 			controlPlaneUnHealthy bool
+			expectErr             bool
+			expectResult          ctrl.Result
 		}{
 			{
 				name:          "etcd health check fails",
 				etcdUnHealthy: true,
+				expectErr:     true,
+				expectResult:  ctrl.Result{},
 			},
 			{
 				name:                  "controlplane component health check fails",
 				controlPlaneUnHealthy: true,
+				expectErr:             false,
+				expectResult:          ctrl.Result{RequeueAfter: healthCheckFailedRequeueAfter},
 			},
 		}
 		for _, tc := range testCases {
@@ -171,8 +177,10 @@ func TestKubeadmControlPlaneReconciler_scaleUpControlPlane(t *testing.T) {
 			}
 
 			result, err := r.scaleUpControlPlane(context.Background(), cluster.DeepCopy(), kcp.DeepCopy(), controlPlane)
-			g.Expect(result).To(Equal(ctrl.Result{RequeueAfter: healthCheckFailedRequeueAfter}))
-			g.Expect(err).To(BeNil())
+			if tc.expectErr {
+				g.Expect(err).To(HaveOccurred())
+			}
+			g.Expect(result).To(Equal(tc.expectResult))
 
 			controlPlaneMachines := &clusterv1.MachineList{}
 			g.Expect(fakeClient.List(context.Background(), controlPlaneMachines)).To(Succeed())
diff --git a/controlplane/kubeadm/internal/workload_cluster_etcd_test.go b/controlplane/kubeadm/internal/workload_cluster_etcd_test.go
index d4384d4bb..e2b9b9409 100644
--- a/controlplane/kubeadm/internal/workload_cluster_etcd_test.go
+++ b/controlplane/kubeadm/internal/workload_cluster_etcd_test.go
@@ -506,118 +506,6 @@ func TestForwardEtcdLeadership(t *testing.T) {
 	})
 }
 
-func TestReconcileEtcdMembers(t *testing.T) {
-	kubeadmConfig := &corev1.ConfigMap{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      kubeadmConfigKey,
-			Namespace: metav1.NamespaceSystem,
-		},
-		Data: map[string]string{
-			clusterStatusKey: `apiEndpoints:
-  ip-10-0-0-1.ec2.internal:
-    advertiseAddress: 10.0.0.1
-    bindPort: 6443
-  ip-10-0-0-2.ec2.internal:
-    advertiseAddress: 10.0.0.2
-    bindPort: 6443
-    someFieldThatIsAddedInTheFuture: bar
-  ip-10-0-0-3.ec2.internal:
-    advertiseAddress: 10.0.0.3
-    bindPort: 6443
-apiVersion: kubeadm.k8s.io/vNbetaM
-kind: ClusterStatus`,
-		},
-	}
-	node1 := &corev1.Node{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "ip-10-0-0-1.ec2.internal",
-			Namespace: "ns1",
-			Labels: map[string]string{
-				labelNodeRoleControlPlane: "",
-			},
-		},
-	}
-	node2 := node1.DeepCopy()
-	node2.Name = "ip-10-0-0-2.ec2.internal"
-
-	fakeEtcdClient := &fake2.FakeEtcdClient{
-		MemberListResponse: &clientv3.MemberListResponse{
-			Members: []*pb.Member{
-				{Name: "ip-10-0-0-1.ec2.internal", ID: uint64(1)},
-				{Name: "ip-10-0-0-2.ec2.internal", ID: uint64(2)},
-				{Name: "ip-10-0-0-3.ec2.internal", ID: uint64(3)},
-			},
-		},
-		AlarmResponse: &clientv3.AlarmResponse{
-			Alarms: []*pb.AlarmMember{},
-		},
-	}
-
-	tests := []struct {
-		name                string
-		objs                []runtime.Object
-		etcdClientGenerator etcdClientFor
-		expectErr           bool
-		assert              func(*WithT)
-	}{
-		{
-			// the node to be removed is ip-10-0-0-3.ec2.internal since the
-			// other two have nodes
-			name: "successfully removes the etcd member without a node and removes the node from kubeadm config",
-			objs: []runtime.Object{node1.DeepCopy(), node2.DeepCopy(), kubeadmConfig.DeepCopy()},
-			etcdClientGenerator: &fakeEtcdClientGenerator{
-				forNodesClient: &etcd.Client{
-					EtcdClient: fakeEtcdClient,
-				},
-			},
-			expectErr: false,
-			assert: func(g *WithT) {
-				g.Expect(fakeEtcdClient.RemovedMember).To(Equal(uint64(3)))
-			},
-		},
-		{
-			name: "return error if there aren't enough control plane nodes",
-			objs: []runtime.Object{node1.DeepCopy(), kubeadmConfig.DeepCopy()},
-			etcdClientGenerator: &fakeEtcdClientGenerator{
-				forNodesClient: &etcd.Client{
-					EtcdClient: fakeEtcdClient,
-				},
-			},
-			expectErr: true,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			g := NewWithT(t)
-
-			for _, o := range tt.objs {
-				g.Expect(testEnv.CreateObj(ctx, o)).To(Succeed())
-				defer func(do runtime.Object) {
-					g.Expect(testEnv.Cleanup(ctx, do)).To(Succeed())
-				}(o)
-			}
-
-			w := &Workload{
-				Client:              testEnv,
-				etcdClientGenerator: tt.etcdClientGenerator,
-			}
-			ctx := context.TODO()
-			err := w.ReconcileEtcdMembers(ctx)
-			if tt.expectErr {
-				g.Expect(err).To(HaveOccurred())
-				return
-			}
-			g.Expect(err).ToNot(HaveOccurred())
-
-			if tt.assert != nil {
-				tt.assert(g)
-			}
-		})
-	}
-
-}
-
 type fakeEtcdClientGenerator struct {
 	forNodesClient  *etcd.Client
 	forLeaderClient *etcd.Client
diff --git a/exp/addons/controllers/clusterresourceset_controller_test.go b/exp/addons/controllers/clusterresourceset_controller_test.go
index 671a7d228..f2551f160 100644
--- a/exp/addons/controllers/clusterresourceset_controller_test.go
+++ b/exp/addons/controllers/clusterresourceset_controller_test.go
@@ -20,9 +20,6 @@ import (
 	"fmt"
 	"time"
 
-	"sigs.k8s.io/cluster-api/util"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 
@@ -31,6 +28,8 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
 	addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1alpha3"
+	"sigs.k8s.io/cluster-api/util"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
 const (
@@ -393,7 +392,6 @@ metadata:
 	})
 	It("Should add finalizer after reconcile", func() {
 		dt := metav1.Now()
-		labels := map[string]string{"foo": "bar"}
 		clusterResourceSetInstance := &addonsv1.ClusterResourceSet{
 			ObjectMeta: metav1.ObjectMeta{
 				Name:              "test-clusterresourceset",
@@ -401,11 +399,6 @@ metadata:
 				Finalizers:        []string{addonsv1.ClusterResourceSetFinalizer},
 				DeletionTimestamp: &dt,
 			},
-			Spec: addonsv1.ClusterResourceSetSpec{
-				ClusterSelector: metav1.LabelSelector{
-					MatchLabels: labels,
-				},
-			},
 		}
 		// Create the ClusterResourceSet.
 		Expect(testEnv.Create(ctx, clusterResourceSetInstance)).To(Succeed())
diff --git a/test/helpers/envtest.go b/test/helpers/envtest.go
index c4475dd57..9d6b30edb 100644
--- a/test/helpers/envtest.go
+++ b/test/helpers/envtest.go
@@ -19,18 +19,11 @@ package helpers
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
-	"net"
 	"path"
 	"path/filepath"
 	goruntime "runtime"
-	"strconv"
-	"strings"
-	"time"
 
 	"github.com/onsi/ginkgo"
-	admissionv1 "k8s.io/api/admissionregistration/v1"
-	admissionv1beta1 "k8s.io/api/admissionregistration/v1"
 	corev1 "k8s.io/api/core/v1"
 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -44,15 +37,12 @@ import (
 	"k8s.io/klog/klogr"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
 	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha3"
-	"sigs.k8s.io/cluster-api/cmd/clusterctl/log"
 	"sigs.k8s.io/cluster-api/controllers/external"
-	kcpv1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha3"
+	controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha3"
 	addonv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1alpha3"
-	crs "sigs.k8s.io/cluster-api/exp/addons/api/v1alpha3"
 	expv1 "sigs.k8s.io/cluster-api/exp/api/v1alpha3"
 	"sigs.k8s.io/cluster-api/util"
 	"sigs.k8s.io/cluster-api/util/kubeconfig"
-	utilyaml "sigs.k8s.io/cluster-api/util/yaml"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/envtest"
@@ -61,12 +51,7 @@ import (
 
 func init() {
 	klog.InitFlags(nil)
-	logger := klogr.New()
-	// use klog as the internal logger for this envtest environment.
-	log.SetLogger(logger)
-	// additionally force all of the controllers to use the Ginkgo logger.
-	ctrl.SetLogger(logger)
-	// add logger for ginkgo
+	ctrl.SetLogger(klogr.New())
 	klog.SetOutput(ginkgo.GinkgoWriter)
 }
 
@@ -80,11 +65,8 @@ func init() {
 	utilruntime.Must(clusterv1.AddToScheme(scheme.Scheme))
 	utilruntime.Must(bootstrapv1.AddToScheme(scheme.Scheme))
 	utilruntime.Must(expv1.AddToScheme(scheme.Scheme))
-	utilruntime.Must(crs.AddToScheme(scheme.Scheme))
+	utilruntime.Must(controlplanev1.AddToScheme(scheme.Scheme))
 	utilruntime.Must(addonv1.AddToScheme(scheme.Scheme))
-	utilruntime.Must(kcpv1.AddToScheme(scheme.Scheme))
-	utilruntime.Must(admissionv1beta1.AddToScheme(scheme.Scheme))
-	utilruntime.Must(admissionv1.AddToScheme(scheme.Scheme))
 
 	// Get the root of the current file to use in CRD paths.
 	_, filename, _, _ := goruntime.Caller(0) //nolint
@@ -121,61 +103,15 @@ type TestEnvironment struct {
 // This function should be called only once for each package you're running tests within,
 // usually the environment is initialized in a suite_test.go file within a `BeforeSuite` ginkgo block.
 func NewTestEnvironment() *TestEnvironment {
-
-	// initialize webhook here to be able to test the envtest install via webhookOptions
-	// This should set LocalServingCertDir and LocalServingPort that are used below.
-	initializeWebhookInEnvironment()
-
 	if _, err := env.Start(); err != nil {
 		panic(err)
 	}
 
-	options := manager.Options{
+	mgr, err := manager.New(env.Config, manager.Options{
 		Scheme:             scheme.Scheme,
 		MetricsBindAddress: "0",
 		NewClient:          util.ManagerDelegatingClientFunc,
-		CertDir:            env.WebhookInstallOptions.LocalServingCertDir,
-		Port:               env.WebhookInstallOptions.LocalServingPort,
-	}
-
-	mgr, err := ctrl.NewManager(env.Config, options)
-
-	//Set minNodeStartupTimeout for Test, so it does not need to be at least 30s
-	clusterv1.SetMinNodeStartupTimeout(metav1.Duration{Duration: 1 * time.Millisecond})
-
-	if err := (&clusterv1.Cluster{}).SetupWebhookWithManager(mgr); err != nil {
-		klog.Fatalf("unable to create webhook: %+v", err)
-	}
-	if err := (&clusterv1.Machine{}).SetupWebhookWithManager(mgr); err != nil {
-		klog.Fatalf("unable to create webhook: %+v", err)
-	}
-	if err := (&clusterv1.MachineHealthCheck{}).SetupWebhookWithManager(mgr); err != nil {
-		klog.Fatalf("unable to create webhook: %+v", err)
-	}
-	if err := (&clusterv1.Machine{}).SetupWebhookWithManager(mgr); err != nil {
-		klog.Fatalf("unable to create webhook: %+v", err)
-	}
-	if err := (&clusterv1.MachineSet{}).SetupWebhookWithManager(mgr); err != nil {
-		klog.Fatalf("unable to create webhook: %+v", err)
-	}
-	if err := (&clusterv1.MachineDeployment{}).SetupWebhookWithManager(mgr); err != nil {
-		klog.Fatalf("unable to create webhook: %+v", err)
-	}
-	if err := (&bootstrapv1.KubeadmConfig{}).SetupWebhookWithManager(mgr); err != nil {
-		klog.Fatalf("unable to create webhook: %+v", err)
-	}
-	if err := (&bootstrapv1.KubeadmConfigTemplate{}).SetupWebhookWithManager(mgr); err != nil {
-		klog.Fatalf("unable to create webhook: %+v", err)
-	}
-	if err := (&bootstrapv1.KubeadmConfigTemplateList{}).SetupWebhookWithManager(mgr); err != nil {
-		klog.Fatalf("unable to create webhook: %+v", err)
-	}
-	if err := (&kcpv1.KubeadmControlPlane{}).SetupWebhookWithManager(mgr); err != nil {
-		klog.Fatalf("unable to create webhook: %+v", err)
-	}
-	if err := (&crs.ClusterResourceSet{}).SetupWebhookWithManager(mgr); err != nil {
-		klog.Fatalf("unable to create webhook for crs: %+v", err)
-	}
+	})
 	if err != nil {
 		klog.Fatalf("Failed to start testenv manager: %v", err)
 	}
@@ -188,110 +124,10 @@ func NewTestEnvironment() *TestEnvironment {
 	}
 }
 
-const (
-	mutatingWebhookKind   = "MutatingWebhookConfiguration"
-	validatingWebhookKind = "ValidatingWebhookConfiguration"
-	mutatingwebhook       = "mutating-webhook-configuration"
-	validatingwebhook     = "validating-webhook-configuration"
-)
-
-// Mutate the name of each webhook, because kubebuilder generates the same name for all controllers.
-// In normal usage, kustomize will prefix the controller name, which we have to do manually here.
-func appendWebhookConfiguration(mutatingWebhooks []runtime.Object, validatingWebhooks []runtime.Object, configyamlFile []byte, tag string) ([]runtime.Object, []runtime.Object, error) {
-
-	objs, err := utilyaml.ToUnstructured(configyamlFile)
-	if err != nil {
-		klog.Fatalf("failed to parse yaml")
-	}
-	// look for resources of kind MutatingWebhookConfiguration
-	for i := range objs {
-		o := objs[i]
-		if o.GetKind() == mutatingWebhookKind {
-			// update the name in metadata
-			if o.GetName() == mutatingwebhook {
-				o.SetName(strings.Join([]string{mutatingwebhook, "-", tag}, ""))
-				mutatingWebhooks = append(mutatingWebhooks, &o)
-			}
-		}
-		if o.GetKind() == validatingWebhookKind {
-			// update the name in metadata
-			if o.GetName() == validatingwebhook {
-				o.SetName(strings.Join([]string{validatingwebhook, "-", tag}, ""))
-				validatingWebhooks = append(validatingWebhooks, &o)
-			}
-		}
-	}
-	return mutatingWebhooks, validatingWebhooks, err
-}
-
-func initializeWebhookInEnvironment() {
-
-	validatingWebhooks := []runtime.Object{}
-	mutatingWebhooks := []runtime.Object{}
-
-	// Get the root of the current file to use in CRD paths.
-	_, filename, _, _ := goruntime.Caller(0) //nolint
-	root := path.Join(path.Dir(filename), "..", "..")
-	configyamlFile, err := ioutil.ReadFile(filepath.Join(root, "config", "webhook", "manifests.yaml"))
-	if err != nil {
-
-		klog.Fatalf("Failed to read core webhook configuration file: %v ", err)
-	}
-	if err != nil {
-		klog.Fatalf("failed to parse yaml")
-	}
-	// append the webhook with suffix to avoid clashing webhooks. repeated for every webhook
-	mutatingWebhooks, validatingWebhooks, err = appendWebhookConfiguration(mutatingWebhooks, validatingWebhooks, configyamlFile, "config")
-	if err != nil {
-		klog.Fatalf("Failed to append core controller webhook config: %v", err)
-	}
-
-	bootstrapyamlFile, err := ioutil.ReadFile(filepath.Join(root, "bootstrap", "kubeadm", "config", "webhook", "manifests.yaml"))
-	if err != nil {
-		klog.Fatalf("Failed to get bootstrap yaml file: %v", err)
-	}
-	mutatingWebhooks, validatingWebhooks, err = appendWebhookConfiguration(mutatingWebhooks, validatingWebhooks, bootstrapyamlFile, "bootstrap")
-
-	if err != nil {
-		klog.Fatalf("Failed to append bootstrap controller webhook config: %v", err)
-	}
-	controlplaneyamlFile, err := ioutil.ReadFile(filepath.Join(root, "controlplane", "kubeadm", "config", "webhook", "manifests.yaml"))
-	if err != nil {
-		klog.Fatalf(" Failed to get controlplane yaml file err: %v", err)
-	}
-	mutatingWebhooks, validatingWebhooks, err = appendWebhookConfiguration(mutatingWebhooks, validatingWebhooks, controlplaneyamlFile, "cp")
-	if err != nil {
-		klog.Fatalf("Failed to append cocontrolplane controller webhook config: %v", err)
-	}
-	env.WebhookInstallOptions = envtest.WebhookInstallOptions{
-		MaxTime:            20 * time.Second,
-		PollInterval:       time.Second,
-		ValidatingWebhooks: validatingWebhooks,
-		MutatingWebhooks:   mutatingWebhooks,
-	}
-}
 func (t *TestEnvironment) StartManager() error {
 	return t.Manager.Start(t.doneMgr)
 }
 
-func (t *TestEnvironment) WaitForWebhooks() {
-	port := env.WebhookInstallOptions.LocalServingPort
-
-	klog.V(2).Infof("Waiting for webhook port %d to be open prior to running tests", port)
-	timeout := 1 * time.Second
-	for {
-		time.Sleep(1 * time.Second)
-		conn, err := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)), timeout)
-		if err != nil {
-			klog.V(2).Infof("Webhook port is not ready, will retry in %v: %s", timeout, err)
-			continue
-		}
-		conn.Close()
-		klog.V(2).Info("Webhook port is now open. Continuing with tests...")
-		return
-	}
-}
-
 func (t *TestEnvironment) Stop() error {
 	t.doneMgr <- struct{}{}
 	return env.Stop()
diff --git a/test/infrastructure/docker/controllers/dockercluster_controller.go b/test/infrastructure/docker/controllers/dockercluster_controller.go
index a88c61978..ea0ccdff9 100644
--- a/test/infrastructure/docker/controllers/dockercluster_controller.go
+++ b/test/infrastructure/docker/controllers/dockercluster_controller.go
@@ -84,13 +84,21 @@ func (r *DockerClusterReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, re
 	}
 
 	// Initialize the patch helper
-	patchHelper, err := patch.NewHelper(dockerCluster, r.Client)
+	patchHelper, err := patch.NewHelper(dockerCluster, r)
 	if err != nil {
 		return ctrl.Result{}, err
 	}
 	// Always attempt to Patch the DockerCluster object and status after each reconciliation.
 	defer func() {
-		if err := patchDockerCluster(ctx, patchHelper, dockerCluster); err != nil {
+		// always update the readyCondition; the summary is represented using the "1 of x completed" notation.
+		conditions.SetSummary(dockerCluster,
+			conditions.WithConditions(
+				infrav1.LoadBalancerAvailableCondition,
+			),
+			conditions.WithStepCounter(),
+		)
+
+		if err := patchHelper.Patch(ctx, dockerCluster); err != nil {
 			log.Error(err, "failed to patch DockerCluster")
 			if rerr == nil {
 				rerr = err
@@ -111,35 +119,14 @@ func (r *DockerClusterReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, re
 
 	// Handle deleted clusters
 	if !dockerCluster.DeletionTimestamp.IsZero() {
-		return r.reconcileDelete(ctx, dockerCluster, externalLoadBalancer)
+		return reconcileDelete(ctx, dockerCluster, externalLoadBalancer)
 	}
 
 	// Handle non-deleted clusters
-	return r.reconcileNormal(ctx, dockerCluster, externalLoadBalancer)
+	return reconcileNormal(ctx, dockerCluster, externalLoadBalancer)
 }
 
-func patchDockerCluster(ctx context.Context, patchHelper *patch.Helper, dockerCluster *infrav1.DockerCluster) error {
-	// Always update the readyCondition by summarizing the state of other conditions.
-	// A step counter is added to represent progress during the provisioning process (instead we are hiding it during the deletion process).
-	conditions.SetSummary(dockerCluster,
-		conditions.WithConditions(
-			infrav1.LoadBalancerAvailableCondition,
-		),
-		conditions.WithStepCounterIf(dockerCluster.ObjectMeta.DeletionTimestamp.IsZero()),
-	)
-
-	// Patch the object, ignoring conflicts on the conditions owned by this controller.
-	return patchHelper.Patch(
-		ctx,
-		dockerCluster,
-		patch.WithOwnedConditions{Conditions: []clusterv1.ConditionType{
-			clusterv1.ReadyCondition,
-			infrav1.LoadBalancerAvailableCondition,
-		}},
-	)
-}
-
-func (r *DockerClusterReconciler) reconcileNormal(ctx context.Context, dockerCluster *infrav1.DockerCluster, externalLoadBalancer *docker.LoadBalancer) (ctrl.Result, error) {
+func reconcileNormal(ctx context.Context, dockerCluster *infrav1.DockerCluster, externalLoadBalancer *docker.LoadBalancer) (ctrl.Result, error) {
 	//Create the docker container hosting the load balancer
 	if err := externalLoadBalancer.Create(); err != nil {
 		conditions.MarkFalse(dockerCluster, infrav1.LoadBalancerAvailableCondition, infrav1.LoadBalancerProvisioningFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
@@ -165,20 +152,7 @@ func (r *DockerClusterReconciler) reconcileNormal(ctx context.Context, dockerClu
 	return ctrl.Result{}, nil
 }
 
-func (r *DockerClusterReconciler) reconcileDelete(ctx context.Context, dockerCluster *infrav1.DockerCluster, externalLoadBalancer *docker.LoadBalancer) (ctrl.Result, error) {
-	// Set the LoadBalancerAvailableCondition reporting delete is started, and issue a patch in order to make
-	// this visible to the users.
-	// NB. The operation in docker is fast, so there is the chance the user will not notice the status change;
-	// nevertheless we are issuing a patch so we can test a pattern that will be used by other providers as well
-	patchHelper, err := patch.NewHelper(dockerCluster, r.Client)
-	if err != nil {
-		return ctrl.Result{}, err
-	}
-	conditions.MarkFalse(dockerCluster, infrav1.LoadBalancerAvailableCondition, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "")
-	if err := patchDockerCluster(ctx, patchHelper, dockerCluster); err != nil {
-		return ctrl.Result{}, errors.Wrap(err, "failed to patch DockerCluster")
-	}
-
+func reconcileDelete(ctx context.Context, dockerCluster *infrav1.DockerCluster, externalLoadBalancer *docker.LoadBalancer) (ctrl.Result, error) {
 	// Delete the docker container hosting the load balancer
 	if err := externalLoadBalancer.Delete(ctx); err != nil {
 		return ctrl.Result{}, errors.Wrap(err, "failed to delete load balancer")
diff --git a/test/infrastructure/docker/controllers/dockermachine_controller.go b/test/infrastructure/docker/controllers/dockermachine_controller.go
index 7b0b73394..1ca9d96aa 100644
--- a/test/infrastructure/docker/controllers/dockermachine_controller.go
+++ b/test/infrastructure/docker/controllers/dockermachine_controller.go
@@ -116,7 +116,16 @@ func (r *DockerMachineReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, re
 	}
 	// Always attempt to Patch the DockerMachine object and status after each reconciliation.
 	defer func() {
-		if err := patchDockerMachine(ctx, patchHelper, dockerMachine); err != nil {
+		// always update the readyCondition; the summary is represented using the "1 of x completed" notation.
+		conditions.SetSummary(dockerMachine,
+			conditions.WithConditions(
+				infrav1.ContainerProvisionedCondition,
+				infrav1.BootstrapExecSucceededCondition,
+			),
+			conditions.WithStepCounter(),
+		)
+
+		if err := patchHelper.Patch(ctx, dockerMachine); err != nil {
 			log.Error(err, "failed to patch DockerMachine")
 			if rerr == nil {
 				rerr = err
@@ -161,29 +170,6 @@ func (r *DockerMachineReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, re
 	return r.reconcileNormal(ctx, machine, dockerMachine, externalMachine, externalLoadBalancer, log)
 }
 
-func patchDockerMachine(ctx context.Context, patchHelper *patch.Helper, dockerMachine *infrav1.DockerMachine) error {
-	// Always update the readyCondition by summarizing the state of other conditions.
-	// A step counter is added to represent progress during the provisioning process (instead we are hiding the step counter during the deletion process).
-	conditions.SetSummary(dockerMachine,
-		conditions.WithConditions(
-			infrav1.ContainerProvisionedCondition,
-			infrav1.BootstrapExecSucceededCondition,
-		),
-		conditions.WithStepCounterIf(dockerMachine.ObjectMeta.DeletionTimestamp.IsZero()),
-	)
-
-	// Patch the object, ignoring conflicts on the conditions owned by this controller.
-	return patchHelper.Patch(
-		ctx,
-		dockerMachine,
-		patch.WithOwnedConditions{Conditions: []clusterv1.ConditionType{
-			clusterv1.ReadyCondition,
-			infrav1.ContainerProvisionedCondition,
-			infrav1.BootstrapExecSucceededCondition,
-		}},
-	)
-}
-
 func (r *DockerMachineReconciler) reconcileNormal(ctx context.Context, machine *clusterv1.Machine, dockerMachine *infrav1.DockerMachine, externalMachine *docker.Machine, externalLoadBalancer *docker.LoadBalancer, log logr.Logger) (res ctrl.Result, retErr error) {
 	// if the machine is already provisioned, return
 	if dockerMachine.Spec.ProviderID != nil {
@@ -231,6 +217,9 @@ func (r *DockerMachineReconciler) reconcileNormal(ctx context.Context, machine *
 		}
 	}
 
+	// Update the ContainerProvisionedCondition condition
+	conditions.MarkTrue(dockerMachine, infrav1.ContainerProvisionedCondition)
+
 	// Preload images into the container
 	if len(dockerMachine.Spec.PreLoadImages) > 0 {
 		if err := externalMachine.PreloadLoadImages(ctx, dockerMachine.Spec.PreLoadImages); err != nil {
@@ -248,24 +237,13 @@ func (r *DockerMachineReconciler) reconcileNormal(ctx context.Context, machine *
 		dockerMachine.Status.LoadBalancerConfigured = true
 	}
 
-	// Update the ContainerProvisionedCondition condition
-	// NOTE: it is required to create the patch helper before this change otherwise it wont surface if
-	// we issue a patch down in the code (because if we create patch helper after this point the ContainerProvisionedCondition=True exists both on before and after).
-	patchHelper, err := patch.NewHelper(dockerMachine, r.Client)
-	if err != nil {
-		return ctrl.Result{}, err
-	}
-	conditions.MarkTrue(dockerMachine, infrav1.ContainerProvisionedCondition)
-
 	// At, this stage, we are ready for bootstrap. However, if the BootstrapExecSucceededCondition is missing we add it and we
-	// issue an patch so the user can see the change of state before the bootstrap actually starts.
+	// requeue so the user can see the change of state before the bootstrap actually starts.
 	// NOTE: usually controller should not rely on status they are setting, but on the observed state; however
 	// in this case we are doing this because we explicitly want to give a feedback to users.
 	if !conditions.Has(dockerMachine, infrav1.BootstrapExecSucceededCondition) {
 		conditions.MarkFalse(dockerMachine, infrav1.BootstrapExecSucceededCondition, infrav1.BootstrappingReason, clusterv1.ConditionSeverityInfo, "")
-		if err := patchDockerMachine(ctx, patchHelper, dockerMachine); err != nil {
-			return ctrl.Result{}, errors.Wrap(err, "failed to patch DockerMachine")
-		}
+		return ctrl.Result{Requeue: true}, nil
 	}
 
 	// if the machine isn't bootstrapped, only then run bootstrap scripts
@@ -328,18 +306,9 @@ func (r *DockerMachineReconciler) reconcileNormal(ctx context.Context, machine *
 }
 
 func (r *DockerMachineReconciler) reconcileDelete(ctx context.Context, machine *clusterv1.Machine, dockerMachine *infrav1.DockerMachine, externalMachine *docker.Machine, externalLoadBalancer *docker.LoadBalancer) (ctrl.Result, error) {
-	// Set the ContainerProvisionedCondition reporting delete is started, and issue a patch in order to make
-	// this visible to the users.
-	// NB. The operation in docker is fast, so there is the chance the user will not notice the status change;
-	// nevertheless we are issuing a patch so we can test a pattern that will be used by other providers as well
-	patchHelper, err := patch.NewHelper(dockerMachine, r.Client)
-	if err != nil {
-		return ctrl.Result{}, err
-	}
-	conditions.MarkFalse(dockerMachine, infrav1.ContainerProvisionedCondition, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "")
-	if err := patchDockerMachine(ctx, patchHelper, dockerMachine); err != nil {
-		return ctrl.Result{}, errors.Wrap(err, "failed to patch DockerMachine")
-	}
+	// Long lived CAPD clusters (unadvised) that are using Machine resources for the control plane machines
+	// will have to manually keep the kubeadm config-map on the workload cluster up to date.
+	// This is automated when using the KubeadmControlPlane.
 
 	// delete the machine
 	if err := externalMachine.Delete(ctx); err != nil {
diff --git a/test/infrastructure/docker/go.sum b/test/infrastructure/docker/go.sum
index 2c32fe139..a120b3d30 100644
--- a/test/infrastructure/docker/go.sum
+++ b/test/infrastructure/docker/go.sum
@@ -162,8 +162,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -415,8 +413,6 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
-golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -496,8 +492,6 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/util/conditions/getter_test.go b/util/conditions/getter_test.go
index 0119c0599..6c77e4d41 100644
--- a/util/conditions/getter_test.go
+++ b/util/conditions/getter_test.go
@@ -158,30 +158,6 @@ func TestSummary(t *testing.T) {
 			options: []MergeOption{WithStepCounter()},
 			want:    FalseCondition(clusterv1.ReadyCondition, "reason falseInfo1", clusterv1.ConditionSeverityInfo, "1 of 2 completed"),
 		},
-		{
-			name:    "Returns ready condition with the summary of existing conditions (using WithStepCounterIf options)",
-			from:    getterWithConditions(foo, bar),
-			options: []MergeOption{WithStepCounterIf(false)},
-			want:    FalseCondition(clusterv1.ReadyCondition, "reason falseInfo1", clusterv1.ConditionSeverityInfo, "message falseInfo1"),
-		},
-		{
-			name:    "Returns ready condition with the summary of existing conditions (using WithStepCounterIf options)",
-			from:    getterWithConditions(foo, bar),
-			options: []MergeOption{WithStepCounterIf(true)},
-			want:    FalseCondition(clusterv1.ReadyCondition, "reason falseInfo1", clusterv1.ConditionSeverityInfo, "1 of 2 completed"),
-		},
-		{
-			name:    "Returns ready condition with the summary of existing conditions (using WithStepCounterIf and WithStepCounterIfOnly options)",
-			from:    getterWithConditions(bar),
-			options: []MergeOption{WithStepCounter(), WithStepCounterIfOnly("bar")},
-			want:    FalseCondition(clusterv1.ReadyCondition, "reason falseInfo1", clusterv1.ConditionSeverityInfo, "0 of 1 completed"),
-		},
-		{
-			name:    "Returns ready condition with the summary of existing conditions (using WithStepCounterIf and WithStepCounterIfOnly options)",
-			from:    getterWithConditions(foo, bar),
-			options: []MergeOption{WithStepCounter(), WithStepCounterIfOnly("foo")},
-			want:    FalseCondition(clusterv1.ReadyCondition, "reason falseInfo1", clusterv1.ConditionSeverityInfo, "message falseInfo1"),
-		},
 		{
 			name:    "Returns ready condition with the summary of selected conditions (using WithConditions options)",
 			from:    getterWithConditions(foo, bar),
@@ -195,15 +171,15 @@ func TestSummary(t *testing.T) {
 			want:    FalseCondition(clusterv1.ReadyCondition, "reason falseInfo1", clusterv1.ConditionSeverityInfo, "1 of 2 completed"),
 		},
 		{
-			name:    "Returns ready condition with the summary of selected conditions (using WithConditions and WithStepCounterIfOnly options)",
+			name:    "Returns ready condition with the summary of selected conditions (using WithConditions and WithStepCounterIfOnlyConditions options)",
 			from:    getterWithConditions(bar),
-			options: []MergeOption{WithConditions("bar", "baz"), WithStepCounter(), WithStepCounterIfOnly("bar")}, // there is only bar, the step counter should be set and counts only a subset of conditions
+			options: []MergeOption{WithConditions("bar", "baz"), WithStepCounterIfOnly("bar")}, // there is only bar, the step counter should be set and counts only a subset of conditions
 			want:    FalseCondition(clusterv1.ReadyCondition, "reason falseInfo1", clusterv1.ConditionSeverityInfo, "0 of 1 completed"),
 		},
 		{
-			name:    "Returns ready condition with the summary of selected conditions (using WithConditions and WithStepCounterIfOnly options)",
+			name:    "Returns ready condition with the summary of selected conditions (using WithConditions and WithStepCounterIfOnlyConditions options)",
 			from:    getterWithConditions(bar, baz),
-			options: []MergeOption{WithConditions("bar", "baz"), WithStepCounter(), WithStepCounterIfOnly("bar")}, // there is also baz, so the step counter should not be set
+			options: []MergeOption{WithConditions("bar", "baz"), WithStepCounterIfOnly("bar")}, // there is also baz, so the step counter should not be set
 			want:    FalseCondition(clusterv1.ReadyCondition, "reason falseInfo1", clusterv1.ConditionSeverityInfo, "message falseInfo1"),
 		},
 		{
diff --git a/util/conditions/merge_strategies_test.go b/util/conditions/merge_strategies_test.go
index bfcc93956..0e7f01a6d 100644
--- a/util/conditions/merge_strategies_test.go
+++ b/util/conditions/merge_strategies_test.go
@@ -56,11 +56,11 @@ func TestLocalizeReason(t *testing.T) {
 
 	// localize should reason location
 	got := localizeReason("foo", getter)
-	g.Expect(got).To(Equal("foo @ Cluster/test-cluster"))
+	g.Expect(got).To(Equal("foo@Cluster/test-cluster"))
 
 	// localize should not alter existing location
-	got = localizeReason("foo @ SomeKind/some-name", getter)
-	g.Expect(got).To(Equal("foo @ SomeKind/some-name"))
+	got = localizeReason("foo@SomeKind/some-name", getter)
+	g.Expect(got).To(Equal("foo@SomeKind/some-name"))
 }
 
 func TestGetFirstReasonAndMessage(t *testing.T) {
@@ -106,5 +106,5 @@ func TestGetFirstReasonAndMessage(t *testing.T) {
 
 	// getFirstReason should localize reason if required
 	gotReason = getFirstReason(groups, nil, true)
-	g.Expect(gotReason).To(Equal("falseBar @ Cluster/test-cluster"))
+	g.Expect(gotReason).To(Equal("falseBar@Cluster/test-cluster"))
 }
diff --git a/util/patch/patch_test.go b/util/patch/patch_test.go
index 7f0a04e05..82c710d68 100644
--- a/util/patch/patch_test.go
+++ b/util/patch/patch_test.go
@@ -370,7 +370,7 @@ var _ = Describe("Patch Helper", func() {
 		obj := &clusterv1.Cluster{
 			ObjectMeta: metav1.ObjectMeta{
 				GenerateName: "test-",
-				Namespace:    "test-namespace",
+				Namespace:    "default",
 			},
 		}
 
@@ -439,7 +439,6 @@ var _ = Describe("Patch Helper", func() {
 
 		Specify("updating spec", func() {
 			obj := obj.DeepCopy()
-			obj.ObjectMeta.Namespace = "test-namespace"
 
 			By("Creating the object")
 			Expect(testEnv.Create(ctx, obj)).ToNot(HaveOccurred())
@@ -507,7 +506,6 @@ var _ = Describe("Patch Helper", func() {
 
 		Specify("updating both spec and status", func() {
 			obj := obj.DeepCopy()
-			obj.ObjectMeta.Namespace = "test-namespace"
 
 			By("Creating the object")
 			Expect(testEnv.Create(ctx, obj)).ToNot(HaveOccurred())