Repository: kubermatic/machine-controller
Pull request #1445 (2022-09-26 13:36:11)

add support for topology spread constraint for VMs
**What this PR does / why we need it**:
This PR adds support for `TopologySpreadConstraint` for VMs which will spread user cluster nodes across infra-cluster nodes. And removes the Pod affinity/anti-affinity mechanism.

**Which issue(s) this PR fixes**:
<!--optional, in `fixes #<issue number>` format, will close the issue(s) when PR gets merged-->
Fixes https://github.com/kubermatic/kubermatic/issues/10646

**What type of PR is this?**
<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind design

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
/kind chore
-->
/kind feature

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change? Then add your Release Note here**:
<!--
Write your release note:
1. Enter your extended release note in the below block. If the PR requires additional action from users switching to the new release, include the string "action required".
2. If no release note is required, just write "NONE".
-->
```release-note
Add support for topology spread constraint for KubeVirt VirtualMachines.
```

**Documentation**:
<!--
Please do one of the following options:
- Add a link to the existing documentation
- Add a link to the kubermatic/docs pull request
- If no documentation change is applicable then add:
  - TBD (documentation will be added later)
  - NONE (no documentation needed for this PR)
-->
```documentation
TBD
```


Signed-off-by: Sankalp Rangare <sankalprangare786@gmail.com>


kubermatic-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubermatic/machine-controller/pull/1445#" title="Author self-approved">sankalp-r</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **ahmedwaleedmalik** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @ahmedwaleedmalik` 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=kubermatic%2Fmachine-controller).

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

