Repository: kubernetes-sigs/cluster-api-provider-vsphere
Pull request #3049 (2024-06-10 22:23:20)

✨ Add NetworkConfigurations, VMTemplate to FailureDomain
**What this PR does / why we need it**:
This PR adds additional API to Failure Domain. This allows to have different configurations based on the selected region/zone. 

(Edited by chrischdi)
User stories:

* As a user I would like to define network configuration for machines, which differs per failure domain.
* As a user I would like to define a different value for VSphereMachines .spec.template field per failure domain.

(end of Edit by chrischdi)

**Which issue(s) this PR fixes** 
Partially it repeats work that was done for issue #1964 in #1967. But also allows overriding VM template. 


linux-foundation-easycla[bot]:  <a href="https://api.easycla.lfx.linuxfoundation.org/v2/repository-provider/github/sign/20677499/143459502/3049/#/?version=2"><img src="https://s3.amazonaws.com/cla-project-logo-prod/cla-not-signed.svg" alt="CLA Not Signed" align="left" height="28" width="328"></a><br/><br /><ul><li><a href='https://api.easycla.lfx.linuxfoundation.org/v2/repository-provider/github/sign/20677499/143459502/3049/#/?version=2' target='_blank'>:x:</a> - login: @Nutrymaco / name: Efim Smykov . The commit (956ac710585de8ea2b3ecd21e040f3a6081053bc, a6b6fa280873b3bc92cae832b77c3164ff86a9b3) is not authorized under a signed CLA. <a href='https://api.easycla.lfx.linuxfoundation.org/v2/repository-provider/github/sign/20677499/143459502/3049/#/?version=2' target='_blank'>Please click here to be authorized</a>. For further assistance with EasyCLA, <a href='https://jira.linuxfoundation.org/servicedesk/customer/portal/4' target='_blank'>please submit a support request ticket</a>.</li></ul><!-- Date Modified: 2024-06-10 22:23:24.708471 -->
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
**Once this PR has been reviewed and has the lgtm label**, please assign [fabriziopandini](https://github.com/fabriziopandini) for approval. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

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

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

- **[OWNERS](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["fabriziopandini"]} -->
k8s-ci-robot: Welcome @Nutrymaco! <br><br>It looks like this is your first PR to <a href='https://github.com/kubernetes-sigs/cluster-api-provider-vsphere'>kubernetes-sigs/cluster-api-provider-vsphere</a> 🎉. Please refer to our [pull request process documentation](https://git.k8s.io/community/contributors/guide/pull-requests.md) to help your PR have a smooth ride to approval. <br><br>You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. [Here is the bot commands documentation](https://go.k8s.io/bot-commands). <br><br>You can also check if kubernetes-sigs/cluster-api-provider-vsphere has [its own contribution guidelines](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/tree/master/CONTRIBUTING.md). <br><br>You may want to refer to our [testing guide](https://git.k8s.io/community/contributors/devel/sig-testing/testing.md) if you run into trouble with your tests not passing. <br><br>If you are having difficulty getting your pull request seen, please follow the [recommended escalation practices](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#why-is-my-pull-request-not-getting-reviewed). Also, for tips and tricks in the contribution process you may want to read the [Kubernetes contributor cheat sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md). We want to make sure your contribution gets all the attention it needs! <br><br>Thank you, and welcome to Kubernetes. :smiley:
k8s-ci-robot: Hi @Nutrymaco. Thanks for your PR.

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

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

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

<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-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>

chrischdi: /ok-to-test

Do I get it right that the use cases are:

* As a user I would like to define network configuration for machines, which differs per failure domain.
* As a user I would like to define a different value for VSphereMachines `.spec.template` field per failure domain.
k8s-ci-robot: @Nutrymaco: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-vsphere-verify-main | a6b6fa280873b3bc92cae832b77c3164ff86a9b3 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3049/pull-cluster-api-provider-vsphere-verify-main/1801222822945624064) | true | `/test pull-cluster-api-provider-vsphere-verify-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-vsphere&pr=3049). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3ANutrymaco). 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-provider-vsphere/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-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
Nutrymaco: > /ok-to-test
> 
> Do I get it right that the use cases are:
> 
> * As a user I would like to define network configuration for machines, which differs per failure domain.
> * As a user I would like to define a different value for VSphereMachines `.spec.template` field per failure domain.

Yes
k8s-ci-robot: @Nutrymaco: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-vsphere-verify-main | 10157cff81665bf37db0e5351dd4775df0c5c196 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3049/pull-cluster-api-provider-vsphere-verify-main/1801591836024770560) | true | `/test pull-cluster-api-provider-vsphere-verify-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-vsphere&pr=3049). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3ANutrymaco). 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-provider-vsphere/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-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
chrischdi: Question: does it make sense to have the full network definition, or is it enought o have the one originally introduced (and not used) here: https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/blob/c77f5fb7dc6f8244c5d3d2adf1ea19825a14cabe/apis/v1beta1/vspheredeploymentzone_types.go#L63-L74
Nutrymaco: > Sorry for questioning this now and not earlier. But because this is changing APIs we should be careful to only add/change when we think its a long-term commitment because we have to maintain and support it then for a long time.
> 
> Question: what is the reasoning behind wanting to overwrite the template of a machine? Why can't it stay the same?
> 
> A substantial part of a MachineDeployment is being able to have the same machine multiple times. Could you please explain the actual use-case where this comes from?
> 
> Maybe it helps to split up this discussion from the networking part and maybe also handle them as separate Issues/Pull Requests.
Yes, let's discuss it in different issue. I'll remove template related changes. 
Nutrymaco: > Question: does it make sense to have the full network definition, or is it enought only have the one originally introduced (and not used) here:
> 
> https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/blob/c77f5fb7dc6f8244c5d3d2adf1ea19825a14cabe/apis/v1beta1/vspheredeploymentzone_types.go#L63-L74

We at least need `addressesFromPools`, so this struct does not fit
Nutrymaco: @chrischdi @killianmuldoon 
What can i do to make this PR move forward?
chrischdi: cc @lubronzhan , @neolit123 , this is a change to the govmomi API types :-)

