Skip to content

Commit 91c7bc7

Browse files
authored
server,cks: check if vm is cks node during vm destroy (#9057)
Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
1 parent 631d6ad commit 91c7bc7

File tree

10 files changed

+163
-5
lines changed

10 files changed

+163
-5
lines changed

api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
1616
// under the License.
1717
package com.cloud.kubernetes.cluster;
1818

19-
import com.cloud.utils.component.Adapter;
2019
import org.apache.cloudstack.acl.ControlledEntity;
2120

21+
import com.cloud.uservm.UserVm;
22+
import com.cloud.utils.component.Adapter;
23+
2224
public interface KubernetesClusterHelper extends Adapter {
2325

2426
ControlledEntity findByUuid(String uuid);
27+
void checkVmCanBeDestroyed(UserVm userVm);
2528
}

api/src/main/java/com/cloud/uservm/UserVm.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,6 @@ public interface UserVm extends VirtualMachine, ControlledEntity {
4848
void setAccountId(long accountId);
4949

5050
public boolean isDisplayVm();
51+
52+
String getUserVmType();
5153
}

engine/schema/src/main/java/com/cloud/vm/UserVmVO.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ public boolean isUpdateParameters() {
148148
return updateParameters;
149149
}
150150

151+
@Override
151152
public String getUserVmType() {
152153
return userVmType;
153154
}

plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,55 @@
1616
// under the License.
1717
package com.cloud.kubernetes.cluster;
1818

19-
import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao;
20-
import com.cloud.utils.component.AdapterBase;
19+
import javax.inject.Inject;
20+
2121
import org.apache.cloudstack.acl.ControlledEntity;
2222
import org.apache.cloudstack.framework.config.ConfigKey;
2323
import org.apache.cloudstack.framework.config.Configurable;
24+
import org.apache.log4j.Logger;
2425
import org.springframework.stereotype.Component;
2526

26-
import javax.inject.Inject;
27+
import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao;
28+
import com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDao;
29+
import com.cloud.uservm.UserVm;
30+
import com.cloud.utils.component.AdapterBase;
31+
import com.cloud.utils.exception.CloudRuntimeException;
32+
import com.cloud.vm.UserVmManager;
2733

2834
@Component
2935
public class KubernetesClusterHelperImpl extends AdapterBase implements KubernetesClusterHelper, Configurable {
36+
private static final Logger logger = Logger.getLogger(KubernetesClusterHelperImpl.class);
3037

3138
@Inject
3239
private KubernetesClusterDao kubernetesClusterDao;
40+
@Inject
41+
private KubernetesClusterVmMapDao kubernetesClusterVmMapDao;
3342

3443
@Override
3544
public ControlledEntity findByUuid(String uuid) {
3645
return kubernetesClusterDao.findByUuid(uuid);
3746
}
3847

48+
@Override
49+
public void checkVmCanBeDestroyed(UserVm userVm) {
50+
if (!UserVmManager.CKS_NODE.equals(userVm.getUserVmType())) {
51+
return;
52+
}
53+
KubernetesClusterVmMapVO vmMapVO = kubernetesClusterVmMapDao.findByVmId(userVm.getId());
54+
if (vmMapVO == null) {
55+
return;
56+
}
57+
logger.error(String.format("VM ID: %s is a part of Kubernetes cluster ID: %d", userVm.getId(), vmMapVO.getClusterId()));
58+
KubernetesCluster kubernetesCluster = kubernetesClusterDao.findById(vmMapVO.getClusterId());
59+
String msg = "Instance is a part of a Kubernetes cluster";
60+
if (kubernetesCluster != null) {
61+
msg += String.format(": %s", kubernetesCluster.getName());
62+
}
63+
msg += ". Use Instance delete option from Kubernetes cluster details or scale API for " +
64+
"Kubernetes clusters with 'nodeids' to destroy the instance.";
65+
throw new CloudRuntimeException(msg);
66+
}
67+
3968
@Override
4069
public String getConfigComponentName() {
4170
return KubernetesClusterHelper.class.getSimpleName();

plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDao.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,6 @@ public interface KubernetesClusterVmMapDao extends GenericDao<KubernetesClusterV
2828
int removeByClusterIdAndVmIdsIn(long clusterId, List<Long> vmIds);
2929

3030
public int removeByClusterId(long clusterId);
31+
32+
KubernetesClusterVmMapVO findByVmId(long vmId);
3133
}

plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/dao/KubernetesClusterVmMapDaoImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,14 @@ public int removeByClusterId(long clusterId) {
6969
sc.setParameters("clusterId", clusterId);
7070
return remove(sc);
7171
}
72+
73+
@Override
74+
public KubernetesClusterVmMapVO findByVmId(long vmId) {
75+
SearchBuilder<KubernetesClusterVmMapVO> sb = createSearchBuilder();
76+
sb.and("vmId", sb.entity().getVmId(), SearchCriteria.Op.EQ);
77+
sb.done();
78+
SearchCriteria<KubernetesClusterVmMapVO> sc = sb.create();
79+
sc.setParameters("vmId", vmId);
80+
return findOneBy(sc);
81+
}
7282
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package com.cloud.kubernetes.cluster;
18+
19+
20+
import org.junit.Test;
21+
import org.junit.runner.RunWith;
22+
import org.mockito.InjectMocks;
23+
import org.mockito.Mock;
24+
import org.mockito.Mockito;
25+
import org.mockito.junit.MockitoJUnitRunner;
26+
27+
import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao;
28+
import com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDao;
29+
import com.cloud.uservm.UserVm;
30+
import com.cloud.utils.exception.CloudRuntimeException;
31+
import com.cloud.vm.UserVmManager;
32+
33+
@RunWith(MockitoJUnitRunner.class)
34+
public class KubernetesClusterHelperImplTest {
35+
@Mock
36+
KubernetesClusterVmMapDao kubernetesClusterVmMapDao;
37+
@Mock
38+
KubernetesClusterDao kubernetesClusterDao;
39+
40+
@InjectMocks
41+
KubernetesClusterHelperImpl kubernetesClusterHelper = new KubernetesClusterHelperImpl();
42+
43+
@Test
44+
public void testCheckVmCanBeDestroyedNotCKSNode() {
45+
UserVm vm = Mockito.mock(UserVm.class);
46+
Mockito.when(vm.getUserVmType()).thenReturn("");
47+
kubernetesClusterHelper.checkVmCanBeDestroyed(vm);
48+
Mockito.verify(kubernetesClusterVmMapDao, Mockito.never()).findByVmId(Mockito.anyLong());
49+
}
50+
51+
@Test
52+
public void testCheckVmCanBeDestroyedNotInCluster() {
53+
UserVm vm = Mockito.mock(UserVm.class);
54+
Mockito.when(vm.getId()).thenReturn(1L);
55+
Mockito.when(vm.getUserVmType()).thenReturn(UserVmManager.CKS_NODE);
56+
Mockito.when(kubernetesClusterVmMapDao.findByVmId(1L)).thenReturn(null);
57+
kubernetesClusterHelper.checkVmCanBeDestroyed(vm);
58+
}
59+
60+
@Test(expected = CloudRuntimeException.class)
61+
public void testCheckVmCanBeDestroyedInCluster() {
62+
UserVm vm = Mockito.mock(UserVm.class);
63+
Mockito.when(vm.getId()).thenReturn(1L);
64+
Mockito.when(vm.getUserVmType()).thenReturn(UserVmManager.CKS_NODE);
65+
KubernetesClusterVmMapVO map = Mockito.mock(KubernetesClusterVmMapVO.class);
66+
Mockito.when(map.getClusterId()).thenReturn(1L);
67+
Mockito.when(kubernetesClusterVmMapDao.findByVmId(1L)).thenReturn(map);
68+
Mockito.when(kubernetesClusterDao.findById(1L)).thenReturn(Mockito.mock(KubernetesClusterVO.class));
69+
kubernetesClusterHelper.checkVmCanBeDestroyed(vm);
70+
}
71+
}

server/src/main/java/com/cloud/vm/UserVmManagerImpl.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@
130130
import org.apache.cloudstack.userdata.UserDataManager;
131131
import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
132132
import org.apache.cloudstack.utils.security.ParserUtils;
133-
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
134133
import org.apache.cloudstack.vm.UnmanagedVMsManager;
134+
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
135135
import org.apache.commons.codec.binary.Base64;
136136
import org.apache.commons.collections.CollectionUtils;
137137
import org.apache.commons.collections.MapUtils;
@@ -141,6 +141,7 @@
141141
import org.apache.commons.lang3.builder.ToStringStyle;
142142
import org.apache.log4j.Logger;
143143
import org.jetbrains.annotations.NotNull;
144+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
144145
import org.springframework.beans.factory.annotation.Autowired;
145146
import org.springframework.beans.factory.annotation.Qualifier;
146147
import org.w3c.dom.Document;
@@ -241,6 +242,7 @@
241242
import com.cloud.hypervisor.Hypervisor.HypervisorType;
242243
import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
243244
import com.cloud.hypervisor.kvm.dpdk.DpdkHelper;
245+
import com.cloud.kubernetes.cluster.KubernetesClusterHelper;
244246
import com.cloud.network.IpAddressManager;
245247
import com.cloud.network.Network;
246248
import com.cloud.network.Network.GuestType;
@@ -346,6 +348,7 @@
346348
import com.cloud.utils.Journal;
347349
import com.cloud.utils.NumbersUtil;
348350
import com.cloud.utils.Pair;
351+
import com.cloud.utils.component.ComponentContext;
349352
import com.cloud.utils.component.ManagerBase;
350353
import com.cloud.utils.concurrency.NamedThreadFactory;
351354
import com.cloud.utils.crypt.DBEncryptionUtil;
@@ -595,6 +598,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
595598
@Inject
596599
VMScheduleManager vmScheduleManager;
597600

601+
598602
private ScheduledExecutorService _executor = null;
599603
private ScheduledExecutorService _vmIpFetchExecutor = null;
600604
private int _expungeInterval;
@@ -3280,6 +3284,16 @@ public UserVm rebootVirtualMachine(RebootVMCmd cmd) throws InsufficientCapacityE
32803284
return null;
32813285
}
32823286

3287+
protected void checkPluginsIfVmCanBeDestroyed(UserVm vm) {
3288+
try {
3289+
KubernetesClusterHelper kubernetesClusterHelper =
3290+
ComponentContext.getDelegateComponentOfType(KubernetesClusterHelper.class);
3291+
kubernetesClusterHelper.checkVmCanBeDestroyed(vm);
3292+
} catch (NoSuchBeanDefinitionException ignored) {
3293+
s_logger.debug("No KubernetesClusterHelper bean found");
3294+
}
3295+
}
3296+
32833297
@Override
32843298
@ActionEvent(eventType = EventTypes.EVENT_VM_DESTROY, eventDescription = "destroying Vm", async = true)
32853299
public UserVm destroyVm(DestroyVMCmd cmd) throws ResourceUnavailableException, ConcurrentOperationException {
@@ -3306,6 +3320,9 @@ public UserVm destroyVm(DestroyVMCmd cmd) throws ResourceUnavailableException, C
33063320
// check if vm belongs to AutoScale vm group in Disabled state
33073321
autoScaleManager.checkIfVmActionAllowed(vmId);
33083322

3323+
// check if vm belongs to any plugin resources
3324+
checkPluginsIfVmCanBeDestroyed(vm);
3325+
33093326
// check if there are active volume snapshots tasks
33103327
s_logger.debug("Checking if there are any ongoing snapshots on the ROOT volumes associated with VM with ID " + vmId);
33113328
if (checkStatusOfVolumeSnapshots(vmId, Volume.Type.ROOT)) {

utils/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,10 @@
226226
<artifactId>tink</artifactId>
227227
<version>${cs.tink.version}</version>
228228
</dependency>
229+
<dependency>
230+
<groupId>org.apache.commons</groupId>
231+
<artifactId>commons-collections4</artifactId>
232+
</dependency>
229233
</dependencies>
230234
<build>
231235
<plugins>

utils/src/main/java/com/cloud/utils/component/ComponentContext.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import javax.management.NotCompliantMBeanException;
3030
import javax.naming.ConfigurationException;
3131

32+
import org.apache.commons.collections4.MapUtils;
3233
import org.apache.log4j.Logger;
3334
import org.springframework.aop.framework.Advised;
3435
import org.springframework.beans.BeansException;
@@ -286,4 +287,22 @@ public void setInitializeBeans(boolean initializeBeans) {
286287
private static synchronized void initInitializeBeans(boolean initializeBeans) {
287288
s_initializeBeans = initializeBeans;
288289
}
290+
291+
public static <T> T getDelegateComponentOfType(Class<T> beanType) {
292+
if (s_appContextDelegates == null) {
293+
throw new NoSuchBeanDefinitionException(beanType.getName());
294+
}
295+
T bean = null;
296+
for (ApplicationContext context : s_appContextDelegates.values()) {
297+
Map<String, T> map = context.getBeansOfType(beanType);
298+
if (MapUtils.isNotEmpty(map)) {
299+
bean = (T)map.values().toArray()[0];
300+
break;
301+
}
302+
}
303+
if (bean == null) {
304+
throw new NoSuchBeanDefinitionException(beanType.getName());
305+
}
306+
return bean;
307+
}
289308
}

0 commit comments

Comments
 (0)