Repository: kubermatic/machine-controller
Pull request #1860 (2024-09-25 21:29:56)

Support topology aware zones and regions in KubeVirt
**What this PR does / why we need it**:
Support for the two labels `topology.kubernetes.io/region` and `topology.kubernetes.io/zone` labels where they can deployed on both Node level(The kubelet or the external cloud-controller-manager populates this with the information from the cloud provider) and on PVC level(topology-aware volume provisioners will automatically set node affinity constraints on a PersistentVolume.). 

A zone represents a logical failure domain. It is common for Kubernetes clusters to span multiple zones for increased availability and a region represents a larger domain, made up of one or more zones. 

**Which issue(s) this PR fixes**:
<!--optional, in `fixes #<issue number>` format, will close the issue(s) when PR gets merged-->
Fixes #

**What type of PR is this?**
/kind feature
<!--
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
-->

**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
region and zone aware labels support
```

**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
None
```


kubermatic-bot: @moadqassem: Adding the "do-not-merge/docs-needed" label because no documentation block was detected, please follow our [documentation process](https://github.com/kubermatic/docs/blob/master/PROCESS.md) to remove it.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
kubermatic-bot: Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our [release note process](https://git.k8s.io/community/contributors/guide/release-notes.md) to remove it.

<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>
kubermatic-bot: [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 [moadqassem](https://github.com/moadqassem). 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=kubermatic%2Fmachine-controller).

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

- **[OWNERS](https://github.com/kubermatic/machine-controller/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":["moadqassem"]} -->
kubermatic-bot: @moadqassem: 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 | e6b0f4b5adafa39b884358619034d70b27940282 | [link](https://public-prow.loodse.com/view/gs/prow-dev-public-data/pr-logs/pull/kubermatic_machine-controller/1860/pull-machine-controller-lint/1839054778080956416) | true | `/test pull-machine-controller-lint`

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

<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 -->
kubermatic-bot: @moadqassem: 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-yamllint | 0d316f1015ac539449b229d5099f1aa2b71d1080 | [link](https://public-prow.loodse.com/view/gs/prow-dev-public-data/pr-logs/pull/kubermatic_machine-controller/1860/pull-machine-controller-yamllint/1839069605390192640) | true | `/test pull-machine-controller-yamllint`

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

<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 -->
yaa110: /approve
kubermatic-bot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubermatic/machine-controller/pull/1860#issuecomment-2376223277" title="Approved">yaa110</a>*

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

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

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

- ~~[OWNERS](https://github.com/kubermatic/machine-controller/blob/main/OWNERS)~~ [yaa110]

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":[]} -->
yaa110: /lgtm
kubermatic-bot: LGTM label has been added.  <details>Git tree hash: 5cc0934c2a81c1b586c4c7d7fd737135a35b30cd</details>

Patch:
diff --git a/pkg/cloudprovider/provider/kubevirt/provider.go b/pkg/cloudprovider/provider/kubevirt/provider.go
index fa385cf1..b57b833a 100644
--- a/pkg/cloudprovider/provider/kubevirt/provider.go
+++ b/pkg/cloudprovider/provider/kubevirt/provider.go
@@ -76,6 +76,10 @@ const (
 	registrySource imageSource = "registry"
 	// pvcSource defines the pvc source type for VM Disk Image.
 	pvcSource imageSource = "pvc"
+	// topologyRegionKey and topologyZoneKey  on PVC is a topology-aware volume provisioners will automatically set
+	// node affinity constraints on a PersistentVolume.
+	topologyRegionKey = "topology.kubernetes.io/region"
+	topologyZoneKey   = "topology.kubernetes.io/zone"
 )
 
 type provider struct {
@@ -106,6 +110,8 @@ type Config struct {
 	SecondaryDisks            []SecondaryDisks
 	NodeAffinityPreset        NodeAffinityPreset
 	TopologySpreadConstraints []corev1.TopologySpreadConstraint
+	Region                    string
+	Zone                      string
 }
 
 // StorageTarget represents targeted storage definition that will be used to provision VirtualMachine volumes. Currently,
@@ -331,6 +337,11 @@ func (p *provider) getConfig(provSpec clusterv1alpha1.ProviderSpec) (*Config, *p
 		return nil, nil, fmt.Errorf(`failed to parse "topologySpreadConstraints" field: %w`, err)
 	}
 
+	if rawConfig.VirtualMachine.Location != nil {
+		config.Zone = rawConfig.VirtualMachine.Location.Zone
+		config.Region = rawConfig.VirtualMachine.Location.Region
+	}
+
 	return &config, pconfig, nil
 }
 
@@ -649,6 +660,14 @@ func (p *provider) newVirtualMachine(_ context.Context, c *Config, pc *providerc
 	labels["cluster.x-k8s.io/cluster-name"] = c.ClusterName
 	labels["cluster.x-k8s.io/role"] = "worker"
 
+	if c.Region != "" {
+		labels[topologyRegionKey] = c.Region
+	}
+
+	if c.Zone != "" {
+		labels[topologyZoneKey] = c.Zone
+	}
+
 	var (
 		dataVolumeName = machine.Name
 		annotations    = map[string]string{}
diff --git a/pkg/cloudprovider/provider/kubevirt/types/types.go b/pkg/cloudprovider/provider/kubevirt/types/types.go
index 52a2c2e6..e5505e71 100644
--- a/pkg/cloudprovider/provider/kubevirt/types/types.go
+++ b/pkg/cloudprovider/provider/kubevirt/types/types.go
@@ -57,6 +57,7 @@ type VirtualMachine struct {
 	Template   Template                            `json:"template,omitempty"`
 	DNSPolicy  providerconfigtypes.ConfigVarString `json:"dnsPolicy,omitempty"`
 	DNSConfig  *corev1.PodDNSConfig                `json:"dnsConfig,omitempty"`
+	Location   *Location                           `json:"location,omitempty"`
 }
 
 // Flavor.
@@ -125,6 +126,12 @@ type TopologySpreadConstraint struct {
 	WhenUnsatisfiable providerconfigtypes.ConfigVarString `json:"whenUnsatisfiable,omitempty"`
 }
 
+// Location describes the region and zone where the machines are created at and where the deployed resources will reside.
+type Location struct {
+	Region string `json:"region,omitempty"`
+	Zone   string `json:"zone,omitempty"`
+}
+
 func GetConfig(pconfig providerconfigtypes.Config) (*RawConfig, error) {
 	rawConfig := &RawConfig{}
 


Test patch:
diff --git a/pkg/cloudprovider/provider/kubevirt/provider_test.go b/pkg/cloudprovider/provider/kubevirt/provider_test.go
index a1ef8545..d4ab2bc5 100644
--- a/pkg/cloudprovider/provider/kubevirt/provider_test.go
+++ b/pkg/cloudprovider/provider/kubevirt/provider_test.go
@@ -21,6 +21,7 @@ import (
 	"context"
 	"embed"
 	"html/template"
+	"k8c.io/machine-controller/pkg/cloudprovider/provider/kubevirt/types"
 	"path"
 	"reflect"
 	"testing"
@@ -68,6 +69,7 @@ type kubevirtProviderSpecConf struct {
 	OsImageSource            imageSource
 	OsImageSourceURL         string
 	PullMethod               cdiv1beta1.RegistryPullMethod
+	Location                 *types.Location
 }
 
 func (k kubevirtProviderSpecConf) rawProviderSpec(t *testing.T) []byte {
@@ -101,6 +103,12 @@ func (k kubevirtProviderSpecConf) rawProviderSpec(t *testing.T) []byte {
 		},
 		{{- end }}
 		"virtualMachine": {
+            {{- if .Location }}
+            "location": {
+               "zone": "{{ .Location.Zone }}",
+               "region": "{{ .Location.Region }}"
+            },
+            {{- end }}
 			{{- if .Instancetype }}
 			"instancetype": {
 				"name": "{{ .Instancetype.Name }}",
@@ -202,6 +210,15 @@ func TestNewVirtualMachine(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "location-zone-and-region-aware",
+			specConf: kubevirtProviderSpecConf{
+				Location: &types.Location{
+					Region: "europe-central",
+					Zone:   "hh",
+				},
+			},
+		},
 		{
 			name:     "topologyspreadconstraints",
 			specConf: kubevirtProviderSpecConf{TopologySpreadConstraint: true},
diff --git a/pkg/cloudprovider/provider/kubevirt/testdata/location-zone-and-region-aware.yaml b/pkg/cloudprovider/provider/kubevirt/testdata/location-zone-and-region-aware.yaml
new file mode 100644
index 00000000..66c74735
--- /dev/null
+++ b/pkg/cloudprovider/provider/kubevirt/testdata/location-zone-and-region-aware.yaml
@@ -0,0 +1,83 @@
+apiVersion: kubevirt.io/v1
+kind: VirtualMachine
+metadata:
+  annotations:
+  labels:
+    cluster.x-k8s.io/cluster-name: cluster-name
+    cluster.x-k8s.io/role: worker
+    kubevirt.io/vm: location-zone-and-region-aware
+    topology.kubernetes.io/region: europe-central
+    topology.kubernetes.io/zone: hh
+    md: md-name
+  name: location-zone-and-region-aware
+  namespace: test-namespace
+spec:
+  dataVolumeTemplates:
+    - metadata:
+        name: location-zone-and-region-aware
+      spec:
+        pvc:
+          accessModes:
+            - ReadWriteOnce
+          resources:
+            requests:
+              storage: 10Gi
+          storageClassName: longhorn
+        source:
+          http:
+            url: http://x.y.z.t/ubuntu.img
+  runStrategy: Once
+  template:
+    metadata:
+      creationTimestamp: null
+      annotations:
+        "ovn.kubernetes.io/allow_live_migration": "true"
+        "kubevirt.io/allow-pod-bridge-network-live-migration": "true"
+      labels:
+        cluster.x-k8s.io/cluster-name: cluster-name
+        cluster.x-k8s.io/role: worker
+        kubevirt.io/vm: location-zone-and-region-aware
+        md: md-name
+        topology.kubernetes.io/region: europe-central
+        topology.kubernetes.io/zone: hh
+    spec:
+      affinity: {}
+      domain:
+        devices:
+          disks:
+            - disk:
+                bus: virtio
+              name: datavolumedisk
+            - disk:
+                bus: virtio
+              name: cloudinitdisk
+          interfaces:
+            - name: default
+              bridge: {}
+        resources:
+          limits:
+            cpu: "2"
+            memory: 2Gi
+          requests:
+            cpu: "2"
+            memory: 2Gi
+      networks:
+        - name: default
+          pod: {}
+      terminationGracePeriodSeconds: 30
+      topologyspreadconstraints:
+        - maxskew: 1
+          topologykey: kubernetes.io/hostname
+          whenunsatisfiable: ScheduleAnyway
+          labelselector:
+            matchlabels:
+              md: md-name
+      volumes:
+        - dataVolume:
+            name: location-zone-and-region-aware
+          name: datavolumedisk
+        - cloudInitNoCloud:
+            secretRef:
+              name: udsn
+          name: cloudinitdisk
+      evictionStrategy: External