Can you maybe take a look if you think they make sense on a high level first?
lubronzhan: Should the NetworkConfigurations be marked as deprecated in favor of the new networkDevice under failure domain?
k8s-ci-robot: @Nutrymaco: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-vsphere-e2e-govmomi-blocking-main | b6e9a99106e5bfc610ee4f3fe218d72faf2ee556 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3049/pull-cluster-api-provider-vsphere-e2e-govmomi-blocking-main/1813924883453710336) | true | `/test pull-cluster-api-provider-vsphere-e2e-govmomi-blocking-main`

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

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-vsphere-e2e-govmomi-blocking-main | b6e9a99106e5bfc610ee4f3fe218d72faf2ee556 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3049/pull-cluster-api-provider-vsphere-e2e-govmomi-blocking-main/1813924883453710336) | true | `/test pull-cluster-api-provider-vsphere-e2e-govmomi-blocking-main`
pull-cluster-api-provider-vsphere-e2e-supervisor-blocking-main | b6e9a99106e5bfc610ee4f3fe218d72faf2ee556 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3049/pull-cluster-api-provider-vsphere-e2e-supervisor-blocking-main/1813924883520819200) | true | `/test pull-cluster-api-provider-vsphere-e2e-supervisor-blocking-main`

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

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-vsphere-e2e-govmomi-blocking-main | b6e9a99106e5bfc610ee4f3fe218d72faf2ee556 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3049/pull-cluster-api-provider-vsphere-e2e-govmomi-blocking-main/1813924883453710336) | true | `/test pull-cluster-api-provider-vsphere-e2e-govmomi-blocking-main`
pull-cluster-api-provider-vsphere-e2e-supervisor-blocking-main | b6e9a99106e5bfc610ee4f3fe218d72faf2ee556 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3049/pull-cluster-api-provider-vsphere-e2e-supervisor-blocking-main/1813924883520819200) | true | `/test pull-cluster-api-provider-vsphere-e2e-supervisor-blocking-main`
pull-cluster-api-provider-vsphere-test-main | b6e9a99106e5bfc610ee4f3fe218d72faf2ee556 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3049/pull-cluster-api-provider-vsphere-test-main/1813924883394990080) | true | `/test pull-cluster-api-provider-vsphere-test-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-vsphere&pr=3049). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3ANutrymaco). 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-provider-vsphere/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-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
Nutrymaco: Hi everyone

Just checking in on the status. Is there an estimated timeline for when work on it might continue? Let me know if there’s anything I can do to help move it forward.

Thanks!
chrischdi: Also: needs a proper rebase to not have merge commits.
chrischdi: /test help
k8s-ci-robot: @chrischdi: The specified target(s) for `/test` were not found.
The following commands are available to trigger required jobs:
* `/test pull-cluster-api-provider-vsphere-e2e-vcsim-govmomi-main`
* `/test pull-cluster-api-provider-vsphere-e2e-vcsim-supervisor-main`
* `/test pull-cluster-api-provider-vsphere-test-main`
* `/test pull-cluster-api-provider-vsphere-verify-main`

The following commands are available to trigger optional jobs:
* `/test pull-cluster-api-provider-vsphere-apidiff-main`

Use `/test all` to run the following jobs that were automatically triggered:
* `pull-cluster-api-provider-vsphere-apidiff-main`
* `pull-cluster-api-provider-vsphere-test-main`
* `pull-cluster-api-provider-vsphere-verify-main`



<details>

In response to [this](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/pull/3049#issuecomment-2316994215):

>/test help


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-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
chrischdi: /test pull-cluster-api-provider-vsphere-e2e-vcsim-govmomi-main
/test pull-cluster-api-provider-vsphere-e2e-vcsim-supervisor-main

chrischdi: /lgtm

cc @lubronzhan @neolit123 @rikatz 
chrischdi: pull-cluster-api-provider-vsphere-e2e-govmomi-main at https://github.com/team-cluster-api/cluster-api-provider-vsphere/pull/19 are green
Nutrymaco: Hi @chrischdi! When is this pr planned to be merged? Maybe there is something i need to do? 
chrischdi: @lubronzhan @neolit123 @rikatz : would be awesome to get another pair of eyes on this :-)
lubronzhan: Sorry for the late reply, overall lgtm
Nutrymaco: Hi, @chrischdi !
Any estimate on when PR can be merged?
chrischdi: That should be the final steps :-) 
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:
**Once this PR has been reviewed and has the lgtm label**, please ask for approval from [chrischdi](https://github.com/chrischdi). For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

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

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

- **[OWNERS](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/blob/main/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":["chrischdi"]} -->
Nutrymaco: @chrischdi 
Fixed your comments. Did you have a chance to look?
sbueringer: @Nutrymaco just a few smaller findings from my side
k8s-ci-robot: @Nutrymaco: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-vsphere-test-main | fee6bad16b17208e18d9fe79f4d8e03f29b6eea6 | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3049/pull-cluster-api-provider-vsphere-test-main/1856605495649898496) | true | `/test pull-cluster-api-provider-vsphere-test-main`

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

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-vsphere-test-main | e900210e67302c2d5681de144aa396fa9e603ada | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3049/pull-cluster-api-provider-vsphere-test-main/1856608383365287936) | true | `/test pull-cluster-api-provider-vsphere-test-main`

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

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-vsphere-test-main | f497652b858452e653de8e7c5db82c1143b4d845 | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3049/pull-cluster-api-provider-vsphere-test-main/1856625965367562240) | true | `/test pull-cluster-api-provider-vsphere-test-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-vsphere&pr=3049). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3ANutrymaco). 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-provider-vsphere/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-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
sbueringer: I'll take another look once tests and linter are back to green
k8s-ci-robot: @Nutrymaco: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-vsphere-test-main | f497652b858452e653de8e7c5db82c1143b4d845 | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3049/pull-cluster-api-provider-vsphere-test-main/1856625965367562240) | true | `/test pull-cluster-api-provider-vsphere-test-main`
pull-cluster-api-provider-vsphere-verify-main | edd72e808967a732e5c6eac92903ae64dadf5042 | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3049/pull-cluster-api-provider-vsphere-verify-main/1856638612284641280) | true | `/test pull-cluster-api-provider-vsphere-verify-main`

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

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-vsphere-verify-main | edd72e808967a732e5c6eac92903ae64dadf5042 | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3049/pull-cluster-api-provider-vsphere-verify-main/1856638612284641280) | true | `/test pull-cluster-api-provider-vsphere-verify-main`
pull-cluster-api-provider-vsphere-test-main | edd72e808967a732e5c6eac92903ae64dadf5042 | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_cluster-api-provider-vsphere/3049/pull-cluster-api-provider-vsphere-test-main/1856638612334972928) | true | `/test pull-cluster-api-provider-vsphere-test-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-vsphere&pr=3049). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3ANutrymaco). 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-provider-vsphere/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-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
sbueringer: /test pull-cluster-api-provider-vsphere-e2e-vcsim-govmomi-main
/test pull-cluster-api-provider-vsphere-e2e-vcsim-supervisor-main
sbueringer: Thank you very much!

/lgtm

Will merge once all tests are green
k8s-ci-robot: LGTM label has been added.  <details>Git tree hash: 8f254d42426a20bf548cb025fdf428fc517ca8c8</details>
sbueringer: /approve

Patch:
diff --git a/apis/v1alpha3/topology_conversion.go b/apis/v1alpha3/topology_conversion.go
new file mode 100644
index 00000000..6fe140b1
--- /dev/null
+++ b/apis/v1alpha3/topology_conversion.go
@@ -0,0 +1,26 @@
+/*
+Copyright 2024 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 v1alpha3
+
+import (
+	conversion "k8s.io/apimachinery/pkg/conversion"
+	v1beta1 "sigs.k8s.io/cluster-api-provider-vsphere/apis/v1beta1"
+)
+
+func Convert_v1beta1_Topology_To_v1alpha3_Topology(in *v1beta1.Topology, out *Topology, s conversion.Scope) error {
+	return autoConvert_v1beta1_Topology_To_v1alpha3_Topology(in, out, s)
+}
diff --git a/apis/v1alpha3/vspherefailuredomain_conversion.go b/apis/v1alpha3/vspherefailuredomain_conversion.go
index 357490d2..9a0d262a 100644
--- a/apis/v1alpha3/vspherefailuredomain_conversion.go
+++ b/apis/v1alpha3/vspherefailuredomain_conversion.go
@@ -17,6 +17,7 @@ limitations under the License.
 package v1alpha3
 
 import (
+	utilconversion "sigs.k8s.io/cluster-api/util/conversion"
 	"sigs.k8s.io/controller-runtime/pkg/conversion"
 
 	infrav1 "sigs.k8s.io/cluster-api-provider-vsphere/apis/v1beta1"
@@ -25,13 +26,36 @@ import (
 // ConvertTo converts this VSphereFailureDomain to the Hub version (v1beta1).
 func (src *VSphereFailureDomain) ConvertTo(dstRaw conversion.Hub) error {
 	dst := dstRaw.(*infrav1.VSphereFailureDomain)
-	return Convert_v1alpha3_VSphereFailureDomain_To_v1beta1_VSphereFailureDomain(src, dst, nil)
+
+	if err := Convert_v1alpha3_VSphereFailureDomain_To_v1beta1_VSphereFailureDomain(src, dst, nil); err != nil {
+		return err
+	}
+
+	// Manually restore data.
+	restored := &infrav1.VSphereFailureDomain{}
+	if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok {
+		return err
+	}
+
+	dst.Spec.Topology.NetworkConfigurations = restored.Spec.Topology.NetworkConfigurations
+
+	return nil
 }
 
 // ConvertFrom converts from the Hub version (v1beta1) to this VSphereFailureDomain.
 func (dst *VSphereFailureDomain) ConvertFrom(srcRaw conversion.Hub) error {
 	src := srcRaw.(*infrav1.VSphereFailureDomain)
-	return Convert_v1beta1_VSphereFailureDomain_To_v1alpha3_VSphereFailureDomain(src, dst, nil)
+
+	if err := Convert_v1beta1_VSphereFailureDomain_To_v1alpha3_VSphereFailureDomain(src, dst, nil); err != nil {
+		return err
+	}
+
+	// Preserve Hub data on down-conversion except for metadata
+	if err := utilconversion.MarshalData(src, dst); err != nil {
+		return err
+	}
+
+	return nil
 }
 
 // ConvertTo converts this VSphereFailureDomainList to the Hub version (v1beta1).
diff --git a/apis/v1alpha3/zz_generated.conversion.go b/apis/v1alpha3/zz_generated.conversion.go
index 01772851..2af41228 100644
--- a/apis/v1alpha3/zz_generated.conversion.go
+++ b/apis/v1alpha3/zz_generated.conversion.go
@@ -155,11 +155,6 @@ func RegisterConversions(s *runtime.Scheme) error {
 	}); err != nil {
 		return err
 	}
-	if err := s.AddGeneratedConversionFunc((*v1beta1.Topology)(nil), (*Topology)(nil), func(a, b interface{}, scope conversion.Scope) error {
-		return Convert_v1beta1_Topology_To_v1alpha3_Topology(a.(*v1beta1.Topology), b.(*Topology), scope)
-	}); err != nil {
-		return err
-	}
 	if err := s.AddGeneratedConversionFunc((*VSphereCluster)(nil), (*v1beta1.VSphereCluster)(nil), func(a, b interface{}, scope conversion.Scope) error {
 		return Convert_v1alpha3_VSphereCluster_To_v1beta1_VSphereCluster(a.(*VSphereCluster), b.(*v1beta1.VSphereCluster), scope)
 	}); err != nil {
@@ -460,6 +455,11 @@ func RegisterConversions(s *runtime.Scheme) error {
 	}); err != nil {
 		return err
 	}
+	if err := s.AddConversionFunc((*v1beta1.Topology)(nil), (*Topology)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_Topology_To_v1alpha3_Topology(a.(*v1beta1.Topology), b.(*Topology), scope)
+	}); err != nil {
+		return err
+	}
 	if err := s.AddConversionFunc((*v1beta1.VSphereClusterSpec)(nil), (*VSphereClusterSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
 		return Convert_v1beta1_VSphereClusterSpec_To_v1alpha3_VSphereClusterSpec(a.(*v1beta1.VSphereClusterSpec), b.(*VSphereClusterSpec), scope)
 	}); err != nil {
@@ -859,15 +859,11 @@ func autoConvert_v1beta1_Topology_To_v1alpha3_Topology(in *v1beta1.Topology, out
 	out.ComputeCluster = (*string)(unsafe.Pointer(in.ComputeCluster))
 	out.Hosts = (*FailureDomainHosts)(unsafe.Pointer(in.Hosts))
 	out.Networks = *(*[]string)(unsafe.Pointer(&in.Networks))
+	// WARNING: in.NetworkConfigurations requires manual conversion: does not exist in peer-type
 	out.Datastore = in.Datastore
 	return nil
 }
 
-// Convert_v1beta1_Topology_To_v1alpha3_Topology is an autogenerated conversion function.
-func Convert_v1beta1_Topology_To_v1alpha3_Topology(in *v1beta1.Topology, out *Topology, s conversion.Scope) error {
-	return autoConvert_v1beta1_Topology_To_v1alpha3_Topology(in, out, s)
-}
-
 func autoConvert_v1alpha3_VSphereCluster_To_v1beta1_VSphereCluster(in *VSphereCluster, out *v1beta1.VSphereCluster, s conversion.Scope) error {
 	out.ObjectMeta = in.ObjectMeta
 	if err := Convert_v1alpha3_VSphereClusterSpec_To_v1beta1_VSphereClusterSpec(&in.Spec, &out.Spec, s); err != nil {
@@ -1220,7 +1216,17 @@ func Convert_v1beta1_VSphereFailureDomain_To_v1alpha3_VSphereFailureDomain(in *v
 
 func autoConvert_v1alpha3_VSphereFailureDomainList_To_v1beta1_VSphereFailureDomainList(in *VSphereFailureDomainList, out *v1beta1.VSphereFailureDomainList, s conversion.Scope) error {
 	out.ListMeta = in.ListMeta
-	out.Items = *(*[]v1beta1.VSphereFailureDomain)(unsafe.Pointer(&in.Items))
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]v1beta1.VSphereFailureDomain, len(*in))
+		for i := range *in {
+			if err := Convert_v1alpha3_VSphereFailureDomain_To_v1beta1_VSphereFailureDomain(&(*in)[i], &(*out)[i], s); err != nil {
+				return err
+			}
+		}
+	} else {
+		out.Items = nil
+	}
 	return nil
 }
 
@@ -1231,7 +1237,17 @@ func Convert_v1alpha3_VSphereFailureDomainList_To_v1beta1_VSphereFailureDomainLi
 
 func autoConvert_v1beta1_VSphereFailureDomainList_To_v1alpha3_VSphereFailureDomainList(in *v1beta1.VSphereFailureDomainList, out *VSphereFailureDomainList, s conversion.Scope) error {
 	out.ListMeta = in.ListMeta
-	out.Items = *(*[]VSphereFailureDomain)(unsafe.Pointer(&in.Items))
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]VSphereFailureDomain, len(*in))
+		for i := range *in {
+			if err := Convert_v1beta1_VSphereFailureDomain_To_v1alpha3_VSphereFailureDomain(&(*in)[i], &(*out)[i], s); err != nil {
+				return err
+			}
+		}
+	} else {
+		out.Items = nil
+	}
 	return nil
 }
 
diff --git a/apis/v1alpha4/topology_conversion.go b/apis/v1alpha4/topology_conversion.go
new file mode 100644
index 00000000..fa5f5159
--- /dev/null
+++ b/apis/v1alpha4/topology_conversion.go
@@ -0,0 +1,26 @@
+/*
+Copyright 2024 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 v1alpha4
+
+import (
+	conversion "k8s.io/apimachinery/pkg/conversion"
+	v1beta1 "sigs.k8s.io/cluster-api-provider-vsphere/apis/v1beta1"
+)
+
+func Convert_v1beta1_Topology_To_v1alpha4_Topology(in *v1beta1.Topology, out *Topology, s conversion.Scope) error {
+	return autoConvert_v1beta1_Topology_To_v1alpha4_Topology(in, out, s)
+}
diff --git a/apis/v1alpha4/vspherefailuredomain_conversion.go b/apis/v1alpha4/vspherefailuredomain_conversion.go
index 006033f9..1357cf84 100644
--- a/apis/v1alpha4/vspherefailuredomain_conversion.go
+++ b/apis/v1alpha4/vspherefailuredomain_conversion.go
@@ -17,6 +17,7 @@ limitations under the License.
 package v1alpha4
 
 import (
+	utilconversion "sigs.k8s.io/cluster-api/util/conversion"
 	"sigs.k8s.io/controller-runtime/pkg/conversion"
 
 	infrav1 "sigs.k8s.io/cluster-api-provider-vsphere/apis/v1beta1"
@@ -25,13 +26,36 @@ import (
 // ConvertTo converts this VSphereFailureDomain to the Hub version (v1beta1).
 func (src *VSphereFailureDomain) ConvertTo(dstRaw conversion.Hub) error {
 	dst := dstRaw.(*infrav1.VSphereFailureDomain)
-	return Convert_v1alpha4_VSphereFailureDomain_To_v1beta1_VSphereFailureDomain(src, dst, nil)
+
+	if err := Convert_v1alpha4_VSphereFailureDomain_To_v1beta1_VSphereFailureDomain(src, dst, nil); err != nil {
+		return err
+	}
+
+	// Manually restore data.
+	restored := &infrav1.VSphereFailureDomain{}
+	if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok {
+		return err
+	}
+
+	dst.Spec.Topology.NetworkConfigurations = restored.Spec.Topology.NetworkConfigurations
+
+	return nil
 }
 
 // ConvertFrom converts from the Hub version (v1beta1) to this VSphereFailureDomain.
 func (dst *VSphereFailureDomain) ConvertFrom(srcRaw conversion.Hub) error {
 	src := srcRaw.(*infrav1.VSphereFailureDomain)
-	return Convert_v1beta1_VSphereFailureDomain_To_v1alpha4_VSphereFailureDomain(src, dst, nil)
+
+	if err := Convert_v1beta1_VSphereFailureDomain_To_v1alpha4_VSphereFailureDomain(src, dst, nil); err != nil {
+		return err
+	}
+
+	// Preserve Hub data on down-conversion except for metadata
+	if err := utilconversion.MarshalData(src, dst); err != nil {
+		return err
+	}
+
+	return nil
 }
 
 // ConvertTo converts this VSphereFailureDomainList to the Hub version (v1beta1).
diff --git a/apis/v1alpha4/zz_generated.conversion.go b/apis/v1alpha4/zz_generated.conversion.go
index 92e8dc98..25993eba 100644
--- a/apis/v1alpha4/zz_generated.conversion.go
+++ b/apis/v1alpha4/zz_generated.conversion.go
@@ -155,11 +155,6 @@ func RegisterConversions(s *runtime.Scheme) error {
 	}); err != nil {
 		return err
 	}
-	if err := s.AddGeneratedConversionFunc((*v1beta1.Topology)(nil), (*Topology)(nil), func(a, b interface{}, scope conversion.Scope) error {
-		return Convert_v1beta1_Topology_To_v1alpha4_Topology(a.(*v1beta1.Topology), b.(*Topology), scope)
-	}); err != nil {
-		return err
-	}
 	if err := s.AddGeneratedConversionFunc((*VSphereCluster)(nil), (*v1beta1.VSphereCluster)(nil), func(a, b interface{}, scope conversion.Scope) error {
 		return Convert_v1alpha4_VSphereCluster_To_v1beta1_VSphereCluster(a.(*VSphereCluster), b.(*v1beta1.VSphereCluster), scope)
 	}); err != nil {
@@ -500,6 +495,11 @@ func RegisterConversions(s *runtime.Scheme) error {
 	}); err != nil {
 		return err
 	}
+	if err := s.AddConversionFunc((*v1beta1.Topology)(nil), (*Topology)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_Topology_To_v1alpha4_Topology(a.(*v1beta1.Topology), b.(*Topology), scope)
+	}); err != nil {
+		return err
+	}
 	if err := s.AddConversionFunc((*v1beta1.VSphereClusterSpec)(nil), (*VSphereClusterSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
 		return Convert_v1beta1_VSphereClusterSpec_To_v1alpha4_VSphereClusterSpec(a.(*v1beta1.VSphereClusterSpec), b.(*VSphereClusterSpec), scope)
 	}); err != nil {
@@ -895,15 +895,11 @@ func autoConvert_v1beta1_Topology_To_v1alpha4_Topology(in *v1beta1.Topology, out
 	out.ComputeCluster = (*string)(unsafe.Pointer(in.ComputeCluster))
 	out.Hosts = (*FailureDomainHosts)(unsafe.Pointer(in.Hosts))
 	out.Networks = *(*[]string)(unsafe.Pointer(&in.Networks))
+	// WARNING: in.NetworkConfigurations requires manual conversion: does not exist in peer-type
 	out.Datastore = in.Datastore
 	return nil
 }
 
-// Convert_v1beta1_Topology_To_v1alpha4_Topology is an autogenerated conversion function.
-func Convert_v1beta1_Topology_To_v1alpha4_Topology(in *v1beta1.Topology, out *Topology, s conversion.Scope) error {
-	return autoConvert_v1beta1_Topology_To_v1alpha4_Topology(in, out, s)
-}
-
 func autoConvert_v1alpha4_VSphereCluster_To_v1beta1_VSphereCluster(in *VSphereCluster, out *v1beta1.VSphereCluster, s conversion.Scope) error {
 	out.ObjectMeta = in.ObjectMeta
 	if err := Convert_v1alpha4_VSphereClusterSpec_To_v1beta1_VSphereClusterSpec(&in.Spec, &out.Spec, s); err != nil {
@@ -1374,7 +1370,17 @@ func Convert_v1beta1_VSphereFailureDomain_To_v1alpha4_VSphereFailureDomain(in *v
 
 func autoConvert_v1alpha4_VSphereFailureDomainList_To_v1beta1_VSphereFailureDomainList(in *VSphereFailureDomainList, out *v1beta1.VSphereFailureDomainList, s conversion.Scope) error {
 	out.ListMeta = in.ListMeta
-	out.Items = *(*[]v1beta1.VSphereFailureDomain)(unsafe.Pointer(&in.Items))
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]v1beta1.VSphereFailureDomain, len(*in))
+		for i := range *in {
+			if err := Convert_v1alpha4_VSphereFailureDomain_To_v1beta1_VSphereFailureDomain(&(*in)[i], &(*out)[i], s); err != nil {
+				return err
+			}
+		}
+	} else {
+		out.Items = nil
+	}
 	return nil
 }
 
@@ -1385,7 +1391,17 @@ func Convert_v1alpha4_VSphereFailureDomainList_To_v1beta1_VSphereFailureDomainLi
 
 func autoConvert_v1beta1_VSphereFailureDomainList_To_v1alpha4_VSphereFailureDomainList(in *v1beta1.VSphereFailureDomainList, out *VSphereFailureDomainList, s conversion.Scope) error {
 	out.ListMeta = in.ListMeta
-	out.Items = *(*[]VSphereFailureDomain)(unsafe.Pointer(&in.Items))
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]VSphereFailureDomain, len(*in))
+		for i := range *in {
+			if err := Convert_v1beta1_VSphereFailureDomain_To_v1alpha4_VSphereFailureDomain(&(*in)[i], &(*out)[i], s); err != nil {
+				return err
+			}
+		}
+	} else {
+		out.Items = nil
+	}
 	return nil
 }
 
diff --git a/apis/v1beta1/vspherefailuredomain_types.go b/apis/v1beta1/vspherefailuredomain_types.go
index bcb139a9..bea116c7 100644
--- a/apis/v1beta1/vspherefailuredomain_types.go
+++ b/apis/v1beta1/vspherefailuredomain_types.go
@@ -17,6 +17,7 @@ limitations under the License.
 package v1beta1
 
 import (
+	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -81,12 +82,69 @@ type Topology struct {
 	// +optional
 	Networks []string `json:"networks,omitempty"`
 
+	// NetworkConfigurations is a list of network configurations within this failure domain.
+	// +optional
+	// +listType=map
+	// +listMapKey=networkName
+	NetworkConfigurations []NetworkConfiguration `json:"networkConfigurations,omitempty"`
+
 	// Datastore is the name or inventory path of the datastore in which the
 	// virtual machine is created/located.
 	// +optional
 	Datastore string `json:"datastore,omitempty"`
 }
 
+// NetworkConfiguration defines a network configuration that should be used when consuming
+// a failure domain.
+type NetworkConfiguration struct {
+	// NetworkName is the network name for this machine's VM.
+	// +kubebuilder:validation:Required
+	NetworkName string `json:"networkName"`
+
+	// DHCP4 is a flag that indicates whether or not to use DHCP for IPv4.
+	// +optional
+	DHCP4 *bool `json:"dhcp4,omitempty"`
+
+	// DHCP6 is a flag that indicates whether or not to use DHCP for IPv6.
+	// +optional
+	DHCP6 *bool `json:"dhcp6,omitempty"`
+
+	// Nameservers is a list of IPv4 and/or IPv6 addresses used as DNS
+	// nameservers.
+	// Please note that Linux allows only three nameservers (https://linux.die.net/man/5/resolv.conf).
+	// +optional
+	Nameservers []string `json:"nameservers,omitempty"`
+
+	// SearchDomains is a list of search domains used when resolving IP
+	// addresses with DNS.
+	// +optional
+	SearchDomains []string `json:"searchDomains,omitempty"`
+
+	// DHCP4Overrides allows for the control over several DHCP behaviors.
+	// Overrides will only be applied when the corresponding DHCP flag is set.
+	// Only configured values will be sent, omitted values will default to
+	// distribution defaults.
+	// Dependent on support in the network stack for your distribution.
+	// For more information see the netplan reference (https://netplan.io/reference#dhcp-overrides)
+	// +optional
+	DHCP4Overrides *DHCPOverrides `json:"dhcp4Overrides,omitempty"`
+
+	// DHCP6Overrides allows for the control over several DHCP behaviors.
+	// Overrides will only be applied when the corresponding DHCP flag is set.
+	// Only configured values will be sent, omitted values will default to
+	// distribution defaults.
+	// Dependent on support in the network stack for your distribution.
+	// For more information see the netplan reference (https://netplan.io/reference#dhcp-overrides)
+	// +optional
+	DHCP6Overrides *DHCPOverrides `json:"dhcp6Overrides,omitempty"`
+
+	// AddressesFromPools is a list of IPAddressPools that should be assigned
+	// to IPAddressClaims. The machine's cloud-init metadata will be populated
+	// with IPAddresses fulfilled by an IPAM provider.
+	// +optional
+	AddressesFromPools []corev1.TypedLocalObjectReference `json:"addressesFromPools,omitempty"`
+}
+
 // FailureDomainHosts has information required for placement of machines on VSphere hosts.
 type FailureDomainHosts struct {
 	// VMGroupName is the name of the VM group
diff --git a/apis/v1beta1/zz_generated.deepcopy.go b/apis/v1beta1/zz_generated.deepcopy.go
index 44d12a65..6398eb52 100644
--- a/apis/v1beta1/zz_generated.deepcopy.go
+++ b/apis/v1beta1/zz_generated.deepcopy.go
@@ -194,6 +194,58 @@ func (in *Network) DeepCopy() *Network {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *NetworkConfiguration) DeepCopyInto(out *NetworkConfiguration) {
+	*out = *in
+	if in.DHCP4 != nil {
+		in, out := &in.DHCP4, &out.DHCP4
+		*out = new(bool)
+		**out = **in
+	}
+	if in.DHCP6 != nil {
+		in, out := &in.DHCP6, &out.DHCP6
+		*out = new(bool)
+		**out = **in
+	}
+	if in.Nameservers != nil {
+		in, out := &in.Nameservers, &out.Nameservers
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.SearchDomains != nil {
+		in, out := &in.SearchDomains, &out.SearchDomains
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.DHCP4Overrides != nil {
+		in, out := &in.DHCP4Overrides, &out.DHCP4Overrides
+		*out = new(DHCPOverrides)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.DHCP6Overrides != nil {
+		in, out := &in.DHCP6Overrides, &out.DHCP6Overrides
+		*out = new(DHCPOverrides)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.AddressesFromPools != nil {
+		in, out := &in.AddressesFromPools, &out.AddressesFromPools
+		*out = make([]v1.TypedLocalObjectReference, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkConfiguration.
+func (in *NetworkConfiguration) DeepCopy() *NetworkConfiguration {
+	if in == nil {
+		return nil
+	}
+	out := new(NetworkConfiguration)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *NetworkDeviceSpec) DeepCopyInto(out *NetworkDeviceSpec) {
 	*out = *in
@@ -391,6 +443,13 @@ func (in *Topology) DeepCopyInto(out *Topology) {
 		*out = make([]string, len(*in))
 		copy(*out, *in)
 	}
+	if in.NetworkConfigurations != nil {
+		in, out := &in.NetworkConfigurations, &out.NetworkConfigurations
+		*out = make([]NetworkConfiguration, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Topology.
diff --git a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspherefailuredomains.yaml b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspherefailuredomains.yaml
index 0e102bda..b690297e 100644
--- a/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspherefailuredomains.yaml
+++ b/config/default/crd/bases/infrastructure.cluster.x-k8s.io_vspherefailuredomains.yaml
@@ -362,6 +362,190 @@ spec:
                     - hostGroupName
                     - vmGroupName
                     type: object
+                  networkConfigurations:
+                    description: NetworkConfigurations is a list of network configurations
+                      within this failure domain.
+                    items:
+                      description: |-
+                        NetworkConfiguration defines a network configuration that should be used when consuming
+                        a failure domain.
+                      properties:
+                        addressesFromPools:
+                          description: |-
+                            AddressesFromPools is a list of IPAddressPools that should be assigned
+                            to IPAddressClaims. The machine's cloud-init metadata will be populated
+                            with IPAddresses fulfilled by an IPAM provider.
+                          items:
+                            description: |-
+                              TypedLocalObjectReference contains enough information to let you locate the
+                              typed referenced object inside the same namespace.
+                            properties:
+                              apiGroup:
+                                description: |-
+                                  APIGroup is the group for the resource being referenced.
+                                  If APIGroup is not specified, the specified Kind must be in the core API group.
+                                  For any other third-party types, APIGroup is required.
+                                type: string
+                              kind:
+                                description: Kind is the type of resource being referenced
+                                type: string
+                              name:
+                                description: Name is the name of resource being referenced
+                                type: string
+                            required:
+                            - kind
+                            - name
+                            type: object
+                            x-kubernetes-map-type: atomic
+                          type: array
+                        dhcp4:
+                          description: DHCP4 is a flag that indicates whether or not
+                            to use DHCP for IPv4.
+                          type: boolean
+                        dhcp4Overrides:
+                          description: |-
+                            DHCP4Overrides allows for the control over several DHCP behaviors.
+                            Overrides will only be applied when the corresponding DHCP flag is set.
+                            Only configured values will be sent, omitted values will default to
+                            distribution defaults.
+                            Dependent on support in the network stack for your distribution.
+                            For more information see the netplan reference (https://netplan.io/reference#dhcp-overrides)
+                          properties:
+                            hostname:
+                              description: |-
+                                Hostname is the name which will be sent to the DHCP server instead of
+                                the machine's hostname.
+                              type: string
+                            routeMetric:
+                              description: |-
+                                RouteMetric is used to prioritize routes for devices. A lower metric for
+                                an interface will have a higher priority.
+                              type: integer
+                            sendHostname:
+                              description: |-
+                                SendHostname when `true`, the hostname of the machine will be sent to the
+                                DHCP server.
+                              type: boolean
+                            useDNS:
+                              description: |-
+                                UseDNS when `true`, the DNS servers in the DHCP server will be used and
+                                take precedence.
+                              type: boolean
+                            useDomains:
+                              description: |-
+                                UseDomains can take the values `true`, `false`, or `route`. When `true`,
+                                the domain name from the DHCP server will be used as the DNS search
+                                domain for this device. When `route`, the domain name from the DHCP
+                                response will be used for routing DNS only, not for searching.
+                              type: string
+                            useHostname:
+                              description: |-
+                                UseHostname when `true`, the hostname from the DHCP server will be set
+                                as the transient hostname of the machine.
+                              type: boolean
+                            useMTU:
+                              description: |-
+                                UseMTU when `true`, the MTU from the DHCP server will be set as the
+                                MTU of the device.
+                              type: boolean
+                            useNTP:
+                              description: |-
+                                UseNTP when `true`, the NTP servers from the DHCP server will be used
+                                by systemd-timesyncd and take precedence.
+                              type: boolean
+                            useRoutes:
+                              description: |-
+                                UseRoutes when `true`, the routes from the DHCP server will be installed
+                                in the routing table.
+                              type: string
+                          type: object
+                        dhcp6:
+                          description: DHCP6 is a flag that indicates whether or not
+                            to use DHCP for IPv6.
+                          type: boolean
+                        dhcp6Overrides:
+                          description: |-
+                            DHCP6Overrides allows for the control over several DHCP behaviors.
+                            Overrides will only be applied when the corresponding DHCP flag is set.
+                            Only configured values will be sent, omitted values will default to
+                            distribution defaults.
+                            Dependent on support in the network stack for your distribution.
+                            For more information see the netplan reference (https://netplan.io/reference#dhcp-overrides)
+                          properties:
+                            hostname:
+                              description: |-
+                                Hostname is the name which will be sent to the DHCP server instead of
+                                the machine's hostname.
+                              type: string
+                            routeMetric:
+                              description: |-
+                                RouteMetric is used to prioritize routes for devices. A lower metric for
+                                an interface will have a higher priority.
+                              type: integer
+                            sendHostname:
+                              description: |-
+                                SendHostname when `true`, the hostname of the machine will be sent to the
+                                DHCP server.
+                              type: boolean
+                            useDNS:
+                              description: |-
+                                UseDNS when `true`, the DNS servers in the DHCP server will be used and
+                                take precedence.
+                              type: boolean
+                            useDomains:
+                              description: |-
+                                UseDomains can take the values `true`, `false`, or `route`. When `true`,
+                                the domain name from the DHCP server will be used as the DNS search
+                                domain for this device. When `route`, the domain name from the DHCP
+                                response will be used for routing DNS only, not for searching.
+                              type: string
+                            useHostname:
+                              description: |-
+                                UseHostname when `true`, the hostname from the DHCP server will be set
+                                as the transient hostname of the machine.
+                              type: boolean
+                            useMTU:
+                              description: |-
+                                UseMTU when `true`, the MTU from the DHCP server will be set as the
+                                MTU of the device.
+                              type: boolean
+                            useNTP:
+                              description: |-
+                                UseNTP when `true`, the NTP servers from the DHCP server will be used
+                                by systemd-timesyncd and take precedence.
+                              type: boolean
+                            useRoutes:
+                              description: |-
+                                UseRoutes when `true`, the routes from the DHCP server will be installed
+                                in the routing table.
+                              type: string
+                          type: object
+                        nameservers:
+                          description: |-
+                            Nameservers is a list of IPv4 and/or IPv6 addresses used as DNS
+                            nameservers.
+                            Please note that Linux allows only three nameservers (https://linux.die.net/man/5/resolv.conf).
+                          items:
+                            type: string
+                          type: array
+                        networkName:
+                          description: NetworkName is the network name for this machine's
+                            VM.
+                          type: string
+                        searchDomains:
+                          description: |-
+                            SearchDomains is a list of search domains used when resolving IP
+                            addresses with DNS.
+                          items:
+                            type: string
+                          type: array
+                      required:
+                      - networkName
+                      type: object
+                    type: array
+                    x-kubernetes-list-map-keys:
+                    - networkName
+                    x-kubernetes-list-type: map
                   networks:
                     description: Networks is the list of networks within this failure
                       domain
diff --git a/controllers/vspheredeploymentzone_controller_domain.go b/controllers/vspheredeploymentzone_controller_domain.go
index b5b23ee8..32052bf1 100644
--- a/controllers/vspheredeploymentzone_controller_domain.go
+++ b/controllers/vspheredeploymentzone_controller_domain.go
@@ -95,11 +95,18 @@ func (r vsphereDeploymentZoneReconciler) reconcileTopology(ctx context.Context,
 
 	for _, network := range topology.Networks {
 		if _, err := deploymentZoneCtx.AuthSession.Finder.Network(ctx, network); err != nil {
-			conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition, infrav1.NetworkNotFoundReason, clusterv1.ConditionSeverityError, "network %s is misconfigured", network)
+			conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition, infrav1.NetworkNotFoundReason, clusterv1.ConditionSeverityError, "network %s is not found", network)
 			return errors.Wrapf(err, "unable to find network %s", network)
 		}
 	}
 
+	for _, networkConfig := range topology.NetworkConfigurations {
+		if _, err := deploymentZoneCtx.AuthSession.Finder.Network(ctx, networkConfig.NetworkName); err != nil {
+			conditions.MarkFalse(deploymentZoneCtx.VSphereDeploymentZone, infrav1.VSphereFailureDomainValidatedCondition, infrav1.NetworkNotFoundReason, clusterv1.ConditionSeverityError, "network %s is not found", networkConfig.NetworkName)
+			return errors.Wrapf(err, "unable to find network %s", networkConfig.NetworkName)
+		}
+	}
+
 	if hostPlacementInfo := topology.Hosts; hostPlacementInfo != nil {
 		rule, err := cluster.VerifyAffinityRule(ctx, deploymentZoneCtx, *topology.ComputeCluster, hostPlacementInfo.HostGroupName, hostPlacementInfo.VMGroupName)
 		switch {
diff --git a/internal/webhooks/vspherefailuredomain.go b/internal/webhooks/vspherefailuredomain.go
index 53c0a238..6cc41add 100644
--- a/internal/webhooks/vspherefailuredomain.go
+++ b/internal/webhooks/vspherefailuredomain.go
@@ -77,6 +77,16 @@ func (webhook *VSphereFailureDomainWebhook) ValidateCreate(_ context.Context, ra
 		allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "Topology", "ComputeCluster"), fmt.Sprintf("cannot be nil if zone's Failure Domain type is %s", obj.Spec.Zone.Type)))
 	}
 
+	if len(obj.Spec.Topology.NetworkConfigurations) != 0 && len(obj.Spec.Topology.Networks) != 0 {
+		allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "topology", "networks"), "cannot be set if spec.topology.networkConfigurations is already set"))
+	}
+
+	for i, networkConfig := range obj.Spec.Topology.NetworkConfigurations {
+		if networkConfig.NetworkName == "" {
+			allErrs = append(allErrs, field.Required(field.NewPath("spec", "topology", "networkConfigurations").Index(i).Child("networkName"), "cannot be empty"))
+		}
+	}
+
 	return nil, AggregateObjErrors(obj.GroupVersionKind().GroupKind(), obj.Name, allErrs)
 }
 
diff --git a/pkg/services/vimmachine.go b/pkg/services/vimmachine.go
index abda5dc5..04268eaf 100644
--- a/pkg/services/vimmachine.go
+++ b/pkg/services/vimmachine.go
@@ -436,7 +436,10 @@ func (v *VimMachineService) generateOverrideFunc(ctx context.Context, vimMachine
 			vm.Spec.Datastore = vsphereFailureDomain.Spec.Topology.Datastore
 		}
 		if len(vsphereFailureDomain.Spec.Topology.Networks) > 0 {
-			vm.Spec.Network.Devices = overrideNetworkDeviceSpecs(vm.Spec.Network.Devices, vsphereFailureDomain.Spec.Topology.Networks)
+			vm.Spec.Network.Devices = overrideNetworkDeviceSpecs(vm.Spec.Network.Devices, vsphereFailureDomain.Spec.Topology.Networks, mergeFailureDomainNetworkName)
+		}
+		if len(vsphereFailureDomain.Spec.Topology.NetworkConfigurations) > 0 {
+			vm.Spec.Network.Devices = overrideNetworkDeviceSpecs(vm.Spec.Network.Devices, vsphereFailureDomain.Spec.Topology.NetworkConfigurations, mergeNetworkConfigurationInNetworkDeviceSpec)
 		}
 	}
 	return overrideWithFailureDomainFunc, true
@@ -446,7 +449,7 @@ func (v *VimMachineService) generateOverrideFunc(ctx context.Context, vimMachine
 // The substitution is done based on the order in which the network devices have been defined.
 //
 // In case there are more network definitions than the number of network devices specified, the definitions are appended to the list.
-func overrideNetworkDeviceSpecs(deviceSpecs []infrav1.NetworkDeviceSpec, networks []string) []infrav1.NetworkDeviceSpec {
+func overrideNetworkDeviceSpecs[T any](deviceSpecs []infrav1.NetworkDeviceSpec, networks []T, mergeFunc func(device *infrav1.NetworkDeviceSpec, network T)) []infrav1.NetworkDeviceSpec {
 	index, length := 0, len(networks)
 
 	devices := make([]infrav1.NetworkDeviceSpec, 0, max(length, len(deviceSpecs)))
@@ -455,16 +458,54 @@ func overrideNetworkDeviceSpecs(deviceSpecs []infrav1.NetworkDeviceSpec, network
 		vmNetworkDeviceSpec := deviceSpecs[i]
 		if i < length {
 			index++
-			vmNetworkDeviceSpec.NetworkName = networks[i]
+			mergeFunc(&vmNetworkDeviceSpec, networks[i])
 		}
 		devices = append(devices, vmNetworkDeviceSpec)
 	}
 	// append the remaining network definitions to the VM spec
 	for ; index < length; index++ {
-		devices = append(devices, infrav1.NetworkDeviceSpec{
-			NetworkName: networks[index],
-		})
+		device := infrav1.NetworkDeviceSpec{}
+		mergeFunc(&device, networks[index])
+		devices = append(devices, device)
 	}
 
 	return devices
 }
+
+func mergeFailureDomainNetworkName(device *infrav1.NetworkDeviceSpec, network string) {
+	device.NetworkName = network
+}
+
+func mergeNetworkConfigurationInNetworkDeviceSpec(device *infrav1.NetworkDeviceSpec, nc infrav1.NetworkConfiguration) {
+	if nc.NetworkName != "" {
+		device.NetworkName = nc.NetworkName
+	}
+	if nc.DHCP4 != nil {
+		device.DHCP4 = *nc.DHCP4
+	}
+	if nc.DHCP6 != nil {
+		device.DHCP6 = *nc.DHCP6
+	}
+	if len(nc.Nameservers) > 0 {
+		device.Nameservers = make([]string, len(nc.Nameservers))
+		copy(device.Nameservers, nc.Nameservers)
+	}
+
+	if len(nc.SearchDomains) > 0 {
+		device.SearchDomains = make([]string, len(nc.SearchDomains))
+		copy(device.SearchDomains, nc.SearchDomains)
+	}
+
+	if nc.DHCP4Overrides != nil {
+		device.DHCP4Overrides = nc.DHCP4Overrides.DeepCopy()
+	}
+
+	if nc.DHCP6Overrides != nil {
+		device.DHCP6Overrides = nc.DHCP6Overrides.DeepCopy()
+	}
+
+	if len(nc.AddressesFromPools) > 0 {
+		device.AddressesFromPools = make([]corev1.TypedLocalObjectReference, len(nc.AddressesFromPools))
+		copy(device.AddressesFromPools, nc.AddressesFromPools)
+	}
+}


Test patch:
diff --git a/internal/webhooks/vspherefailuredomain_test.go b/internal/webhooks/vspherefailuredomain_test.go
index 5425d29f..4d6e0d78 100644
--- a/internal/webhooks/vspherefailuredomain_test.go
+++ b/internal/webhooks/vspherefailuredomain_test.go
@@ -132,6 +132,27 @@ func TestVSphereFailureDomain_ValidateCreate(t *testing.T) {
 				},
 			}},
 		},
+		{
+			name: "topology.networks and topology.networkConfigurations set simultaneously",
+			failureDomain: infrav1.VSphereFailureDomain{Spec: infrav1.VSphereFailureDomainSpec{
+				Region: infrav1.FailureDomain{
+					Name:        "foo",
+					Type:        infrav1.DatacenterFailureDomain,
+					TagCategory: "k8s-bar",
+				},
+				Zone: infrav1.FailureDomain{
+					Name:        "foo",
+					Type:        infrav1.ComputeClusterFailureDomain,
+					TagCategory: "k8s-bar",
+				},
+				Topology: infrav1.Topology{
+					Datacenter:            "/blah",
+					ComputeCluster:        ptr.To("blah2"),
+					Networks:              []string{"nw-1"},
+					NetworkConfigurations: []infrav1.NetworkConfiguration{{NetworkName: "nw-1"}},
+				},
+			}},
+		},
 	}
 
 	for _, tt := range tests {
@@ -140,7 +161,7 @@ func TestVSphereFailureDomain_ValidateCreate(t *testing.T) {
 		t.Run(tt.name, func(*testing.T) {
 			webhook := &VSphereFailureDomainWebhook{}
 			_, err := webhook.ValidateCreate(context.Background(), &tt.failureDomain)
-			if tt.errExpected == nil || !*tt.errExpected {
+			if tt.errExpected == nil || *tt.errExpected {
 				g.Expect(err).To(HaveOccurred())
 			} else {
 				g.Expect(err).NotTo(HaveOccurred())
diff --git a/pkg/services/vimmachine_test.go b/pkg/services/vimmachine_test.go
index 27dab085..c3961bb8 100644
--- a/pkg/services/vimmachine_test.go
+++ b/pkg/services/vimmachine_test.go
@@ -65,6 +65,28 @@ func Test_VimMachineService_GenerateOverrideFunc(t *testing.T) {
 		}
 	}
 
+	failureDomainWithNetworkConfigs := func(suffix string) *infrav1.VSphereFailureDomain {
+		return &infrav1.VSphereFailureDomain{
+			ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("fd-%s", suffix)},
+			Spec: infrav1.VSphereFailureDomainSpec{
+				Topology: infrav1.Topology{
+					Datacenter: fmt.Sprintf("dc-%s", suffix),
+					Datastore:  fmt.Sprintf("ds-%s", suffix),
+					NetworkConfigurations: []infrav1.NetworkConfiguration{
+						{
+							NetworkName: fmt.Sprintf("nw-%s", suffix),
+							DHCP4:       ptr.To(true),
+						},
+						{
+							NetworkName: "another-nw",
+							DHCP6:       ptr.To(true),
+						},
+					},
+				},
+			},
+		}
+	}
+
 	t.Run("does not generate an override function when Failure Domain is not present", func(t *testing.T) {
 		g := NewWithT(t)
 		controllerManagerContext := fake.NewControllerManagerContext(deplZone("one"), deplZone("two"), failureDomain("one"), failureDomain("two"))
@@ -145,6 +167,35 @@ func Test_VimMachineService_GenerateOverrideFunc(t *testing.T) {
 		g.Expect(devices[1].NetworkName).To(Equal("another-nw"))
 	})
 
+	t.Run("overrides the n/w config from the network config list of the topology for equal number of networks", func(t *testing.T) {
+		g := NewWithT(t)
+		controllerManagerContext := fake.NewControllerManagerContext(deplZone("one"), deplZone("two"), failureDomainWithNetworkConfigs("one"), failureDomainWithNetworkConfigs("two"))
+		machineCtx := fake.NewMachineContext(ctx, fake.NewClusterContext(ctx, controllerManagerContext), controllerManagerContext)
+		machineCtx.Machine.Spec.FailureDomain = ptr.To("zone-one")
+		vimMachineService := &VimMachineService{controllerManagerContext.Client}
+
+		vm := &infrav1.VSphereVM{
+			Spec: infrav1.VSphereVMSpec{
+				VirtualMachineCloneSpec: infrav1.VirtualMachineCloneSpec{
+					Network: infrav1.NetworkSpec{Devices: []infrav1.NetworkDeviceSpec{{NetworkName: "foo", DHCP4: false}, {NetworkName: "bar", DHCP6: false}}},
+				},
+			},
+		}
+
+		overrideFunc, ok := vimMachineService.generateOverrideFunc(ctx, machineCtx)
+		g.Expect(ok).To(BeTrue())
+
+		overrideFunc(vm)
+
+		devices := vm.Spec.Network.Devices
+		g.Expect(devices).To(HaveLen(2))
+		g.Expect(devices[0].NetworkName).To(Equal("nw-one"))
+		g.Expect(devices[0].DHCP4).To(BeTrue())
+
+		g.Expect(devices[1].NetworkName).To(Equal("another-nw"))
+		g.Expect(devices[1].DHCP6).To(BeTrue())
+	})
+
 	t.Run("appends the n/w names present in the networks list of the topology with number of devices in VMSpec < number of networks in the placement constraint", func(t *testing.T) {
 		g := NewWithT(t)
 		controllerManagerContext := fake.NewControllerManagerContext(deplZone("one"), deplZone("two"), failureDomain("one"), failureDomain("two"))
@@ -172,6 +223,35 @@ func Test_VimMachineService_GenerateOverrideFunc(t *testing.T) {
 		g.Expect(devices[1].NetworkName).To(Equal("another-nw"))
 	})
 
+	t.Run("appends the n/w configs present in the network config list of the topology with number of devices in VMSpec < number of networks in the placement constraint", func(t *testing.T) {
+		g := NewWithT(t)
+		controllerManagerContext := fake.NewControllerManagerContext(deplZone("one"), deplZone("two"), failureDomainWithNetworkConfigs("one"), failureDomainWithNetworkConfigs("two"))
+		machineCtx := fake.NewMachineContext(ctx, fake.NewClusterContext(ctx, controllerManagerContext), controllerManagerContext)
+		machineCtx.Machine.Spec.FailureDomain = ptr.To("zone-one")
+		vimMachineService := &VimMachineService{controllerManagerContext.Client}
+
+		vm := &infrav1.VSphereVM{
+			Spec: infrav1.VSphereVMSpec{
+				VirtualMachineCloneSpec: infrav1.VirtualMachineCloneSpec{
+					Network: infrav1.NetworkSpec{Devices: []infrav1.NetworkDeviceSpec{{NetworkName: "foo", DHCP4: false}}},
+				},
+			},
+		}
+
+		overrideFunc, ok := vimMachineService.generateOverrideFunc(ctx, machineCtx)
+		g.Expect(ok).To(BeTrue())
+
+		overrideFunc(vm)
+
+		devices := vm.Spec.Network.Devices
+		g.Expect(devices).To(HaveLen(2))
+		g.Expect(devices[0].NetworkName).To(Equal("nw-one"))
+		g.Expect(devices[0].DHCP4).To(BeTrue())
+
+		g.Expect(devices[1].NetworkName).To(Equal("another-nw"))
+		g.Expect(devices[1].DHCP6).To(BeTrue())
+	})
+
 	t.Run("only overrides the n/w names present in the networks list of the topology with number of devices in VMSpec > number of networks in the placement constraint", func(t *testing.T) {
 		g := NewWithT(t)
 		controllerManagerContext := fake.NewControllerManagerContext(deplZone("one"), deplZone("two"), failureDomain("one"), failureDomain("two"))
@@ -200,6 +280,92 @@ func Test_VimMachineService_GenerateOverrideFunc(t *testing.T) {
 
 		g.Expect(devices[2].NetworkName).To(Equal("baz"))
 	})
+
+	t.Run("only overrides the n/w configs present in the network config list of the topology with number of devices in VMSpec > number of networks in the placement constraint", func(t *testing.T) {
+		g := NewWithT(t)
+		controllerManagerContext := fake.NewControllerManagerContext(deplZone("one"), deplZone("two"), failureDomainWithNetworkConfigs("one"), failureDomainWithNetworkConfigs("two"))
+		machineCtx := fake.NewMachineContext(ctx, fake.NewClusterContext(ctx, controllerManagerContext), controllerManagerContext)
+		machineCtx.Machine.Spec.FailureDomain = ptr.To("zone-one")
+		vimMachineService := &VimMachineService{controllerManagerContext.Client}
+
+		vm := &infrav1.VSphereVM{
+			Spec: infrav1.VSphereVMSpec{
+				VirtualMachineCloneSpec: infrav1.VirtualMachineCloneSpec{
+					Network: infrav1.NetworkSpec{Devices: []infrav1.NetworkDeviceSpec{{NetworkName: "foo", DHCP4: false}, {NetworkName: "bar", DHCP6: false}, {NetworkName: "baz", DHCP6: false}}},
+				},
+			},
+		}
+
+		overrideFunc, ok := vimMachineService.generateOverrideFunc(ctx, machineCtx)
+		g.Expect(ok).To(BeTrue())
+
+		overrideFunc(vm)
+
+		devices := vm.Spec.Network.Devices
+		g.Expect(devices).To(HaveLen(3))
+		g.Expect(devices[0].NetworkName).To(Equal("nw-one"))
+		g.Expect(devices[0].DHCP4).To(BeTrue())
+
+		g.Expect(devices[1].NetworkName).To(Equal("another-nw"))
+		g.Expect(devices[1].DHCP6).To(BeTrue())
+
+		g.Expect(devices[2].NetworkName).To(Equal("baz"))
+		g.Expect(devices[2].DHCP6).To(BeFalse())
+	})
+}
+
+func Test_mergeNetworkConfigurationToNetworkDeviceSpec(t *testing.T) {
+	t.Run("all fields from NetworkConfiguration are overridden", func(t *testing.T) {
+		g := NewWithT(t)
+
+		device := infrav1.NetworkDeviceSpec{}
+
+		mergeNetworkConfigurationInNetworkDeviceSpec(&device, infrav1.NetworkConfiguration{
+			NetworkName:   "nw-name",
+			DHCP4:         ptr.To(true),
+			DHCP6:         ptr.To(false),
+			Nameservers:   []string{"1.1.1.1"},
+			SearchDomains: []string{"vmware.ci"},
+			DHCP4Overrides: &infrav1.DHCPOverrides{
+				Hostname:    ptr.To("hal"),
+				RouteMetric: ptr.To(12345),
+			},
+			DHCP6Overrides: &infrav1.DHCPOverrides{
+				Hostname:    ptr.To("hal"),
+				RouteMetric: ptr.To(23456),
+			},
+			AddressesFromPools: []corev1.TypedLocalObjectReference{
+				{
+					APIGroup: ptr.To("api-group"),
+					Name:     "my-pool-1",
+					Kind:     "my-pool-kind",
+				},
+			},
+		})
+
+		g.Expect(device).To(Equal(infrav1.NetworkDeviceSpec{
+			NetworkName:   "nw-name",
+			DHCP4:         true,
+			DHCP6:         false,
+			Nameservers:   []string{"1.1.1.1"},
+			SearchDomains: []string{"vmware.ci"},
+			DHCP4Overrides: &infrav1.DHCPOverrides{
+				Hostname:    ptr.To("hal"),
+				RouteMetric: ptr.To(12345),
+			},
+			DHCP6Overrides: &infrav1.DHCPOverrides{
+				Hostname:    ptr.To("hal"),
+				RouteMetric: ptr.To(23456),
+			},
+			AddressesFromPools: []corev1.TypedLocalObjectReference{
+				{
+					APIGroup: ptr.To("api-group"),
+					Name:     "my-pool-1",
+					Kind:     "my-pool-kind",
+				},
+			},
+		}))
+	})
 }
 
 func Test_VimMachineService_GetHostInfo(t *testing.T) {