From 4ca9f14b5d3a8459df69f7703c42f76d535f07c0 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Thu, 12 Mar 2026 07:41:46 -0300 Subject: [PATCH 1/2] [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 47a5e8cfce0350d1fae3cce6416b7cdb404d0015 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Thu, 12 Mar 2026 09:47:47 -0300 Subject: [PATCH 2/2] 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; }