- ~~[OWNERS](https://github.com/kubermatic/machine-controller/blob/master/OWNERS)~~ [sankalp-r]
- **[pkg/cloudprovider/provider/OWNERS](https://github.com/kubermatic/machine-controller/blob/master/pkg/cloudprovider/provider/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":["ahmedwaleedmalik"]} -->
kubermatic-bot: @sankalp-r: 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-machine-controller-lint | be79273107c7f20418adb1e29069983cea520a20 | [link](https://public-prow.loodse.com/view/gs/prow-dev-public-data/pr-logs/pull/kubermatic_machine-controller/1445/pull-machine-controller-lint/1574392422169841664) | true | `/test pull-machine-controller-lint`

[Full PR test history](https://public-prow.loodse.com/pr-history?org=kubermatic&repo=machine-controller&pr=1445)

<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 -->
kubermatic-bot: @sankalp-r: 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-machine-controller-lint | be79273107c7f20418adb1e29069983cea520a20 | [link](https://public-prow.loodse.com/view/gs/prow-dev-public-data/pr-logs/pull/kubermatic_machine-controller/1445/pull-machine-controller-lint/1574392422169841664) | true | `/test pull-machine-controller-lint`
pull-machine-controller-e2e-azure-custom-image-reference | be79273107c7f20418adb1e29069983cea520a20 | [link](https://public-prow.loodse.com/view/gs/prow-dev-public-data/pr-logs/pull/kubermatic_machine-controller/1445/pull-machine-controller-e2e-azure-custom-image-reference/1574392413772845056) | true | `/test pull-machine-controller-e2e-azure-custom-image-reference`
pull-machine-controller-e2e-azure | be79273107c7f20418adb1e29069983cea520a20 | [link](https://public-prow.loodse.com/view/gs/prow-dev-public-data/pr-logs/pull/kubermatic_machine-controller/1445/pull-machine-controller-e2e-azure/1574392412933984256) | true | `/test pull-machine-controller-e2e-azure`

[Full PR test history](https://public-prow.loodse.com/pr-history?org=kubermatic&repo=machine-controller&pr=1445)

<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 -->
kubermatic-bot: @sankalp-r: 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-machine-controller-e2e-azure | be79273107c7f20418adb1e29069983cea520a20 | [link](https://public-prow.loodse.com/view/gs/prow-dev-public-data/pr-logs/pull/kubermatic_machine-controller/1445/pull-machine-controller-e2e-azure/1574392412933984256) | true | `/test pull-machine-controller-e2e-azure`
pull-machine-controller-e2e-azure-custom-image-reference | a32d71633bcd30fe3a1494cea56c5fc7822cad14 | [link](https://public-prow.loodse.com/view/gs/prow-dev-public-data/pr-logs/pull/kubermatic_machine-controller/1445/pull-machine-controller-e2e-azure-custom-image-reference/1574401015141109760) | true | `/test pull-machine-controller-e2e-azure-custom-image-reference`

[Full PR test history](https://public-prow.loodse.com/pr-history?org=kubermatic&repo=machine-controller&pr=1445)

<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 -->
kubermatic-bot: @sankalp-r: 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-machine-controller-e2e-azure-custom-image-reference | a32d71633bcd30fe3a1494cea56c5fc7822cad14 | [link](https://public-prow.loodse.com/view/gs/prow-dev-public-data/pr-logs/pull/kubermatic_machine-controller/1445/pull-machine-controller-e2e-azure-custom-image-reference/1574401015141109760) | true | `/test pull-machine-controller-e2e-azure-custom-image-reference`
pull-machine-controller-e2e-azure | a32d71633bcd30fe3a1494cea56c5fc7822cad14 | [link](https://public-prow.loodse.com/view/gs/prow-dev-public-data/pr-logs/pull/kubermatic_machine-controller/1445/pull-machine-controller-e2e-azure/1574401014310637568) | true | `/test pull-machine-controller-e2e-azure`

[Full PR test history](https://public-prow.loodse.com/pr-history?org=kubermatic&repo=machine-controller&pr=1445)

<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 -->
kubermatic-bot: @sankalp-r: 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-machine-controller-e2e-azure-custom-image-reference | a32d71633bcd30fe3a1494cea56c5fc7822cad14 | [link](https://public-prow.loodse.com/view/gs/prow-dev-public-data/pr-logs/pull/kubermatic_machine-controller/1445/pull-machine-controller-e2e-azure-custom-image-reference/1574401015141109760) | true | `/test pull-machine-controller-e2e-azure-custom-image-reference`
pull-machine-controller-e2e-azure | a32d71633bcd30fe3a1494cea56c5fc7822cad14 | [link](https://public-prow.loodse.com/view/gs/prow-dev-public-data/pr-logs/pull/kubermatic_machine-controller/1445/pull-machine-controller-e2e-azure/1574401014310637568) | true | `/test pull-machine-controller-e2e-azure`
pull-machine-controller-e2e-aws | a32d71633bcd30fe3a1494cea56c5fc7822cad14 | [link](https://public-prow.loodse.com/view/gs/prow-dev-public-data/pr-logs/pull/kubermatic_machine-controller/1445/pull-machine-controller-e2e-aws/1574401013039763456) | true | `/test pull-machine-controller-e2e-aws`

[Full PR test history](https://public-prow.loodse.com/pr-history?org=kubermatic&repo=machine-controller&pr=1445)

<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 -->
sankalp-r: /retest
kubermatic-bot: @sankalp-r: 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-machine-controller-e2e-hetzner | e55b4a38120f2f5a3b87402330f2674ddabf050a | [link](https://public-prow.loodse.com/view/gs/prow-dev-public-data/pr-logs/pull/kubermatic_machine-controller/1445/pull-machine-controller-e2e-hetzner/1574739511164801024) | true | `/test pull-machine-controller-e2e-hetzner`

[Full PR test history](https://public-prow.loodse.com/pr-history?org=kubermatic&repo=machine-controller&pr=1445)

<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 -->
sankalp-r: /retest
mfranczy: @sankalp-r do you consider this PR ready or are you still working on it?
sankalp-r: > @sankalp-r do you consider this PR ready or are you still working on it?

PR is ready from my perspective.
kubermatic-bot: @sankalp-r: 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-machine-controller-e2e-hetzner | e55b4a38120f2f5a3b87402330f2674ddabf050a | [link](https://public-prow.loodse.com/view/gs/prow-dev-public-data/pr-logs/pull/kubermatic_machine-controller/1445/pull-machine-controller-e2e-hetzner/1574752059331186688) | true | `/test pull-machine-controller-e2e-hetzner`

[Full PR test history](https://public-prow.loodse.com/pr-history?org=kubermatic&repo=machine-controller&pr=1445)

<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 -->
sankalp-r: /retest
kubermatic-bot: LGTM label has been added.  <details>Git tree hash: af793510b385c1fde474dd8d4aca06c73c9c1eb3</details>
kubermatic-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubermatic/machine-controller/pull/1445#pullrequestreview-1123661186" title="LGTM">mfranczy</a>*, *<a href="https://github.com/kubermatic/machine-controller/pull/1445#" title="Author self-approved">sankalp-r</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **ahmedwaleedmalik** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @ahmedwaleedmalik` 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=kubermatic%2Fmachine-controller).

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

- ~~[OWNERS](https://github.com/kubermatic/machine-controller/blob/master/OWNERS)~~ [mfranczy,sankalp-r]
- **[pkg/cloudprovider/provider/OWNERS](https://github.com/kubermatic/machine-controller/blob/master/pkg/cloudprovider/provider/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":["ahmedwaleedmalik"]} -->
mfranczy: @sankalp-r as I understand, prior to this change if somebody uses PodAffinity then later when recreating VM it will have applied topology spread constraints, right? Please don't forget to test this behaviour.
kubermatic-bot: New changes are detected. LGTM label has been removed.
sankalp-r: > @sankalp-r as I understand, prior to this change if somebody uses PodAffinity then later when recreating VM it will have applied topology spread constraints, right? Please don't forget to test this behaviour that migration is smooth.

@mfranczy Before this change assume if we have a MachineDeployments with PodAffnity, corresponding VMs will also have those.
After this change, the existing MD and corresponding VM stay the same, meaning PodAffnity spec remains in the VM.
But if now we try to scale the existing old MD to more replicas, new VMs will be created and they will not have PodAffnity, but now they will have default topology constraints. The old existing MD object will still persist those Affinity specs until we remove them.
sankalp-r: > @sankalp-r as I understand, prior to this change if somebody uses PodAffinity then later when recreating VM it will have applied topology spread constraints, right? Please don't forget to test this behaviour that migration is smooth.

@mfranczy Before this change assume if we have a MachineDeployments with PodAffnity, corresponding VMs will also have those.
After this change, the existing MD and corresponding VM stay the same, meaning PodAffnity spec remains in the VM.
But if now we try to scale the existing old MD to more replicas, new VMs will be created and they will not have PodAffnity(because it is ignored), but now they will have default topology constraints. The old existing MD object will still persist those Affinity specs until we remove them.
mfranczy: /approve
/lgtm
kubermatic-bot: LGTM label has been added.  <details>Git tree hash: aa6fb7661701a05ba146403ffc32d3e03f1bb2bd</details>
kubermatic-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubermatic/machine-controller/pull/1445#issuecomment-1262191975" title="LGTM">mfranczy</a>*, *<a href="https://github.com/kubermatic/machine-controller/pull/1445#" title="Author self-approved">sankalp-r</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **ahmedwaleedmalik** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @ahmedwaleedmalik` 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=kubermatic%2Fmachine-controller).

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

- ~~[OWNERS](https://github.com/kubermatic/machine-controller/blob/master/OWNERS)~~ [mfranczy,sankalp-r]
- **[pkg/cloudprovider/provider/OWNERS](https://github.com/kubermatic/machine-controller/blob/master/pkg/cloudprovider/provider/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":["ahmedwaleedmalik"]} -->
kubermatic-bot: @sankalp-r: 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-machine-controller-e2e-azure | b0b5f95450c8dc0cf3bbcce651a7c833d70a8387 | [link](https://public-prow.loodse.com/view/gs/prow-dev-public-data/pr-logs/pull/kubermatic_machine-controller/1445/pull-machine-controller-e2e-azure/1575458557950693376) | true | `/test pull-machine-controller-e2e-azure`

[Full PR test history](https://public-prow.loodse.com/pr-history?org=kubermatic&repo=machine-controller&pr=1445)

<details>

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

Patch:
diff --git a/examples/gce-machinedeployment.yaml b/examples/gce-machinedeployment.yaml
index c7ab56ff..84f24707 100644
--- a/examples/gce-machinedeployment.yaml
+++ b/examples/gce-machinedeployment.yaml
@@ -69,6 +69,8 @@ spec:
             assignPublicIPAddress: true
             customImage: "myCustomImage"
             disableMachineServiceAccount: false
+            enableNestedVirtualization: false
+            minCPUPlatform: "Intel Haswell"
           # Can be 'ubuntu' or 'rhel'
           operatingSystem: "ubuntu"
           operatingSystemSpec:
diff --git a/examples/kubevirt-machinedeployment.yaml b/examples/kubevirt-machinedeployment.yaml
index c7d959a2..f2eecf71 100644
--- a/examples/kubevirt-machinedeployment.yaml
+++ b/examples/kubevirt-machinedeployment.yaml
@@ -40,13 +40,20 @@ spec:
                   size: "10Gi"
                   storageClassName: kubermatic-fast
             affinity:
+              # Deprecated: Use topologySpreadConstraints instead.
               podAffinityPreset: "" # Allowed values: "", "soft", "hard"
+              # Deprecated: Use topologySpreadConstraints instead.
               podAntiAffinityPreset: "" # Allowed values: "", "soft", "hard"
               nodeAffinityPreset:
                 type: "" # Allowed values: "", "soft", "hard"
                 key: "foo"
                 values:
                   - bar
+            topologySpreadConstraints:
+              - maxSkew: "1"
+                topologyKey: "kubernetes.io/hostname"
+                whenUnsatisfiable: "" # Allowed values: "DoNotSchedule", "ScheduleAnyway"
+
           # Can also be `centos`, must align with he configured registryImage above
           operatingSystem: "ubuntu"
           operatingSystemSpec:
diff --git a/go.mod b/go.mod
index 6518d124..26511e31 100644
--- a/go.mod
+++ b/go.mod
@@ -57,7 +57,7 @@ require (
 	k8s.io/klog v1.0.0
 	k8s.io/kubelet v0.24.2
 	k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed
-	kubevirt.io/api v0.54.0
+	kubevirt.io/api v0.57.1
 	kubevirt.io/containerized-data-importer-api v1.50.0
 	sigs.k8s.io/controller-runtime v0.12.1
 	sigs.k8s.io/yaml v1.3.0
diff --git a/go.sum b/go.sum
index 0c6d23ea..e5ecf23c 100644
--- a/go.sum
+++ b/go.sum
@@ -1588,8 +1588,8 @@ k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/
 k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
 k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4=
 k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
-kubevirt.io/api v0.54.0 h1:rVHaKrsxpYf5Cu6rhASOxNTChS76Nvtn5tArtG2M2Ds=
-kubevirt.io/api v0.54.0/go.mod h1:mK8ilpVLcZraqgo7hv2OSNQ5vdsA3G9Pxn8LY2/1+IY=
+kubevirt.io/api v0.57.1 h1:z6ImWKCQL2efFYqMWmxEsDNyt8c6mbWk7oCY6ZAa06U=
+kubevirt.io/api v0.57.1/go.mod h1:U0CQlZR0JoJCaC+Va0wz4dMOtYDdVywJ98OT1KmOkzI=
 kubevirt.io/containerized-data-importer-api v1.50.0 h1:O01F8L5K8qRLnkYICIfmAu0dU0P48jdO42uFPElht38=
 kubevirt.io/containerized-data-importer-api v1.50.0/go.mod h1:yjD8pGZVMCeqcN46JPUQdZ2JwRVoRCOXrTVyNuFvrLo=
 kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 h1:QMrd0nKP0BGbnxTqakhDZAUhGKxPiPiN5gSDqKUmGGc=
diff --git a/pkg/cloudprovider/provider/gce/config.go b/pkg/cloudprovider/provider/gce/config.go
index 9901b7af..28777faa 100644
--- a/pkg/cloudprovider/provider/gce/config.go
+++ b/pkg/cloudprovider/provider/gce/config.go
@@ -112,6 +112,8 @@ type config struct {
 	regional                     bool
 	customImage                  string
 	disableMachineServiceAccount bool
+	enableNestedVirtualization   bool
+	minCPUPlatform               string
 }
 
 // newConfig creates a Provider configuration out of the passed resolver and spec.
@@ -220,6 +222,16 @@ func newConfig(resolver *providerconfig.ConfigVarResolver, spec v1alpha1.Provide
 		return nil, fmt.Errorf("failed to retrieve disable machine service account: %w", err)
 	}
 
+	cfg.enableNestedVirtualization, _, err = resolver.GetConfigVarBoolValue(cpSpec.EnableNestedVirtualization)
+	if err != nil {
+		return nil, fmt.Errorf("failed to retrieve enable nested virtualization: %w", err)
+	}
+
+	cfg.minCPUPlatform, err = resolver.GetConfigVarStringValue(cpSpec.MinCPUPlatform)
+	if err != nil {
+		return nil, fmt.Errorf("failed to retrieve min cpu platform: %w", err)
+	}
+
 	return cfg, nil
 }
 
diff --git a/pkg/cloudprovider/provider/gce/provider.go b/pkg/cloudprovider/provider/gce/provider.go
index 58245db0..28e7cc3f 100644
--- a/pkg/cloudprovider/provider/gce/provider.go
+++ b/pkg/cloudprovider/provider/gce/provider.go
@@ -280,6 +280,16 @@ func (p *Provider) Create(ctx context.Context, machine *clusterv1alpha1.Machine,
 		inst.Scheduling.ProvisioningModel = *cfg.provisioningModel
 	}
 
+	if cfg.enableNestedVirtualization {
+		inst.AdvancedMachineFeatures = &compute.AdvancedMachineFeatures{
+			EnableNestedVirtualization: true,
+		}
+	}
+
+	if cfg.minCPUPlatform != "" {
+		inst.MinCpuPlatform = cfg.minCPUPlatform
+	}
+
 	op, err := svc.Instances.Insert(cfg.projectID, cfg.zone, inst).Do()
 	if err != nil {
 		return nil, newError(common.InvalidConfigurationMachineError, errInsertInstance, err)
diff --git a/pkg/cloudprovider/provider/gce/types/types.go b/pkg/cloudprovider/provider/gce/types/types.go
index 0ac0f333..0bc95de0 100644
--- a/pkg/cloudprovider/provider/gce/types/types.go
+++ b/pkg/cloudprovider/provider/gce/types/types.go
@@ -48,6 +48,8 @@ type CloudProviderSpec struct {
 	Regional                     providerconfigtypes.ConfigVarBool    `json:"regional"`
 	CustomImage                  providerconfigtypes.ConfigVarString  `json:"customImage,omitempty"`
 	DisableMachineServiceAccount providerconfigtypes.ConfigVarBool    `json:"disableMachineServiceAccount,omitempty"`
+	EnableNestedVirtualization   providerconfigtypes.ConfigVarBool    `json:"enableNestedVirtualization,omitempty"`
+	MinCPUPlatform               providerconfigtypes.ConfigVarString  `json:"MinCPUPlatform,omitempty"`
 }
 
 // UpdateProviderSpec updates the given provider spec with changed
diff --git a/pkg/cloudprovider/provider/kubevirt/provider.go b/pkg/cloudprovider/provider/kubevirt/provider.go
index 2523c62f..fab92764 100644
--- a/pkg/cloudprovider/provider/kubevirt/provider.go
+++ b/pkg/cloudprovider/provider/kubevirt/provider.go
@@ -86,21 +86,22 @@ func New(configVarResolver *providerconfig.ConfigVarResolver) cloudprovidertypes
 }
 
 type Config struct {
-	Kubeconfig            string
-	RestConfig            *rest.Config
-	DNSConfig             *corev1.PodDNSConfig
-	DNSPolicy             corev1.DNSPolicy
-	CPUs                  string
-	Memory                string
-	Namespace             string
-	OsImage               OSImage
-	StorageClassName      string
-	PVCSize               resource.Quantity
-	FlavorName            string
-	SecondaryDisks        []SecondaryDisks
-	PodAffinityPreset     AffinityType
-	PodAntiAffinityPreset AffinityType
-	NodeAffinityPreset    NodeAffinityPreset
+	Kubeconfig                string
+	RestConfig                *rest.Config
+	DNSConfig                 *corev1.PodDNSConfig
+	DNSPolicy                 corev1.DNSPolicy
+	CPUs                      string
+	Memory                    string
+	Namespace                 string
+	OsImage                   OSImage
+	StorageClassName          string
+	PVCSize                   resource.Quantity
+	FlavorName                string
+	SecondaryDisks            []SecondaryDisks
+	PodAffinityPreset         AffinityType
+	PodAntiAffinityPreset     AffinityType
+	NodeAffinityPreset        NodeAffinityPreset
+	TopologySpreadConstraints []corev1.TopologySpreadConstraint
 }
 
 type AffinityType string
@@ -304,19 +305,14 @@ func (p *provider) getConfig(provSpec clusterv1alpha1.ProviderSpec) (*Config, *p
 		})
 	}
 
-	// Affinity/AntiAffinity
-	config.PodAffinityPreset, err = p.affinityType(rawConfig.Affinity.PodAffinityPreset)
-	if err != nil {
-		return nil, nil, fmt.Errorf(`failed to parse "podAffinityPreset" field: %w`, err)
-	}
-	config.PodAntiAffinityPreset, err = p.affinityType(rawConfig.Affinity.PodAntiAffinityPreset)
-	if err != nil {
-		return nil, nil, fmt.Errorf(`failed to parse "podAntiAffinityPreset" field: %w`, err)
-	}
 	config.NodeAffinityPreset, err = p.parseNodeAffinityPreset(rawConfig.Affinity.NodeAffinityPreset)
 	if err != nil {
 		return nil, nil, fmt.Errorf(`failed to parse "nodeAffinityPreset" field: %w`, err)
 	}
+	config.TopologySpreadConstraints, err = p.parseTopologySpreadConstraint(rawConfig.TopologySpreadConstraints)
+	if err != nil {
+		return nil, nil, fmt.Errorf(`failed to parse "topologySpreadConstraints" field: %w`, err)
+	}
 
 	return &config, pconfig, nil
 }
@@ -343,6 +339,34 @@ func (p *provider) parseNodeAffinityPreset(nodeAffinityPreset kubevirttypes.Node
 	return nodeAffinity, nil
 }
 
+func (p *provider) parseTopologySpreadConstraint(topologyConstraints []kubevirttypes.TopologySpreadConstraint) ([]corev1.TopologySpreadConstraint, error) {
+	parsedTopologyConstraints := make([]corev1.TopologySpreadConstraint, 0, len(topologyConstraints))
+	for _, constraint := range topologyConstraints {
+		maxSkewString, err := p.configVarResolver.GetConfigVarStringValue(constraint.MaxSkew)
+		if err != nil {
+			return nil, fmt.Errorf(`failed to parse "topologySpreadConstraint.maxSkew" field: %w`, err)
+		}
+		maxSkew, err := strconv.ParseInt(maxSkewString, 10, 32)
+		if err != nil {
+			return nil, fmt.Errorf(`failed to parse "topologySpreadConstraint.maxSkew" field: %w`, err)
+		}
+		topologyKey, err := p.configVarResolver.GetConfigVarStringValue(constraint.TopologyKey)
+		if err != nil {
+			return nil, fmt.Errorf(`failed to parse "topologySpreadConstraint.topologyKey" field: %w`, err)
+		}
+		whenUnsatisfiable, err := p.configVarResolver.GetConfigVarStringValue(constraint.WhenUnsatisfiable)
+		if err != nil {
+			return nil, fmt.Errorf(`failed to parse "topologySpreadConstraint.whenUnsatisfiable" field: %w`, err)
+		}
+		parsedTopologyConstraints = append(parsedTopologyConstraints, corev1.TopologySpreadConstraint{
+			MaxSkew:           int32(maxSkew),
+			TopologyKey:       topologyKey,
+			WhenUnsatisfiable: corev1.UnsatisfiableConstraintAction(whenUnsatisfiable),
+		})
+	}
+	return parsedTopologyConstraints, nil
+}
+
 // getNamespace returns the namespace where the VM is created.
 // VM is created in a dedicated namespace <cluster-id>
 // which is the namespace where the machine-controller pod is running.
@@ -572,6 +596,7 @@ func (p *provider) Create(ctx context.Context, machine *clusterv1alpha1.Machine,
 					Volumes:                       getVMVolumes(c, dataVolumeName, userDataSecretName),
 					DNSPolicy:                     c.DNSPolicy,
 					DNSConfig:                     c.DNSConfig,
+					TopologySpreadConstraints:     getTopologySpreadConstraints(c, map[string]string{machineDeploymentLabelKey: labels[machineDeploymentLabelKey]}),
 				},
 			},
 			DataVolumeTemplates: getDataVolumeTemplates(c, dataVolumeName),
@@ -781,30 +806,6 @@ func getDataVolumeSource(osImage OSImage) *cdiv1beta1.DataVolumeSource {
 func getAffinity(config *Config, matchKey, matchValue string) *corev1.Affinity {
 	affinity := &corev1.Affinity{}
 
-	// PodAffinity
-	switch config.PodAffinityPreset {
-	case softAffinityType:
-		affinity.PodAffinity = &corev1.PodAffinity{
-			PreferredDuringSchedulingIgnoredDuringExecution: hostnameWeightedAffinityTerm(matchKey, matchValue),
-		}
-	case hardAffinityType:
-		affinity.PodAffinity = &corev1.PodAffinity{
-			RequiredDuringSchedulingIgnoredDuringExecution: hostnameAffinityTerm(matchKey, matchValue),
-		}
-	}
-
-	// PodAntiAffinity
-	switch config.PodAntiAffinityPreset {
-	case softAffinityType:
-		affinity.PodAntiAffinity = &corev1.PodAntiAffinity{
-			PreferredDuringSchedulingIgnoredDuringExecution: hostnameWeightedAffinityTerm(matchKey, matchValue),
-		}
-	case hardAffinityType:
-		affinity.PodAntiAffinity = &corev1.PodAntiAffinity{
-			RequiredDuringSchedulingIgnoredDuringExecution: hostnameAffinityTerm(matchKey, matchValue),
-		}
-	}
-
 	// NodeAffinity
 	switch config.NodeAffinityPreset.Type {
 	case softAffinityType:
@@ -845,37 +846,26 @@ func getAffinity(config *Config, matchKey, matchValue string) *corev1.Affinity {
 	return affinity
 }
 
-func hostnameWeightedAffinityTerm(matchKey, matchValue string) []corev1.WeightedPodAffinityTerm {
-	return []corev1.WeightedPodAffinityTerm{
-		{
-			Weight: 1,
-			PodAffinityTerm: corev1.PodAffinityTerm{
-				LabelSelector: &metav1.LabelSelector{
-					MatchLabels: map[string]string{
-						matchKey: matchValue,
-					},
-				},
-				TopologyKey: topologyKeyHostname,
-			},
-		},
+func addPrefixToSecondaryDisk(secondaryDisks []SecondaryDisks, prefix string) {
+	for i := range secondaryDisks {
+		secondaryDisks[i].Name = fmt.Sprintf("%s-%s", prefix, secondaryDisks[i].Name)
 	}
 }
 
-func hostnameAffinityTerm(matchKey, matchValue string) []corev1.PodAffinityTerm {
-	return []corev1.PodAffinityTerm{
+func getTopologySpreadConstraints(config *Config, matchLabels map[string]string) []corev1.TopologySpreadConstraint {
+	if len(config.TopologySpreadConstraints) != 0 {
+		for i := range config.TopologySpreadConstraints {
+			config.TopologySpreadConstraints[i].LabelSelector = &metav1.LabelSelector{MatchLabels: matchLabels}
+		}
+		return config.TopologySpreadConstraints
+	}
+	// Return default TopologySpreadConstraint
+	return []corev1.TopologySpreadConstraint{
 		{
-			LabelSelector: &metav1.LabelSelector{
-				MatchLabels: map[string]string{
-					matchKey: matchValue,
-				},
-			},
-			TopologyKey: topologyKeyHostname,
+			MaxSkew:           1,
+			TopologyKey:       topologyKeyHostname,
+			WhenUnsatisfiable: corev1.ScheduleAnyway,
+			LabelSelector:     &metav1.LabelSelector{MatchLabels: matchLabels},
 		},
 	}
 }
-
-func addPrefixToSecondaryDisk(secondaryDisks []SecondaryDisks, prefix string) {
-	for i := range secondaryDisks {
-		secondaryDisks[i].Name = fmt.Sprintf("%s-%s", prefix, secondaryDisks[i].Name)
-	}
-}
diff --git a/pkg/cloudprovider/provider/kubevirt/types/types.go b/pkg/cloudprovider/provider/kubevirt/types/types.go
index 8eaab19b..7cca0a96 100644
--- a/pkg/cloudprovider/provider/kubevirt/types/types.go
+++ b/pkg/cloudprovider/provider/kubevirt/types/types.go
@@ -24,9 +24,10 @@ import (
 )
 
 type RawConfig struct {
-	Auth           Auth           `json:"auth,omitempty"`
-	VirtualMachine VirtualMachine `json:"virtualMachine,omitempty"`
-	Affinity       Affinity       `json:"affinity,omitempty"`
+	Auth                      Auth                       `json:"auth,omitempty"`
+	VirtualMachine            VirtualMachine             `json:"virtualMachine,omitempty"`
+	Affinity                  Affinity                   `json:"affinity,omitempty"`
+	TopologySpreadConstraints []TopologySpreadConstraint `json:"topologySpreadConstraints"`
 }
 
 // Auth.
@@ -75,7 +76,9 @@ type Disk struct {
 
 // Affinity.
 type Affinity struct {
-	PodAffinityPreset     providerconfigtypes.ConfigVarString `json:"podAffinityPreset,omitempty"`
+	// Deprecated: Use TopologySpreadConstraint instead.
+	PodAffinityPreset providerconfigtypes.ConfigVarString `json:"podAffinityPreset,omitempty"`
+	// Deprecated: Use TopologySpreadConstraint instead.
 	PodAntiAffinityPreset providerconfigtypes.ConfigVarString `json:"podAntiAffinityPreset,omitempty"`
 	NodeAffinityPreset    NodeAffinityPreset                  `json:"nodeAffinityPreset,omitempty"`
 }
@@ -87,6 +90,17 @@ type NodeAffinityPreset struct {
 	Values []providerconfigtypes.ConfigVarString `json:"values,omitempty"`
 }
 
+// TopologySpreadConstraint describes topology spread constraints for VMs.
+type TopologySpreadConstraint struct {
+	// MaxSkew describes the degree to which VMs may be unevenly distributed.
+	MaxSkew providerconfigtypes.ConfigVarString `json:"maxSkew,omitempty"`
+	// TopologyKey is the key of infra-node labels.
+	TopologyKey providerconfigtypes.ConfigVarString `json:"topologyKey,omitempty"`
+	// WhenUnsatisfiable indicates how to deal with a VM if it doesn't satisfy
+	// the spread constraint.
+	WhenUnsatisfiable providerconfigtypes.ConfigVarString `json:"whenUnsatisfiable,omitempty"`
+}
+
 func GetConfig(pconfig providerconfigtypes.Config) (*RawConfig, error) {
 	rawConfig := &RawConfig{}
 


Test patch:
diff --git a/pkg/cloudprovider/provider/gce/provider_test.go b/pkg/cloudprovider/provider/gce/provider_test.go
index c792c655..f3ffeddc 100644
--- a/pkg/cloudprovider/provider/gce/provider_test.go
+++ b/pkg/cloudprovider/provider/gce/provider_test.go
@@ -55,6 +55,8 @@ func testProviderSpec() map[string]interface{} {
 			},
 			"zone":                         "europe-west2-a",
 			"disableMachineServiceAccount": false,
+			"enableNestedVirtualization":   true,
+			"minCPUPlatform":               "Intel Haswell",
 		},
 		"operatingSystem": "ubuntu",
 		"operatingSystemSpec": map[string]interface{}{
diff --git a/pkg/cloudprovider/provider/kubevirt/provider_test.go b/pkg/cloudprovider/provider/kubevirt/provider_test.go
new file mode 100644
index 00000000..b6517831
--- /dev/null
+++ b/pkg/cloudprovider/provider/kubevirt/provider_test.go
@@ -0,0 +1,57 @@
+/*
+Copyright 2022 The Machine Controller 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 kubevirt
+
+import (
+	"reflect"
+	"testing"
+
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestTopologySpreadConstraint(t *testing.T) {
+	tests := []struct {
+		desc     string
+		config   Config
+		expected []corev1.TopologySpreadConstraint
+	}{
+		{
+			desc:   "default topology constraint",
+			config: Config{TopologySpreadConstraints: nil},
+			expected: []corev1.TopologySpreadConstraint{
+				{MaxSkew: 1, TopologyKey: topologyKeyHostname, WhenUnsatisfiable: corev1.ScheduleAnyway, LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"md": "test-md"}}},
+			},
+		},
+		{
+			desc:   "custom topology constraint",
+			config: Config{TopologySpreadConstraints: []corev1.TopologySpreadConstraint{{MaxSkew: 1, TopologyKey: "test-topology-key", WhenUnsatisfiable: corev1.DoNotSchedule}}},
+			expected: []corev1.TopologySpreadConstraint{
+				{MaxSkew: 1, TopologyKey: "test-topology-key", WhenUnsatisfiable: corev1.DoNotSchedule, LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"md": "test-md"}}},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			result := getTopologySpreadConstraints(&test.config, map[string]string{"md": "test-md"})
+			if !reflect.DeepEqual(result, test.expected) {
+				t.Errorf("expected ToplogySpreadConstraint: %v, got: %v", test.expected, result)
+			}
+		})
+	}
+}