Repository: kubernetes-sigs/cluster-api
Pull request #3670 (2020-09-21 16:32:58)

🌱 Add Node related condition to Machine conditions
**What this PR does / why we need it**:
This PR adds a condition for Node health to Machine conditions that will be managed by Machine controller.

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes # https://github.com/kubernetes-sigs/cluster-api/issues/3138

k8s-ci-robot: Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with `/test all`
sedefsavas: cc @fabriziopandini 
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: @sedefsavas: The following test **failed**, say `/retest` to rerun all failed tests:

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

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

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

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3670). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asedefsavas). 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: Unit test fails because CacheTracker panics in the tests that doesn't use testEnv. Probably will need to use testEnv for the tests using CacheTracker.
k8s-ci-robot: @sedefsavas: The following test **failed**, say `/retest` to rerun all failed tests:

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

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

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

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3670). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asedefsavas). 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: 
/test pull-cluster-api-test

fabriziopandini: Report from testing:
- Changes (with the additional changes in the above comments) seems good to me
- When deleting a machine, we should set NodeHealthy to false, reason Deleting
vincepri: /milestone v0.3.11


k8s-ci-robot: @sedefsavas: 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>
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** after the PR has been reviewed.
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: @sedefsavas: The following test **failed**, say `/retest` to rerun all failed tests:

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

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-make | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make/1311568102411997184) | `/test pull-cluster-api-make`
pull-cluster-api-e2e | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e/1311568102550409216) | `/test pull-cluster-api-e2e`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-make | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make/1311568102411997184) | `/test pull-cluster-api-make`
pull-cluster-api-e2e | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e/1311568102550409216) | `/test pull-cluster-api-e2e`
pull-cluster-api-build | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-build/1311568102206476288) | `/test pull-cluster-api-build`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-make | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make/1311568102411997184) | `/test pull-cluster-api-make`
pull-cluster-api-e2e | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e/1311568102550409216) | `/test pull-cluster-api-e2e`
pull-cluster-api-build | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-build/1311568102206476288) | `/test pull-cluster-api-build`
pull-cluster-api-test | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test/1311568102525243392) | `/test pull-cluster-api-test`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-make | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make/1311568102411997184) | `/test pull-cluster-api-make`
pull-cluster-api-e2e | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e/1311568102550409216) | `/test pull-cluster-api-e2e`
pull-cluster-api-build | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-build/1311568102206476288) | `/test pull-cluster-api-build`
pull-cluster-api-test | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test/1311568102525243392) | `/test pull-cluster-api-test`
pull-cluster-api-apidiff | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311568102458134528) | `/test pull-cluster-api-apidiff`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-make | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make/1311568102411997184) | `/test pull-cluster-api-make`
pull-cluster-api-e2e | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e/1311568102550409216) | `/test pull-cluster-api-e2e`
pull-cluster-api-build | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-build/1311568102206476288) | `/test pull-cluster-api-build`
pull-cluster-api-test | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test/1311568102525243392) | `/test pull-cluster-api-test`
pull-cluster-api-apidiff | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311568102458134528) | `/test pull-cluster-api-apidiff`
pull-cluster-api-verify | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-verify/1311568102491688960) | `/test pull-cluster-api-verify`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-make | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make/1311568102411997184) | `/test pull-cluster-api-make`
pull-cluster-api-e2e | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e/1311568102550409216) | `/test pull-cluster-api-e2e`
pull-cluster-api-build | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-build/1311568102206476288) | `/test pull-cluster-api-build`
pull-cluster-api-test | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test/1311568102525243392) | `/test pull-cluster-api-test`
pull-cluster-api-verify | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-verify/1311568102491688960) | `/test pull-cluster-api-verify`
pull-cluster-api-apidiff | 86262caeafb079c6593cde67665e28fcaffbb62f | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311569898266169344) | `/test pull-cluster-api-apidiff`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-e2e | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e/1311568102550409216) | `/test pull-cluster-api-e2e`
pull-cluster-api-verify | ff4c7e14343ca6ed4474fa676ae760c1c1af8a33 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-verify/1311568102491688960) | `/test pull-cluster-api-verify`
pull-cluster-api-apidiff | 86262caeafb079c6593cde67665e28fcaffbb62f | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311569898266169344) | `/test pull-cluster-api-apidiff`
pull-cluster-api-test | 86262caeafb079c6593cde67665e28fcaffbb62f | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test/1311569898324889600) | `/test pull-cluster-api-test`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-test | 86262caeafb079c6593cde67665e28fcaffbb62f | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test/1311569898324889600) | `/test pull-cluster-api-test`
pull-cluster-api-apidiff | c4e8cf8ad1b88f02cf4b0f732ff4b8382380cc8c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311701378153844736) | `/test pull-cluster-api-apidiff`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-test | 86262caeafb079c6593cde67665e28fcaffbb62f | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test/1311569898324889600) | `/test pull-cluster-api-test`
pull-cluster-api-apidiff | c4e8cf8ad1b88f02cf4b0f732ff4b8382380cc8c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311701378153844736) | `/test pull-cluster-api-apidiff`
pull-cluster-api-make | c4e8cf8ad1b88f02cf4b0f732ff4b8382380cc8c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make/1311701378137067520) | `/test pull-cluster-api-make`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-make | c4e8cf8ad1b88f02cf4b0f732ff4b8382380cc8c | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make/1311701378137067520) | `/test pull-cluster-api-make`
pull-cluster-api-apidiff | 054fab6ef39749b2bf71aeaf953571d4fcf6129f | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311709079965536256) | `/test pull-cluster-api-apidiff`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 054fab6ef39749b2bf71aeaf953571d4fcf6129f | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311709079965536256) | `/test pull-cluster-api-apidiff`
pull-cluster-api-make | aa2829323f32cc1468a629913e45cbeaf9811752 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make/1311785102660866048) | `/test pull-cluster-api-make`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-make | aa2829323f32cc1468a629913e45cbeaf9811752 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make/1311785102660866048) | `/test pull-cluster-api-make`
pull-cluster-api-apidiff | aa2829323f32cc1468a629913e45cbeaf9811752 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311785102686031872) | `/test pull-cluster-api-apidiff`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-make | aa2829323f32cc1468a629913e45cbeaf9811752 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make/1311785102660866048) | `/test pull-cluster-api-make`
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-apidiff-main | d35a292528efcae01b6c42ab5d4c31e276a3aa83 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1313509482646474752) | `/test pull-cluster-api-apidiff-main`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-apidiff-main | d35a292528efcae01b6c42ab5d4c31e276a3aa83 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1313509482646474752) | `/test pull-cluster-api-apidiff-main`
pull-cluster-api-test-main | d35a292528efcae01b6c42ab5d4c31e276a3aa83 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-main/1313509482688417792) | `/test pull-cluster-api-test-main`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-apidiff-main | d35a292528efcae01b6c42ab5d4c31e276a3aa83 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1313509482646474752) | `/test pull-cluster-api-apidiff-main`
pull-cluster-api-test-main | d35a292528efcae01b6c42ab5d4c31e276a3aa83 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-main/1313509482688417792) | `/test pull-cluster-api-test-main`
pull-cluster-api-make-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make-main/1314305810553114624) | `/test pull-cluster-api-make-main`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-apidiff-main | d35a292528efcae01b6c42ab5d4c31e276a3aa83 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1313509482646474752) | `/test pull-cluster-api-apidiff-main`
pull-cluster-api-make-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make-main/1314305810553114624) | `/test pull-cluster-api-make-main`
pull-cluster-api-test-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-main/1314305810628612096) | `/test pull-cluster-api-test-main`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-apidiff-main | d35a292528efcae01b6c42ab5d4c31e276a3aa83 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1313509482646474752) | `/test pull-cluster-api-apidiff-main`
pull-cluster-api-make-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make-main/1314305810553114624) | `/test pull-cluster-api-make-main`
pull-cluster-api-test-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-main/1314305810628612096) | `/test pull-cluster-api-test-main`
pull-cluster-api-verify | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-verify/1314305810607640576) | `/test pull-cluster-api-verify`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-make-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make-main/1314305810553114624) | `/test pull-cluster-api-make-main`
pull-cluster-api-test-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-main/1314305810628612096) | `/test pull-cluster-api-test-main`
pull-cluster-api-verify | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-verify/1314305810607640576) | `/test pull-cluster-api-verify`
pull-cluster-api-apidiff-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1314305810578280448) | `/test pull-cluster-api-apidiff-main`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-make-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make-main/1314305810553114624) | `/test pull-cluster-api-make-main`
pull-cluster-api-test-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-main/1314305810628612096) | `/test pull-cluster-api-test-main`
pull-cluster-api-verify | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-verify/1314305810607640576) | `/test pull-cluster-api-verify`
pull-cluster-api-apidiff-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1314305810578280448) | `/test pull-cluster-api-apidiff-main`
pull-cluster-api-build-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-build-main/1314305810527948800) | `/test pull-cluster-api-build-main`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-make-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make-main/1314305810553114624) | `/test pull-cluster-api-make-main`
pull-cluster-api-test-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-main/1314305810628612096) | `/test pull-cluster-api-test-main`
pull-cluster-api-verify | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-verify/1314305810607640576) | `/test pull-cluster-api-verify`
pull-cluster-api-apidiff-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1314305810578280448) | `/test pull-cluster-api-apidiff-main`
pull-cluster-api-build-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-build-main/1314305810527948800) | `/test pull-cluster-api-build-main`
pull-cluster-api-e2e-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e-main/1314305810649583616) | `/test pull-cluster-api-e2e-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3670). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asedefsavas). 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: /unhold
sedefsavas: Rebased to release-03. We will forward-port it to the master.
k8s-ci-robot: @sedefsavas: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-make-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make-main/1314305810553114624) | `/test pull-cluster-api-make-main`
pull-cluster-api-test-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-main/1314305810628612096) | `/test pull-cluster-api-test-main`
pull-cluster-api-verify | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-verify/1314305810607640576) | `/test pull-cluster-api-verify`
pull-cluster-api-apidiff-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1314305810578280448) | `/test pull-cluster-api-apidiff-main`
pull-cluster-api-build-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-build-main/1314305810527948800) | `/test pull-cluster-api-build-main`
pull-cluster-api-e2e-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e-main/1314305810649583616) | `/test pull-cluster-api-e2e-main`
pull-cluster-api-apidiff-release-0-3 | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-release-0-3/1314306303719378944) | `/test pull-cluster-api-apidiff-release-0-3`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3670). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asedefsavas). 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: This PR is ready to go to release-0.3.
vincepri: ```
sigs.k8s.io/cluster-api/controllers
  Incompatible changes:
  - ErrNodeNotFound: removed
```

Can we restore this?
vincepri: /retest
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** after the PR has been reviewed.
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/release-0.3/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"]} -->
vincepri: @sedefsavas Can you rebase + squash? There is something going on with apidiff 
k8s-ci-robot: @sedefsavas: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-make-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make-main/1314305810553114624) | `/test pull-cluster-api-make-main`
pull-cluster-api-test-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-main/1314305810628612096) | `/test pull-cluster-api-test-main`
pull-cluster-api-apidiff-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1314305810578280448) | `/test pull-cluster-api-apidiff-main`
pull-cluster-api-build-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-build-main/1314305810527948800) | `/test pull-cluster-api-build-main`
pull-cluster-api-e2e-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e-main/1314305810649583616) | `/test pull-cluster-api-e2e-main`
pull-cluster-api-apidiff-release-0-3 | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-release-0-3/1314608701667020800) | `/test pull-cluster-api-apidiff-release-0-3`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-make-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make-main/1314305810553114624) | `/test pull-cluster-api-make-main`
pull-cluster-api-test-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-main/1314305810628612096) | `/test pull-cluster-api-test-main`
pull-cluster-api-apidiff-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1314305810578280448) | `/test pull-cluster-api-apidiff-main`
pull-cluster-api-build-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-build-main/1314305810527948800) | `/test pull-cluster-api-build-main`
pull-cluster-api-e2e-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e-main/1314305810649583616) | `/test pull-cluster-api-e2e-main`
pull-cluster-api-apidiff-release-0-3 | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-release-0-3/1314608701667020800) | `/test pull-cluster-api-apidiff-release-0-3`
pull-cluster-api-test-release-0-3 | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-release-0-3/1314608701683798016) | `/test pull-cluster-api-test-release-0-3`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-make-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make-main/1314305810553114624) | `/test pull-cluster-api-make-main`
pull-cluster-api-test-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-main/1314305810628612096) | `/test pull-cluster-api-test-main`
pull-cluster-api-apidiff-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1314305810578280448) | `/test pull-cluster-api-apidiff-main`
pull-cluster-api-build-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-build-main/1314305810527948800) | `/test pull-cluster-api-build-main`
pull-cluster-api-e2e-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e-main/1314305810649583616) | `/test pull-cluster-api-e2e-main`
pull-cluster-api-test-release-0-3 | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-release-0-3/1314608701683798016) | `/test pull-cluster-api-test-release-0-3`
pull-cluster-api-apidiff-release-0-3 | 5f83ead2cb4ec24ab8cc4604510a4a8e069c29fc | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-release-0-3/1314619061434322944) | `/test pull-cluster-api-apidiff-release-0-3`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-make-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make-main/1314305810553114624) | `/test pull-cluster-api-make-main`
pull-cluster-api-test-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-main/1314305810628612096) | `/test pull-cluster-api-test-main`
pull-cluster-api-apidiff-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1314305810578280448) | `/test pull-cluster-api-apidiff-main`
pull-cluster-api-build-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-build-main/1314305810527948800) | `/test pull-cluster-api-build-main`
pull-cluster-api-e2e-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e-main/1314305810649583616) | `/test pull-cluster-api-e2e-main`
pull-cluster-api-test-release-0-3 | 6d2aad2628e8e0c85aa65fcbe501e044139996db | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-release-0-3/1314621216958451712) | `/test pull-cluster-api-test-release-0-3`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-make-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make-main/1314305810553114624) | `/test pull-cluster-api-make-main`
pull-cluster-api-test-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-main/1314305810628612096) | `/test pull-cluster-api-test-main`
pull-cluster-api-apidiff-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1314305810578280448) | `/test pull-cluster-api-apidiff-main`
pull-cluster-api-build-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-build-main/1314305810527948800) | `/test pull-cluster-api-build-main`
pull-cluster-api-e2e-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e-main/1314305810649583616) | `/test pull-cluster-api-e2e-main`
pull-cluster-api-test-release-0-3 | 049d7af23f1d7c7367b856fc5cc1b5346aa1a1ad | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-release-0-3/1314668509921284096) | `/test pull-cluster-api-test-release-0-3`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3670). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asedefsavas). 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-release-0-3


k8s-ci-robot: @sedefsavas: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-make-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make-main/1314305810553114624) | `/test pull-cluster-api-make-main`
pull-cluster-api-test-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-main/1314305810628612096) | `/test pull-cluster-api-test-main`
pull-cluster-api-apidiff-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1314305810578280448) | `/test pull-cluster-api-apidiff-main`
pull-cluster-api-build-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-build-main/1314305810527948800) | `/test pull-cluster-api-build-main`
pull-cluster-api-e2e-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e-main/1314305810649583616) | `/test pull-cluster-api-e2e-main`
pull-cluster-api-test-release-0-3 | 049d7af23f1d7c7367b856fc5cc1b5346aa1a1ad | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-release-0-3/1314681986643660800) | `/test pull-cluster-api-test-release-0-3`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3670). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asedefsavas). 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: @sedefsavas: 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: /hold 

Given the comment above. We should cover deletion and proper cleanup during e2e validation
k8s-ci-robot: @sedefsavas: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-make-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make-main/1314305810553114624) | `/test pull-cluster-api-make-main`
pull-cluster-api-test-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-main/1314305810628612096) | `/test pull-cluster-api-test-main`
pull-cluster-api-apidiff-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1314305810578280448) | `/test pull-cluster-api-apidiff-main`
pull-cluster-api-build-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-build-main/1314305810527948800) | `/test pull-cluster-api-build-main`
pull-cluster-api-e2e-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e-main/1314305810649583616) | `/test pull-cluster-api-e2e-main`
pull-cluster-api-test-release-0-3 | b6a3ce25b9fb9b2db8d4ac8cb1d77805e7fa9157 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-release-0-3/1316220436656164864) | `/test pull-cluster-api-test-release-0-3`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-make-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make-main/1314305810553114624) | `/test pull-cluster-api-make-main`
pull-cluster-api-test-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-main/1314305810628612096) | `/test pull-cluster-api-test-main`
pull-cluster-api-apidiff-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1314305810578280448) | `/test pull-cluster-api-apidiff-main`
pull-cluster-api-build-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-build-main/1314305810527948800) | `/test pull-cluster-api-build-main`
pull-cluster-api-e2e-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e-main/1314305810649583616) | `/test pull-cluster-api-e2e-main`
pull-cluster-api-test-release-0-3 | 536223d26f2f1f0e1eeef0436b8d5d1ca1651b7d | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-release-0-3/1316223872017633280) | `/test pull-cluster-api-test-release-0-3`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3670). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asedefsavas). 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: @fabriziopandini 
1) Thanks for this important catch. Setting the returned `obj` to nil in reconcileDeleteExternal() was causing reconcileDelete() to continue to the end, whereas it was reconciling each time an external ref gets deleted. This was not a relevant change here anyways. So removed it.
2) Added WaitingForNodeRef reason in case Machine.spec.providerID is not set yet.reconcilePhase
fabriziopandini: @sedefsavas great work!
please squash commits
/hold cancel
/lgtm
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** after the PR has been reviewed.
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/release-0.3/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"]} -->
fabriziopandini: /lgtm
k8s-ci-robot: @sedefsavas: 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>
k8s-ci-robot: New changes are detected. LGTM label has been removed.
sedefsavas: This was ready to go, just solved conflicts.
cc @vincepri @fabriziopandini 
fabriziopandini: /lgtm
k8s-ci-robot: New changes are detected. LGTM label has been removed.
sedefsavas: I had renamed `machine_controller_noderef` to `machine_controller_node` after addressing the previous comments.
But since there are new comments, reverted it back for better visibility.

cc @vincepri 
k8s-ci-robot: @sedefsavas: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cluster-api-apidiff | 6a11470918d291a331793f0697cbc65169528223 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff/1311786395949338624) | `/test pull-cluster-api-apidiff`
pull-cluster-api-make-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-make-main/1314305810553114624) | `/test pull-cluster-api-make-main`
pull-cluster-api-test-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-test-main/1314305810628612096) | `/test pull-cluster-api-test-main`
pull-cluster-api-apidiff-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-apidiff-main/1314305810578280448) | `/test pull-cluster-api-apidiff-main`
pull-cluster-api-build-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-build-main/1314305810527948800) | `/test pull-cluster-api-build-main`
pull-cluster-api-e2e-main | 07e4fc31e27fbd592599c39701353fc245c0aff1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-e2e-main/1314305810649583616) | `/test pull-cluster-api-e2e-main`
pull-cluster-api-verify | 2a6f90db8a565132de0b04698c4176aec1fb7b25 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api/3670/pull-cluster-api-verify/1318711087612301312) | `/test pull-cluster-api-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api&pr=3670). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asedefsavas). 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: /hold cancel
fabriziopandini: @sedefsavas if you squash commits, then this one can be merged

Patch:
diff --git a/api/v1alpha2/conversion.go b/api/v1alpha2/conversion.go
index a10ef92d7..757b56df7 100644
--- a/api/v1alpha2/conversion.go
+++ b/api/v1alpha2/conversion.go
@@ -119,6 +119,7 @@ func (src *Machine) ConvertTo(dstRaw conversion.Hub) error {
 	restoreMachineSpec(&restored.Spec, &dst.Spec)
 	dst.Status.ObservedGeneration = restored.Status.ObservedGeneration
 	dst.Status.Conditions = restored.Status.Conditions
+
 	return nil
 }
 
diff --git a/api/v1alpha3/condition_consts.go b/api/v1alpha3/condition_consts.go
index fd75a3a2c..2bbed0a35 100644
--- a/api/v1alpha3/condition_consts.go
+++ b/api/v1alpha3/condition_consts.go
@@ -65,13 +65,6 @@ const (
 	// to be available.
 	// NOTE: This reason is used only as a fallback when the control plane object is not reporting its own ready condition.
 	WaitingForControlPlaneFallbackReason = "WaitingForControlPlane"
-
-	// WaitingForControlPlaneAvailableReason (Severity=Info) documents a Cluster API object
-	// waiting for the control plane machine to be available.
-	//
-	// NOTE: Having the control plane machine available is a pre-condition for joining additional control planes
-	// or workers nodes.
-	WaitingForControlPlaneAvailableReason = "WaitingForControlPlaneAvailable"
 )
 
 // Conditions and condition Reasons for the Machine object
