This is an automated email from the ASF dual-hosted git repository. dahn pushed a commit to branch 4.19 in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.19 by this push: new 91c7bc722f2 server,cks: check if vm is cks node during vm destroy (#9057) 91c7bc722f2 is described below commit 91c7bc722f21625a4879aea1dffcd293712cbbad Author: Abhishek Kumar <abhishek.mr...@gmail.com> AuthorDate: Thu Jun 6 14:24:02 2024 +0530 server,cks: check if vm is cks node during vm destroy (#9057) Signed-off-by: Abhishek Kumar <abhishek.mr...@gmail.com> --- .../cluster/KubernetesClusterHelper.java | 5 +- api/src/main/java/com/cloud/uservm/UserVm.java | 2 + .../src/main/java/com/cloud/vm/UserVmVO.java | 1 + .../cluster/KubernetesClusterHelperImpl.java | 35 ++++++++++- .../cluster/dao/KubernetesClusterVmMapDao.java | 2 + .../cluster/dao/KubernetesClusterVmMapDaoImpl.java | 10 +++ .../cluster/KubernetesClusterHelperImplTest.java | 71 ++++++++++++++++++++++ .../main/java/com/cloud/vm/UserVmManagerImpl.java | 19 +++++- utils/pom.xml | 4 ++ .../cloud/utils/component/ComponentContext.java | 19 ++++++ 10 files changed, 163 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java index e445e50f82c..e67e5277cee 100644 --- a/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java +++ b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java @@ -16,10 +16,13 @@ // under the License. package com.cloud.kubernetes.cluster; -import com.cloud.utils.component.Adapter; import org.apache.cloudstack.acl.ControlledEntity; +import com.cloud.uservm.UserVm; +import com.cloud.utils.component.Adapter; + public interface KubernetesClusterHelper extends Adapter { ControlledEntity findByUuid(String uuid); + void checkVmCanBeDestroyed(UserVm userVm); } diff --git a/api/src/main/java/com/cloud/uservm/UserVm.java b/api/src/main/java/com/cloud/uservm/UserVm.java index e30f5e03054..9035d2903c9 100644 --- a/api/src/main/java/com/cloud/uservm/UserVm.java +++ b/api/src/main/java/com/cloud/uservm/UserVm.java @@ -48,4 +48,6 @@ public interface UserVm extends VirtualMachine, ControlledEntity { void setAccountId(long accountId); public boolean isDisplayVm(); + + String getUserVmType(); } diff --git a/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java b/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java index ce0bd2d5717..ce3a9a84a34 100644 --- a/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java @@ -148,6 +148,7 @@ public class UserVmVO extends VMInstanceVO implements UserVm { return updateParameters; } + @Override public String getUserVmType() { return userVmType; } diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java index 0ef916ab959..74f97426d85 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java @@ -16,26 +16,55 @@ // under the License. package com.cloud.kubernetes.cluster; -import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao; -import com.cloud.utils.component.AdapterBase; +import javax.inject.Inject; + import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; +import org.apache.log4j.Logger; import org.springframework.stereotype.Component; -import javax.inject.Inject; +import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao; +import com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDao; +import com.cloud.uservm.UserVm; +import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.UserVmManager; @Component public class KubernetesClusterHelperImpl extends AdapterBase implements KubernetesClusterHelper, Configurable { + private static final Logger logger = Logger.getLogger(KubernetesClusterHelperImpl.class); @Inject private KubernetesClusterDao kubernetesClusterDao; + @Inject + private KubernetesClusterVmMapDao kubernetesClusterVmMapDao; @Override public ControlledEntity findByUuid(String uuid) { return kubernetesClusterDao.findByUuid(uuid); } + @Override + public void checkVmCanBeDestroyed(UserVm userVm) { + if (!UserVmManager.CKS_NODE.equals(userVm.getUserVmType())) { + return; + } + KubernetesClusterVmMapVO vmMapVO = kubernetesClusterVmMapDao.findByVmId(userVm.getId()); + if (vmMapVO == null) { + return; + } + logger.error(String.format("VM ID: %s is a part of Kubernetes cluster ID: %d", userVm.getId(), vmMapVO.getClusterId())); + KubernetesCluster kubernetesCluster = kubernetesClusterDao.findById(vmMapVO.getClusterId()); + String msg = "Instance is a part of a Kubernetes cluster"; + if (kubernetesCluster != null) { + msg += String.format(": %s", kubernetesCluster.getName()); + } + msg += ". Use Instance delete option from Kubernetes cluster details or scale API for " + + "Kubernetes clusters with 'nodeids' to destroy the instance."; + throw new CloudRuntimeException(msg); + } + @Override public String getConfigComponentName() { return KubernetesClusterHelper.class.getSimpleName(); diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDao.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDao.java index 688a611ac99..abb7c3b5d01 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDao.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDao.java @@ -28,4 +28,6 @@ public interface KubernetesClusterVmMapDao extends GenericDao<KubernetesClusterV int removeByClusterIdAndVmIdsIn(long clusterId, List<Long> vmIds); public int removeByClusterId(long clusterId); + + KubernetesClusterVmMapVO findByVmId(long vmId); } diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDaoImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDaoImpl.java index b9f2ec917b2..3625873d029 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDaoImpl.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDaoImpl.java @@ -69,4 +69,14 @@ public class KubernetesClusterVmMapDaoImpl extends GenericDaoBase<KubernetesClus sc.setParameters("clusterId", clusterId); return remove(sc); } + + @Override + public KubernetesClusterVmMapVO findByVmId(long vmId) { + SearchBuilder<KubernetesClusterVmMapVO> sb = createSearchBuilder(); + sb.and("vmId", sb.entity().getVmId(), SearchCriteria.Op.EQ); + sb.done(); + SearchCriteria<KubernetesClusterVmMapVO> sc = sb.create(); + sc.setParameters("vmId", vmId); + return findOneBy(sc); + } } diff --git a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImplTest.java b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImplTest.java new file mode 100644 index 00000000000..167ade67cfb --- /dev/null +++ b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImplTest.java @@ -0,0 +1,71 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 com.cloud.kubernetes.cluster; + + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao; +import com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDao; +import com.cloud.uservm.UserVm; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.UserVmManager; + +@RunWith(MockitoJUnitRunner.class) +public class KubernetesClusterHelperImplTest { + @Mock + KubernetesClusterVmMapDao kubernetesClusterVmMapDao; + @Mock + KubernetesClusterDao kubernetesClusterDao; + + @InjectMocks + KubernetesClusterHelperImpl kubernetesClusterHelper = new KubernetesClusterHelperImpl(); + + @Test + public void testCheckVmCanBeDestroyedNotCKSNode() { + UserVm vm = Mockito.mock(UserVm.class); + Mockito.when(vm.getUserVmType()).thenReturn(""); + kubernetesClusterHelper.checkVmCanBeDestroyed(vm); + Mockito.verify(kubernetesClusterVmMapDao, Mockito.never()).findByVmId(Mockito.anyLong()); + } + + @Test + public void testCheckVmCanBeDestroyedNotInCluster() { + UserVm vm = Mockito.mock(UserVm.class); + Mockito.when(vm.getId()).thenReturn(1L); + Mockito.when(vm.getUserVmType()).thenReturn(UserVmManager.CKS_NODE); + Mockito.when(kubernetesClusterVmMapDao.findByVmId(1L)).thenReturn(null); + kubernetesClusterHelper.checkVmCanBeDestroyed(vm); + } + + @Test(expected = CloudRuntimeException.class) + public void testCheckVmCanBeDestroyedInCluster() { + UserVm vm = Mockito.mock(UserVm.class); + Mockito.when(vm.getId()).thenReturn(1L); + Mockito.when(vm.getUserVmType()).thenReturn(UserVmManager.CKS_NODE); + KubernetesClusterVmMapVO map = Mockito.mock(KubernetesClusterVmMapVO.class); + Mockito.when(map.getClusterId()).thenReturn(1L); + Mockito.when(kubernetesClusterVmMapDao.findByVmId(1L)).thenReturn(map); + Mockito.when(kubernetesClusterDao.findById(1L)).thenReturn(Mockito.mock(KubernetesClusterVO.class)); + kubernetesClusterHelper.checkVmCanBeDestroyed(vm); + } +} diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 3c172051463..d9484761998 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -130,8 +130,8 @@ import org.apache.cloudstack.storage.template.VnfTemplateManager; import org.apache.cloudstack.userdata.UserDataManager; import org.apache.cloudstack.utils.bytescale.ByteScaleUtils; import org.apache.cloudstack.utils.security.ParserUtils; -import org.apache.cloudstack.vm.schedule.VMScheduleManager; import org.apache.cloudstack.vm.UnmanagedVMsManager; +import org.apache.cloudstack.vm.schedule.VMScheduleManager; import org.apache.commons.codec.binary.Base64; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; @@ -141,6 +141,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.w3c.dom.Document; @@ -241,6 +242,7 @@ import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao; import com.cloud.hypervisor.kvm.dpdk.DpdkHelper; +import com.cloud.kubernetes.cluster.KubernetesClusterHelper; import com.cloud.network.IpAddressManager; import com.cloud.network.Network; import com.cloud.network.Network.GuestType; @@ -346,6 +348,7 @@ import com.cloud.utils.DateUtil; import com.cloud.utils.Journal; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; +import com.cloud.utils.component.ComponentContext; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.crypt.DBEncryptionUtil; @@ -595,6 +598,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir @Inject VMScheduleManager vmScheduleManager; + private ScheduledExecutorService _executor = null; private ScheduledExecutorService _vmIpFetchExecutor = null; private int _expungeInterval; @@ -3280,6 +3284,16 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return null; } + protected void checkPluginsIfVmCanBeDestroyed(UserVm vm) { + try { + KubernetesClusterHelper kubernetesClusterHelper = + ComponentContext.getDelegateComponentOfType(KubernetesClusterHelper.class); + kubernetesClusterHelper.checkVmCanBeDestroyed(vm); + } catch (NoSuchBeanDefinitionException ignored) { + s_logger.debug("No KubernetesClusterHelper bean found"); + } + } + @Override @ActionEvent(eventType = EventTypes.EVENT_VM_DESTROY, eventDescription = "destroying Vm", async = true) public UserVm destroyVm(DestroyVMCmd cmd) throws ResourceUnavailableException, ConcurrentOperationException { @@ -3306,6 +3320,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir // check if vm belongs to AutoScale vm group in Disabled state autoScaleManager.checkIfVmActionAllowed(vmId); + // check if vm belongs to any plugin resources + checkPluginsIfVmCanBeDestroyed(vm); + // check if there are active volume snapshots tasks s_logger.debug("Checking if there are any ongoing snapshots on the ROOT volumes associated with VM with ID " + vmId); if (checkStatusOfVolumeSnapshots(vmId, Volume.Type.ROOT)) { diff --git a/utils/pom.xml b/utils/pom.xml index d52903a1df9..bf0d2bc346b 100755 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -226,6 +226,10 @@ <artifactId>tink</artifactId> <version>${cs.tink.version}</version> </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-collections4</artifactId> + </dependency> </dependencies> <build> <plugins> diff --git a/utils/src/main/java/com/cloud/utils/component/ComponentContext.java b/utils/src/main/java/com/cloud/utils/component/ComponentContext.java index 8486dbf4bd4..0a086809a9d 100644 --- a/utils/src/main/java/com/cloud/utils/component/ComponentContext.java +++ b/utils/src/main/java/com/cloud/utils/component/ComponentContext.java @@ -29,6 +29,7 @@ import javax.management.MalformedObjectNameException; import javax.management.NotCompliantMBeanException; import javax.naming.ConfigurationException; +import org.apache.commons.collections4.MapUtils; import org.apache.log4j.Logger; import org.springframework.aop.framework.Advised; import org.springframework.beans.BeansException; @@ -286,4 +287,22 @@ public class ComponentContext implements ApplicationContextAware { private static synchronized void initInitializeBeans(boolean initializeBeans) { s_initializeBeans = initializeBeans; } + + public static <T> T getDelegateComponentOfType(Class<T> beanType) { + if (s_appContextDelegates == null) { + throw new NoSuchBeanDefinitionException(beanType.getName()); + } + T bean = null; + for (ApplicationContext context : s_appContextDelegates.values()) { + Map<String, T> map = context.getBeansOfType(beanType); + if (MapUtils.isNotEmpty(map)) { + bean = (T)map.values().toArray()[0]; + break; + } + } + if (bean == null) { + throw new NoSuchBeanDefinitionException(beanType.getName()); + } + return bean; + } }