From 29df0479d3de797f3b50f0317dd6269b4e7834fe Mon Sep 17 00:00:00 2001 From: nvazquez Date: Thu, 12 Mar 2026 07:41:46 -0300 Subject: [PATCH 1/5] [4.22] Prevent unmanaging VM from CloudStack if it is part of a CKS cluster --- .../vm/UnmanagedVMsManagerImpl.java | 14 +++++ .../vm/UnmanagedVMsManagerImplTest.java | 57 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 13fa2608016c..4bc2a6e09bc2 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -71,6 +71,7 @@ import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.HypervisorGuru; import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.kubernetes.cluster.KubernetesServiceHelper; import com.cloud.network.Network; import com.cloud.network.NetworkModel; import com.cloud.network.Networks; @@ -313,6 +314,8 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { private DataStoreManager dataStoreManager; @Inject private ImportVmTasksManager importVmTasksManager; + @Inject + private KubernetesServiceHelper kubernetesServiceHelper; protected Gson gson; @@ -2365,6 +2368,8 @@ public List> getCommands() { * Perform validations before attempting to unmanage a VM from CloudStack: * - VM must not have any associated volume snapshot * - VM must not have an attached ISO + * - VM must not belong to any CKS cluster + * @throws UnsupportedServiceException in case any of the validations above fail */ void performUnmanageVMInstancePrechecks(VMInstanceVO vmVO) { if (hasVolumeSnapshotsPriorToUnmanageVM(vmVO)) { @@ -2376,6 +2381,15 @@ void performUnmanageVMInstancePrechecks(VMInstanceVO vmVO) { throw new UnsupportedServiceException("Cannot unmanage VM with id = " + vmVO.getUuid() + " as there is an ISO attached. Please detach ISO before unmanaging."); } + + if (belongsToCksCluster(vmVO)) { + throw new UnsupportedServiceException("Cannot unmanage VM with id = " + vmVO.getUuid() + + " as it belongs to a CKS cluster. Please remove the VM from the CKS cluster before unmanaging."); + } + } + + private boolean belongsToCksCluster(VMInstanceVO vmVO) { + return kubernetesServiceHelper.findByVmId(vmVO.getId()) != null; } private boolean hasVolumeSnapshotsPriorToUnmanageVM(VMInstanceVO vmVO) { diff --git a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java index a24ba7f068b2..c870b1d868ac 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -31,6 +31,7 @@ import java.net.URI; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; @@ -38,8 +39,13 @@ import java.util.Map; import java.util.UUID; +import com.cloud.kubernetes.cluster.KubernetesServiceHelper; import com.cloud.offering.DiskOffering; +import com.cloud.storage.Snapshot; +import com.cloud.storage.SnapshotVO; +import com.cloud.storage.dao.SnapshotDao; import com.cloud.vm.ImportVMTaskVO; +import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ResponseGenerator; import org.apache.cloudstack.api.ResponseObject; @@ -241,6 +247,10 @@ public class UnmanagedVMsManagerImplTest { private StoragePoolHostDao storagePoolHostDao; @Mock private ImportVmTasksManager importVmTasksManager; + @Mock + private KubernetesServiceHelper kubernetesServiceHelper; + @Mock + private SnapshotDao snapshotDao; @Mock private VMInstanceVO virtualMachine; @@ -568,6 +578,53 @@ public void unmanageVMInstanceStoppedInstanceTest() { unmanagedVMsManager.unmanageVMInstance(virtualMachineId, null, false); } + @Test(expected = UnsupportedServiceException.class) + public void testUnmanageVMInstanceWithVolumeSnapshotsFail() { + when(virtualMachine.getType()).thenReturn(VirtualMachine.Type.User); + when(virtualMachine.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Stopped); + when(virtualMachine.getId()).thenReturn(virtualMachineId); + UserVmVO userVmVO = mock(UserVmVO.class); + when(userVmDao.findById(anyLong())).thenReturn(userVmVO); + when(vmDao.findById(virtualMachineId)).thenReturn(virtualMachine); + VolumeVO volumeVO = mock(VolumeVO.class); + long volumeId = 20L; + when(volumeVO.getId()).thenReturn(volumeId); + SnapshotVO snapshotVO = mock(SnapshotVO.class); + when(snapshotVO.getState()).thenReturn(Snapshot.State.BackedUp); + when(snapshotDao.listByVolumeId(volumeId)).thenReturn(Collections.singletonList(snapshotVO)); + when(volumeDao.findByInstance(virtualMachineId)).thenReturn(Collections.singletonList(volumeVO)); + unmanagedVMsManager.unmanageVMInstance(virtualMachineId, null, false); + } + + @Test(expected = UnsupportedServiceException.class) + public void testUnmanageVMInstanceWithAssociatedIsoFail() { + when(virtualMachine.getType()).thenReturn(VirtualMachine.Type.User); + when(virtualMachine.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Stopped); + when(virtualMachine.getId()).thenReturn(virtualMachineId); + UserVmVO userVmVO = mock(UserVmVO.class); + when(userVmVO.getIsoId()).thenReturn(null); + when(userVmDao.findById(anyLong())).thenReturn(userVmVO); + when(vmDao.findById(virtualMachineId)).thenReturn(virtualMachine); + when(userVmVO.getIsoId()).thenReturn(1L); + unmanagedVMsManager.unmanageVMInstance(virtualMachineId, null, false); + } + + @Test(expected = UnsupportedServiceException.class) + public void testUnmanageVMInstanceBelongingToCksClusterFail() { + when(virtualMachine.getType()).thenReturn(VirtualMachine.Type.User); + when(virtualMachine.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Stopped); + when(virtualMachine.getId()).thenReturn(virtualMachineId); + UserVmVO userVmVO = mock(UserVmVO.class); + when(userVmVO.getIsoId()).thenReturn(null); + when(userVmDao.findById(anyLong())).thenReturn(userVmVO); + when(vmDao.findById(virtualMachineId)).thenReturn(virtualMachine); + when(kubernetesServiceHelper.findByVmId(virtualMachineId)).thenReturn(mock(ControlledEntity.class)); + unmanagedVMsManager.unmanageVMInstance(virtualMachineId, null, false); + } + @Test public void testListRemoteInstancesTest() { ListVmsForImportCmd cmd = Mockito.mock(ListVmsForImportCmd.class); From ac7a0fedec9a02845e35da99c18c499dcf93dad0 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Thu, 12 Mar 2026 09:47:47 -0300 Subject: [PATCH 2/5] Refactor method name --- .../org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 4bc2a6e09bc2..a53900ad4aed 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -2382,13 +2382,13 @@ void performUnmanageVMInstancePrechecks(VMInstanceVO vmVO) { " as there is an ISO attached. Please detach ISO before unmanaging."); } - if (belongsToCksCluster(vmVO)) { + if (isVmPartOfCKSCluster(vmVO)) { throw new UnsupportedServiceException("Cannot unmanage VM with id = " + vmVO.getUuid() + " as it belongs to a CKS cluster. Please remove the VM from the CKS cluster before unmanaging."); } } - private boolean belongsToCksCluster(VMInstanceVO vmVO) { + private boolean isVmPartOfCKSCluster(VMInstanceVO vmVO) { return kubernetesServiceHelper.findByVmId(vmVO.getId()) != null; } From 49bc5e822177e3c409546ad6782850d10cd9338e Mon Sep 17 00:00:00 2001 From: nvazquez Date: Tue, 17 Mar 2026 01:31:12 -0300 Subject: [PATCH 3/5] Fix dependencies --- .../apache/cloudstack/vm/UnmanagedVMsManagerImpl.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index a53900ad4aed..0721c4aa02af 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -123,6 +123,7 @@ import com.cloud.utils.LogUtils; import com.cloud.utils.Pair; import com.cloud.utils.UuidUtils; +import com.cloud.utils.component.ComponentContext; import com.cloud.utils.db.EntityManager; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; @@ -314,9 +315,13 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { private DataStoreManager dataStoreManager; @Inject private ImportVmTasksManager importVmTasksManager; - @Inject + private KubernetesServiceHelper kubernetesServiceHelper; + private void setKubernetesServiceHelper(KubernetesServiceHelper helper) { + this.kubernetesServiceHelper = helper; + } + protected Gson gson; public UnmanagedVMsManagerImpl() { @@ -2389,6 +2394,9 @@ void performUnmanageVMInstancePrechecks(VMInstanceVO vmVO) { } private boolean isVmPartOfCKSCluster(VMInstanceVO vmVO) { + if (kubernetesServiceHelper == null) { + setKubernetesServiceHelper(ComponentContext.getComponent(KubernetesServiceHelper.class)); + } return kubernetesServiceHelper.findByVmId(vmVO.getId()) != null; } From 8fd6b8f14999d52d29d57aa951c85ee933cfd343 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Tue, 17 Mar 2026 10:23:23 -0300 Subject: [PATCH 4/5] Fix bean injection --- .../cloudstack/vm/UnmanagedVMsManagerImpl.java | 12 ++++-------- .../server-compute/spring-server-compute-context.xml | 4 +++- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 0721c4aa02af..ecd8683f83d5 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -123,7 +123,6 @@ import com.cloud.utils.LogUtils; import com.cloud.utils.Pair; import com.cloud.utils.UuidUtils; -import com.cloud.utils.component.ComponentContext; import com.cloud.utils.db.EntityManager; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; @@ -316,10 +315,10 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { @Inject private ImportVmTasksManager importVmTasksManager; - private KubernetesServiceHelper kubernetesServiceHelper; + private List kubernetesServiceHelpers; - private void setKubernetesServiceHelper(KubernetesServiceHelper helper) { - this.kubernetesServiceHelper = helper; + public void setKubernetesServiceHelpers(final List kubernetesServiceHelpers) { + this.kubernetesServiceHelpers = kubernetesServiceHelpers; } protected Gson gson; @@ -2394,10 +2393,7 @@ void performUnmanageVMInstancePrechecks(VMInstanceVO vmVO) { } private boolean isVmPartOfCKSCluster(VMInstanceVO vmVO) { - if (kubernetesServiceHelper == null) { - setKubernetesServiceHelper(ComponentContext.getComponent(KubernetesServiceHelper.class)); - } - return kubernetesServiceHelper.findByVmId(vmVO.getId()) != null; + return kubernetesServiceHelpers.get(0).findByVmId(vmVO.getId()) != null; } private boolean hasVolumeSnapshotsPriorToUnmanageVM(VMInstanceVO vmVO) { diff --git a/server/src/main/resources/META-INF/cloudstack/server-compute/spring-server-compute-context.xml b/server/src/main/resources/META-INF/cloudstack/server-compute/spring-server-compute-context.xml index 3afae7676b7b..0aa1bb5fdf8d 100644 --- a/server/src/main/resources/META-INF/cloudstack/server-compute/spring-server-compute-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/server-compute/spring-server-compute-context.xml @@ -35,7 +35,9 @@ - + + + From 27101f64c9df0cef2eb030edd3b9babc4ce7c1bb Mon Sep 17 00:00:00 2001 From: nvazquez Date: Tue, 17 Mar 2026 11:04:46 -0300 Subject: [PATCH 5/5] Fix unit tests --- .../org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java index c870b1d868ac..13aa7d4852c6 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -289,6 +289,8 @@ public void setUp() throws Exception { UserVO user = new UserVO(1, "adminuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN); CallContext.register(user, account); + unmanagedVMsManager.setKubernetesServiceHelpers(List.of(kubernetesServiceHelper)); + instance = new UnmanagedInstanceTO(); instance.setName("TestInstance"); instance.setCpuCores(2);