@@ -116,9 +109,6 @@ const (
 	// MachineHasFailureReason is the reason used when a machine has either a FailureReason or a FailureMessage set on its status.
 	MachineHasFailureReason = "MachineHasFailure"
 
-	// NodeNotFoundReason is the reason used when a machine's node has previously been observed but is now gone.
-	NodeNotFoundReason = "NodeNotFound"
-
 	// NodeStartupTimeoutReason is the reason used when a machine's node does not appear within the specified timeout.
 	NodeStartupTimeoutReason = "NodeStartupTimeout"
 
@@ -133,30 +123,25 @@ const (
 
 	// WaitingForRemediationReason is the reason used when a machine fails a health check and remediation is needed.
 	WaitingForRemediationReason = "WaitingForRemediation"
+)
 
-	// ExternalRemediationTemplateAvailable is set on machinehealthchecks when MachineHealthCheck controller uses external remediation.
-	// ExternalRemediationTemplateAvailable is set to false if external remediation template is not found.
-	ExternalRemediationTemplateAvailable ConditionType = "ExternalRemediationTemplateAvailable"
-
-	// ExternalRemediationTemplateNotFound is the reason used when a machine health check fails to find external remediation template.
-	ExternalRemediationTemplateNotFound = "ExternalRemediationTemplateNotFound"
-
-	// ExternalRemediationRequestAvailable is set on machinehealthchecks when MachineHealthCheck controller uses external remediation.
-	// ExternalRemediationRequestAvailable is set to false if creating external remediation request fails.
-	ExternalRemediationRequestAvailable ConditionType = "ExternalRemediationRequestAvailable"
+// Conditions and condition Reasons for the Machine's Node object
+const (
+	// MachineNodeHealthyCondition provides info about the operational state of the Kubernetes node hosted on the machine by summarizing  node conditions.
+	// If the conditions defined in a Kubernetes node (i.e., NodeReady, NodeMemoryPressure, NodeDiskPressure, NodePIDPressure, and NodeNetworkUnavailable) are in a healthy state, it will be set to True.
+	MachineNodeHealthyCondition ConditionType = "NodeHealthy"
 
-	// ExternalRemediationRequestCreationFailed is the reason used when a machine health check fails to create external remediation request.
-	ExternalRemediationRequestCreationFailed = "ExternalRemediationRequestCreationFailed"
-)
+	// WaitingForNodeRefReason (Severity=Info) documents a machine.spec.providerId is not assigned yet.
+	WaitingForNodeRefReason = "WaitingForNodeRef"
 
-// Conditions and condition Reasons for the MachineHealthCheck object
+	// NodeProvisioningReason (Severity=Info) documents machine in the process of provisioning a node.
+	// NB. provisioning --> NodeRef == ""
+	NodeProvisioningReason = "NodeProvisioning"
 
-const (
-	// RemediationAllowedCondition is set on MachineHealthChecks to show the status of whether the MachineHealthCheck is
-	// allowed to remediate any Machines or whether it is blocked from remediating any further.
-	RemediationAllowedCondition ConditionType = "RemediationAllowed"
+	// NodeNotFoundReason (Severity=Error) documents a machine's node has previously been observed but is now gone.
+	// NB. provisioned --> NodeRef != ""
+	NodeNotFoundReason = "NodeNotFound"
 
-	// TooManyUnhealthy is the reason used when too many Machines are unhealthy and the MachineHealthCheck is blocked
-	// from making any further remediations.
-	TooManyUnhealthyReason = "TooManyUnhealthy"
+	// NodeConditionsFailedReason (Severity=Warning) documents a node is not in a healthy state due to the failed state of at least 1 Kubelet condition.
+	NodeConditionsFailedReason = "NodeConditionsFailed"
 )
diff --git a/api/v1alpha3/machinehealthcheck_types.go b/api/v1alpha3/machinehealthcheck_types.go
index 6eec56949..0e8b70adf 100644
--- a/api/v1alpha3/machinehealthcheck_types.go
+++ b/api/v1alpha3/machinehealthcheck_types.go
@@ -49,15 +49,6 @@ type MachineHealthCheckSpec struct {
 	// failed and will be remediated.
 	// +optional
 	NodeStartupTimeout *metav1.Duration `json:"nodeStartupTimeout,omitempty"`
-
-	// RemediationTemplate is a reference to a remediation template
-	// provided by an infrastructure provider.
-	//
-	// This field is completely optional, when filled, the MachineHealthCheck controller
-	// creates a new object from the template referenced and hands off remediation of the machine to
-	// a controller that lives outside of Cluster API.
-	// +optional
-	RemediationTemplate *corev1.ObjectReference `json:"remediationTemplate,omitempty"`
 }
 
 // ANCHOR_END: MachineHealthCHeckSpec
@@ -93,11 +84,6 @@ type MachineHealthCheckStatus struct {
 	// +kubebuilder:validation:Minimum=0
 	CurrentHealthy int32 `json:"currentHealthy,omitempty"`
 
-	// RemediationsAllowed is the number of further remediations allowed by this machine health check before
-	// maxUnhealthy short circuiting will be applied
-	// +kubebuilder:validation:Minimum=0
-	RemediationsAllowed int32 `json:"remediationsAllowed,omitempty"`
-
 	// ObservedGeneration is the latest generation observed by the controller.
 	// +optional
 	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
@@ -105,10 +91,6 @@ type MachineHealthCheckStatus struct {
 	// Targets shows the current list of machines the machine health check is watching
 	// +optional
 	Targets []string `json:"targets,omitempty"`
-
-	// Conditions defines current service state of the MachineHealthCheck.
-	// +optional
-	Conditions Conditions `json:"conditions,omitempty"`
 }
 
 // ANCHOR_END: MachineHealthCheckStatus
@@ -133,14 +115,6 @@ type MachineHealthCheck struct {
 	Status MachineHealthCheckStatus `json:"status,omitempty"`
 }
 
-func (m *MachineHealthCheck) GetConditions() Conditions {
-	return m.Status.Conditions
-}
-
-func (m *MachineHealthCheck) SetConditions(conditions Conditions) {
-	m.Status.Conditions = conditions
-}
-
 // +kubebuilder:object:root=true
 
 // MachineHealthCheckList contains a list of MachineHealthCheck
diff --git a/api/v1alpha3/zz_generated.deepcopy.go b/api/v1alpha3/zz_generated.deepcopy.go
index 14dfa97f9..9d577525b 100644
--- a/api/v1alpha3/zz_generated.deepcopy.go
+++ b/api/v1alpha3/zz_generated.deepcopy.go
@@ -587,11 +587,6 @@ func (in *MachineHealthCheckSpec) DeepCopyInto(out *MachineHealthCheckSpec) {
 		*out = new(metav1.Duration)
 		**out = **in
 	}
-	if in.RemediationTemplate != nil {
-		in, out := &in.RemediationTemplate, &out.RemediationTemplate
-		*out = new(v1.ObjectReference)
-		**out = **in
-	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineHealthCheckSpec.
@@ -612,13 +607,6 @@ func (in *MachineHealthCheckStatus) DeepCopyInto(out *MachineHealthCheckStatus)
 		*out = make([]string, len(*in))
 		copy(*out, *in)
 	}
-	if in.Conditions != nil {
-		in, out := &in.Conditions, &out.Conditions
-		*out = make(Conditions, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
-		}
-	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineHealthCheckStatus.
diff --git a/bootstrap/kubeadm/api/v1alpha3/condition_consts.go b/bootstrap/kubeadm/api/v1alpha3/condition_consts.go
index d0788d17c..345ab71e7 100644
--- a/bootstrap/kubeadm/api/v1alpha3/condition_consts.go
+++ b/bootstrap/kubeadm/api/v1alpha3/condition_consts.go
@@ -40,10 +40,7 @@ const (
 	//
 	// NOTE: Having the control plane machine available is a pre-condition for joining additional control planes
 	// or workers nodes.
-	// DEPRECATED: This has been deprecated in v1alpha3 and will be removed in a future version.
-	// Switch to WaitingForControlPlaneAvailableReason constant from the `sigs.k8s.io/cluster-api/api/v1alpha3`
-	// package.
-	WaitingForControlPlaneAvailableReason = clusterv1.WaitingForControlPlaneAvailableReason
+	WaitingForControlPlaneAvailableReason = "WaitingForControlPlaneAvailable"
 
 	// DataSecretGenerationFailedReason (Severity=Warning) documents a KubeadmConfig controller detecting
 	// an error while generating a data secret; those kind of errors are usually due to misconfigurations
diff --git a/bootstrap/kubeadm/controllers/kubeadmconfig_controller.go b/bootstrap/kubeadm/controllers/kubeadmconfig_controller.go
index f467a15c7..7b865cffc 100644
--- a/bootstrap/kubeadm/controllers/kubeadmconfig_controller.go
+++ b/bootstrap/kubeadm/controllers/kubeadmconfig_controller.go
@@ -295,7 +295,7 @@ func (r *KubeadmConfigReconciler) handleClusterNotInitialized(ctx context.Contex
 	// this is required in order to avoid the condition's LastTransitionTime to flicker in case of errors surfacing
 	// using the DataSecretGeneratedFailedReason
 	if conditions.GetReason(scope.Config, bootstrapv1.DataSecretAvailableCondition) != bootstrapv1.DataSecretGenerationFailedReason {
-		conditions.MarkFalse(scope.Config, bootstrapv1.DataSecretAvailableCondition, clusterv1.WaitingForControlPlaneAvailableReason, clusterv1.ConditionSeverityInfo, "")
+		conditions.MarkFalse(scope.Config, bootstrapv1.DataSecretAvailableCondition, bootstrapv1.WaitingForControlPlaneAvailableReason, clusterv1.ConditionSeverityInfo, "")
 	}
 
 	// if it's NOT a control plane machine, requeue
diff --git a/config/crd/bases/cluster.x-k8s.io_machinehealthchecks.yaml b/config/crd/bases/cluster.x-k8s.io_machinehealthchecks.yaml
index 366be1c70..6f28c2312 100644
--- a/config/crd/bases/cluster.x-k8s.io_machinehealthchecks.yaml
+++ b/config/crd/bases/cluster.x-k8s.io_machinehealthchecks.yaml
@@ -71,47 +71,6 @@ spec:
                 description: Machines older than this duration without a node will
                   be considered to have failed and will be remediated.
                 type: string
-              remediationTemplate:
-                description: "RemediationTemplate is a reference to a remediation
-                  template provided by an infrastructure provider. \n This field is
-                  completely optional, when filled, the MachineHealthCheck controller
-                  creates a new object from the template referenced and hands off
-                  remediation of the machine to a controller that lives outside of
-                  Cluster API."
-                properties:
-                  apiVersion:
-                    description: API version of the referent.
-                    type: string
-                  fieldPath:
-                    description: 'If referring to a piece of an object instead of
-                      an entire object, this string should contain a valid JSON/Go
-                      field access statement, such as desiredState.manifest.containers[2].
-                      For example, if the object reference is to a container within
-                      a pod, this would take on a value like: "spec.containers{name}"
-                      (where "name" refers to the name of the container that triggered
-                      the event) or if no container name is specified "spec.containers[2]"
-                      (container with index 2 in this pod). This syntax is chosen
-                      only to have some well-defined way of referencing a part of
-                      an object. TODO: this design is not final and this field is
-                      subject to change in the future.'
-                    type: string
-                  kind:
-                    description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
-                    type: string
-                  name:
-                    description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
-                    type: string
-                  namespace:
-                    description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
-                    type: string
-                  resourceVersion:
-                    description: 'Specific resourceVersion to which this reference
-                      is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
-                    type: string
-                  uid:
-                    description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
-                    type: string
-                type: object
               selector:
                 description: Label selector to match machines whose health will be
                   exercised
@@ -191,50 +150,6 @@ spec:
           status:
             description: Most recently observed status of MachineHealthCheck resource
             properties:
-              conditions:
-                description: Conditions defines current service state of the MachineHealthCheck.
-                items:
-                  description: Condition defines an observation of a Cluster API resource
-                    operational state.
-                  properties:
-                    lastTransitionTime:
-                      description: Last time the condition transitioned from one status
-                        to another. This should be when the underlying condition changed.
-                        If that is not known, then using the time when the API field
-                        changed is acceptable.
-                      format: date-time
-                      type: string
-                    message:
-                      description: A human readable message indicating details about
-                        the transition. This field may be empty.
-                      type: string
-                    reason:
-                      description: The reason for the condition's last transition
-                        in CamelCase. The specific API may choose whether or not this
-                        field is considered a guaranteed API. This field may not be
-                        empty.
-                      type: string
-                    severity:
-                      description: Severity provides an explicit classification of
-                        Reason code, so the users or machines can immediately understand
-                        the current situation and act accordingly. The Severity field
-                        MUST be set only when Status=False.
-                      type: string
-                    status:
-                      description: Status of the condition, one of True, False, Unknown.
-                      type: string
-                    type:
-                      description: Type of condition in CamelCase or in foo.example.com/CamelCase.
-                        Many .condition.type values are consistent across resources
-                        like Available, but because arbitrary conditions can be useful
-                        (see .node.status.conditions), the ability to deconflict is
-                        important.
-                      type: string
-                  required:
-                  - status
-                  - type
-                  type: object
-                type: array
               currentHealthy:
                 description: total number of healthy machines counted by this machine
                   health check
@@ -252,13 +167,6 @@ spec:
                   by the controller.
                 format: int64
                 type: integer
-              remediationsAllowed:
-                description: RemediationsAllowed is the number of further remediations
-                  allowed by this machine health check before maxUnhealthy short circuiting
-                  will be applied
-                format: int32
-                minimum: 0
-                type: integer
               targets:
                 description: Targets shows the current list of machines the machine
                   health check is watching
diff --git a/controllers/machine_controller.go b/controllers/machine_controller.go
index e284fab65..6d7bf3ad9 100644
--- a/controllers/machine_controller.go
+++ b/controllers/machine_controller.go
@@ -271,7 +271,7 @@ func (r *MachineReconciler) reconcile(ctx context.Context, cluster *clusterv1.Cl
 	phases := []func(context.Context, *clusterv1.Cluster, *clusterv1.Machine) (ctrl.Result, error){
 		r.reconcileBootstrap,
 		r.reconcileInfrastructure,
-		r.reconcileNodeRef,
+		r.reconcileNode,
 	}
 
 	res := ctrl.Result{}
@@ -355,6 +355,16 @@ func (r *MachineReconciler) reconcileDelete(ctx context.Context, cluster *cluste
 	// Return early and don't remove the finalizer if we got an error or
 	// the external reconciliation deletion isn't ready.
 
+	patchHelper, err := patch.NewHelper(m, r.Client)
+	if err != nil {
+		return ctrl.Result{}, err
+	}
+	conditions.MarkFalse(m, clusterv1.MachineNodeHealthyCondition, clusterv1.DeletingReason, clusterv1.ConditionSeverityInfo, "")
+	if err := patchMachine(ctx, patchHelper, m); err != nil {
+		conditions.MarkFalse(m, clusterv1.MachineNodeHealthyCondition, clusterv1.DeletionFailedReason, clusterv1.ConditionSeverityInfo, "")
+		return ctrl.Result{}, errors.Wrap(err, "failed to patch Machine")
+	}
+
 	if ok, err := r.reconcileDeleteInfrastructure(ctx, m); !ok || err != nil {
 		return ctrl.Result{}, err
 	}
@@ -377,6 +387,7 @@ func (r *MachineReconciler) reconcileDelete(ctx context.Context, cluster *cluste
 		})
 		if waitErr != nil {
 			logger.Error(deleteNodeErr, "Timed out deleting node, moving on", "node", m.Status.NodeRef.Name)
+			conditions.MarkFalse(m, clusterv1.MachineNodeHealthyCondition, clusterv1.DeletionFailedReason, clusterv1.ConditionSeverityWarning, "")
 			r.recorder.Eventf(m, corev1.EventTypeWarning, "FailedDeleteNode", "error deleting Machine's node: %v", deleteNodeErr)
 		}
 	}
diff --git a/controllers/machine_controller_noderef.go b/controllers/machine_controller_noderef.go
index 9fa4f2105..82587540c 100644
--- a/controllers/machine_controller_noderef.go
+++ b/controllers/machine_controller_noderef.go
@@ -19,13 +19,13 @@ package controllers
 import (
 	"context"
 	"fmt"
-	"time"
 
 	"github.com/pkg/errors"
-	apicorev1 "k8s.io/api/core/v1"
+	corev1 "k8s.io/api/core/v1"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
 	"sigs.k8s.io/cluster-api/controllers/noderefutil"
 	"sigs.k8s.io/cluster-api/util"
+	"sigs.k8s.io/cluster-api/util/conditions"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
@@ -34,23 +34,13 @@ var (
 	ErrNodeNotFound = errors.New("cannot find node with matching ProviderID")
 )
 
-func (r *MachineReconciler) reconcileNodeRef(ctx context.Context, cluster *clusterv1.Cluster, machine *clusterv1.Machine) (ctrl.Result, error) {
+func (r *MachineReconciler) reconcileNode(ctx context.Context, cluster *clusterv1.Cluster, machine *clusterv1.Machine) (ctrl.Result, error) {
 	logger := r.Log.WithValues("machine", machine.Name, "namespace", machine.Namespace)
-	// Check that the Machine hasn't been deleted or in the process.
-	if !machine.DeletionTimestamp.IsZero() {
-		return ctrl.Result{}, nil
-	}
-
-	// Check that the Machine doesn't already have a NodeRef.
-	if machine.Status.NodeRef != nil {
-		return ctrl.Result{}, nil
-	}
-
-	logger = logger.WithValues("cluster", cluster.Name)
 
 	// Check that the Machine has a valid ProviderID.
 	if machine.Spec.ProviderID == nil || *machine.Spec.ProviderID == "" {
-		logger.Info("Machine doesn't have a valid ProviderID yet")
+		logger.Info("Cannot reconcile Machine's Node, no valid ProviderID yet")
+		conditions.MarkFalse(machine, clusterv1.MachineNodeHealthyCondition, clusterv1.WaitingForNodeRefReason, clusterv1.ConditionSeverityInfo, "")
 		return ctrl.Result{}, nil
 	}
 
@@ -64,29 +54,93 @@ func (r *MachineReconciler) reconcileNodeRef(ctx context.Context, cluster *clust
 		return ctrl.Result{}, err
 	}
 
-	// Get the Node reference.
-	nodeRef, err := r.getNodeReference(remoteClient, providerID)
+	// Even if Status.NodeRef exists, continue to do the following checks to make sure Node is healthy
+	node, err := r.getNode(remoteClient, providerID)
 	if err != nil {
 		if err == ErrNodeNotFound {
-			logger.Info(fmt.Sprintf("Cannot assign NodeRef to Machine: %s, requeuing", ErrNodeNotFound.Error()))
-			return ctrl.Result{RequeueAfter: 20 * time.Second}, nil
+			// While a NodeRef is set in the status, failing to get that node means the node is deleted.
+			// If Status.NodeRef is not set before, node still can be in the provisioning state.
+			if machine.Status.NodeRef != nil {
+				conditions.MarkFalse(machine, clusterv1.MachineNodeHealthyCondition, clusterv1.NodeNotFoundReason, clusterv1.ConditionSeverityError, "")
+				return ctrl.Result{}, errors.Wrapf(err, "no matching Node for Machine %q in namespace %q", machine.Name, machine.Namespace)
+			}
+			conditions.MarkFalse(machine, clusterv1.MachineNodeHealthyCondition, clusterv1.NodeProvisioningReason, clusterv1.ConditionSeverityWarning, "")
+			return ctrl.Result{Requeue: true}, nil
 		}
-		logger.Error(err, "Failed to assign NodeRef")
-		r.recorder.Event(machine, apicorev1.EventTypeWarning, "FailedSetNodeRef", err.Error())
+		logger.Error(err, "Failed to retrieve Node by ProviderID")
+		r.recorder.Event(machine, corev1.EventTypeWarning, "Failed to retrieve Node by ProviderID", err.Error())
 		return ctrl.Result{}, err
 	}
 
 	// Set the Machine NodeRef.
-	machine.Status.NodeRef = nodeRef
-	logger.Info("Set Machine's NodeRef", "noderef", machine.Status.NodeRef.Name)
-	r.recorder.Event(machine, apicorev1.EventTypeNormal, "SuccessfulSetNodeRef", machine.Status.NodeRef.Name)
+	if machine.Status.NodeRef == nil {
+		machine.Status.NodeRef = &corev1.ObjectReference{
+			Kind:       node.Kind,
+			APIVersion: node.APIVersion,
+			Name:       node.Name,
+			UID:        node.UID,
+		}
+		logger.Info("Set Machine's NodeRef", "noderef", machine.Status.NodeRef.Name)
+		r.recorder.Event(machine, corev1.EventTypeNormal, "SuccessfulSetNodeRef", machine.Status.NodeRef.Name)
+	}
+
+	// Do the remaining node health checks, then set the node health to true if all checks pass.
+	status, message := summarizeNodeConditions(node)
+	if status == corev1.ConditionFalse {
+		conditions.MarkFalse(machine, clusterv1.MachineNodeHealthyCondition, clusterv1.NodeConditionsFailedReason, clusterv1.ConditionSeverityWarning, message)
+		return ctrl.Result{}, nil
+	}
+
+	conditions.MarkTrue(machine, clusterv1.MachineNodeHealthyCondition)
 	return ctrl.Result{}, nil
 }
 
-func (r *MachineReconciler) getNodeReference(c client.Reader, providerID *noderefutil.ProviderID) (*apicorev1.ObjectReference, error) {
+// summarizeNodeConditions summarizes a Node's conditions and returns the summary of condition statuses and concatenate failed condition messages:
+// if there is at least 1 semantically-negative condition, summarized status = False;
+// if there is at least 1 semantically-positive condition when there is 0 semantically negative condition, summarized status = True;
+// if all conditions are unknown,  summarized status = Unknown.
+// (semantically true conditions: NodeMemoryPressure/NodeDiskPressure/NodePIDPressure == false or Ready == true.)
+func summarizeNodeConditions(node *corev1.Node) (corev1.ConditionStatus, string) {
+	totalNumOfConditionsChecked := 4
+	semanticallyFalseStatus := 0
+	unknownStatus := 0
+
+	message := ""
+	for _, condition := range node.Status.Conditions {
+		switch condition.Type {
+		case corev1.NodeMemoryPressure, corev1.NodeDiskPressure, corev1.NodePIDPressure:
+			if condition.Status != corev1.ConditionFalse {
+				message += fmt.Sprintf("Node condition %s is %s", condition.Type, condition.Status) + ". "
+				if condition.Status == corev1.ConditionUnknown {
+					unknownStatus++
+					continue
+				}
+				semanticallyFalseStatus++
+			}
+		case corev1.NodeReady:
+			if condition.Status != corev1.ConditionTrue {
+				message += fmt.Sprintf("Node condition %s is %s", condition.Type, condition.Status) + ". "
+				if condition.Status == corev1.ConditionUnknown {
+					unknownStatus++
+					continue
+				}
+				semanticallyFalseStatus++
+			}
+		}
+	}
+	if semanticallyFalseStatus > 0 {
+		return corev1.ConditionFalse, message
+	}
+	if semanticallyFalseStatus+unknownStatus < totalNumOfConditionsChecked {
+		return corev1.ConditionTrue, message
+	}
+	return corev1.ConditionUnknown, message
+}
+
+func (r *MachineReconciler) getNode(c client.Reader, providerID *noderefutil.ProviderID) (*corev1.Node, error) {
 	logger := r.Log.WithValues("providerID", providerID)
 
-	nodeList := apicorev1.NodeList{}
+	nodeList := corev1.NodeList{}
 	for {
 		if err := c.List(context.TODO(), &nodeList, client.Continue(nodeList.Continue)); err != nil {
 			return nil, err
@@ -100,12 +154,7 @@ func (r *MachineReconciler) getNodeReference(c client.Reader, providerID *nodere
 			}
 
 			if providerID.Equals(nodeProviderID) {
-				return &apicorev1.ObjectReference{
-					Kind:       node.Kind,
-					APIVersion: node.APIVersion,
-					Name:       node.Name,
-					UID:        node.UID,
-				}, nil
+				return &node, nil
 			}
 		}
 
diff --git a/controllers/machinehealthcheck_controller.go b/controllers/machinehealthcheck_controller.go
index 3c39f3e7c..c47b98104 100644
--- a/controllers/machinehealthcheck_controller.go
+++ b/controllers/machinehealthcheck_controller.go
@@ -19,7 +19,6 @@ package controllers
 import (
 	"context"
 	"fmt"
-	"strings"
 	"time"
 
 	"github.com/go-logr/logr"
@@ -27,14 +26,12 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
 	kerrors "k8s.io/apimachinery/pkg/util/errors"
 	"k8s.io/apimachinery/pkg/util/intstr"
 	"k8s.io/client-go/tools/record"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
-	"sigs.k8s.io/cluster-api/controllers/external"
 	"sigs.k8s.io/cluster-api/controllers/remote"
 	"sigs.k8s.io/cluster-api/util"
 	"sigs.k8s.io/cluster-api/util/annotations"
@@ -220,27 +217,15 @@ func (r *MachineHealthCheckReconciler) reconcile(ctx context.Context, logger log
 			"max unhealthy", m.Spec.MaxUnhealthy,
 			"unhealthy targets", len(unhealthy),
 		)
-		message := fmt.Sprintf("Remediation is not allowed, the number of not started or unhealthy machines exceeds maxUnhealthy (total: %v, unhealthy: %v, maxUnhealthy: %v)",
-			totalTargets,
-			len(unhealthy),
-			m.Spec.MaxUnhealthy,
-		)
-
-		// Remediation not allowed, the number of not started or unhealthy machines exceeds maxUnhealthy
-		m.Status.RemediationsAllowed = 0
-		conditions.Set(m, &clusterv1.Condition{
-			Type:     clusterv1.RemediationAllowedCondition,
-			Status:   corev1.ConditionFalse,
-			Severity: clusterv1.ConditionSeverityWarning,
-			Reason:   clusterv1.TooManyUnhealthyReason,
-			Message:  message,
-		})
 
 		r.recorder.Eventf(
 			m,
 			corev1.EventTypeWarning,
 			EventRemediationRestricted,
-			message,
+			"Remediation restricted due to exceeded number of unhealthy machines (total: %v, unhealthy: %v, maxUnhealthy: %v)",
+			totalTargets,
+			m.Status.CurrentHealthy,
+			m.Spec.MaxUnhealthy,
 		)
 		for _, t := range append(healthy, unhealthy...) {
 			if err := t.patchHelper.Patch(ctx, t.Machine); err != nil {
@@ -256,15 +241,6 @@ func (r *MachineHealthCheckReconciler) reconcile(ctx context.Context, logger log
 		"unhealthy targets", len(unhealthy),
 	)
 
-	maxUnhealthy, err := getMaxUnhealthy(m)
-	if err != nil {
-		return ctrl.Result{}, errors.Wrapf(err, "Failed to get value for maxUnhealthy")
-	}
-
-	// Remediation is allowed so maxUnhealthy - unhealthyMachineCount >= 0
-	m.Status.RemediationsAllowed = int32(maxUnhealthy - unhealthyMachineCount(m))
-	conditions.MarkTrue(m, clusterv1.RemediationAllowedCondition)
-
 	// mark for remediation
 	errList := []error{}
 	for _, t := range unhealthy {
@@ -273,58 +249,9 @@ func (r *MachineHealthCheckReconciler) reconcile(ctx context.Context, logger log
 		if annotations.IsPaused(cluster, t.Machine) {
 			logger.Info("Machine has failed health check, but machine is paused so skipping remediation", "target", t.string(), "reason", condition.Reason, "message", condition.Message)
 		} else {
-			if m.Spec.RemediationTemplate != nil {
-				// If external remediation request already exists,
-				// return early
-				if r.externalRemediationRequestExists(ctx, m, t.Machine.Name) {
-					return ctrl.Result{}, nil
-				}
-
-				cloneOwnerRef := &metav1.OwnerReference{
-					APIVersion: clusterv1.GroupVersion.String(),
-					Kind:       "Machine",
-					Name:       t.Machine.Name,
-					UID:        t.Machine.UID,
-				}
-
-				from, err := external.Get(ctx, r.Client, m.Spec.RemediationTemplate, t.Machine.Namespace)
-				if err != nil {
-					conditions.MarkFalse(m, clusterv1.ExternalRemediationTemplateAvailable, clusterv1.ExternalRemediationTemplateNotFound, clusterv1.ConditionSeverityError, err.Error())
-					return ctrl.Result{}, errors.Wrapf(err, "error retrieving remediation template %v %q for machine %q in namespace %q within cluster %q", m.Spec.RemediationTemplate.GroupVersionKind(), m.Spec.RemediationTemplate.Name, t.Machine.Name, t.Machine.Namespace, m.Spec.ClusterName)
-				}
-
-				generateTemplateInput := &external.GenerateTemplateInput{
-					Template:    from,
-					TemplateRef: m.Spec.RemediationTemplate,
-					Namespace:   t.Machine.Namespace,
-					ClusterName: t.Machine.ClusterName,
-					OwnerRef:    cloneOwnerRef,
-				}
-				to, err := external.GenerateTemplate(generateTemplateInput)
-				if err != nil {
-					return ctrl.Result{}, errors.Wrapf(err, "failed to create template for remediation request %v %q for machine %q in namespace %q within cluster %q", m.Spec.RemediationTemplate.GroupVersionKind(), m.Spec.RemediationTemplate.Name, t.Machine.Name, t.Machine.Namespace, m.Spec.ClusterName)
-				}
-
-				// Set the Remediation Request to match the Machine name, the name is used to
-				// guarantee uniqueness between runs. A Machine should only ever have a single
-				// remediation object of a specific GVK created.
-				//
-				// NOTE: This doesn't guarantee uniqueness across different MHC objects watching
-				// the same Machine, users are in charge of setting health checks and remediation properly.
-				to.SetName(t.Machine.Name)
-
-				logger.Info("Target has failed health check, creating an external remediation request", "remediation request name", to.GetName(), "target", t.string(), "reason", condition.Reason, "message", condition.Message)
-				// Create the external clone.
-				if err := r.Client.Create(ctx, to); err != nil {
-					conditions.MarkFalse(m, clusterv1.ExternalRemediationRequestAvailable, clusterv1.ExternalRemediationRequestCreationFailed, clusterv1.ConditionSeverityError, err.Error())
-					return ctrl.Result{}, errors.Wrapf(err, "error creating remediation request for machine %q in namespace %q within cluster %q", t.Machine.Name, t.Machine.Namespace, t.Machine.ClusterName)
-				}
-			} else {
-				logger.Info("Target has failed health check, marking for remediation", "target", t.string(), "reason", condition.Reason, "message", condition.Message)
-				conditions.MarkFalse(t.Machine, clusterv1.MachineOwnerRemediatedCondition, clusterv1.WaitingForRemediationReason, clusterv1.ConditionSeverityWarning, "MachineHealthCheck failed")
-			}
+			logger.Info("Target has failed health check, marking for remediation", "target", t.string(), "reason", condition.Reason, "message", condition.Message)
+			conditions.MarkFalse(t.Machine, clusterv1.MachineOwnerRemediatedCondition, clusterv1.WaitingForRemediationReason, clusterv1.ConditionSeverityWarning, "MachineHealthCheck failed")
 		}
-
 		if err := t.patchHelper.Patch(ctx, t.Machine); err != nil {
 			logger.Error(err, "failed to patch unhealthy machine status for machine", "machine", t.Machine)
 			return ctrl.Result{}, err
@@ -337,27 +264,7 @@ func (r *MachineHealthCheckReconciler) reconcile(ctx context.Context, logger log
 			t.string(),
 		)
 	}
-
 	for _, t := range healthy {
-		if m.Spec.RemediationTemplate != nil {
-
-			// Get remediation request object
-			obj, err := r.getExternalRemediationRequest(ctx, m, t.Machine.Name)
-			if err != nil {
-				if apierrors.IsNotFound(errors.Cause(err)) {
-					continue
-				}
-				logger.Error(err, "failed to fetch remediation request for machine %q in namespace %q within cluster %q", t.Machine.Name, t.Machine.Namespace, t.Machine.ClusterName)
-			}
-			// Check that obj has no DeletionTimestamp to avoid hot loop
-			if obj.GetDeletionTimestamp() == nil {
-				// Issue a delete for remediation request.
-				if err := r.Client.Delete(ctx, obj); err != nil && !apierrors.IsNotFound(err) {
-					logger.Error(err, "failed to delete %v %q for Machine %q", obj.GroupVersionKind(), obj.GetName(), t.Machine.Name)
-				}
-			}
-		}
-
 		if err := t.patchHelper.Patch(ctx, t.Machine); err != nil {
 			logger.Error(err, "failed to patch healthy machine status for machine", "machine", t.Machine.GetName())
 			return reconcile.Result{}, err
@@ -501,36 +408,17 @@ func (r *MachineHealthCheckReconciler) watchClusterNodes(ctx context.Context, cl
 // isAllowedRemediation checks the value of the MaxUnhealthy field to determine
 // whether remediation should be allowed or not
 func isAllowedRemediation(mhc *clusterv1.MachineHealthCheck) bool {
-	// TODO(JoelSpeed): return an error from isAllowedRemediation when maxUnhealthy
-	// is nil, we expect it to be defaulted always.
 	if mhc.Spec.MaxUnhealthy == nil {
 		return true
 	}
-
-	maxUnhealthy, err := getMaxUnhealthy(mhc)
-	if err != nil {
-		return false
-	}
-
-	// Remediation is not allowed if unhealthy is above maxUnhealthy
-	return unhealthyMachineCount(mhc) <= maxUnhealthy
-}
-
-func getMaxUnhealthy(mhc *clusterv1.MachineHealthCheck) (int, error) {
-	if mhc.Spec.MaxUnhealthy == nil {
-		return 0, errors.New("spec.maxUnhealthy must be set")
-	}
 	maxUnhealthy, err := intstr.GetValueFromIntOrPercent(mhc.Spec.MaxUnhealthy, int(mhc.Status.ExpectedMachines), false)
 	if err != nil {
-		return 0, err
+		return false
 	}
-	return maxUnhealthy, nil
-}
 
-// unhealthyMachineCount calculates the number of presently unhealthy or missing machines
-// ie the delta between the expected number of machines and the current number deemed healthy
-func unhealthyMachineCount(mhc *clusterv1.MachineHealthCheck) int {
-	return int(mhc.Status.ExpectedMachines - mhc.Status.CurrentHealthy)
+	// If unhealthy is above maxUnhealthy, short circuit any further remediation
+	unhealthy := mhc.Status.ExpectedMachines - mhc.Status.CurrentHealthy
+	return int(unhealthy) <= maxUnhealthy
 }
 
 func machineNames(machines []*clusterv1.Machine) []string {
@@ -540,27 +428,3 @@ func machineNames(machines []*clusterv1.Machine) []string {
 	}
 	return result
 }
-
-// getExternalRemediationRequest gets reference to External Remediation Request, unstructured object.
-func (r *MachineHealthCheckReconciler) getExternalRemediationRequest(ctx context.Context, m *clusterv1.MachineHealthCheck, machineName string) (*unstructured.Unstructured, error) {
-	remediationRef := &corev1.ObjectReference{
-		APIVersion: m.Spec.RemediationTemplate.APIVersion,
-		Kind:       strings.TrimSuffix(m.Spec.RemediationTemplate.Kind, external.TemplateSuffix),
-		Name:       machineName,
-	}
-	remediationReq, err := external.Get(ctx, r.Client, remediationRef, m.Namespace)
-	if err != nil {
-		return nil, errors.Wrapf(err, "failed to retrieve external remediation request object")
-	}
-	return remediationReq, nil
-}
-
-// externalRemediationRequestExists checks if the External Remediation Request is created
-// for the machine.
-func (r *MachineHealthCheckReconciler) externalRemediationRequestExists(ctx context.Context, m *clusterv1.MachineHealthCheck, machineName string) bool {
-	remediationReq, err := r.getExternalRemediationRequest(ctx, m, machineName)
-	if err != nil {
-		return false
-	}
-	return remediationReq != nil
-}
diff --git a/controllers/machinehealthcheck_status_matcher.go b/controllers/machinehealthcheck_status_matcher.go
index ca3fadc6b..295681841 100644
--- a/controllers/machinehealthcheck_status_matcher.go
+++ b/controllers/machinehealthcheck_status_matcher.go
@@ -22,7 +22,6 @@ import (
 	. "github.com/onsi/gomega"
 	"github.com/onsi/gomega/types"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
-	"sigs.k8s.io/cluster-api/util/conditions"
 )
 
 // MatchMachineHealthCheckStatus returns a custom matcher to check equality of clusterv1.MachineHealthCheckStatus
@@ -50,15 +49,7 @@ func (m machineHealthCheckStatusMatcher) Match(actual interface{}) (success bool
 	if !ok {
 		return ok, err
 	}
-	ok, err = Equal(m.expected.RemediationsAllowed).Match(actualStatus.RemediationsAllowed)
-	if !ok {
-		return ok, err
-	}
 	ok, err = ConsistOf(m.expected.Targets).Match(actualStatus.Targets)
-	if !ok {
-		return ok, err
-	}
-	ok, err = conditions.MatchConditions(m.expected.Conditions).Match(actualStatus.Conditions)
 	return ok, err
 }
 
diff --git a/controllers/remote/cluster_cache.go b/controllers/remote/cluster_cache.go
index 9bc7d4fa2..9cdcb1b19 100644
--- a/controllers/remote/cluster_cache.go
+++ b/controllers/remote/cluster_cache.go
@@ -31,6 +31,7 @@ import (
 	"k8s.io/client-go/kubernetes/scheme"
 	"k8s.io/client-go/rest"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
+	"sigs.k8s.io/cluster-api/cmd/clusterctl/log"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/cache"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -68,6 +69,26 @@ func NewClusterCacheTracker(log logr.Logger, manager ctrl.Manager) (*ClusterCach
 	}, nil
 }
 
+// NewTestClusterCacheTracker creates a new dummy ClusterCacheTracker that can be used by unit tests with fake client.
+func NewTestClusterCacheTracker(cl client.Client, scheme *runtime.Scheme, objKey client.ObjectKey) *ClusterCacheTracker {
+	testCacheTracker := &ClusterCacheTracker{
+		log:              log.Log,
+		client:           cl,
+		scheme:           scheme,
+		clusterAccessors: make(map[client.ObjectKey]*clusterAccessor),
+	}
+	testCacheTracker.clusterAccessors[objKey] = &clusterAccessor{
+		cache: nil,
+		client: &client.DelegatingClient{
+			Reader:       cl,
+			Writer:       cl,
+			StatusClient: cl,
+		},
+		watches: nil,
+	}
+	return testCacheTracker
+}
+
 // GetClient returns a client for the given cluster.
 func (t *ClusterCacheTracker) GetClient(ctx context.Context, cluster client.ObjectKey) (client.Client, error) {
 	t.lock.Lock()
diff --git a/controlplane/kubeadm/controllers/controller.go b/controlplane/kubeadm/controllers/controller.go
index 1d3309c1e..32f7840c4 100644
--- a/controlplane/kubeadm/controllers/controller.go
+++ b/controlplane/kubeadm/controllers/controller.go
@@ -31,6 +31,13 @@ import (
 	kerrors "k8s.io/apimachinery/pkg/util/errors"
 	"k8s.io/client-go/tools/record"
 	"k8s.io/utils/pointer"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/controller"
+	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
+	"sigs.k8s.io/controller-runtime/pkg/source"
+
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
 	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha3"
 	kubeadmv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/types/v1beta1"
@@ -44,12 +51,6 @@ import (
 	"sigs.k8s.io/cluster-api/util/patch"
 	"sigs.k8s.io/cluster-api/util/predicates"
 	"sigs.k8s.io/cluster-api/util/secret"
-	ctrl "sigs.k8s.io/controller-runtime"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/controller"
-	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
-	"sigs.k8s.io/controller-runtime/pkg/handler"
-	"sigs.k8s.io/controller-runtime/pkg/source"
 )
 
 // +kubebuilder:rbac:groups=core,resources=events,verbs=get;list;watch;create;patch
@@ -302,13 +303,7 @@ func (r *KubeadmControlPlaneReconciler) reconcile(ctx context.Context, cluster *
 
 	// 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.
-	conditions.SetAggregate(controlPlane.KCP, controlplanev1.MachinesReadyCondition, controlPlane.Machines.ConditionGetters(), conditions.AddSourceRef())
-
-	// reconcileControlPlaneHealth returns err if there is a machine being deleted or if the control plane is unhealthy.
-	// If the control plane is not yet initialized, this call shouldn't fail.
-	if result, err := r.reconcileControlPlaneHealth(ctx, cluster, kcp, controlPlane); err != nil || !result.IsZero() {
-		return result, err
-	}
+	conditions.SetAggregate(controlPlane.KCP, controlplanev1.MachinesReadyCondition, ownedMachines.ConditionGetters(), conditions.AddSourceRef())
 
 	// Control plane machines rollout due to configuration changes (e.g. upgrades) takes precedence over other operations.
 	needRollout := controlPlane.MachinesNeedingRollout()
@@ -384,28 +379,6 @@ func (r *KubeadmControlPlaneReconciler) reconcileDelete(ctx context.Context, clu
 	logger := r.Log.WithValues("namespace", kcp.Namespace, "kubeadmControlPlane", kcp.Name, "cluster", cluster.Name)
 	logger.Info("Reconcile KubeadmControlPlane deletion")
 
-	// Ignore the health check results here as well as the errors, health check functions are to set health related conditions on Machines.
-	// Errors may be due to not being able to get workload cluster nodes.
-	workloadCluster, err := r.managementCluster.GetWorkloadCluster(ctx, util.ObjectKey(cluster))
-	if err != nil {
-		r.Log.V(2).Info("Cannot get remote client to workload cluster during delete reconciliation", "err", err.Error())
-	} else {
-		// Do a health check of the Control Plane components
-		_, err = workloadCluster.ControlPlaneIsHealthy(ctx)
-		if err != nil {
-			// Do nothing
-			r.Log.V(2).Info("Control plane did not pass control plane health check during delete reconciliation", "err", err.Error())
-		}
-
-		// Do a health check of the etcd
-		_, err = workloadCluster.EtcdIsHealthy(ctx)
-		if err != nil {
-			// Do nothing
-			r.Log.V(2).Info("Control plane did not pass etcd health check during delete reconciliation", "err", err.Error())
-		}
-	}
-
-	// Gets all machines, not just control plane machines.
 	allMachines, err := r.managementCluster.GetMachinesForCluster(ctx, util.ObjectKey(cluster))
 	if err != nil {
 		return ctrl.Result{}, err
@@ -469,40 +442,22 @@ func (r *KubeadmControlPlaneReconciler) ClusterToKubeadmControlPlane(o handler.M
 	return nil
 }
 
-// reconcileControlPlaneHealth performs health checks for control plane components and etcd
+// reconcileHealth performs health checks for control plane components and etcd
 // 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) reconcileControlPlaneHealth(ctx context.Context, cluster *clusterv1.Cluster, kcp *controlplanev1.KubeadmControlPlane, controlPlane *internal.ControlPlane) (ctrl.Result, error) {
-	// If there is no KCP-owned control-plane machines, then control-plane has not been initialized yet.
-	if controlPlane.Machines.Len() == 0 {
-		return ctrl.Result{}, nil
-	}
-
-	workloadCluster, err := r.managementCluster.GetWorkloadCluster(ctx, util.ObjectKey(cluster))
-	if err != nil {
-		// Failing at connecting to the workload cluster can mean workload cluster is unhealthy for a variety of reasons such as etcd quorum loss.
-		return ctrl.Result{}, errors.Wrap(err, "cannot get remote client to workload cluster")
-	}
-
-	errList := []error{}
+func (r *KubeadmControlPlaneReconciler) reconcileHealth(ctx context.Context, cluster *clusterv1.Cluster, kcp *controlplanev1.KubeadmControlPlane, controlPlane *internal.ControlPlane) (ctrl.Result, error) {
 
 	// Do a health check of the Control Plane components
-	checkResult, err := workloadCluster.ControlPlaneIsHealthy(ctx)
-	if err != nil {
-		errList = append(errList, errors.Wrap(err, "failed to pass control-plane health check"))
-	} else if err := checkResult.Aggregate(controlPlane); err != nil {
+	if err := r.managementCluster.TargetClusterControlPlaneIsHealthy(ctx, util.ObjectKey(cluster)); err != nil {
 		r.recorder.Eventf(kcp, corev1.EventTypeWarning, "ControlPlaneUnhealthy",
 			"Waiting for control plane to pass control plane health check to continue reconciliation: %v", err)
-		errList = append(errList, errors.Wrap(err, "failed to pass control-plane health check"))
+		return ctrl.Result{RequeueAfter: healthCheckFailedRequeueAfter}, nil
 	}
 
 	// If KCP should manage etcd, ensure etcd is healthy.
 	if controlPlane.IsEtcdManaged() {
-		checkResult, err := workloadCluster.EtcdIsHealthy(ctx)
-		if err != nil {
-			errList = append(errList, errors.Wrap(err, "failed to pass etcd health check"))
-		} else if err := checkResult.Aggregate(controlPlane); err != nil {
-			errList = append(errList, errors.Wrap(err, "failed to pass etcd health check"))
+		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.
@@ -513,13 +468,10 @@ func (r *KubeadmControlPlaneReconciler) reconcileControlPlaneHealth(ctx context.
 			} 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"))
 			}
+			return ctrl.Result{}, kerrors.NewAggregate(errList)
 		}
 	}
 
-	if len(errList) > 0 {
-		return ctrl.Result{}, kerrors.NewAggregate(errList)
-	}
-
 	// We need this check for scale up as well as down to avoid scaling up when there is a machine being deleted.
 	// This should be at the end of this method as no need to wait for machine to be completely deleted to reconcile etcd.
 	// TODO: Revisit during machine remediation implementation which may need to cover other machine phases.
diff --git a/controlplane/kubeadm/controllers/scale.go b/controlplane/kubeadm/controllers/scale.go
index 27faa9e77..a89d5b8e5 100644
--- a/controlplane/kubeadm/controllers/scale.go
+++ b/controlplane/kubeadm/controllers/scale.go
@@ -26,6 +26,7 @@ import (
 	controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha3"
 	"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal"
 	"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal/machinefilters"
+	capierrors "sigs.k8s.io/cluster-api/errors"
 	"sigs.k8s.io/cluster-api/util"
 	ctrl "sigs.k8s.io/controller-runtime"
 )
@@ -62,6 +63,11 @@ func (r *KubeadmControlPlaneReconciler) initializeControlPlane(ctx context.Conte
 func (r *KubeadmControlPlaneReconciler) scaleUpControlPlane(ctx context.Context, cluster *clusterv1.Cluster, kcp *controlplanev1.KubeadmControlPlane, controlPlane *internal.ControlPlane) (ctrl.Result, error) {
 	logger := controlPlane.Logger()
 
+	// reconcileHealth returns err if there is a machine being delete which is a required condition to check before scaling up
+	if result, err := r.reconcileHealth(ctx, cluster, kcp, controlPlane); err != nil || !result.IsZero() {
+		return result, err
+	}
+
 	// Create the bootstrap configuration
 	bootstrapSpec := controlPlane.JoinControlPlaneConfig()
 	fd := controlPlane.NextFailureDomainForScaleUp()
@@ -84,6 +90,10 @@ func (r *KubeadmControlPlaneReconciler) scaleDownControlPlane(
 ) (ctrl.Result, error) {
 	logger := controlPlane.Logger()
 
+	if result, err := r.reconcileHealth(ctx, cluster, kcp, controlPlane); err != nil || !result.IsZero() {
+		return result, err
+	}
+
 	workloadCluster, err := r.managementCluster.GetWorkloadCluster(ctx, util.ObjectKey(cluster))
 	if err != nil {
 		logger.Error(err, "Failed to create client to workload cluster")
@@ -113,6 +123,13 @@ func (r *KubeadmControlPlaneReconciler) scaleDownControlPlane(
 		}
 	}
 
+	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 before removing a control plane machine", "cause", err)
+		r.recorder.Eventf(kcp, corev1.EventTypeWarning, "ControlPlaneUnhealthy",
+			"Waiting for control plane to pass control plane health check before removing a control plane machine: %v", err)
+		return ctrl.Result{}, &capierrors.RequeueAfterError{RequeueAfter: healthCheckFailedRequeueAfter}
+
+	}
 	if err := workloadCluster.RemoveMachineFromKubeadmConfigMap(ctx, machineToDelete); err != nil {
 		logger.Error(err, "Failed to remove machine from kubeadm ConfigMap")
 		return ctrl.Result{}, err
diff --git a/controlplane/kubeadm/internal/cluster.go b/controlplane/kubeadm/internal/cluster.go
index f4943d242..0789c27e8 100644
--- a/controlplane/kubeadm/internal/cluster.go
+++ b/controlplane/kubeadm/internal/cluster.go
@@ -26,13 +26,15 @@ import (
 	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/runtime"
+	kerrors "k8s.io/apimachinery/pkg/util/errors"
 	"k8s.io/client-go/kubernetes/scheme"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
+
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
 	"sigs.k8s.io/cluster-api/controllers/remote"
 	"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal/machinefilters"
 	"sigs.k8s.io/cluster-api/util/secret"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
 )
 
 // ManagementCluster defines all behaviors necessary for something to function as a management cluster.
@@ -40,6 +42,8 @@ type ManagementCluster interface {
 	ctrlclient.Reader
 
 	GetMachinesForCluster(ctx context.Context, cluster client.ObjectKey, filters ...machinefilters.Func) (FilterableMachineCollection, error)
+	TargetClusterEtcdIsHealthy(ctx context.Context, clusterKey client.ObjectKey) error
+	TargetClusterControlPlaneIsHealthy(ctx context.Context, clusterKey client.ObjectKey) error
 	GetWorkloadCluster(ctx context.Context, clusterKey client.ObjectKey) (WorkloadCluster, error)
 }
 
@@ -175,3 +179,64 @@ func (m *Management) getApiServerEtcdClientCert(ctx context.Context, clusterKey
 	}
 	return tls.X509KeyPair(crtData, keyData)
 }
+
+type healthCheck func(context.Context) (HealthCheckResult, error)
+
+// HealthCheck will run a generic health check function and report any errors discovered.
+// In addition to the health check, it also ensures there is a 1;1 match between nodes and machines.
+func (m *Management) healthCheck(ctx context.Context, check healthCheck, clusterKey client.ObjectKey) error {
+	var errorList []error
+	nodeChecks, err := check(ctx)
+	if err != nil {
+		errorList = append(errorList, err)
+	}
+	for nodeName, err := range nodeChecks {
+		if err != nil {
+			errorList = append(errorList, fmt.Errorf("node %q: %v", nodeName, err))
+		}
+	}
+	if len(errorList) != 0 {
+		return kerrors.NewAggregate(errorList)
+	}
+
+	// Make sure Cluster API is aware of all the nodes.
+	machines, err := m.GetMachinesForCluster(ctx, clusterKey, machinefilters.ControlPlaneMachines(clusterKey.Name))
+	if err != nil {
+		return err
+	}
+
+	// This check ensures there is a 1 to 1 correspondence of nodes and machines.
+	// If a machine was not checked this is considered an error.
+	for _, machine := range machines {
+		if machine.Status.NodeRef == nil {
+			return errors.Errorf("control plane machine %s/%s has no status.nodeRef", machine.Namespace, machine.Name)
+		}
+		if _, ok := nodeChecks[machine.Status.NodeRef.Name]; !ok {
+			return errors.Errorf("machine's (%s/%s) node (%s) was not checked", machine.Namespace, machine.Name, machine.Status.NodeRef.Name)
+		}
+	}
+	if len(nodeChecks) != len(machines) {
+		return errors.Errorf("number of nodes and machines in namespace %s did not match: %d nodes %d machines", clusterKey.Namespace, len(nodeChecks), len(machines))
+	}
+	return nil
+}
+
+// TargetClusterControlPlaneIsHealthy checks every node for control plane health.
+func (m *Management) TargetClusterControlPlaneIsHealthy(ctx context.Context, clusterKey client.ObjectKey) error {
+	// TODO: add checks for expected taints/labels
+	cluster, err := m.GetWorkloadCluster(ctx, clusterKey)
+	if err != nil {
+		return err
+	}
+	return m.healthCheck(ctx, cluster.ControlPlaneIsHealthy, clusterKey)
+}
+
+// TargetClusterEtcdIsHealthy runs a series of checks over a target cluster's etcd cluster.
+// In addition, it verifies that there are the same number of etcd members as control plane Machines.
+func (m *Management) TargetClusterEtcdIsHealthy(ctx context.Context, clusterKey client.ObjectKey) error {
+	cluster, err := m.GetWorkloadCluster(ctx, clusterKey)
+	if err != nil {
+		return err
+	}
+	return m.healthCheck(ctx, cluster.EtcdIsHealthy, clusterKey)
+}
diff --git a/controlplane/kubeadm/internal/machine_collection.go b/controlplane/kubeadm/internal/machine_collection.go
index 975aea467..425cea203 100644
--- a/controlplane/kubeadm/internal/machine_collection.go
+++ b/controlplane/kubeadm/internal/machine_collection.go
@@ -83,8 +83,8 @@ func (s FilterableMachineCollection) SortedByCreationTimestamp() []*clusterv1.Ma
 	return res
 }
 
-// UnsortedList returns the slice with contents in random order.
-func (s FilterableMachineCollection) UnsortedList() []*clusterv1.Machine {
+// unsortedList returns the slice with contents in random order.
+func (s FilterableMachineCollection) unsortedList() []*clusterv1.Machine {
 	res := make([]*clusterv1.Machine, 0, len(s))
 	for _, value := range s {
 		res = append(res, value)
@@ -110,12 +110,12 @@ func newFilteredMachineCollection(filter machinefilters.Func, machines ...*clust
 
 // Filter returns a FilterableMachineCollection containing only the Machines that match all of the given MachineFilters
 func (s FilterableMachineCollection) Filter(filters ...machinefilters.Func) FilterableMachineCollection {
-	return newFilteredMachineCollection(machinefilters.And(filters...), s.UnsortedList()...)
+	return newFilteredMachineCollection(machinefilters.And(filters...), s.unsortedList()...)
 }
 
 // AnyFilter returns a FilterableMachineCollection containing only the Machines that match any of the given MachineFilters
 func (s FilterableMachineCollection) AnyFilter(filters ...machinefilters.Func) FilterableMachineCollection {
-	return newFilteredMachineCollection(machinefilters.Or(filters...), s.UnsortedList()...)
+	return newFilteredMachineCollection(machinefilters.Or(filters...), s.unsortedList()...)
 }
 
 // Oldest returns the Machine with the oldest CreationTimestamp
diff --git a/controlplane/kubeadm/internal/workload_cluster.go b/controlplane/kubeadm/internal/workload_cluster.go
index 825cf7e13..d0db1fe68 100644
--- a/controlplane/kubeadm/internal/workload_cluster.go
+++ b/controlplane/kubeadm/internal/workload_cluster.go
@@ -34,7 +34,6 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	kerrors "k8s.io/apimachinery/pkg/util/errors"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
 	controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha3"
 	"sigs.k8s.io/cluster-api/util"
@@ -110,40 +109,6 @@ func (w *Workload) getConfigMap(ctx context.Context, configMap ctrlclient.Object
 // HealthCheckResult maps nodes that are checked to any errors the node has related to the check.
 type HealthCheckResult map[string]error
 
-// Aggregate will analyse HealthCheckResult and report any errors discovered.
-// It also ensures there is a 1;1 match between nodes and machines.
-func (h HealthCheckResult) Aggregate(controlPlane *ControlPlane) error {
-	var errorList []error
-	kcpMachines := controlPlane.Machines.UnsortedList()
-	// Make sure Cluster API is aware of all the nodes.
-
-	for nodeName, err := range h {
-		if err != nil {
-			errorList = append(errorList, fmt.Errorf("node %q: %v", nodeName, err))
-		}
-	}
-	if len(errorList) != 0 {
-		return kerrors.NewAggregate(errorList)
-	}
-
-	// This check ensures there is a 1 to 1 correspondence of nodes and machines.
-	// If a machine was not checked this is considered an error.
-	for _, machine := range kcpMachines {
-		if machine.Status.NodeRef == nil {
-			// The condition for this case is set by the Machine controller
-			return errors.Errorf("control plane machine %s/%s has no status.nodeRef", machine.Namespace, machine.Name)
-		}
-		if _, ok := h[machine.Status.NodeRef.Name]; !ok {
-			return errors.Errorf("machine's (%s/%s) node (%s) was not checked", machine.Namespace, machine.Name, machine.Status.NodeRef.Name)
-		}
-	}
-	if len(h) != len(kcpMachines) {
-		// MachinesReadyCondition covers this health failure.
-		return errors.Errorf("number of nodes and machines in namespace %s did not match: %d nodes %d machines", controlPlane.Cluster.Namespace, len(h), len(kcpMachines))
-	}
-	return nil
-}
-
 // controlPlaneIsHealthy does a best effort check of the control plane components the kubeadm control plane cares about.
 // The return map is a map of node names as keys to error that that node encountered.
 // All nodes will exist in the map with nil errors if there were no errors for that node.
diff --git a/docs/book/src/user/quick-start.md b/docs/book/src/user/quick-start.md
index 831f60809..0ed19a86b 100644
--- a/docs/book/src/user/quick-start.md
+++ b/docs/book/src/user/quick-start.md
@@ -275,7 +275,7 @@ clusterctl init --infrastructure openstack
 {{#/tab }}
 {{#tab Metal3}}
 
-Please visit the [Metal3 project][Metal3 provider].
+Please visit the [Metal3 project][Metal3 getting started guide].
 
 {{#/tab }}
 {{#tab Packet}}
@@ -494,27 +494,7 @@ A full configuration reference can be found in [configuration.md](https://github
 {{#/tab }}
 {{#tab Metal3}}
 
-```bash
-# The URL of the kernel to deploy.
-export DEPLOY_KERNEL_URL="http://172.22.0.1:6180/images/ironic-python-agent.kernel"
-# The URL of the ramdisk to deploy.
-export DEPLOY_RAMDISK_URL="http://172.22.0.1:6180/images/ironic-python-agent.initramfs"
-# The URL of the Ironic endpoint.
-export IRONIC_URL="http://172.22.0.1:6385/v1/"
-# The URL of the Ironic inspector endpoint.
-export IRONIC_INSPECTOR_URL="http://172.22.0.1:5050/v1/"
-# Do not use a dedicated CA certificate for Ironic API. Any value provided in this variable disables additional CA certificate validation.
-# To provide a CA certificate, leave this variable unset. If unset, then IRONIC_CA_CERT_B64 must be set.
-export IRONIC_NO_CA_CERT=true
-# Disables basic authentication for Ironic API. Any value provided in this variable disables authentication.
-# To enable authentication, leave this variable unset. If unset, then IRONIC_USERNAME and IRONIC_PASSWORD must be set.
-export IRONIC_NO_BASIC_AUTH=true
-# Disables basic authentication for Ironic inspector API. Any value provided in this variable disables authentication.
-# To enable authentication, leave this variable unset. If unset, then IRONIC_INSPECTOR_USERNAME and IRONIC_INSPECTOR_PASSWORD must be set.
-export IRONIC_INSPECTOR_NO_BASIC_AUTH=true
-```
-
-Please visit the [Metal3 getting started guide] for more details.
+Please visit the [Metal3 getting started guide].
 
 {{#/tab }}
 {{#tab Packet}}
@@ -731,8 +711,7 @@ See the [clusterctl] documentation for more detail about clusterctl supported ac
 [KubeadmControlPlane]: ../developer/architecture/controllers/control-plane.md
 [kubectl]: https://kubernetes.io/docs/tasks/tools/install-kubectl/
 [management cluster]: ../reference/glossary.md#management-cluster
-[Metal3 provider]: https://github.com/metal3-io/cluster-api-provider-metal3/
-[Metal3 getting started guide]: https://github.com/metal3-io/cluster-api-provider-metal3/blob/master/docs/getting-started.md
+[Metal3 getting started guide]: https://github.com/metal3-io/cluster-api-provider-metal3/
 [Packet getting started guide]: https://github.com/kubernetes-sigs/cluster-api-provider-packet#using
 [provider components]: ../reference/glossary.md#provider-components
 [vSphere getting started guide]: https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/
diff --git a/docs/proposals/YYYYMMDD-template.md b/docs/proposals/YYYYMMDD-template.md
index 2f57388b7..d6fef24e2 100644
--- a/docs/proposals/YYYYMMDD-template.md
+++ b/docs/proposals/YYYYMMDD-template.md
@@ -29,7 +29,7 @@ To get started with this template:
   Aim for single topic PRs to keep discussions focused.
   If you disagree with what is already in a document, open a new PR with suggested changes.
 
-The canonical place for the latest set of instructions (and the likely source of this file) is [here](/docs/proposals/YYYYMMDD-template.md).
+The canonical place for the latest set of instructions (and the likely source of this file) is [here](/proposals/YYYYMMDD-proposal-template.md).
 
 The `Metadata` section above is intended to support the creation of tooling around the proposal process.
 This will be a YAML section that is fenced as a code block.
diff --git a/go.mod b/go.mod
index c452140e4..0dedc704c 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.13
 require (
 	github.com/MakeNowJust/heredoc v1.0.0
 	github.com/blang/semver v3.5.1+incompatible
-	github.com/coredns/corefile-migration v1.0.11
+	github.com/coredns/corefile-migration v1.0.10
 	github.com/davecgh/go-spew v1.1.1
 	github.com/docker/distribution v2.7.1+incompatible
 	github.com/drone/envsubst v1.0.3-0.20200709223903-efdb65b94e5a
diff --git a/go.sum b/go.sum
index 8380f3bec..0d7503b57 100644
--- a/go.sum
+++ b/go.sum
@@ -60,8 +60,8 @@ github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlR
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
-github.com/coredns/corefile-migration v1.0.11 h1:ptBYGW2ADXIB7ZEBPrhhTvNwJLQfxE3Q9IUMBhJCEeI=
-github.com/coredns/corefile-migration v1.0.11/go.mod h1:RMy/mXdeDlYwzt0vdMEJvT2hGJ2I86/eO0UdXmH9XNI=
+github.com/coredns/corefile-migration v1.0.10 h1:7HI4r5S5Fne749a+JDxUZppqBpYoZK8Q53ZVK9cn3aM=
+github.com/coredns/corefile-migration v1.0.10/go.mod h1:RMy/mXdeDlYwzt0vdMEJvT2hGJ2I86/eO0UdXmH9XNI=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
diff --git a/util/conditions/matcher.go b/util/conditions/matcher.go
deleted file mode 100644
index c30cee782..000000000
--- a/util/conditions/matcher.go
+++ /dev/null
@@ -1,102 +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 conditions
-
-import (
-	"fmt"
-
-	. "github.com/onsi/gomega"
-	"github.com/onsi/gomega/types"
-	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
-)
-
-// MatchConditions returns a custom matcher to check equality of clusterv1.Conditions
-func MatchConditions(expected clusterv1.Conditions) types.GomegaMatcher {
-	return &matchConditions{
-		expected: expected,
-	}
-}
-
-type matchConditions struct {
-	expected clusterv1.Conditions
-}
-
-func (m matchConditions) Match(actual interface{}) (success bool, err error) {
-	elems := []interface{}{}
-	for _, condition := range m.expected {
-		elems = append(elems, MatchCondition(condition))
-	}
-
-	return ConsistOf(elems).Match(actual)
-}
-
-func (m matchConditions) FailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected\n\t%#v\nto match\n\t%#v\n", actual, m.expected)
-}
-
-func (m matchConditions) NegatedFailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected\n\t%#v\nto not match\n\t%#v\n", actual, m.expected)
-}
-
-// MatchCondition returns a custom matcher to check equality of clusterv1.Condition
-func MatchCondition(expected clusterv1.Condition) types.GomegaMatcher {
-	return &matchCondition{
-		expected: expected,
-	}
-}
-
-type matchCondition struct {
-	expected clusterv1.Condition
-}
-
-func (m matchCondition) Match(actual interface{}) (success bool, err error) {
-	actualCondition, ok := actual.(clusterv1.Condition)
-	if !ok {
-		return false, fmt.Errorf("actual should be of type Condition")
-	}
-
-	ok, err = Equal(m.expected.Type).Match(actualCondition.Type)
-	if !ok {
-		return ok, err
-	}
-	ok, err = Equal(m.expected.Status).Match(actualCondition.Status)
-	if !ok {
-		return ok, err
-	}
-	ok, err = Equal(m.expected.Severity).Match(actualCondition.Severity)
-	if !ok {
-		return ok, err
-	}
-	ok, err = Equal(m.expected.Reason).Match(actualCondition.Reason)
-	if !ok {
-		return ok, err
-	}
-	ok, err = Equal(m.expected.Message).Match(actualCondition.Message)
-	if !ok {
-		return ok, err
-	}
-
-	return ok, err
-}
-
-func (m matchCondition) FailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected\n\t%#v\nto match\n\t%#v\n", actual, m.expected)
-}
-
-func (m matchCondition) NegatedFailureMessage(actual interface{}) (message string) {
-	return fmt.Sprintf("expected\n\t%#v\nto not match\n\t%#v\n", actual, m.expected)
-}


Test patch:
diff --git a/bootstrap/kubeadm/controllers/kubeadmconfig_controller_test.go b/bootstrap/kubeadm/controllers/kubeadmconfig_controller_test.go
index 559e3acd9..5918a7280 100644
--- a/bootstrap/kubeadm/controllers/kubeadmconfig_controller_test.go
+++ b/bootstrap/kubeadm/controllers/kubeadmconfig_controller_test.go
@@ -422,7 +422,7 @@ func TestKubeadmConfigReconciler_Reconcile_RequeueJoiningNodesIfControlPlaneNotI
 			g.Expect(err).NotTo(HaveOccurred())
 			g.Expect(result.Requeue).To(BeFalse())
 			g.Expect(result.RequeueAfter).To(Equal(30 * time.Second))
-			assertHasFalseCondition(g, myclient, tc.request, bootstrapv1.DataSecretAvailableCondition, clusterv1.ConditionSeverityInfo, clusterv1.WaitingForControlPlaneAvailableReason)
+			assertHasFalseCondition(g, myclient, tc.request, bootstrapv1.DataSecretAvailableCondition, clusterv1.ConditionSeverityInfo, bootstrapv1.WaitingForControlPlaneAvailableReason)
 		})
 	}
 }
diff --git a/controllers/external/testing.go b/controllers/external/testing.go
index 9b466bb46..7ee5c579d 100644
--- a/controllers/external/testing.go
+++ b/controllers/external/testing.go
@@ -207,96 +207,4 @@ var (
 			},
 		},
 	}
-
-	TestGenericInfrastructureRemediationCRD = &apiextensionsv1.CustomResourceDefinition{
-		TypeMeta: metav1.TypeMeta{
-			APIVersion: apiextensionsv1.SchemeGroupVersion.String(),
-			Kind:       "CustomResourceDefinition",
-		},
-		ObjectMeta: metav1.ObjectMeta{
-			Name: "infrastructureremediations.infrastructure.cluster.x-k8s.io",
-			Labels: map[string]string{
-				clusterv1.GroupVersion.String(): "v1alpha3",
-			},
-		},
-		Spec: apiextensionsv1.CustomResourceDefinitionSpec{
-			Group: "infrastructure.cluster.x-k8s.io",
-			Scope: apiextensionsv1.NamespaceScoped,
-			Names: apiextensionsv1.CustomResourceDefinitionNames{
-				Kind:   "InfrastructureRemediation",
-				Plural: "infrastructureremediations",
-			},
-			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
-				{
-					Name:    "v1alpha3",
-					Served:  true,
-					Storage: true,
-					Subresources: &apiextensionsv1.CustomResourceSubresources{
-						Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
-					},
-					Schema: &apiextensionsv1.CustomResourceValidation{
-						OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
-							Type: "object",
-							Properties: map[string]apiextensionsv1.JSONSchemaProps{
-								"spec": {
-									Type:                   "object",
-									XPreserveUnknownFields: pointer.BoolPtr(true),
-								},
-								"status": {
-									Type:                   "object",
-									XPreserveUnknownFields: pointer.BoolPtr(true),
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-
-	TestGenericInfrastructureRemediationTemplateCRD = &apiextensionsv1.CustomResourceDefinition{
-		TypeMeta: metav1.TypeMeta{
-			APIVersion: apiextensionsv1.SchemeGroupVersion.String(),
-			Kind:       "CustomResourceDefinition",
-		},
-		ObjectMeta: metav1.ObjectMeta{
-			Name: "infrastructureremediationtemplates.infrastructure.cluster.x-k8s.io",
-			Labels: map[string]string{
-				clusterv1.GroupVersion.String(): "v1alpha3",
-			},
-		},
-		Spec: apiextensionsv1.CustomResourceDefinitionSpec{
-			Group: "infrastructure.cluster.x-k8s.io",
-			Scope: apiextensionsv1.NamespaceScoped,
-			Names: apiextensionsv1.CustomResourceDefinitionNames{
-				Kind:   "InfrastructureRemediationTemplate",
-				Plural: "infrastructureremediationtemplates",
-			},
-			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
-				{
-					Name:    "v1alpha3",
-					Served:  true,
-					Storage: true,
-					Subresources: &apiextensionsv1.CustomResourceSubresources{
-						Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
-					},
-					Schema: &apiextensionsv1.CustomResourceValidation{
-						OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
-							Type: "object",
-							Properties: map[string]apiextensionsv1.JSONSchemaProps{
-								"spec": {
-									Type:                   "object",
-									XPreserveUnknownFields: pointer.BoolPtr(true),
-								},
-								"status": {
-									Type:                   "object",
-									XPreserveUnknownFields: pointer.BoolPtr(true),
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
 )
diff --git a/controllers/machine_controller_noderef_test.go b/controllers/machine_controller_noderef_test.go
index ddd838471..0359c2b63 100644
--- a/controllers/machine_controller_noderef_test.go
+++ b/controllers/machine_controller_noderef_test.go
@@ -26,11 +26,10 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes/scheme"
 	"k8s.io/client-go/tools/record"
-	"sigs.k8s.io/controller-runtime/pkg/client/fake"
-	"sigs.k8s.io/controller-runtime/pkg/log"
-
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
 	"sigs.k8s.io/cluster-api/controllers/noderefutil"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+	"sigs.k8s.io/controller-runtime/pkg/log"
 )
 
 func TestGetNodeReference(t *testing.T) {
@@ -108,7 +107,7 @@ func TestGetNodeReference(t *testing.T) {
 			providerID, err := noderefutil.NewProviderID(test.providerID)
 			gt.Expect(err).NotTo(HaveOccurred(), "Expected no error parsing provider id %q, got %v", test.providerID, err)
 
-			reference, err := r.getNodeReference(client, providerID)
+			node, err := r.getNode(client, providerID)
 			if test.err == nil {
 				g.Expect(err).To(BeNil())
 			} else {
@@ -116,13 +115,77 @@ func TestGetNodeReference(t *testing.T) {
 				gt.Expect(err).To(Equal(test.err), "Expected error %v, got %v", test.err, err)
 			}
 
-			if test.expected == nil && reference == nil {
+			if test.expected == nil && node == nil {
 				return
 			}
 
-			gt.Expect(reference.Name).To(Equal(test.expected.Name), "Expected NodeRef's name to be %v, got %v", reference.Name, test.expected.Name)
-			gt.Expect(reference.Namespace).To(Equal(test.expected.Namespace), "Expected NodeRef's namespace to be %v, got %v", reference.Namespace, test.expected.Namespace)
+			gt.Expect(node.Name).To(Equal(test.expected.Name), "Expected NodeRef's name to be %v, got %v", node.Name, test.expected.Name)
+			gt.Expect(node.Namespace).To(Equal(test.expected.Namespace), "Expected NodeRef's namespace to be %v, got %v", node.Namespace, test.expected.Namespace)
 		})
 
 	}
 }
+
+func TestSummarizeNodeConditions(t *testing.T) {
+	testCases := []struct {
+		name       string
+		conditions []corev1.NodeCondition
+		status     corev1.ConditionStatus
+	}{
+		{
+			name: "node is healthy",
+			conditions: []corev1.NodeCondition{
+				{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
+				{Type: corev1.NodeMemoryPressure, Status: corev1.ConditionFalse},
+				{Type: corev1.NodeDiskPressure, Status: corev1.ConditionFalse},
+				{Type: corev1.NodePIDPressure, Status: corev1.ConditionFalse},
+			},
+			status: corev1.ConditionTrue,
+		},
+		{
+			name: "all conditions are unknown",
+			conditions: []corev1.NodeCondition{
+				{Type: corev1.NodeReady, Status: corev1.ConditionUnknown},
+				{Type: corev1.NodeMemoryPressure, Status: corev1.ConditionUnknown},
+				{Type: corev1.NodeDiskPressure, Status: corev1.ConditionUnknown},
+				{Type: corev1.NodePIDPressure, Status: corev1.ConditionUnknown},
+			},
+			status: corev1.ConditionUnknown,
+		},
+		{
+			name: "multiple semantically failed condition",
+			conditions: []corev1.NodeCondition{
+				{Type: corev1.NodeReady, Status: corev1.ConditionUnknown},
+				{Type: corev1.NodeMemoryPressure, Status: corev1.ConditionTrue},
+				{Type: corev1.NodeDiskPressure, Status: corev1.ConditionTrue},
+				{Type: corev1.NodePIDPressure, Status: corev1.ConditionTrue},
+			},
+			status: corev1.ConditionFalse,
+		},
+		{
+			name: "one positive condition when the rest is unknown",
+			conditions: []corev1.NodeCondition{
+				{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
+				{Type: corev1.NodeMemoryPressure, Status: corev1.ConditionUnknown},
+				{Type: corev1.NodeDiskPressure, Status: corev1.ConditionUnknown},
+				{Type: corev1.NodePIDPressure, Status: corev1.ConditionUnknown},
+			},
+			status: corev1.ConditionTrue,
+		},
+	}
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			g := NewWithT(t)
+			node := &corev1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "node-1",
+				},
+				Status: corev1.NodeStatus{
+					Conditions: test.conditions,
+				},
+			}
+			status, _ := summarizeNodeConditions(node)
+			g.Expect(status).To(Equal(test.status))
+		})
+	}
+}
diff --git a/controllers/machine_controller_phases_test.go b/controllers/machine_controller_phases_test.go
index 66520bff5..5f8c7a21e 100644
--- a/controllers/machine_controller_phases_test.go
+++ b/controllers/machine_controller_phases_test.go
@@ -30,11 +30,15 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/client-go/kubernetes/scheme"
 	"k8s.io/client-go/rest"
+	"k8s.io/client-go/tools/record"
 	"k8s.io/utils/pointer"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
 	"sigs.k8s.io/cluster-api/controllers/external"
+	"sigs.k8s.io/cluster-api/controllers/remote"
+	"sigs.k8s.io/cluster-api/util/conditions"
 	"sigs.k8s.io/cluster-api/util/kubeconfig"
 	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 	"sigs.k8s.io/controller-runtime/pkg/log"
 )
@@ -258,18 +262,28 @@ var _ = Describe("Reconcile Machine Phases", func() {
 		lastUpdated := metav1.NewTime(time.Now().Add(-10 * time.Second))
 		machine.Status.LastUpdated = &lastUpdated
 
+		node := &corev1.Node{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "machine-test-node",
+				Namespace: metav1.NamespaceDefault,
+			},
+			Spec: corev1.NodeSpec{ProviderID: "test://id-1"},
+		}
+		cl := fake.NewFakeClientWithScheme(scheme.Scheme,
+			defaultCluster,
+			machine,
+			node,
+			external.TestGenericBootstrapCRD.DeepCopy(),
+			external.TestGenericInfrastructureCRD.DeepCopy(),
+			bootstrapConfig,
+			infraConfig,
+			defaultKubeconfigSecret,
+		)
 		r := &MachineReconciler{
-			Client: fake.NewFakeClientWithScheme(scheme.Scheme,
-				defaultCluster,
-				defaultKubeconfigSecret,
-				machine,
-				external.TestGenericBootstrapCRD.DeepCopy(),
-				external.TestGenericInfrastructureCRD.DeepCopy(),
-				bootstrapConfig,
-				infraConfig,
-			),
-			Log:    log.Log,
-			scheme: scheme.Scheme,
+			Client:  cl,
+			Tracker: remote.NewTestClusterCacheTracker(cl, scheme.Scheme, client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
+			Log:     log.Log,
+			scheme:  scheme.Scheme,
 		}
 
 		res, err := r.reconcile(context.Background(), defaultCluster, machine)
@@ -312,18 +326,28 @@ var _ = Describe("Reconcile Machine Phases", func() {
 		lastUpdated := metav1.NewTime(time.Now().Add(-10 * time.Second))
 		machine.Status.LastUpdated = &lastUpdated
 
+		node := &corev1.Node{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "machine-test-node",
+				Namespace: metav1.NamespaceDefault,
+			},
+			Spec: corev1.NodeSpec{ProviderID: "test://id-1"},
+		}
+		cl := fake.NewFakeClientWithScheme(scheme.Scheme,
+			defaultCluster,
+			machine,
+			node,
+			external.TestGenericBootstrapCRD.DeepCopy(),
+			external.TestGenericInfrastructureCRD.DeepCopy(),
+			bootstrapConfig,
+			infraConfig,
+			defaultKubeconfigSecret,
+		)
 		r := &MachineReconciler{
-			Client: fake.NewFakeClientWithScheme(scheme.Scheme,
-				defaultCluster,
-				defaultKubeconfigSecret,
-				machine,
-				external.TestGenericBootstrapCRD.DeepCopy(),
-				external.TestGenericInfrastructureCRD.DeepCopy(),
-				bootstrapConfig,
-				infraConfig,
-			),
-			Log:    log.Log,
-			scheme: scheme.Scheme,
+			Client:  cl,
+			Tracker: remote.NewTestClusterCacheTracker(cl, scheme.Scheme, client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
+			Log:     log.Log,
+			scheme:  scheme.Scheme,
 		}
 
 		res, err := r.reconcile(context.Background(), defaultCluster, machine)
@@ -376,19 +400,28 @@ var _ = Describe("Reconcile Machine Phases", func() {
 		// Set the LastUpdated to be able to verify it is updated when the phase changes
 		lastUpdated := metav1.NewTime(time.Now().Add(-10 * time.Second))
 		machine.Status.LastUpdated = &lastUpdated
-
+		node := &corev1.Node{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "machine-test-node",
+				Namespace: metav1.NamespaceDefault,
+			},
+			Spec: corev1.NodeSpec{ProviderID: "test://id-1"},
+		}
+		cl := fake.NewFakeClientWithScheme(scheme.Scheme,
+			defaultCluster,
+			machine,
+			node,
+			external.TestGenericBootstrapCRD.DeepCopy(),
+			external.TestGenericInfrastructureCRD.DeepCopy(),
+			bootstrapConfig,
+			infraConfig,
+			defaultKubeconfigSecret,
+		)
 		r := &MachineReconciler{
-			Client: fake.NewFakeClientWithScheme(scheme.Scheme,
-				defaultCluster,
-				defaultKubeconfigSecret,
-				machine,
-				external.TestGenericBootstrapCRD.DeepCopy(),
-				external.TestGenericInfrastructureCRD.DeepCopy(),
-				bootstrapConfig,
-				infraConfig,
-			),
-			Log:    log.Log,
-			scheme: scheme.Scheme,
+			Client:  cl,
+			Tracker: remote.NewTestClusterCacheTracker(cl, scheme.Scheme, client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
+			Log:     log.Log,
+			scheme:  scheme.Scheme,
 		}
 
 		res, err := r.reconcile(context.Background(), defaultCluster, machine)
@@ -450,6 +483,9 @@ var _ = Describe("Reconcile Machine Phases", func() {
 
 	It("Should set `Deleting` when Machine is being deleted", func() {
 		machine := defaultMachine.DeepCopy()
+		// Need the second Machine to allow deletion of one.
+		machineSecond := defaultMachine.DeepCopy()
+
 		bootstrapConfig := defaultBootstrap.DeepCopy()
 		infraConfig := defaultInfra.DeepCopy()
 
@@ -479,6 +515,11 @@ var _ = Describe("Reconcile Machine Phases", func() {
 		}, "addresses")
 		Expect(err).NotTo(HaveOccurred())
 
+		// Set Cluster label.
+		machine.Labels[clusterv1.ClusterLabelName] = machine.Spec.ClusterName
+		machine.ResourceVersion = "1"
+		machineSecond.Labels[clusterv1.ClusterLabelName] = machine.Spec.ClusterName
+		machineSecond.Name = "SecondMachine"
 		// Set NodeRef.
 		machine.Status.NodeRef = &corev1.ObjectReference{Kind: "Node", Name: "machine-test-node"}
 
@@ -489,27 +530,35 @@ var _ = Describe("Reconcile Machine Phases", func() {
 		lastUpdated := metav1.NewTime(time.Now().Add(-10 * time.Second))
 		machine.Status.LastUpdated = &lastUpdated
 
+		cl := fake.NewFakeClientWithScheme(scheme.Scheme,
+			defaultCluster,
+			defaultKubeconfigSecret,
+			machine,
+			machineSecond,
+			external.TestGenericBootstrapCRD.DeepCopy(),
+			external.TestGenericInfrastructureCRD.DeepCopy(),
+			bootstrapConfig,
+			infraConfig,
+		)
 		r := &MachineReconciler{
-			Client: fake.NewFakeClientWithScheme(scheme.Scheme,
-				defaultCluster,
-				defaultKubeconfigSecret,
-				machine,
-				external.TestGenericBootstrapCRD.DeepCopy(),
-				external.TestGenericInfrastructureCRD.DeepCopy(),
-				bootstrapConfig,
-				infraConfig,
-			),
-			Log:    log.Log,
-			scheme: scheme.Scheme,
+			Client:   cl,
+			Tracker:  remote.NewTestClusterCacheTracker(cl, scheme.Scheme, client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
+			Log:      log.Log,
+			scheme:   scheme.Scheme,
+			recorder: record.NewFakeRecorder(32),
 		}
 
-		res, err := r.reconcile(context.Background(), defaultCluster, machine)
+		res, err := r.reconcileDelete(context.Background(), defaultCluster, machine)
 		Expect(err).NotTo(HaveOccurred())
 		Expect(res.Requeue).To(BeFalse())
 
 		r.reconcilePhase(context.Background(), machine)
 		Expect(machine.Status.GetTypedPhase()).To(Equal(clusterv1.MachinePhaseDeleting))
 
+		nodeHealthyCondition := conditions.Get(machine, clusterv1.MachineNodeHealthyCondition)
+		Expect(nodeHealthyCondition.Status).To(Equal(corev1.ConditionFalse))
+		Expect(nodeHealthyCondition.Reason).To(Equal(clusterv1.DeletingReason))
+
 		// Verify that the LastUpdated timestamp was updated
 		Expect(machine.Status.LastUpdated).ToNot(BeNil())
 		Expect(machine.Status.LastUpdated.After(lastUpdated.Time)).To(BeTrue())
diff --git a/controllers/machine_controller_test.go b/controllers/machine_controller_test.go
index 2d5217020..1c579a2e8 100644
--- a/controllers/machine_controller_test.go
+++ b/controllers/machine_controller_test.go
@@ -18,12 +18,14 @@ package controllers
 
 import (
 	"context"
+	"sigs.k8s.io/cluster-api/controllers/remote"
 	"testing"
 	"time"
 
 	. "github.com/onsi/gomega"
 
 	corev1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -214,6 +216,146 @@ func TestIndexMachineByNodeName(t *testing.T) {
 	}
 }
 
+func TestMachine_Reconcile(t *testing.T) {
+	t.Run("reconcile create", func(t *testing.T) {
+		g := NewWithT(t)
+		infraMachine := &unstructured.Unstructured{
+			Object: map[string]interface{}{
+				"kind":       "InfrastructureMachine",
+				"apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3",
+				"metadata": map[string]interface{}{
+					"name":      "infra-config1",
+					"namespace": "default",
+				},
+				"spec": map[string]interface{}{
+					"providerID": "test://id-1",
+				},
+			},
+		}
+
+		defaultBootstrap := &unstructured.Unstructured{
+			Object: map[string]interface{}{
+				"kind":       "BootstrapMachine",
+				"apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3",
+				"metadata": map[string]interface{}{
+					"name":      "bootstrap-config-machinereconcile",
+					"namespace": "default",
+				},
+				"spec":   map[string]interface{}{},
+				"status": map[string]interface{}{},
+			},
+		}
+
+		testCluster := &clusterv1.Cluster{
+			ObjectMeta: metav1.ObjectMeta{
+				GenerateName: "machine-reconcile-",
+				Namespace:    "default",
+			},
+		}
+
+		g.Expect(testEnv.Create(ctx, testCluster)).To(BeNil())
+		g.Expect(testEnv.Create(ctx, infraMachine)).To(BeNil())
+		g.Expect(testEnv.Create(ctx, defaultBootstrap)).To(BeNil())
+
+		defer func(do ...runtime.Object) {
+			g.Expect(testEnv.Cleanup(ctx, do...)).To(Succeed())
+		}(testCluster)
+
+		machine := &clusterv1.Machine{
+			ObjectMeta: metav1.ObjectMeta{
+				GenerateName: "machine-created-",
+				Namespace:    "default",
+				Finalizers:   []string{clusterv1.MachineFinalizer},
+			},
+			Spec: clusterv1.MachineSpec{
+				ClusterName: testCluster.Name,
+				InfrastructureRef: corev1.ObjectReference{
+					APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3",
+					Kind:       "InfrastructureMachine",
+					Name:       "infra-config1",
+				},
+				Bootstrap: clusterv1.Bootstrap{
+					ConfigRef: &corev1.ObjectReference{
+						APIVersion: "bootstrap.cluster.x-k8s.io/v1alpha3",
+						Kind:       "BootstrapMachine",
+						Name:       "bootstrap-config-machinereconcile",
+					},
+				}},
+			Status: clusterv1.MachineStatus{
+				NodeRef: &corev1.ObjectReference{
+					Name: "test",
+				},
+			},
+		}
+		g.Expect(testEnv.Create(ctx, machine)).To(BeNil())
+
+		key := client.ObjectKey{Name: machine.Name, Namespace: machine.Namespace}
+
+		// Wait for reconciliation to happen when infra and bootstrap objects are not ready.
+		g.Eventually(func() bool {
+			if err := testEnv.Get(ctx, key, machine); err != nil {
+				return false
+			}
+			return len(machine.Finalizers) > 0
+		}, timeout).Should(BeTrue())
+
+		// Set bootstrap ready.
+		bootstrapPatch := client.MergeFrom(defaultBootstrap.DeepCopy())
+		g.Expect(unstructured.SetNestedField(defaultBootstrap.Object, true, "status", "ready")).NotTo(HaveOccurred())
+		g.Expect(testEnv.Status().Patch(ctx, defaultBootstrap, bootstrapPatch)).To(Succeed())
+
+		// Set infrastructure ready.
+		infraMachinePatch := client.MergeFrom(infraMachine.DeepCopy())
+		g.Expect(unstructured.SetNestedField(infraMachine.Object, true, "status", "ready")).To(Succeed())
+		g.Expect(testEnv.Status().Patch(ctx, infraMachine, infraMachinePatch)).To(Succeed())
+
+		// Wait for Machine Ready Condition to become True.
+		g.Eventually(func() bool {
+			if err := testEnv.Get(ctx, key, machine); err != nil {
+				return false
+			}
+			if conditions.Has(machine, clusterv1.InfrastructureReadyCondition) != true {
+				return false
+			}
+			readyCondition := conditions.Get(machine, clusterv1.ReadyCondition)
+			return readyCondition.Status == corev1.ConditionTrue
+		}, timeout).Should(BeTrue())
+
+		g.Expect(testEnv.Delete(ctx, machine)).NotTo(HaveOccurred())
+		// Wait for Machine to be deleted.
+		g.Eventually(func() bool {
+			if err := testEnv.Get(ctx, key, machine); err != nil {
+				if apierrors.IsNotFound(err) {
+					return true
+				}
+			}
+			return false
+		}, timeout).Should(BeTrue())
+
+		// Check if Machine deletion successfully deleted infrastructure external reference.
+		keyInfra := client.ObjectKey{Name: infraMachine.GetName(), Namespace: infraMachine.GetNamespace()}
+		g.Eventually(func() bool {
+			if err := testEnv.Get(ctx, keyInfra, infraMachine); err != nil {
+				if apierrors.IsNotFound(err) {
+					return true
+				}
+			}
+			return false
+		}, timeout).Should(BeTrue())
+
+		// Check if Machine deletion successfully deleted bootstrap external reference.
+		keyBootstrap := client.ObjectKey{Name: defaultBootstrap.GetName(), Namespace: defaultBootstrap.GetNamespace()}
+		g.Eventually(func() bool {
+			if err := testEnv.Get(ctx, keyBootstrap, defaultBootstrap); err != nil {
+				if apierrors.IsNotFound(err) {
+					return true
+				}
+			}
+			return false
+		}, timeout).Should(BeTrue())
+	})
+}
+
 func TestMachineFinalizer(t *testing.T) {
 	bootstrapData := "some valid data"
 	clusterCorrectMeta := &clusterv1.Cluster{
@@ -507,6 +649,14 @@ func TestReconcileRequest(t *testing.T) {
 		},
 	}
 
+	node := &corev1.Node{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test",
+			Namespace: "default",
+		},
+		Spec: corev1.NodeSpec{ProviderID: "test://id-1"},
+	}
+
 	type expected struct {
 		result reconcile.Result
 		err    bool
@@ -605,6 +755,7 @@ func TestReconcileRequest(t *testing.T) {
 
 			clientFake := helpers.NewFakeClientWithScheme(
 				scheme.Scheme,
+				node,
 				&testCluster,
 				&tc.machine,
 				external.TestGenericInfrastructureCRD.DeepCopy(),
@@ -612,9 +763,10 @@ func TestReconcileRequest(t *testing.T) {
 			)
 
 			r := &MachineReconciler{
-				Client: clientFake,
-				Log:    log.Log,
-				scheme: scheme.Scheme,
+				Client:  clientFake,
+				Log:     log.Log,
+				scheme:  scheme.Scheme,
+				Tracker: remote.NewTestClusterCacheTracker(clientFake, scheme.Scheme, client.ObjectKey{Name: testCluster.Name, Namespace: testCluster.Namespace}),
 			}
 
 			result, err := r.Reconcile(reconcile.Request{NamespacedName: util.ObjectKey(&tc.machine)})
@@ -692,6 +844,7 @@ func TestMachineConditions(t *testing.T) {
 			Finalizers: []string{clusterv1.MachineFinalizer},
 		},
 		Spec: clusterv1.MachineSpec{
+			ProviderID:  pointer.StringPtr("test://id-1"),
 			ClusterName: "test-cluster",
 			InfrastructureRef: corev1.ObjectReference{
 				APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3",
@@ -714,6 +867,14 @@ func TestMachineConditions(t *testing.T) {
 		},
 	}
 
+	node := &corev1.Node{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test",
+			Namespace: metav1.NamespaceDefault,
+		},
+		Spec: corev1.NodeSpec{ProviderID: "test://id-1"},
+	}
+
 	testcases := []struct {
 		name               string
 		infraReady         bool
@@ -846,12 +1007,14 @@ func TestMachineConditions(t *testing.T) {
 				infra,
 				external.TestGenericBootstrapCRD.DeepCopy(),
 				bootstrap,
+				node,
 			)
 
 			r := &MachineReconciler{
-				Client: clientFake,
-				Log:    log.Log,
-				scheme: scheme.Scheme,
+				Client:  clientFake,
+				Log:     log.Log,
+				scheme:  scheme.Scheme,
+				Tracker: remote.NewTestClusterCacheTracker(clientFake, scheme.Scheme, client.ObjectKey{Name: testCluster.Name, Namespace: testCluster.Namespace}),
 			}
 
 			_, err := r.Reconcile(reconcile.Request{NamespacedName: util.ObjectKey(&machine)})
@@ -905,22 +1068,6 @@ func TestReconcileDeleteExternal(t *testing.T) {
 		expectError     bool
 		expected        *unstructured.Unstructured
 	}{
-		{
-			name:            "should continue to reconcile delete of external refs if 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,
-		},
 		{
 			name:            "should no longer reconcile deletion of external refs since it doesn't exist",
 			bootstrapExists: false,
diff --git a/controllers/machinehealthcheck_controller_test.go b/controllers/machinehealthcheck_controller_test.go
index 83bb0eb2e..baab2bddb 100644
--- a/controllers/machinehealthcheck_controller_test.go
+++ b/controllers/machinehealthcheck_controller_test.go
@@ -17,7 +17,6 @@ package controllers
 
 import (
 	"context"
-	"errors"
 	"fmt"
 	"testing"
 	"time"
@@ -208,18 +207,11 @@ func TestMachineHealthCheck_Reconcile(t *testing.T) {
 			}
 			return &mhc.Status
 		}).Should(MatchMachineHealthCheckStatus(&clusterv1.MachineHealthCheckStatus{
-			ExpectedMachines:    2,
-			CurrentHealthy:      2,
-			RemediationsAllowed: 2,
-			ObservedGeneration:  1,
-			Targets:             targetMachines,
-			Conditions: clusterv1.Conditions{
-				{
-					Type:   clusterv1.RemediationAllowedCondition,
-					Status: corev1.ConditionTrue,
-				},
-			},
-		}))
+			ExpectedMachines:   2,
+			CurrentHealthy:     2,
+			ObservedGeneration: 1,
+			Targets:            targetMachines},
+		))
 	})
 
 	t.Run("it marks unhealthy machines for remediation when there is one unhealthy Machine", func(t *testing.T) {
@@ -264,17 +256,10 @@ func TestMachineHealthCheck_Reconcile(t *testing.T) {
 			}
 			return &mhc.Status
 		}).Should(MatchMachineHealthCheckStatus(&clusterv1.MachineHealthCheckStatus{
-			ExpectedMachines:    3,
-			CurrentHealthy:      2,
-			RemediationsAllowed: 2,
-			ObservedGeneration:  1,
-			Targets:             targetMachines,
-			Conditions: clusterv1.Conditions{
-				{
-					Type:   clusterv1.RemediationAllowedCondition,
-					Status: corev1.ConditionTrue,
-				},
-			},
+			ExpectedMachines:   3,
+			CurrentHealthy:     2,
+			ObservedGeneration: 1,
+			Targets:            targetMachines,
 		}))
 	})
 
@@ -322,21 +307,11 @@ func TestMachineHealthCheck_Reconcile(t *testing.T) {
 			}
 			return &mhc.Status
 		}).Should(MatchMachineHealthCheckStatus(&clusterv1.MachineHealthCheckStatus{
-			ExpectedMachines:    3,
-			CurrentHealthy:      1,
-			RemediationsAllowed: 0,
-			ObservedGeneration:  1,
-			Targets:             targetMachines,
-			Conditions: clusterv1.Conditions{
-				{
-					Type:     clusterv1.RemediationAllowedCondition,
-					Status:   corev1.ConditionFalse,
-					Severity: clusterv1.ConditionSeverityWarning,
-					Reason:   clusterv1.TooManyUnhealthyReason,
-					Message:  "Remediation is not allowed, the number of not started or unhealthy machines exceeds maxUnhealthy (total: 3, unhealthy: 2, maxUnhealthy: 40%)",
-				},
-			},
-		}))
+			ExpectedMachines:   3,
+			CurrentHealthy:     1,
+			ObservedGeneration: 1,
+			Targets:            targetMachines},
+		))
 
 		// Calculate how many Machines have health check succeeded = false.
 		g.Eventually(func() (unhealthy int) {
@@ -418,18 +393,11 @@ func TestMachineHealthCheck_Reconcile(t *testing.T) {
 			}
 			return &mhc.Status
 		}).Should(MatchMachineHealthCheckStatus(&clusterv1.MachineHealthCheckStatus{
-			ExpectedMachines:    3,
-			CurrentHealthy:      2,
-			RemediationsAllowed: 2,
-			ObservedGeneration:  1,
-			Targets:             targetMachines,
-			Conditions: clusterv1.Conditions{
-				{
-					Type:   clusterv1.RemediationAllowedCondition,
-					Status: corev1.ConditionTrue,
-				},
-			},
-		}))
+			ExpectedMachines:   3,
+			CurrentHealthy:     2,
+			ObservedGeneration: 1,
+			Targets:            targetMachines},
+		))
 
 		// Calculate how many Machines have health check succeeded = false.
 		g.Eventually(func() (unhealthy int) {
@@ -516,18 +484,11 @@ func TestMachineHealthCheck_Reconcile(t *testing.T) {
 			}
 			return &mhc.Status
 		}, timeout, 100*time.Millisecond).Should(MatchMachineHealthCheckStatus(&clusterv1.MachineHealthCheckStatus{
-			ExpectedMachines:    3,
-			CurrentHealthy:      2,
-			RemediationsAllowed: 2,
-			ObservedGeneration:  1,
-			Targets:             targetMachines,
-			Conditions: clusterv1.Conditions{
-				{
-					Type:   clusterv1.RemediationAllowedCondition,
-					Status: corev1.ConditionTrue,
-				},
-			},
-		}))
+			ExpectedMachines:   3,
+			CurrentHealthy:     2,
+			ObservedGeneration: 1,
+			Targets:            targetMachines},
+		))
 
 		// Calculate how many Machines have health check succeeded = false.
 		g.Eventually(func() (unhealthy int) {
@@ -611,18 +572,11 @@ func TestMachineHealthCheck_Reconcile(t *testing.T) {
 			}
 			return &mhc.Status
 		}).Should(MatchMachineHealthCheckStatus(&clusterv1.MachineHealthCheckStatus{
-			ExpectedMachines:    3,
-			CurrentHealthy:      2,
-			RemediationsAllowed: 2,
-			ObservedGeneration:  1,
-			Targets:             targetMachines,
-			Conditions: clusterv1.Conditions{
-				{
-					Type:   clusterv1.RemediationAllowedCondition,
-					Status: corev1.ConditionTrue,
-				},
-			},
-		}))
+			ExpectedMachines:   3,
+			CurrentHealthy:     2,
+			ObservedGeneration: 1,
+			Targets:            targetMachines},
+		))
 
 		// Calculate how many Machines have health check succeeded = false.
 		g.Eventually(func() (unhealthy int) {
@@ -694,18 +648,11 @@ func TestMachineHealthCheck_Reconcile(t *testing.T) {
 			}
 			return &mhc.Status
 		}).Should(MatchMachineHealthCheckStatus(&clusterv1.MachineHealthCheckStatus{
-			ExpectedMachines:    1,
-			CurrentHealthy:      1,
-			RemediationsAllowed: 1,
-			ObservedGeneration:  1,
-			Targets:             targetMachines,
-			Conditions: clusterv1.Conditions{
-				{
-					Type:   clusterv1.RemediationAllowedCondition,
-					Status: corev1.ConditionTrue,
-				},
-			},
-		}))
+			ExpectedMachines:   1,
+			CurrentHealthy:     1,
+			ObservedGeneration: 1,
+			Targets:            targetMachines},
+		))
 
 		// Transition the node to unhealthy.
 		node := nodes[0]
@@ -730,14 +677,8 @@ func TestMachineHealthCheck_Reconcile(t *testing.T) {
 			ExpectedMachines:   1,
 			CurrentHealthy:     0,
 			ObservedGeneration: 1,
-			Targets:            targetMachines,
-			Conditions: clusterv1.Conditions{
-				{
-					Type:   clusterv1.RemediationAllowedCondition,
-					Status: corev1.ConditionTrue,
-				},
-			},
-		}))
+			Targets:            targetMachines},
+		))
 
 		// Calculate how many Machines have health check succeeded = false.
 		g.Eventually(func() (unhealthy int) {
@@ -954,14 +895,8 @@ func TestMachineHealthCheck_Reconcile(t *testing.T) {
 			ExpectedMachines:   1,
 			CurrentHealthy:     1,
 			ObservedGeneration: 1,
-			Targets:            targetMachines,
-			Conditions: clusterv1.Conditions{
-				{
-					Type:   clusterv1.RemediationAllowedCondition,
-					Status: corev1.ConditionTrue,
-				},
-			},
-		}))
+			Targets:            targetMachines},
+		))
 
 		// Pause the machine
 		machinePatch := client.MergeFrom(machines[0].DeepCopy())
@@ -990,18 +925,11 @@ func TestMachineHealthCheck_Reconcile(t *testing.T) {
 			}
 			return &mhc.Status
 		}).Should(MatchMachineHealthCheckStatus(&clusterv1.MachineHealthCheckStatus{
-			ExpectedMachines:    1,
-			CurrentHealthy:      0,
-			RemediationsAllowed: 0,
-			ObservedGeneration:  1,
-			Targets:             targetMachines,
-			Conditions: clusterv1.Conditions{
-				{
-					Type:   clusterv1.RemediationAllowedCondition,
-					Status: corev1.ConditionTrue,
-				},
-			},
-		}))
+			ExpectedMachines:   1,
+			CurrentHealthy:     0,
+			ObservedGeneration: 1,
+			Targets:            targetMachines},
+		))
 
 		// Calculate how many Machines have health check succeeded = false.
 		g.Eventually(func() (unhealthy int) {
@@ -1039,347 +967,6 @@ func TestMachineHealthCheck_Reconcile(t *testing.T) {
 			return
 		}).Should(Equal(0))
 	})
-
-	t.Run("When remediationTemplate is set and node transitions to unhealthy, new Remediation Request should be created", func(t *testing.T) {
-		g := NewWithT(t)
-		cluster := createNamespaceAndCluster(g)
-
-		// Create remediation template resource.
-		infraRemediationResource := map[string]interface{}{
-			"kind":       "InfrastructureRemediation",
-			"apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3",
-			"metadata":   map[string]interface{}{},
-			"spec": map[string]interface{}{
-				"size": "3xlarge",
-			},
-		}
-		infraRemediationTmpl := &unstructured.Unstructured{
-			Object: map[string]interface{}{
-				"spec": map[string]interface{}{
-					"template": infraRemediationResource,
-				},
-			},
-		}
-		infraRemediationTmpl.SetKind("InfrastructureRemediationTemplate")
-		infraRemediationTmpl.SetAPIVersion("infrastructure.cluster.x-k8s.io/v1alpha3")
-		infraRemediationTmpl.SetGenerateName("remediation-template-name-")
-		infraRemediationTmpl.SetNamespace(cluster.Namespace)
-		g.Expect(testEnv.Create(ctx, infraRemediationTmpl)).To(Succeed())
-
-		remediationTemplate := &corev1.ObjectReference{
-			APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3",
-			Kind:       "InfrastructureRemediationTemplate",
-			Name:       infraRemediationTmpl.GetName(),
-		}
-
-		mhc := newMachineHealthCheck(cluster.Namespace, cluster.Name)
-		mhc.Spec.RemediationTemplate = remediationTemplate
-		g.Expect(testEnv.Create(ctx, mhc)).To(Succeed())
-		defer func(do ...runtime.Object) {
-			g.Expect(testEnv.Cleanup(ctx, do...)).To(Succeed())
-		}(cluster, mhc, infraRemediationTmpl)
-
-		// Healthy nodes and machines.
-		nodes, machines, cleanup := createMachinesWithNodes(g, cluster,
-			count(1),
-			createNodeRefForMachine(true),
-			markNodeAsHealthy(true),
-			machineLabels(mhc.Spec.Selector.MatchLabels),
-		)
-		defer cleanup()
-		targetMachines := make([]string, len(machines))
-		for i, m := range machines {
-			targetMachines[i] = m.Name
-		}
-
-		// Make sure the status matches.
-		g.Eventually(func() *clusterv1.MachineHealthCheckStatus {
-			err := testEnv.Get(ctx, util.ObjectKey(mhc), mhc)
-			if err != nil {
-				return nil
-			}
-			return &mhc.Status
-		}).Should(MatchMachineHealthCheckStatus(&clusterv1.MachineHealthCheckStatus{
-			ExpectedMachines:    1,
-			CurrentHealthy:      1,
-			RemediationsAllowed: 1,
-			ObservedGeneration:  1,
-			Targets:             targetMachines,
-			Conditions: clusterv1.Conditions{
-				{
-					Type:   clusterv1.RemediationAllowedCondition,
-					Status: corev1.ConditionTrue,
-				},
-			},
-		}))
-
-		// Transition the node to unhealthy.
-		node := nodes[0]
-		nodePatch := client.MergeFrom(node.DeepCopy())
-		node.Status.Conditions = []corev1.NodeCondition{
-			{
-				Type:               corev1.NodeReady,
-				Status:             corev1.ConditionUnknown,
-				LastTransitionTime: metav1.NewTime(time.Now().Add(-10 * time.Minute)),
-			},
-		}
-		g.Expect(testEnv.Status().Patch(ctx, node, nodePatch)).To(Succeed())
-
-		// Make sure the status matches.
-		g.Eventually(func() *clusterv1.MachineHealthCheckStatus {
-			err := testEnv.Get(ctx, util.ObjectKey(mhc), mhc)
-			if err != nil {
-				return nil
-			}
-			return &mhc.Status
-		}).Should(MatchMachineHealthCheckStatus(&clusterv1.MachineHealthCheckStatus{
-			ExpectedMachines:    1,
-			CurrentHealthy:      0,
-			RemediationsAllowed: 0,
-			ObservedGeneration:  1,
-			Targets:             targetMachines,
-			Conditions: clusterv1.Conditions{
-				{
-					Type:   clusterv1.RemediationAllowedCondition,
-					Status: corev1.ConditionTrue,
-				},
-			},
-		}))
-
-		// Calculate how many Machines have health check succeeded = false.
-		g.Eventually(func() (unhealthy int) {
-			machines := &clusterv1.MachineList{}
-			err := testEnv.List(ctx, machines, client.MatchingLabels{
-				"selector": mhc.Spec.Selector.MatchLabels["selector"],
-			})
-			if err != nil {
-				return -1
-			}
-
-			for i := range machines.Items {
-				if conditions.IsFalse(&machines.Items[i], clusterv1.MachineHealthCheckSuccededCondition) {
-					unhealthy++
-				}
-			}
-			return
-		}).Should(Equal(1))
-
-		ref := corev1.ObjectReference{
-			APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3",
-			Kind:       "InfrastructureRemediation",
-		}
-
-		obj := util.ObjectReferenceToUnstructured(ref)
-		// Make sure the Remeditaion Request is created.
-		g.Eventually(func() *unstructured.Unstructured {
-			key := client.ObjectKey{
-				Namespace: machines[0].Namespace,
-				Name:      machines[0].Name,
-			}
-			err := testEnv.Get(ctx, key, obj)
-			if err != nil {
-				return nil
-			}
-			return obj
-		}, timeout, 100*time.Millisecond).ShouldNot(BeNil())
-		g.Expect(obj.GetOwnerReferences()).To(HaveLen(1))
-		g.Expect(obj.GetOwnerReferences()[0].Name).To(Equal(machines[0].Name))
-	})
-
-	t.Run("When remediationTemplate is set and node transitions back to healthy, new Remediation Request should be deleted", func(t *testing.T) {
-		g := NewWithT(t)
-		cluster := createNamespaceAndCluster(g)
-
-		// Create remediation template resource.
-		infraRemediationResource := map[string]interface{}{
-			"kind":       "InfrastructureRemediation",
-			"apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3",
-			"metadata":   map[string]interface{}{},
-			"spec": map[string]interface{}{
-				"size": "3xlarge",
-			},
-		}
-		infraRemediationTmpl := &unstructured.Unstructured{
-			Object: map[string]interface{}{
-				"spec": map[string]interface{}{
-					"template": infraRemediationResource,
-				},
-			},
-		}
-		infraRemediationTmpl.SetKind("InfrastructureRemediationTemplate")
-		infraRemediationTmpl.SetAPIVersion("infrastructure.cluster.x-k8s.io/v1alpha3")
-		infraRemediationTmpl.SetGenerateName("remediation-template-name-")
-		infraRemediationTmpl.SetNamespace(cluster.Namespace)
-		g.Expect(testEnv.Create(ctx, infraRemediationTmpl)).To(Succeed())
-
-		remediationTemplate := &corev1.ObjectReference{
-			APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3",
-			Kind:       "InfrastructureRemediationTemplate",
-			Name:       infraRemediationTmpl.GetName(),
-		}
-
-		mhc := newMachineHealthCheck(cluster.Namespace, cluster.Name)
-		mhc.Spec.RemediationTemplate = remediationTemplate
-		g.Expect(testEnv.Create(ctx, mhc)).To(Succeed())
-		defer func(do ...runtime.Object) {
-			g.Expect(testEnv.Cleanup(ctx, do...)).To(Succeed())
-		}(cluster, mhc, infraRemediationTmpl)
-
-		// Healthy nodes and machines.
-		nodes, machines, cleanup := createMachinesWithNodes(g, cluster,
-			count(1),
-			createNodeRefForMachine(true),
-			markNodeAsHealthy(true),
-			machineLabels(mhc.Spec.Selector.MatchLabels),
-		)
-		defer cleanup()
-		targetMachines := make([]string, len(machines))
-		for i, m := range machines {
-			targetMachines[i] = m.Name
-		}
-
-		// Make sure the status matches.
-		g.Eventually(func() *clusterv1.MachineHealthCheckStatus {
-			err := testEnv.Get(ctx, util.ObjectKey(mhc), mhc)
-			if err != nil {
-				return nil
-			}
-			return &mhc.Status
-		}).Should(MatchMachineHealthCheckStatus(&clusterv1.MachineHealthCheckStatus{
-			ExpectedMachines:    1,
-			CurrentHealthy:      1,
-			RemediationsAllowed: 1,
-			ObservedGeneration:  1,
-			Targets:             targetMachines,
-			Conditions: clusterv1.Conditions{
-				{
-					Type:   clusterv1.RemediationAllowedCondition,
-					Status: corev1.ConditionTrue,
-				},
-			},
-		}))
-
-		// Transition the node to unhealthy.
-		node := nodes[0]
-		nodePatch := client.MergeFrom(node.DeepCopy())
-		node.Status.Conditions = []corev1.NodeCondition{
-			{
-				Type:               corev1.NodeReady,
-				Status:             corev1.ConditionUnknown,
-				LastTransitionTime: metav1.NewTime(time.Now().Add(-10 * time.Minute)),
-			},
-		}
-		g.Expect(testEnv.Status().Patch(ctx, node, nodePatch)).To(Succeed())
-
-		// Make sure the status matches.
-		g.Eventually(func() *clusterv1.MachineHealthCheckStatus {
-			err := testEnv.Get(ctx, util.ObjectKey(mhc), mhc)
-			if err != nil {
-				return nil
-			}
-			return &mhc.Status
-		}).Should(MatchMachineHealthCheckStatus(&clusterv1.MachineHealthCheckStatus{
-			ExpectedMachines:    1,
-			CurrentHealthy:      0,
-			RemediationsAllowed: 0,
-			ObservedGeneration:  1,
-			Targets:             targetMachines,
-			Conditions: clusterv1.Conditions{
-				{
-					Type:   clusterv1.RemediationAllowedCondition,
-					Status: corev1.ConditionTrue,
-				},
-			},
-		}))
-
-		// Calculate how many Machines have health check succeeded = false.
-		g.Eventually(func() (unhealthy int) {
-			machines := &clusterv1.MachineList{}
-			err := testEnv.List(ctx, machines, client.MatchingLabels{
-				"selector": mhc.Spec.Selector.MatchLabels["selector"],
-			})
-			if err != nil {
-				return -1
-			}
-
-			for i := range machines.Items {
-				if conditions.IsFalse(&machines.Items[i], clusterv1.MachineHealthCheckSuccededCondition) {
-					unhealthy++
-				}
-			}
-			return
-		}).Should(Equal(1))
-
-		// Transition the node back to healthy.
-		node = nodes[0]
-		nodePatch = client.MergeFrom(node.DeepCopy())
-		node.Status.Conditions = []corev1.NodeCondition{
-			{
-				Type:               corev1.NodeReady,
-				Status:             corev1.ConditionTrue,
-				LastTransitionTime: metav1.NewTime(time.Now().Add(-10 * time.Minute)),
-			},
-		}
-		g.Expect(testEnv.Status().Patch(ctx, node, nodePatch)).To(Succeed())
-
-		// Make sure the status matches.
-		g.Eventually(func() *clusterv1.MachineHealthCheckStatus {
-			err := testEnv.Get(ctx, util.ObjectKey(mhc), mhc)
-			if err != nil {
-				return nil
-			}
-			return &mhc.Status
-		}).Should(MatchMachineHealthCheckStatus(&clusterv1.MachineHealthCheckStatus{
-			ExpectedMachines:    1,
-			CurrentHealthy:      1,
-			RemediationsAllowed: 1,
-			ObservedGeneration:  1,
-			Targets:             targetMachines,
-			Conditions: clusterv1.Conditions{
-				{
-					Type:   clusterv1.RemediationAllowedCondition,
-					Status: corev1.ConditionTrue,
-				},
-			},
-		}))
-
-		// Calculate how many Machines have health check succeeded = false.
-		g.Eventually(func() (unhealthy int) {
-			machines := &clusterv1.MachineList{}
-			err := testEnv.List(ctx, machines, client.MatchingLabels{
-				"selector": mhc.Spec.Selector.MatchLabels["selector"],
-			})
-			if err != nil {
-				return -1
-			}
-
-			for i := range machines.Items {
-				if conditions.IsFalse(&machines.Items[i], clusterv1.MachineHealthCheckSuccededCondition) {
-					unhealthy++
-				}
-			}
-			return
-		}).Should(Equal(0))
-
-		ref := corev1.ObjectReference{
-			APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3",
-			Kind:       "InfrastructureRemediation",
-		}
-
-		obj := util.ObjectReferenceToUnstructured(ref)
-		// Make sure the Remediation Request is deleted.
-		g.Eventually(func() *unstructured.Unstructured {
-			key := client.ObjectKey{
-				Namespace: machines[0].Namespace,
-				Name:      machines[0].Name,
-			}
-			err := testEnv.Get(ctx, key, obj)
-			if err != nil {
-				return nil
-			}
-			return obj
-		}, timeout, 100*time.Millisecond).Should(BeNil())
-	})
 }
 
 func TestClusterToMachineHealthCheck(t *testing.T) {
@@ -1811,75 +1398,6 @@ func TestIsAllowedRemediation(t *testing.T) {
 	}
 }
 
-func TestGetMaxUnhealthy(t *testing.T) {
-	testCases := []struct {
-		name                 string
-		maxUnhealthy         *intstr.IntOrString
-		expectedMaxUnhealthy int
-		actualMachineCount   int32
-		expectedErr          error
-	}{
-		{
-			name:                 "when maxUnhealthy is nil",
-			maxUnhealthy:         nil,
-			expectedMaxUnhealthy: 0,
-			actualMachineCount:   7,
-			expectedErr:          errors.New("spec.maxUnhealthy must be set"),
-		},
-		{
-			name:                 "when maxUnhealthy is not an int or percentage",
-			maxUnhealthy:         &intstr.IntOrString{Type: intstr.String, StrVal: "abcdef"},
-			expectedMaxUnhealthy: 0,
-			actualMachineCount:   3,
-			expectedErr:          errors.New("invalid value for IntOrString: invalid value \"abcdef\": strconv.Atoi: parsing \"abcdef\": invalid syntax"),
-		},
-		{
-			name:                 "when maxUnhealthy is an int",
-			maxUnhealthy:         &intstr.IntOrString{Type: intstr.Int, IntVal: 3},
-			actualMachineCount:   2,
-			expectedMaxUnhealthy: 3,
-			expectedErr:          nil,
-		},
-		{
-			name:                 "when maxUnhealthy is a 40% (of 5)",
-			maxUnhealthy:         &intstr.IntOrString{Type: intstr.String, StrVal: "40%"},
-			actualMachineCount:   5,
-			expectedMaxUnhealthy: 2,
-			expectedErr:          nil,
-		},
-		{
-			name:                 "when maxUnhealthy is a 60% (of 7)",
-			maxUnhealthy:         &intstr.IntOrString{Type: intstr.String, StrVal: "60%"},
-			actualMachineCount:   7,
-			expectedMaxUnhealthy: 4,
-			expectedErr:          nil,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			g := NewWithT(t)
-
-			mhc := &clusterv1.MachineHealthCheck{
-				Spec: clusterv1.MachineHealthCheckSpec{
-					MaxUnhealthy: tc.maxUnhealthy,
-				},
-				Status: clusterv1.MachineHealthCheckStatus{
-					ExpectedMachines: tc.actualMachineCount,
-				},
-			}
-
-			maxUnhealthy, err := getMaxUnhealthy(mhc)
-			if tc.expectedErr != nil {
-				g.Expect(err).To(MatchError(tc.expectedErr.Error()))
-			} else {
-				g.Expect(err).ToNot(HaveOccurred())
-			}
-			g.Expect(maxUnhealthy).To(Equal(tc.expectedMaxUnhealthy))
-		})
-	}
-}
-
 func ownerReferenceForCluster(ctx context.Context, g *WithT, c *clusterv1.Cluster) metav1.OwnerReference {
 	// Fetch the cluster to populate the UID
 	cc := &clusterv1.Cluster{}
@@ -2136,7 +1654,6 @@ func newMachineHealthCheckWithLabels(name, namespace, cluster string, labels map
 }
 
 func newMachineHealthCheck(namespace, clusterName string) *clusterv1.MachineHealthCheck {
-	maxUnhealthy := intstr.FromString("100%")
 	return &clusterv1.MachineHealthCheck{
 		ObjectMeta: metav1.ObjectMeta{
 			GenerateName: "test-mhc-",
@@ -2149,7 +1666,6 @@ func newMachineHealthCheck(namespace, clusterName string) *clusterv1.MachineHeal
 					"selector": string(uuid.NewUUID()),
 				},
 			},
-			MaxUnhealthy:       &maxUnhealthy,
 			NodeStartupTimeout: &metav1.Duration{Duration: 1 * time.Millisecond},
 			UnhealthyConditions: []clusterv1.UnhealthyCondition{
 				{
diff --git a/controlplane/kubeadm/controllers/controller_test.go b/controlplane/kubeadm/controllers/controller_test.go
index b6315bc66..01dd7b2bf 100644
--- a/controlplane/kubeadm/controllers/controller_test.go
+++ b/controlplane/kubeadm/controllers/controller_test.go
@@ -396,11 +396,9 @@ func TestKubeadmControlPlaneReconciler_adoption(t *testing.T) {
 		kcp.Spec.Version = version
 
 		fmc := &fakeManagementCluster{
-			Machines: internal.FilterableMachineCollection{},
-			Workload: fakeWorkloadCluster{
-				ControlPlaneHealthy: true,
-				EtcdHealthy:         true,
-			},
+			Machines:            internal.FilterableMachineCollection{},
+			ControlPlaneHealthy: true,
+			EtcdHealthy:         true,
 		}
 		objs := []runtime.Object{cluster.DeepCopy(), kcp.DeepCopy(), tmpl.DeepCopy()}
 		for i := 0; i < 3; i++ {
@@ -467,11 +465,9 @@ func TestKubeadmControlPlaneReconciler_adoption(t *testing.T) {
 		kcp.Spec.Version = version
 
 		fmc := &fakeManagementCluster{
-			Machines: internal.FilterableMachineCollection{},
-			Workload: fakeWorkloadCluster{
-				ControlPlaneHealthy: true,
-				EtcdHealthy:         true,
-			},
+			Machines:            internal.FilterableMachineCollection{},
+			ControlPlaneHealthy: true,
+			EtcdHealthy:         true,
 		}
 		objs := []runtime.Object{cluster.DeepCopy(), kcp.DeepCopy(), tmpl.DeepCopy()}
 		for i := 0; i < 3; i++ {
@@ -576,7 +572,7 @@ func TestKubeadmControlPlaneReconciler_adoption(t *testing.T) {
 		g := NewWithT(t)
 
 		cluster, kcp, tmpl := createClusterWithControlPlane()
-		cluster.Spec.ControlPlaneEndpoint.Host = "nodomain.example.com1"
+		cluster.Spec.ControlPlaneEndpoint.Host = "nodomain.example.com"
 		cluster.Spec.ControlPlaneEndpoint.Port = 6443
 		kcp.Spec.Version = version
 
@@ -584,11 +580,9 @@ func TestKubeadmControlPlaneReconciler_adoption(t *testing.T) {
 		kcp.DeletionTimestamp = &now
 
 		fmc := &fakeManagementCluster{
-			Machines: internal.FilterableMachineCollection{},
-			Workload: fakeWorkloadCluster{
-				ControlPlaneHealthy: true,
-				EtcdHealthy:         true,
-			},
+			Machines:            internal.FilterableMachineCollection{},
+			ControlPlaneHealthy: true,
+			EtcdHealthy:         true,
 		}
 		objs := []runtime.Object{cluster.DeepCopy(), kcp.DeepCopy(), tmpl.DeepCopy()}
 		for i := 0; i < 3; i++ {
@@ -648,7 +642,7 @@ func TestKubeadmControlPlaneReconciler_adoption(t *testing.T) {
 		g := NewWithT(t)
 
 		cluster, kcp, tmpl := createClusterWithControlPlane()
-		cluster.Spec.ControlPlaneEndpoint.Host = "nodomain.example.com2"
+		cluster.Spec.ControlPlaneEndpoint.Host = "nodomain.example.com"
 		cluster.Spec.ControlPlaneEndpoint.Port = 6443
 		kcp.Spec.Version = "v1.17.0"
 
@@ -671,10 +665,8 @@ func TestKubeadmControlPlaneReconciler_adoption(t *testing.T) {
 					},
 				},
 			},
-			Workload: fakeWorkloadCluster{
-				ControlPlaneHealthy: true,
-				EtcdHealthy:         true,
-			},
+			ControlPlaneHealthy: true,
+			EtcdHealthy:         true,
 		}
 
 		fakeClient := newFakeClient(g, cluster.DeepCopy(), kcp.DeepCopy(), tmpl.DeepCopy(), fmc.Machines["test0"].DeepCopy())
@@ -1186,11 +1178,10 @@ func TestKubeadmControlPlaneReconciler_reconcileDelete(t *testing.T) {
 		r := &KubeadmControlPlaneReconciler{
 			Client: fakeClient,
 			managementCluster: &fakeManagementCluster{
-				Management: &internal.Management{Client: fakeClient},
-				Workload: fakeWorkloadCluster{
-					ControlPlaneHealthy: true,
-					EtcdHealthy:         true,
-				},
+				ControlPlaneHealthy: true,
+				EtcdHealthy:         true,
+				Management:          &internal.Management{Client: fakeClient},
+				Workload:            fakeWorkloadCluster{},
 			},
 			Log:      log.Log,
 			recorder: record.NewFakeRecorder(32),
@@ -1239,11 +1230,10 @@ func TestKubeadmControlPlaneReconciler_reconcileDelete(t *testing.T) {
 		r := &KubeadmControlPlaneReconciler{
 			Client: fakeClient,
 			managementCluster: &fakeManagementCluster{
-				Management: &internal.Management{Client: fakeClient},
-				Workload: fakeWorkloadCluster{
-					ControlPlaneHealthy: true,
-					EtcdHealthy:         true,
-				},
+				ControlPlaneHealthy: true,
+				EtcdHealthy:         true,
+				Management:          &internal.Management{Client: fakeClient},
+				Workload:            fakeWorkloadCluster{},
 			},
 			Log:      log.Log,
 			recorder: record.NewFakeRecorder(32),
@@ -1274,11 +1264,10 @@ func TestKubeadmControlPlaneReconciler_reconcileDelete(t *testing.T) {
 		r := &KubeadmControlPlaneReconciler{
 			Client: fakeClient,
 			managementCluster: &fakeManagementCluster{
-				Management: &internal.Management{Client: fakeClient},
-				Workload: fakeWorkloadCluster{
-					ControlPlaneHealthy: true,
-					EtcdHealthy:         true,
-				},
+				ControlPlaneHealthy: true,
+				EtcdHealthy:         true,
+				Management:          &internal.Management{Client: fakeClient},
+				Workload:            fakeWorkloadCluster{},
 			},
 			recorder: record.NewFakeRecorder(32),
 			Log:      log.Log,
diff --git a/controlplane/kubeadm/controllers/fakes_test.go b/controlplane/kubeadm/controllers/fakes_test.go
index 48e8b921f..46af76804 100644
--- a/controlplane/kubeadm/controllers/fakes_test.go
+++ b/controlplane/kubeadm/controllers/fakes_test.go
@@ -30,10 +30,12 @@ import (
 
 type fakeManagementCluster struct {
 	// TODO: once all client interactions are moved to the Management cluster this can go away
-	Management *internal.Management
-	Machines   internal.FilterableMachineCollection
-	Workload   fakeWorkloadCluster
-	Reader     client.Reader
+	Management          *internal.Management
+	ControlPlaneHealthy bool
+	EtcdHealthy         bool
+	Machines            internal.FilterableMachineCollection
+	Workload            fakeWorkloadCluster
+	Reader              client.Reader
 }
 
 func (f *fakeManagementCluster) Get(ctx context.Context, key client.ObjectKey, obj runtime.Object) error {
@@ -55,11 +57,23 @@ func (f *fakeManagementCluster) GetMachinesForCluster(c context.Context, n clien
 	return f.Machines, nil
 }
 
+func (f *fakeManagementCluster) TargetClusterControlPlaneIsHealthy(_ context.Context, _ client.ObjectKey) error {
+	if !f.ControlPlaneHealthy {
+		return errors.New("control plane is not healthy")
+	}
+	return nil
+}
+
+func (f *fakeManagementCluster) TargetClusterEtcdIsHealthy(_ context.Context, _ client.ObjectKey) error {
+	if !f.EtcdHealthy {
+		return errors.New("etcd is not healthy")
+	}
+	return nil
+}
+
 type fakeWorkloadCluster struct {
 	*internal.Workload
-	Status              internal.ClusterStatus
-	ControlPlaneHealthy bool
-	EtcdHealthy         bool
+	Status internal.ClusterStatus
 }
 
 func (f fakeWorkloadCluster) ForwardEtcdLeadership(_ context.Context, _ *clusterv1.Machine, _ *clusterv1.Machine) error {
@@ -98,20 +112,6 @@ func (f fakeWorkloadCluster) UpdateKubeletConfigMap(ctx context.Context, version
 	return nil
 }
 
-func (f fakeWorkloadCluster) ControlPlaneIsHealthy(ctx context.Context) (internal.HealthCheckResult, error) {
-	if !f.ControlPlaneHealthy {
-		return nil, errors.New("control plane is not healthy")
-	}
-	return nil, nil
-}
-
-func (f fakeWorkloadCluster) EtcdIsHealthy(ctx context.Context) (internal.HealthCheckResult, error) {
-	if !f.EtcdHealthy {
-		return nil, errors.New("etcd is not healthy")
-	}
-	return nil, nil
-}
-
 type fakeMigrator struct {
 	migrateCalled    bool
 	migrateErr       error
diff --git a/controlplane/kubeadm/controllers/scale_test.go b/controlplane/kubeadm/controllers/scale_test.go
index 3d372d4ad..ceed678ae 100644
--- a/controlplane/kubeadm/controllers/scale_test.go
+++ b/controlplane/kubeadm/controllers/scale_test.go
@@ -88,11 +88,9 @@ func TestKubeadmControlPlaneReconciler_scaleUpControlPlane(t *testing.T) {
 		initObjs := []runtime.Object{cluster.DeepCopy(), kcp.DeepCopy(), genericMachineTemplate.DeepCopy()}
 
 		fmc := &fakeManagementCluster{
-			Machines: internal.NewFilterableMachineCollection(),
-			Workload: fakeWorkloadCluster{
-				ControlPlaneHealthy: true,
-				EtcdHealthy:         true,
-			},
+			Machines:            internal.NewFilterableMachineCollection(),
+			ControlPlaneHealthy: true,
+			EtcdHealthy:         true,
 		}
 
 		for i := 0; i < 2; i++ {
@@ -126,8 +124,6 @@ func TestKubeadmControlPlaneReconciler_scaleUpControlPlane(t *testing.T) {
 	})
 	t.Run("does not create a control plane Machine if health checks fail", func(t *testing.T) {
 		cluster, kcp, genericMachineTemplate := createClusterWithControlPlane()
-		cluster.Spec.ControlPlaneEndpoint.Host = "nodomain.example.com"
-		cluster.Spec.ControlPlaneEndpoint.Port = 6443
 		initObjs := []runtime.Object{cluster.DeepCopy(), kcp.DeepCopy(), genericMachineTemplate.DeepCopy()}
 
 		beforeMachines := internal.NewFilterableMachineCollection()
@@ -162,11 +158,9 @@ func TestKubeadmControlPlaneReconciler_scaleUpControlPlane(t *testing.T) {
 
 			fakeClient := newFakeClient(g, initObjs...)
 			fmc := &fakeManagementCluster{
-				Machines: beforeMachines.DeepCopy(),
-				Workload: fakeWorkloadCluster{
-					ControlPlaneHealthy: !tc.controlPlaneUnHealthy,
-					EtcdHealthy:         !tc.etcdUnHealthy,
-				},
+				Machines:            beforeMachines.DeepCopy(),
+				ControlPlaneHealthy: !tc.controlPlaneUnHealthy,
+				EtcdHealthy:         !tc.etcdUnHealthy,
 			}
 
 			r := &KubeadmControlPlaneReconciler{
@@ -176,11 +170,18 @@ func TestKubeadmControlPlaneReconciler_scaleUpControlPlane(t *testing.T) {
 				Log:                       log.Log,
 				recorder:                  record.NewFakeRecorder(32),
 			}
+			controlPlane := &internal.ControlPlane{
+				KCP:      kcp,
+				Cluster:  cluster,
+				Machines: beforeMachines,
+			}
 
-			_, err := r.reconcile(context.Background(), cluster, kcp)
-			g.Expect(err).To(HaveOccurred())
+			result, err := r.scaleUpControlPlane(context.Background(), cluster.DeepCopy(), kcp.DeepCopy(), controlPlane)
+			if tc.expectErr {
+				g.Expect(err).To(HaveOccurred())
+			}
+			g.Expect(result).To(Equal(tc.expectResult))
 
-			// scaleUpControlPlane is never called due to health check failure and new machine is not created to scale up.
 			controlPlaneMachines := &clusterv1.MachineList{}
 			g.Expect(fakeClient.List(context.Background(), controlPlaneMachines)).To(Succeed())
 			g.Expect(controlPlaneMachines.Items).To(HaveLen(len(beforeMachines)))
@@ -208,10 +209,8 @@ func TestKubeadmControlPlaneReconciler_scaleDownControlPlane_NoError(t *testing.
 		recorder: record.NewFakeRecorder(32),
 		Client:   newFakeClient(g, machines["one"]),
 		managementCluster: &fakeManagementCluster{
-			Workload: fakeWorkloadCluster{
-				ControlPlaneHealthy: true,
-				EtcdHealthy:         true,
-			},
+			EtcdHealthy:         true,
+			ControlPlaneHealthy: true,
 		},
 	}
 	cluster := &clusterv1.Cluster{}
diff --git a/controlplane/kubeadm/controllers/upgrade_test.go b/controlplane/kubeadm/controllers/upgrade_test.go
index 4a3df31ae..53596c317 100644
--- a/controlplane/kubeadm/controllers/upgrade_test.go
+++ b/controlplane/kubeadm/controllers/upgrade_test.go
@@ -36,8 +36,6 @@ func TestKubeadmControlPlaneReconciler_upgradeControlPlane(t *testing.T) {
 	g := NewWithT(t)
 
 	cluster, kcp, genericMachineTemplate := createClusterWithControlPlane()
-	cluster.Spec.ControlPlaneEndpoint.Host = "nodomain.example.com"
-	cluster.Spec.ControlPlaneEndpoint.Port = 6443
 	kcp.Spec.Version = "v1.17.3"
 	kcp.Spec.KubeadmConfigSpec.ClusterConfiguration = nil
 	kcp.Spec.Replicas = pointer.Int32Ptr(1)
@@ -49,20 +47,16 @@ func TestKubeadmControlPlaneReconciler_upgradeControlPlane(t *testing.T) {
 		Log:      log.Log,
 		recorder: record.NewFakeRecorder(32),
 		managementCluster: &fakeManagementCluster{
-			Management: &internal.Management{Client: fakeClient},
-			Workload: fakeWorkloadCluster{
-				Status:              internal.ClusterStatus{Nodes: 1},
-				ControlPlaneHealthy: true,
-				EtcdHealthy:         true,
-			},
+			Management:          &internal.Management{Client: fakeClient},
+			Workload:            fakeWorkloadCluster{Status: internal.ClusterStatus{Nodes: 1}},
+			ControlPlaneHealthy: true,
+			EtcdHealthy:         true,
 		},
 		managementClusterUncached: &fakeManagementCluster{
-			Management: &internal.Management{Client: fakeClient},
-			Workload: fakeWorkloadCluster{
-				Status:              internal.ClusterStatus{Nodes: 1},
-				ControlPlaneHealthy: true,
-				EtcdHealthy:         true,
-			},
+			Management:          &internal.Management{Client: fakeClient},
+			Workload:            fakeWorkloadCluster{Status: internal.ClusterStatus{Nodes: 1}},
+			ControlPlaneHealthy: true,
+			EtcdHealthy:         true,
 		},
 	}
 	controlPlane := &internal.ControlPlane{
@@ -94,10 +88,10 @@ func TestKubeadmControlPlaneReconciler_upgradeControlPlane(t *testing.T) {
 	g.Expect(bothMachines.Items).To(HaveLen(2))
 
 	// run upgrade a second time, simulate that the node has not appeared yet but the machine exists
-	r.managementCluster.(*fakeManagementCluster).Workload.ControlPlaneHealthy = false
-	// Unhealthy control plane will be detected during reconcile loop and upgrade will never be called.
-	_, err = r.reconcile(context.Background(), cluster, kcp)
-	g.Expect(err).To(HaveOccurred())
+	r.managementCluster.(*fakeManagementCluster).ControlPlaneHealthy = false
+	result, err = r.upgradeControlPlane(context.Background(), cluster, kcp, controlPlane, needingUpgrade)
+	g.Expect(result).To(Equal(ctrl.Result{RequeueAfter: healthCheckFailedRequeueAfter}))
+	g.Expect(err).To(BeNil())
 	g.Expect(fakeClient.List(context.Background(), bothMachines, client.InNamespace(cluster.Namespace))).To(Succeed())
 	g.Expect(bothMachines.Items).To(HaveLen(2))
 
@@ -105,7 +99,7 @@ func TestKubeadmControlPlaneReconciler_upgradeControlPlane(t *testing.T) {
 
 	// manually increase number of nodes, make control plane healthy again
 	r.managementCluster.(*fakeManagementCluster).Workload.Status.Nodes++
-	r.managementCluster.(*fakeManagementCluster).Workload.ControlPlaneHealthy = true
+	r.managementCluster.(*fakeManagementCluster).ControlPlaneHealthy = true
 
 	// run upgrade the second time, expect we scale down
 	result, err = r.upgradeControlPlane(context.Background(), cluster, kcp, controlPlane, controlPlane.Machines)
diff --git a/controlplane/kubeadm/internal/cluster_test.go b/controlplane/kubeadm/internal/cluster_test.go
index 7411e8253..4cc990e90 100644
--- a/controlplane/kubeadm/internal/cluster_test.go
+++ b/controlplane/kubeadm/internal/cluster_test.go
@@ -22,6 +22,7 @@ import (
 	"crypto/rsa"
 	"crypto/x509"
 	"crypto/x509/pkix"
+	"errors"
 	"fmt"
 	"math/big"
 	"testing"
@@ -155,7 +156,7 @@ func TestGetMachinesForCluster(t *testing.T) {
 func TestGetWorkloadCluster(t *testing.T) {
 	g := NewWithT(t)
 
-	ns, err := testEnv.CreateNamespace(ctx, "workload-cluster2")
+	ns, err := testEnv.CreateNamespace(ctx, "workload-cluster")
 	g.Expect(err).ToNot(HaveOccurred())
 	defer func() {
 		g.Expect(testEnv.Cleanup(ctx, ns)).To(Succeed())
@@ -457,3 +458,186 @@ func (f *fakeClient) Update(_ context.Context, _ runtime.Object, _ ...client.Upd
 	}
 	return nil
 }
+
+func TestManagementCluster_healthCheck_NoError(t *testing.T) {
+	tests := []struct {
+		name             string
+		machineList      *clusterv1.MachineList
+		check            healthCheck
+		clusterKey       client.ObjectKey
+		controlPlaneName string
+	}{
+		{
+			name: "simple",
+			machineList: &clusterv1.MachineList{
+				Items: []clusterv1.Machine{
+					controlPlaneMachine("one"),
+					controlPlaneMachine("two"),
+					controlPlaneMachine("three"),
+				},
+			},
+			check: func(ctx context.Context) (HealthCheckResult, error) {
+				return HealthCheckResult{
+					"one":   nil,
+					"two":   nil,
+					"three": nil,
+				}, nil
+			},
+			clusterKey: client.ObjectKey{Namespace: "default", Name: "cluster-name"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			g := NewWithT(t)
+
+			ctx := context.Background()
+			m := &Management{
+				Client: &fakeClient{list: tt.machineList},
+			}
+			g.Expect(m.healthCheck(ctx, tt.check, tt.clusterKey)).To(Succeed())
+		})
+	}
+}
+
+func TestManagementCluster_healthCheck_Errors(t *testing.T) {
+	tests := []struct {
+		name             string
+		machineList      *clusterv1.MachineList
+		check            healthCheck
+		clusterKey       client.ObjectKey
+		controlPlaneName string
+		// expected errors will ensure the error contains this list of strings.
+		// If not supplied, no check on the error's value will occur.
+		expectedErrors []string
+	}{
+		{
+			name: "machine's node was not checked for health",
+			machineList: &clusterv1.MachineList{
+				Items: []clusterv1.Machine{
+					controlPlaneMachine("one"),
+					controlPlaneMachine("two"),
+					controlPlaneMachine("three"),
+				},
+			},
+			check: func(ctx context.Context) (HealthCheckResult, error) {
+				return HealthCheckResult{
+					"one": nil,
+				}, nil
+			},
+		},
+		{
+			name: "health check returns an error not related to the nodes health",
+			machineList: &clusterv1.MachineList{
+				Items: []clusterv1.Machine{
+					controlPlaneMachine("one"),
+					controlPlaneMachine("two"),
+					controlPlaneMachine("three"),
+				},
+			},
+			check: func(ctx context.Context) (HealthCheckResult, error) {
+				return HealthCheckResult{
+					"one":   nil,
+					"two":   errors.New("two"),
+					"three": errors.New("three"),
+				}, errors.New("meta")
+			},
+			expectedErrors: []string{"two", "three", "meta"},
+		},
+		{
+			name: "two nodes error on the check but no overall error occurred",
+			machineList: &clusterv1.MachineList{
+				Items: []clusterv1.Machine{
+					controlPlaneMachine("one"),
+					controlPlaneMachine("two"),
+					controlPlaneMachine("three"),
+				},
+			},
+			check: func(ctx context.Context) (HealthCheckResult, error) {
+				return HealthCheckResult{
+					"one":   nil,
+					"two":   errors.New("two"),
+					"three": errors.New("three"),
+				}, nil
+			},
+			expectedErrors: []string{"two", "three"},
+		},
+		{
+			name: "more nodes than machines were checked (out of band control plane nodes)",
+			machineList: &clusterv1.MachineList{
+				Items: []clusterv1.Machine{
+					controlPlaneMachine("one"),
+				},
+			},
+			check: func(ctx context.Context) (HealthCheckResult, error) {
+				return HealthCheckResult{
+					"one":   nil,
+					"two":   nil,
+					"three": nil,
+				}, nil
+			},
+		},
+		{
+			name: "a machine that has a nil node reference",
+			machineList: &clusterv1.MachineList{
+				Items: []clusterv1.Machine{
+					controlPlaneMachine("one"),
+					controlPlaneMachine("two"),
+					nilNodeRef(controlPlaneMachine("three")),
+				},
+			},
+			check: func(ctx context.Context) (HealthCheckResult, error) {
+				return HealthCheckResult{
+					"one":   nil,
+					"two":   nil,
+					"three": nil,
+				}, nil
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			g := NewWithT(t)
+
+			ctx := context.Background()
+			clusterKey := client.ObjectKey{Namespace: "default", Name: "cluster-name"}
+
+			m := &Management{
+				Client: &fakeClient{list: tt.machineList},
+			}
+			err := m.healthCheck(ctx, tt.check, clusterKey)
+			g.Expect(err).To(HaveOccurred())
+
+			for _, expectedError := range tt.expectedErrors {
+				g.Expect(err).To(MatchError(ContainSubstring(expectedError)))
+			}
+		})
+	}
+}
+
+func controlPlaneMachine(name string) clusterv1.Machine {
+	t := true
+	return clusterv1.Machine{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "default",
+			Name:      name,
+			Labels:    ControlPlaneLabelsForCluster("cluster-name"),
+			OwnerReferences: []metav1.OwnerReference{
+				{
+					Kind:       "KubeadmControlPlane",
+					Name:       "control-plane-name",
+					Controller: &t,
+				},
+			},
+		},
+		Status: clusterv1.MachineStatus{
+			NodeRef: &corev1.ObjectReference{
+				Name: name,
+			},
+		},
+	}
+}
+
+func nilNodeRef(machine clusterv1.Machine) clusterv1.Machine {
+	machine.Status.NodeRef = nil
+	return machine
+}
diff --git a/controlplane/kubeadm/internal/workload_cluster_test.go b/controlplane/kubeadm/internal/workload_cluster_test.go
index 4cee568a4..995f3d479 100644
--- a/controlplane/kubeadm/internal/workload_cluster_test.go
+++ b/controlplane/kubeadm/internal/workload_cluster_test.go
@@ -28,7 +28,6 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
 	cabpkv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1alpha3"
@@ -618,165 +617,3 @@ func newKubeProxyDSWithImage(image string) appsv1.DaemonSet {
 	ds.Spec.Template.Spec.Containers[0].Image = image
 	return ds
 }
-
-func TestHealthCheck_NoError(t *testing.T) {
-	threeMachines := []*clusterv1.Machine{
-		controlPlaneMachine("one"),
-		controlPlaneMachine("two"),
-		controlPlaneMachine("three"),
-	}
-	controlPlane := createControlPlane(threeMachines)
-	tests := []struct {
-		name             string
-		checkResult      HealthCheckResult
-		controlPlaneName string
-		controlPlane     *ControlPlane
-	}{
-		{
-			name: "simple",
-			checkResult: HealthCheckResult{
-				"one":   nil,
-				"two":   nil,
-				"three": nil,
-			},
-			controlPlane: controlPlane,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			g := NewWithT(t)
-
-			g.Expect(tt.checkResult.Aggregate(controlPlane)).To(Succeed())
-		})
-	}
-}
-
-func TestManagementCluster_healthCheck_Errors(t *testing.T) {
-	tests := []struct {
-		name             string
-		checkResult      HealthCheckResult
-		clusterKey       ctrlclient.ObjectKey
-		controlPlaneName string
-		controlPlane     *ControlPlane
-		// expected errors will ensure the error contains this list of strings.
-		// If not supplied, no check on the error's value will occur.
-		expectedErrors []string
-	}{
-		{
-			name: "machine's node was not checked for health",
-			controlPlane: createControlPlane([]*clusterv1.Machine{
-				controlPlaneMachine("one"),
-				controlPlaneMachine("two"),
-				controlPlaneMachine("three"),
-			}),
-			checkResult: HealthCheckResult{
-				"one": nil,
-			},
-		},
-		{
-			name: "two nodes error on the check but no overall error occurred",
-			controlPlane: createControlPlane([]*clusterv1.Machine{
-				controlPlaneMachine("one"),
-				controlPlaneMachine("two"),
-				controlPlaneMachine("three")}),
-			checkResult: HealthCheckResult{
-				"one":   nil,
-				"two":   errors.New("two"),
-				"three": errors.New("three"),
-			},
-			expectedErrors: []string{"two", "three"},
-		},
-		{
-			name: "more nodes than machines were checked (out of band control plane nodes)",
-			controlPlane: createControlPlane([]*clusterv1.Machine{
-				controlPlaneMachine("one")}),
-			checkResult: HealthCheckResult{
-				"one":   nil,
-				"two":   nil,
-				"three": nil,
-			},
-		},
-		{
-			name: "a machine that has a nil node reference",
-			controlPlane: createControlPlane([]*clusterv1.Machine{
-				controlPlaneMachine("one"),
-				controlPlaneMachine("two"),
-				nilNodeRef(controlPlaneMachine("three"))}),
-			checkResult: HealthCheckResult{
-				"one":   nil,
-				"two":   nil,
-				"three": nil,
-			},
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			g := NewWithT(t)
-
-			err := tt.checkResult.Aggregate(tt.controlPlane)
-			g.Expect(err).To(HaveOccurred())
-
-			for _, expectedError := range tt.expectedErrors {
-				g.Expect(err).To(MatchError(ContainSubstring(expectedError)))
-			}
-		})
-	}
-}
-func createControlPlane(machines []*clusterv1.Machine) *ControlPlane {
-	defaultInfra := &unstructured.Unstructured{
-		Object: map[string]interface{}{
-			"kind":       "InfrastructureMachine",
-			"apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3",
-			"metadata": map[string]interface{}{
-				"name":      "infra-config1",
-				"namespace": "default",
-			},
-			"spec":   map[string]interface{}{},
-			"status": map[string]interface{}{},
-		},
-	}
-
-	fakeClient := fake.NewFakeClientWithScheme(runtime.NewScheme(), defaultInfra.DeepCopy())
-
-	controlPlane, _ := NewControlPlane(ctx, fakeClient, &clusterv1.Cluster{}, &v1alpha3.KubeadmControlPlane{}, NewFilterableMachineCollection(machines...))
-	return controlPlane
-}
-
-func controlPlaneMachine(name string) *clusterv1.Machine {
-	t := true
-	infraRef := &corev1.ObjectReference{
-		Kind:       "InfraKind",
-		APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3",
-		Name:       "infra",
-		Namespace:  "default",
-	}
-
-	return &clusterv1.Machine{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: "default",
-			Name:      name,
-			Labels:    ControlPlaneLabelsForCluster("cluster-name"),
-			OwnerReferences: []metav1.OwnerReference{
-				{
-					Kind:       "KubeadmControlPlane",
-					Name:       "control-plane-name",
-					Controller: &t,
-				},
-			},
-		},
-		Spec: clusterv1.MachineSpec{
-			InfrastructureRef: *infraRef.DeepCopy(),
-		},
-		Status: clusterv1.MachineStatus{
-			NodeRef: &corev1.ObjectReference{
-				Name: name,
-			},
-		},
-	}
-}
-
-func nilNodeRef(machine *clusterv1.Machine) *clusterv1.Machine {
-	machine.Status.NodeRef = nil
-	return machine
-}
diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go
index 54f3a71c8..0c77876e4 100644
--- a/test/e2e/e2e_suite_test.go
+++ b/test/e2e/e2e_suite_test.go
@@ -159,23 +159,18 @@ func loadE2EConfig(configPath string) *clusterctl.E2EConfig {
 	config := clusterctl.LoadE2EConfig(context.TODO(), clusterctl.LoadE2EConfigInput{ConfigPath: configPath})
 	Expect(config).ToNot(BeNil(), "Failed to load E2E config from %s", configPath)
 
+	// Read CNI file and set CNI_RESOURCES environmental variable
+	Expect(config.Variables).To(HaveKey(CNIPath), "Missing %s variable in the config", CNIPath)
+	clusterctl.SetCNIEnvVar(config.GetVariable(CNIPath), CNIResources)
+
 	return config
 }
 
 func createClusterctlLocalRepository(config *clusterctl.E2EConfig, repositoryFolder string) string {
-	createRepositoryInput := clusterctl.CreateRepositoryInput{
+	clusterctlConfig := clusterctl.CreateRepository(context.TODO(), clusterctl.CreateRepositoryInput{
 		E2EConfig:        config,
 		RepositoryFolder: repositoryFolder,
-	}
-
-	// Ensuring a CNI file is defined in the config and register a FileTransformation to inject the referenced file as in place of the CNI_RESOURCES envSubst variable.
-	Expect(config.Variables).To(HaveKey(CNIPath), "Missing %s variable in the config", CNIPath)
-	cniPath := config.GetVariable(CNIPath)
-	Expect(cniPath).To(BeAnExistingFile(), "The %s variable should resolve to an existing file", CNIPath)
-
-	createRepositoryInput.RegisterClusterResourceSetConfigMapTransformation(cniPath, CNIResources)
-
-	clusterctlConfig := clusterctl.CreateRepository(context.TODO(), createRepositoryInput)
+	})
 	Expect(clusterctlConfig).To(BeAnExistingFile(), "The clusterctl config file does not exists in the local repository %s", repositoryFolder)
 	return clusterctlConfig
 }
diff --git a/test/framework/clusterctl/e2e_config.go b/test/framework/clusterctl/e2e_config.go
index 71b615450..f8273ac32 100644
--- a/test/framework/clusterctl/e2e_config.go
+++ b/test/framework/clusterctl/e2e_config.go
@@ -18,6 +18,7 @@ package clusterctl
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -65,11 +66,15 @@ func LoadE2EConfig(ctx context.Context, input LoadE2EConfigInput) *E2EConfig {
 
 // SetCNIEnvVar read CNI from cniManifestPath and sets an environmental variable that keeps CNI resources.
 // A ClusterResourceSet can be used to apply CNI using this environmental variable.
-//
-// Deprecated: Use FileTransformations in the CreateRepositoryInput to embedded CNI into cluster templates during create repository.
-// The new approach does not uses env variables so we can avoid https://github.com/kubernetes-sigs/cluster-api/issues/3797;
-// This func is preserved for avoiding to break users in the v0.3 series, but it is now a no-op.
 func SetCNIEnvVar(cniManifestPath string, cniEnvVar string) {
+	cniData, err := ioutil.ReadFile(cniManifestPath)
+	Expect(err).ToNot(HaveOccurred(), "Failed to read the e2e test CNI file")
+	Expect(cniData).ToNot(BeEmpty(), "CNI file should not be empty")
+	data := map[string]interface{}{}
+	data["resources"] = string(cniData)
+	marshalledData, err := json.Marshal(data)
+	Expect(err).NotTo(HaveOccurred())
+	Expect(os.Setenv(cniEnvVar, string(marshalledData))).NotTo(HaveOccurred())
 }
 
 // E2EConfig defines the configuration of an e2e test environment.
diff --git a/test/framework/clusterctl/repository.go b/test/framework/clusterctl/repository.go
index 74c4e748c..f30e80995 100644
--- a/test/framework/clusterctl/repository.go
+++ b/test/framework/clusterctl/repository.go
@@ -17,15 +17,11 @@ limitations under the License.
 package clusterctl
 
 import (
-	"bytes"
 	"context"
-	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
-	"strings"
 
-	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 
 	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
@@ -33,34 +29,11 @@ import (
 )
 
 // Provides helpers for managing a clusterctl local repository to be used for running e2e tests in isolation.
-type RepositoryFileTransformation func([]byte) ([]byte, error)
 
 // CreateRepositoryInput is the input for CreateRepository.
 type CreateRepositoryInput struct {
-	RepositoryFolder    string
-	E2EConfig           *E2EConfig
-	FileTransformations []RepositoryFileTransformation
-}
-
-// RegisterClusterResourceSetConfigMapTransformation registers a FileTransformations that injects a CNI file into
-// a ConfigMap that defines a ClusterResourceSet resource.
-//
-// NOTE: this transformation is specifically designed for replacing "data: ${envSubstVar}".
-func (i *CreateRepositoryInput) RegisterClusterResourceSetConfigMapTransformation(cniManifestPath, envSubstVar string) {
-	By(fmt.Sprintf("Reading the CNI manifest %s", cniManifestPath))
-	cniData, err := ioutil.ReadFile(cniManifestPath)
-	Expect(err).ToNot(HaveOccurred(), "Failed to read the e2e test CNI file")
-	Expect(cniData).ToNot(BeEmpty(), "CNI file should not be empty")
-
-	i.FileTransformations = append(i.FileTransformations, func(template []byte) ([]byte, error) {
-		old := fmt.Sprintf("data: ${%s}", envSubstVar)
-		new := "data:\n"
-		new += "  resources: |\n"
-		for _, l := range strings.Split(string(cniData), "\n") {
-			new += strings.Repeat(" ", 4) + l + "\n"
-		}
-		return bytes.Replace(template, []byte(old), []byte(new), -1), nil
-	})
+	RepositoryFolder string
+	E2EConfig        *E2EConfig
 }
 
 // CreateRepository creates a clusterctl local repository based on the e2e test config, and the returns the path
@@ -99,12 +72,6 @@ func CreateRepository(ctx context.Context, input CreateRepositoryInput) string {
 			data, err := ioutil.ReadFile(file.SourcePath)
 			Expect(err).ToNot(HaveOccurred(), "Failed to read file %q / %q", provider.Name, file.SourcePath)
 
-			// Applies FileTransformations if defined
-			for _, t := range input.FileTransformations {
-				data, err = t(data)
-				Expect(err).ToNot(HaveOccurred(), "Failed to apply transformation func template %q", file)
-			}
-
 			destinationFile := filepath.Join(filepath.Dir(providerURL), file.TargetName)
 			Expect(ioutil.WriteFile(destinationFile, data, 0600)).To(Succeed(), "Failed to write clusterctl local repository file %q / %q", provider.Name, file.TargetName)
 		}
diff --git a/test/helpers/envtest.go b/test/helpers/envtest.go
index cb24758ba..88613a2ed 100644
--- a/test/helpers/envtest.go
+++ b/test/helpers/envtest.go
@@ -103,8 +103,6 @@ func init() {
 			external.TestGenericBootstrapTemplateCRD.DeepCopy(),
 			external.TestGenericInfrastructureCRD.DeepCopy(),
 			external.TestGenericInfrastructureTemplateCRD.DeepCopy(),
-			external.TestGenericInfrastructureRemediationCRD.DeepCopy(),
-			external.TestGenericInfrastructureRemediationTemplateCRD.DeepCopy(),
 		},
 	}
 }
diff --git a/test/infrastructure/docker/controllers/dockermachine_controller.go b/test/infrastructure/docker/controllers/dockermachine_controller.go
index 465a1b47f..beee8051e 100644
--- a/test/infrastructure/docker/controllers/dockermachine_controller.go
+++ b/test/infrastructure/docker/controllers/dockermachine_controller.go
@@ -158,7 +158,7 @@ func (r *DockerMachineReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, re
 	}
 
 	// Handle non-deleted machines
-	return r.reconcileNormal(ctx, cluster, machine, dockerMachine, externalMachine, externalLoadBalancer, log)
+	return r.reconcileNormal(ctx, machine, dockerMachine, externalMachine, externalLoadBalancer, log)
 }
 
 func patchDockerMachine(ctx context.Context, patchHelper *patch.Helper, dockerMachine *infrav1.DockerMachine) error {
@@ -184,7 +184,7 @@ func patchDockerMachine(ctx context.Context, patchHelper *patch.Helper, dockerMa
 	)
 }
 
-func (r *DockerMachineReconciler) reconcileNormal(ctx context.Context, cluster *clusterv1.Cluster, machine *clusterv1.Machine, dockerMachine *infrav1.DockerMachine, externalMachine *docker.Machine, externalLoadBalancer *docker.LoadBalancer, log logr.Logger) (res ctrl.Result, retErr error) {
+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 {
 		// ensure ready state is set.
@@ -196,12 +196,6 @@ func (r *DockerMachineReconciler) reconcileNormal(ctx context.Context, cluster *
 
 	// Make sure bootstrap data is available and populated.
 	if machine.Spec.Bootstrap.DataSecretName == nil {
-		if !util.IsControlPlaneMachine(machine) && !cluster.Status.ControlPlaneInitialized {
-			log.Info("Waiting for the control plane to be initialized")
-			conditions.MarkFalse(dockerMachine, infrav1.ContainerProvisionedCondition, clusterv1.WaitingForControlPlaneAvailableReason, clusterv1.ConditionSeverityInfo, "")
-			return ctrl.Result{}, nil
-		}
-
 		log.Info("Waiting for the Bootstrap provider controller to set bootstrap data")
 		conditions.MarkFalse(dockerMachine, infrav1.ContainerProvisionedCondition, infrav1.WaitingForBootstrapDataReason, clusterv1.ConditionSeverityInfo, "")
 		return ctrl.Result{}, nil
diff --git a/test/infrastructure/docker/controllers/dockermachine_controller_test.go b/test/infrastructure/docker/controllers/dockermachine_controller_test.go
index 72b16f7fe..31c61d308 100644
--- a/test/infrastructure/docker/controllers/dockermachine_controller_test.go
+++ b/test/infrastructure/docker/controllers/dockermachine_controller_test.go
@@ -31,18 +31,6 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/handler"
 )
 
-var (
-	clusterName   = "my-cluster"
-	dockerCluster = newDockerCluster(clusterName, "my-docker-cluster")
-	cluster       = newCluster(clusterName, dockerCluster)
-
-	dockerMachine = newDockerMachine("my-docker-machine-0", "my-machine-0")
-	machine       = newMachine(clusterName, "my-machine-0", dockerMachine)
-
-	anotherDockerMachine = newDockerMachine("my-docker-machine-1", "my-machine-1")
-	anotherMachine       = newMachine(clusterName, "my-machine-1", anotherDockerMachine)
-)
-
 func setupScheme() *runtime.Scheme {
 	s := runtime.NewScheme()
 	if err := clusterv1.AddToScheme(s); err != nil {
@@ -57,11 +45,15 @@ func setupScheme() *runtime.Scheme {
 func TestDockerMachineReconciler_DockerClusterToDockerMachines(t *testing.T) {
 	g := NewWithT(t)
 
+	clusterName := "my-cluster"
+	dockerCluster := newDockerCluster(clusterName, "my-docker-cluster")
+	dockerMachine1 := newDockerMachine("my-docker-machine-0")
+	dockerMachine2 := newDockerMachine("my-docker-machine-1")
 	objects := []runtime.Object{
-		cluster,
+		newCluster(clusterName),
 		dockerCluster,
-		machine,
-		anotherMachine,
+		newMachine(clusterName, "my-machine-0", dockerMachine1),
+		newMachine(clusterName, "my-machine-1", dockerMachine2),
 		// Intentionally omitted
 		newMachine(clusterName, "my-machine-2", nil),
 	}
@@ -82,22 +74,13 @@ func TestDockerMachineReconciler_DockerClusterToDockerMachines(t *testing.T) {
 	g.Expect(machineNames).To(ConsistOf("my-machine-0", "my-machine-1"))
 }
 
-func newCluster(clusterName string, dockerCluster *infrav1.DockerCluster) *clusterv1.Cluster {
-	cluster := &clusterv1.Cluster{
+func newCluster(clusterName string) *clusterv1.Cluster {
+	return &clusterv1.Cluster{
 		TypeMeta: metav1.TypeMeta{},
 		ObjectMeta: metav1.ObjectMeta{
 			Name: clusterName,
 		},
 	}
-	if dockerCluster != nil {
-		cluster.Spec.InfrastructureRef = &v1.ObjectReference{
-			Name:       dockerCluster.Name,
-			Namespace:  dockerCluster.Namespace,
-			Kind:       dockerCluster.Kind,
-			APIVersion: dockerCluster.GroupVersionKind().GroupVersion().String(),
-		}
-	}
-	return cluster
 }
 
 func newDockerCluster(clusterName, dockerName string) *infrav1.DockerCluster {
@@ -136,20 +119,11 @@ func newMachine(clusterName, machineName string, dockerMachine *infrav1.DockerMa
 	return machine
 }
 
-func newDockerMachine(dockerMachineName, machineName string) *infrav1.DockerMachine {
+func newDockerMachine(name string) *infrav1.DockerMachine {
 	return &infrav1.DockerMachine{
 		TypeMeta: metav1.TypeMeta{},
 		ObjectMeta: metav1.ObjectMeta{
-			Name:            dockerMachineName,
-			ResourceVersion: "1",
-			Finalizers:      []string{infrav1.MachineFinalizer},
-			OwnerReferences: []metav1.OwnerReference{
-				{
-					APIVersion: clusterv1.GroupVersion.String(),
-					Kind:       "Machine",
-					Name:       machineName,
-				},
-			},
+			Name: name,
 		},
 		Spec:   infrav1.DockerMachineSpec{},
 		Status: infrav1.DockerMachineStatus{},
diff --git a/test/infrastructure/docker/go.sum b/test/infrastructure/docker/go.sum
index ae954b86e..01dbb686f 100644
--- a/test/infrastructure/docker/go.sum
+++ b/test/infrastructure/docker/go.sum
@@ -52,8 +52,8 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
 github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
-github.com/coredns/corefile-migration v1.0.11 h1:ptBYGW2ADXIB7ZEBPrhhTvNwJLQfxE3Q9IUMBhJCEeI=
-github.com/coredns/corefile-migration v1.0.11/go.mod h1:RMy/mXdeDlYwzt0vdMEJvT2hGJ2I86/eO0UdXmH9XNI=
+github.com/coredns/corefile-migration v1.0.10 h1:7HI4r5S5Fne749a+JDxUZppqBpYoZK8Q53ZVK9cn3aM=
+github.com/coredns/corefile-migration v1.0.10/go.mod h1:RMy/mXdeDlYwzt0vdMEJvT2hGJ2I86/eO0UdXmH9XNI=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
diff --git a/util/conditions/matcher_test.go b/util/conditions/matcher_test.go
deleted file mode 100644
index 3139883df..000000000
--- a/util/conditions/matcher_test.go
+++ /dev/null
@@ -1,316 +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 conditions
-
-import (
-	"testing"
-
-	. "github.com/onsi/gomega"
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
-)
-
-func TestMatchConditions(t *testing.T) {
-	testCases := []struct {
-		name        string
-		actual      interface{}
-		expected    clusterv1.Conditions
-		expectMatch bool
-	}{
-		{
-			name:        "with an empty conditions",
-			actual:      clusterv1.Conditions{},
-			expected:    clusterv1.Conditions{},
-			expectMatch: true,
-		},
-		{
-			name: "with matching conditions",
-			actual: clusterv1.Conditions{
-				{
-					Type:               clusterv1.ConditionType("type"),
-					Status:             corev1.ConditionTrue,
-					Severity:           clusterv1.ConditionSeverityNone,
-					LastTransitionTime: metav1.Now(),
-					Reason:             "reason",
-					Message:            "message",
-				},
-			},
-			expected: clusterv1.Conditions{
-				{
-					Type:               clusterv1.ConditionType("type"),
-					Status:             corev1.ConditionTrue,
-					Severity:           clusterv1.ConditionSeverityNone,
-					LastTransitionTime: metav1.Now(),
-					Reason:             "reason",
-					Message:            "message",
-				},
-			},
-			expectMatch: true,
-		},
-		{
-			name: "with non-matching conditions",
-			actual: clusterv1.Conditions{
-				{
-					Type:               clusterv1.ConditionType("type"),
-					Status:             corev1.ConditionTrue,
-					Severity:           clusterv1.ConditionSeverityNone,
-					LastTransitionTime: metav1.Now(),
-					Reason:             "reason",
-					Message:            "message",
-				},
-				{
-					Type:               clusterv1.ConditionType("type"),
-					Status:             corev1.ConditionTrue,
-					Severity:           clusterv1.ConditionSeverityNone,
-					LastTransitionTime: metav1.Now(),
-					Reason:             "reason",
-					Message:            "message",
-				},
-			},
-			expected: clusterv1.Conditions{
-				{
-					Type:               clusterv1.ConditionType("type"),
-					Status:             corev1.ConditionTrue,
-					Severity:           clusterv1.ConditionSeverityNone,
-					LastTransitionTime: metav1.Now(),
-					Reason:             "reason",
-					Message:            "message",
-				},
-				{
-					Type:               clusterv1.ConditionType("different"),
-					Status:             corev1.ConditionTrue,
-					Severity:           clusterv1.ConditionSeverityNone,
-					LastTransitionTime: metav1.Now(),
-					Reason:             "different",
-					Message:            "different",
-				},
-			},
-			expectMatch: false,
-		},
-		{
-			name: "with a different number of conditions",
-			actual: clusterv1.Conditions{
-				{
-					Type:               clusterv1.ConditionType("type"),
-					Status:             corev1.ConditionTrue,
-					Severity:           clusterv1.ConditionSeverityNone,
-					LastTransitionTime: metav1.Now(),
-					Reason:             "reason",
-					Message:            "message",
-				},
-				{
-					Type:               clusterv1.ConditionType("type"),
-					Status:             corev1.ConditionTrue,
-					Severity:           clusterv1.ConditionSeverityNone,
-					LastTransitionTime: metav1.Now(),
-					Reason:             "reason",
-					Message:            "message",
-				},
-			},
-			expected: clusterv1.Conditions{
-				{
-					Type:               clusterv1.ConditionType("type"),
-					Status:             corev1.ConditionTrue,
-					Severity:           clusterv1.ConditionSeverityNone,
-					LastTransitionTime: metav1.Now(),
-					Reason:             "reason",
-					Message:            "message",
-				},
-			},
-			expectMatch: false,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			g := NewWithT(t)
-			if tc.expectMatch {
-				g.Expect(tc.actual).To(MatchConditions(tc.expected))
-			} else {
-				g.Expect(tc.actual).ToNot(MatchConditions(tc.expected))
-			}
-		})
-	}
-}
-
-func TestMatchCondition(t *testing.T) {
-	testCases := []struct {
-		name        string
-		actual      interface{}
-		expected    clusterv1.Condition
-		expectMatch bool
-	}{
-		{
-			name:        "with an empty condition",
-			actual:      clusterv1.Condition{},
-			expected:    clusterv1.Condition{},
-			expectMatch: true,
-		},
-		{
-			name: "with a matching condition",
-			actual: clusterv1.Condition{
-				Type:               clusterv1.ConditionType("type"),
-				Status:             corev1.ConditionTrue,
-				Severity:           clusterv1.ConditionSeverityNone,
-				LastTransitionTime: metav1.Now(),
-				Reason:             "reason",
-				Message:            "message",
-			},
-			expected: clusterv1.Condition{
-				Type:               clusterv1.ConditionType("type"),
-				Status:             corev1.ConditionTrue,
-				Severity:           clusterv1.ConditionSeverityNone,
-				LastTransitionTime: metav1.Now(),
-				Reason:             "reason",
-				Message:            "message",
-			},
-			expectMatch: true,
-		},
-		{
-			name: "with a different time",
-			actual: clusterv1.Condition{
-				Type:               clusterv1.ConditionType("type"),
-				Status:             corev1.ConditionTrue,
-				Severity:           clusterv1.ConditionSeverityNone,
-				LastTransitionTime: metav1.Now(),
-				Reason:             "reason",
-				Message:            "message",
-			},
-			expected: clusterv1.Condition{
-				Type:               clusterv1.ConditionType("type"),
-				Status:             corev1.ConditionTrue,
-				Severity:           clusterv1.ConditionSeverityNone,
-				LastTransitionTime: metav1.Time{},
-				Reason:             "reason",
-				Message:            "message",
-			},
-			expectMatch: true,
-		},
-		{
-			name: "with a different type",
-			actual: clusterv1.Condition{
-				Type:               clusterv1.ConditionType("type"),
-				Status:             corev1.ConditionTrue,
-				Severity:           clusterv1.ConditionSeverityNone,
-				LastTransitionTime: metav1.Now(),
-				Reason:             "reason",
-				Message:            "message",
-			},
-			expected: clusterv1.Condition{
-				Type:               clusterv1.ConditionType("different"),
-				Status:             corev1.ConditionTrue,
-				Severity:           clusterv1.ConditionSeverityNone,
-				LastTransitionTime: metav1.Now(),
-				Reason:             "reason",
-				Message:            "message",
-			},
-			expectMatch: false,
-		},
-		{
-			name: "with a different status",
-			actual: clusterv1.Condition{
-				Type:               clusterv1.ConditionType("type"),
-				Status:             corev1.ConditionTrue,
-				Severity:           clusterv1.ConditionSeverityNone,
-				LastTransitionTime: metav1.Now(),
-				Reason:             "reason",
-				Message:            "message",
-			},
-			expected: clusterv1.Condition{
-				Type:               clusterv1.ConditionType("type"),
-				Status:             corev1.ConditionFalse,
-				Severity:           clusterv1.ConditionSeverityNone,
-				LastTransitionTime: metav1.Now(),
-				Reason:             "reason",
-				Message:            "message",
-			},
-			expectMatch: false,
-		},
-		{
-			name: "with a different severity",
-			actual: clusterv1.Condition{
-				Type:               clusterv1.ConditionType("type"),
-				Status:             corev1.ConditionTrue,
-				Severity:           clusterv1.ConditionSeverityNone,
-				LastTransitionTime: metav1.Now(),
-				Reason:             "reason",
-				Message:            "message",
-			},
-			expected: clusterv1.Condition{
-				Type:               clusterv1.ConditionType("type"),
-				Status:             corev1.ConditionTrue,
-				Severity:           clusterv1.ConditionSeverityInfo,
-				LastTransitionTime: metav1.Now(),
-				Reason:             "reason",
-				Message:            "message",
-			},
-			expectMatch: false,
-		},
-		{
-			name: "with a different reason",
-			actual: clusterv1.Condition{
-				Type:               clusterv1.ConditionType("type"),
-				Status:             corev1.ConditionTrue,
-				Severity:           clusterv1.ConditionSeverityNone,
-				LastTransitionTime: metav1.Now(),
-				Reason:             "reason",
-				Message:            "message",
-			},
-			expected: clusterv1.Condition{
-				Type:               clusterv1.ConditionType("type"),
-				Status:             corev1.ConditionTrue,
-				Severity:           clusterv1.ConditionSeverityNone,
-				LastTransitionTime: metav1.Now(),
-				Reason:             "different",
-				Message:            "message",
-			},
-			expectMatch: false,
-		},
-		{
-			name: "with a different message",
-			actual: clusterv1.Condition{
-				Type:               clusterv1.ConditionType("type"),
-				Status:             corev1.ConditionTrue,
-				Severity:           clusterv1.ConditionSeverityNone,
-				LastTransitionTime: metav1.Now(),
-				Reason:             "reason",
-				Message:            "message",
-			},
-			expected: clusterv1.Condition{
-				Type:               clusterv1.ConditionType("type"),
-				Status:             corev1.ConditionTrue,
-				Severity:           clusterv1.ConditionSeverityNone,
-				LastTransitionTime: metav1.Now(),
-				Reason:             "reason",
-				Message:            "different",
-			},
-			expectMatch: false,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			g := NewWithT(t)
-			if tc.expectMatch {
-				g.Expect(tc.actual).To(MatchCondition(tc.expected))
-			} else {
-				g.Expect(tc.actual).ToNot(MatchCondition(tc.expected))
-			}
-		})
-	}
-}