From db10b7a07d4b7664e38f7ff81baaa39525b03cea Mon Sep 17 00:00:00 2001 From: Kiran Chavala Date: Fri, 23 Oct 2026 18:17:44 +0530 Subject: [PATCH] CKS: deploy network VR on the same hypervisor as the cluster nodes In zones with clusters of multiple hypervisor types, the virtual router of a CKS cluster's network could be deployed on a different hypervisor (e.g. VMware/XenServer) than the Kubernetes nodes. The network is started with the DeployDestination from plan(), which had no cluster set, so NetworkHelperImpl.getHypervisors() fell back to all hypervisor types in the zone when system.vm.default.hypervisor is not set. Include the cluster of the selected host in the DeployDestination so the router uses the nodes' hypervisor type, as with regular VM deployments. Co-Authored-By: Claude Opus 5.5 (1M context) --- ...esClusterResourceModifierActionWorker.java | 8 +- ...usterResourceModifierActionWorkerTest.java | 73 +++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java index 573e752ce125..0dbb8b03e1d5 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java @@ -217,6 +217,7 @@ protected DeployDestination plan(final long nodesCount, final DataCenter zone, f } boolean suitable_host_found = false; HostVO suitableHost = null; + ClusterVO suitableCluster = null; for (int i = 1; i <= nodesCount; i++) { suitable_host_found = false; for (Map.Entry> hostEntry : hosts_with_resevered_capacity.entrySet()) { @@ -244,6 +245,7 @@ protected DeployDestination plan(final long nodesCount, final DataCenter zone, f hostEntry.setValue(new Pair(h, reserved)); suitable_host_found = true; suitableHost = h; + suitableCluster = cluster; break; } } @@ -259,10 +261,12 @@ protected DeployDestination plan(final long nodesCount, final DataCenter zone, f if (logger.isInfoEnabled()) { logger.info("Suitable hosts found in datacenter: {}, creating deployment destination", zone); } + // Include the cluster so that the network's virtual router is deployed on the same + // hypervisor type as the Kubernetes nodes in zones with mixed hypervisor clusters if (useDedicatedHosts) { - return new DeployDestination(zone, null, null, suitableHost); + return new DeployDestination(zone, null, suitableCluster, suitableHost); } - return new DeployDestination(zone, null, null, null); + return new DeployDestination(zone, null, suitableCluster, null); } String msg = String.format("Cannot find enough capacity for Kubernetes cluster(requested cpu=%d memory=%s) with offering: %s hypervisor: %s and arch: %s", cpu_requested * nodesCount, toHumanReadableSize(ram_requested * nodesCount), offering.getName(), clusterTemplate.getHypervisorType().toString(), arch.getType()); diff --git a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorkerTest.java b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorkerTest.java index c220a3468afb..e4c961d98834 100644 --- a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorkerTest.java +++ b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorkerTest.java @@ -17,12 +17,31 @@ package com.cloud.kubernetes.cluster.actionworkers; +import java.util.List; + +import com.cloud.capacity.CapacityManager; +import com.cloud.cpu.CPU; +import com.cloud.dc.ClusterDetailsDao; +import com.cloud.dc.ClusterDetailsVO; +import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenter; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor; import com.cloud.kubernetes.cluster.KubernetesCluster; import com.cloud.kubernetes.cluster.KubernetesClusterManagerImpl; import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao; import com.cloud.kubernetes.cluster.dao.KubernetesClusterDetailsDao; import com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDao; import com.cloud.kubernetes.version.dao.KubernetesSupportedVersionDao; +import com.cloud.offering.ServiceOffering; +import com.cloud.resource.ResourceManager; +import com.cloud.template.VirtualMachineTemplate; +import org.apache.cloudstack.affinity.dao.AffinityGroupDao; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -135,4 +154,58 @@ public void getKubernetesClusterNodeNamePrefixTestNormalizedPrefixShouldNotStart Mockito.when(kubernetesClusterMock.getName()).thenReturn(originalPrefix); Assert.assertEquals(expectedPrefix, kubernetesClusterResourceModifierActionWorker.getKubernetesClusterNodeNamePrefix()); } + + @Test + public void planTestDeployDestinationShouldContainClusterOfSuitableHost() throws InsufficientServerCapacityException { + long zoneId = 1L; + long clusterId = 2L; + DataCenter zone = Mockito.mock(DataCenter.class); + Mockito.when(zone.getId()).thenReturn(zoneId); + ServiceOffering offering = Mockito.mock(ServiceOffering.class); + Mockito.when(offering.getCpu()).thenReturn(2); + Mockito.when(offering.getSpeed()).thenReturn(1000); + Mockito.when(offering.getRamSize()).thenReturn(2048); + + VirtualMachineTemplate template = Mockito.mock(VirtualMachineTemplate.class); + Mockito.when(template.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + Mockito.when(template.getArch()).thenReturn(CPU.CPUArch.amd64); + kubernetesClusterResourceModifierActionWorker.clusterTemplate = template; + + HostVO kvmHost = Mockito.mock(HostVO.class); + Mockito.when(kvmHost.getUuid()).thenReturn("kvm-host"); + Mockito.when(kvmHost.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + Mockito.when(kvmHost.getArch()).thenReturn(CPU.CPUArch.amd64); + Mockito.when(kvmHost.getClusterId()).thenReturn(clusterId); + HostVO vmwareHost = Mockito.mock(HostVO.class); + Mockito.when(vmwareHost.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.VMware); + + ClusterVO kvmCluster = Mockito.mock(ClusterVO.class); + Mockito.when(kvmCluster.getId()).thenReturn(clusterId); + ClusterDetailsVO overcommitDetail = Mockito.mock(ClusterDetailsVO.class); + Mockito.when(overcommitDetail.getValue()).thenReturn("1.0"); + + AffinityGroupDao affinityGroupDao = Mockito.mock(AffinityGroupDao.class); + ResourceManager resourceManager = Mockito.mock(ResourceManager.class); + Mockito.when(resourceManager.listAllHostsInOneZoneByType(Host.Type.Routing, zoneId)).thenReturn(List.of(vmwareHost, kvmHost)); + ClusterDao clusterDao = Mockito.mock(ClusterDao.class); + Mockito.when(clusterDao.findById(clusterId)).thenReturn(kvmCluster); + ClusterDetailsDao clusterDetailsDao = Mockito.mock(ClusterDetailsDao.class); + Mockito.when(clusterDetailsDao.findDetail(Mockito.eq(clusterId), Mockito.anyString())).thenReturn(overcommitDetail); + CapacityManager capacityManager = Mockito.mock(CapacityManager.class); + Mockito.when(capacityManager.checkIfHostHasCapacity(Mockito.eq(kvmHost), Mockito.anyInt(), Mockito.anyLong(), + Mockito.anyBoolean(), Mockito.anyFloat(), Mockito.anyFloat(), Mockito.anyBoolean())).thenReturn(true); + + kubernetesClusterResourceModifierActionWorker.affinityGroupDao = affinityGroupDao; + kubernetesClusterResourceModifierActionWorker.resourceManager = resourceManager; + kubernetesClusterResourceModifierActionWorker.clusterDao = clusterDao; + kubernetesClusterResourceModifierActionWorker.clusterDetailsDao = clusterDetailsDao; + kubernetesClusterResourceModifierActionWorker.capacityManager = capacityManager; + kubernetesClusterResourceModifierActionWorker.hostDao = Mockito.mock(HostDao.class); + + DeployDestination dest = kubernetesClusterResourceModifierActionWorker.plan(1, zone, offering, null, null, + Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64); + + Assert.assertEquals(kvmCluster, dest.getCluster()); + Assert.assertNull(dest.getHost()); + } }