mirror of https://github.com/apache/cloudstack.git
api,server: allow cleaning up vm extraconfig (#11974)
This commit is contained in:
parent
9d523cbbfe
commit
18972caf5f
|
|
@ -1168,6 +1168,7 @@ public class ApiConstants {
|
|||
public static final String OVM3_VIP = "ovm3vip";
|
||||
public static final String CLEAN_UP_DETAILS = "cleanupdetails";
|
||||
public static final String CLEAN_UP_EXTERNAL_DETAILS = "cleanupexternaldetails";
|
||||
public static final String CLEAN_UP_EXTRA_CONFIG = "cleanupextraconfig";
|
||||
public static final String CLEAN_UP_PARAMETERS = "cleanupparameters";
|
||||
public static final String VIRTUAL_SIZE = "virtualsize";
|
||||
public static final String NETSCALER_CONTROLCENTER_ID = "netscalercontrolcenterid";
|
||||
|
|
|
|||
|
|
@ -163,6 +163,12 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction,
|
|||
description = "Lease expiry action, valid values are STOP and DESTROY")
|
||||
private String leaseExpiryAction;
|
||||
|
||||
@Parameter(name = ApiConstants.CLEAN_UP_EXTRA_CONFIG, type = CommandType.BOOLEAN, since = "4.23.0",
|
||||
description = "Optional boolean field, which indicates if extraconfig for the instance should be " +
|
||||
"cleaned up or not (If set to true, extraconfig removed for this instance, extraconfig field " +
|
||||
"ignored; if false or not set, no action)")
|
||||
private Boolean cleanupExtraConfig;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -271,14 +277,18 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction,
|
|||
return extraConfig;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public Long getOsTypeId() {
|
||||
return osTypeId;
|
||||
}
|
||||
|
||||
public boolean isCleanupExtraConfig() {
|
||||
return Boolean.TRUE.equals(cleanupExtraConfig);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return s_name;
|
||||
|
|
|
|||
|
|
@ -22,4 +22,5 @@ import com.cloud.utils.db.GenericDao;
|
|||
import com.cloud.vm.VMInstanceDetailVO;
|
||||
|
||||
public interface VMInstanceDetailsDao extends GenericDao<VMInstanceDetailVO, Long>, ResourceDetailsDao<VMInstanceDetailVO> {
|
||||
int removeDetailsWithPrefix(long vmId, String prefix);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,13 @@
|
|||
package com.cloud.vm.dao;
|
||||
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase;
|
||||
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import com.cloud.vm.VMInstanceDetailVO;
|
||||
|
||||
@Component
|
||||
|
|
@ -31,4 +34,18 @@ public class VMInstanceDetailsDaoImpl extends ResourceDetailsDaoBase<VMInstanceD
|
|||
super.addDetail(new VMInstanceDetailVO(resourceId, key, value, display));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int removeDetailsWithPrefix(long vmId, String prefix) {
|
||||
if (StringUtils.isBlank(prefix)) {
|
||||
return 0;
|
||||
}
|
||||
SearchBuilder<VMInstanceDetailVO> sb = createSearchBuilder();
|
||||
sb.and("vmId", sb.entity().getResourceId(), SearchCriteria.Op.EQ);
|
||||
sb.and("prefix", sb.entity().getName(), SearchCriteria.Op.LIKE);
|
||||
sb.done();
|
||||
SearchCriteria<VMInstanceDetailVO> sc = sb.create();
|
||||
sc.setParameters("vmId", vmId);
|
||||
sc.setParameters("prefix", prefix + "%");
|
||||
return super.remove(sc);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
// 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.vm.dao;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import com.cloud.vm.VMInstanceDetailVO;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class VMInstanceDetailsDaoImplTest {
|
||||
@Spy
|
||||
@InjectMocks
|
||||
private VMInstanceDetailsDaoImpl vmInstanceDetailsDaoImpl;
|
||||
|
||||
@Test
|
||||
public void removeDetailsWithPrefixReturnsZeroWhenPrefixIsBlank() {
|
||||
Assert.assertEquals(0, vmInstanceDetailsDaoImpl.removeDetailsWithPrefix(1L, ""));
|
||||
Assert.assertEquals(0, vmInstanceDetailsDaoImpl.removeDetailsWithPrefix(1L, " "));
|
||||
Assert.assertEquals(0, vmInstanceDetailsDaoImpl.removeDetailsWithPrefix(1L, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeDetailsWithPrefixRemovesMatchingDetails() {
|
||||
SearchBuilder<VMInstanceDetailVO> sb = mock(SearchBuilder.class);
|
||||
VMInstanceDetailVO entity = mock(VMInstanceDetailVO.class);
|
||||
when(sb.entity()).thenReturn(entity);
|
||||
when(sb.and(anyString(), any(), any(SearchCriteria.Op.class))).thenReturn(sb);
|
||||
SearchCriteria<VMInstanceDetailVO> sc = mock(SearchCriteria.class);
|
||||
when(sb.create()).thenReturn(sc);
|
||||
when(vmInstanceDetailsDaoImpl.createSearchBuilder()).thenReturn(sb);
|
||||
doReturn(3).when(vmInstanceDetailsDaoImpl).remove(sc);
|
||||
int removedCount = vmInstanceDetailsDaoImpl.removeDetailsWithPrefix(1L, "testPrefix");
|
||||
Assert.assertEquals(3, removedCount);
|
||||
Mockito.verify(sc).setParameters("vmId", 1L);
|
||||
Mockito.verify(sc).setParameters("prefix", "testPrefix%");
|
||||
Mockito.verify(vmInstanceDetailsDaoImpl, Mockito.times(1)).remove(sc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeDetailsWithPrefixDoesNotRemoveWhenNoMatch() {
|
||||
SearchBuilder<VMInstanceDetailVO> sb = mock(SearchBuilder.class);
|
||||
VMInstanceDetailVO entity = mock(VMInstanceDetailVO.class);
|
||||
when(sb.entity()).thenReturn(entity);
|
||||
when(sb.and(anyString(), any(), any(SearchCriteria.Op.class))).thenReturn(sb);
|
||||
SearchCriteria<VMInstanceDetailVO> sc = mock(SearchCriteria.class);
|
||||
when(sb.create()).thenReturn(sc);
|
||||
when(vmInstanceDetailsDaoImpl.createSearchBuilder()).thenReturn(sb);
|
||||
doReturn(0).when(vmInstanceDetailsDaoImpl).remove(sc);
|
||||
|
||||
int removedCount = vmInstanceDetailsDaoImpl.removeDetailsWithPrefix(1L, "nonExistentPrefix");
|
||||
|
||||
Assert.assertEquals(0, removedCount);
|
||||
Mockito.verify(sc).setParameters("vmId", 1L);
|
||||
Mockito.verify(sc).setParameters("prefix", "nonExistentPrefix%");
|
||||
Mockito.verify(vmInstanceDetailsDaoImpl, Mockito.times(1)).remove(sc);
|
||||
}
|
||||
}
|
||||
|
|
@ -2866,6 +2866,22 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
}
|
||||
}
|
||||
|
||||
protected void updateVmExtraConfig(UserVmVO userVm, String extraConfig, boolean cleanupExtraConfig) {
|
||||
if (cleanupExtraConfig) {
|
||||
logger.info("Cleaning up extraconfig from user vm: {}", userVm.getUuid());
|
||||
vmInstanceDetailsDao.removeDetailsWithPrefix(userVm.getId(), ApiConstants.EXTRA_CONFIG);
|
||||
return;
|
||||
}
|
||||
if (StringUtils.isNotBlank(extraConfig)) {
|
||||
if (EnableAdditionalVmConfig.valueIn(userVm.getAccountId())) {
|
||||
logger.info("Adding extra configuration to user vm: {}", userVm.getUuid());
|
||||
addExtraConfig(userVm, extraConfig);
|
||||
} else {
|
||||
throw new InvalidParameterValueException("attempted setting extraconfig but enable.additional.vm.configuration is disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_UPDATE, eventDescription = "updating Vm")
|
||||
public UserVm updateVirtualMachine(UpdateVMCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException {
|
||||
|
|
@ -2883,6 +2899,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
List<Long> securityGroupIdList = getSecurityGroupIdList(cmd);
|
||||
boolean cleanupDetails = cmd.isCleanupDetails();
|
||||
String extraConfig = cmd.getExtraConfig();
|
||||
boolean cleanupExtraConfig = cmd.isCleanupExtraConfig();
|
||||
|
||||
UserVmVO vmInstance = _vmDao.findById(cmd.getId());
|
||||
VMTemplateVO template = _templateDao.findById(vmInstance.getTemplateId());
|
||||
|
|
@ -2919,7 +2936,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
.map(item -> (item).trim())
|
||||
.collect(Collectors.toList());
|
||||
List<VMInstanceDetailVO> existingDetails = vmInstanceDetailsDao.listDetails(id);
|
||||
if (cleanupDetails){
|
||||
if (cleanupDetails) {
|
||||
if (caller != null && caller.getType() == Account.Type.ADMIN) {
|
||||
for (final VMInstanceDetailVO detail : existingDetails) {
|
||||
if (detail != null && detail.isDisplay() && !isExtraConfig(detail.getName())) {
|
||||
|
|
@ -2982,15 +2999,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
vmInstance.setDetails(details);
|
||||
_vmDao.saveDetails(vmInstance);
|
||||
}
|
||||
if (StringUtils.isNotBlank(extraConfig)) {
|
||||
if (EnableAdditionalVmConfig.valueIn(accountId)) {
|
||||
logger.info("Adding extra configuration to user vm: " + vmInstance.getUuid());
|
||||
addExtraConfig(vmInstance, extraConfig);
|
||||
} else {
|
||||
throw new InvalidParameterValueException("attempted setting extraconfig but enable.additional.vm.configuration is disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
updateVmExtraConfig(userVm, extraConfig, cleanupExtraConfig);
|
||||
|
||||
if (VMLeaseManager.InstanceLeaseEnabled.value() && cmd.getLeaseDuration() != null) {
|
||||
applyLeaseOnUpdateInstance(vmInstance, cmd.getLeaseDuration(), cmd.getLeaseExpiryAction());
|
||||
|
|
|
|||
|
|
@ -40,8 +40,10 @@ import static org.mockito.Mockito.mockStatic;
|
|||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
|
|
@ -59,8 +61,6 @@ import java.util.Map;
|
|||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.storage.dao.SnapshotPolicyDao;
|
||||
import org.apache.cloudstack.acl.ControlledEntity;
|
||||
import org.apache.cloudstack.acl.SecurityChecker;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
|
|
@ -88,6 +88,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
|
|||
import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
import org.apache.cloudstack.storage.template.VnfTemplateManager;
|
||||
|
|
@ -117,6 +118,7 @@ import com.cloud.deploy.DataCenterDeployment;
|
|||
import com.cloud.deploy.DeployDestination;
|
||||
import com.cloud.deploy.DeploymentPlanner;
|
||||
import com.cloud.deploy.DeploymentPlanningManager;
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.domain.DomainVO;
|
||||
import com.cloud.domain.dao.DomainDao;
|
||||
import com.cloud.event.ActionEventUtils;
|
||||
|
|
@ -142,9 +144,9 @@ import com.cloud.network.dao.LoadBalancerVMMapDao;
|
|||
import com.cloud.network.dao.LoadBalancerVMMapVO;
|
||||
import com.cloud.network.dao.NetworkDao;
|
||||
import com.cloud.network.dao.NetworkVO;
|
||||
import com.cloud.network.element.UserDataServiceProvider;
|
||||
import com.cloud.network.dao.PhysicalNetworkDao;
|
||||
import com.cloud.network.dao.PhysicalNetworkVO;
|
||||
import com.cloud.network.element.UserDataServiceProvider;
|
||||
import com.cloud.network.guru.NetworkGuru;
|
||||
import com.cloud.network.rules.FirewallRuleVO;
|
||||
import com.cloud.network.rules.PortForwardingRule;
|
||||
|
|
@ -172,6 +174,7 @@ import com.cloud.storage.VolumeVO;
|
|||
import com.cloud.storage.dao.DiskOfferingDao;
|
||||
import com.cloud.storage.dao.GuestOSDao;
|
||||
import com.cloud.storage.dao.SnapshotDao;
|
||||
import com.cloud.storage.dao.SnapshotPolicyDao;
|
||||
import com.cloud.storage.dao.VMTemplateDao;
|
||||
import com.cloud.storage.dao.VolumeDao;
|
||||
import com.cloud.template.VirtualMachineTemplate;
|
||||
|
|
@ -467,6 +470,24 @@ public class UserVmManagerImplTest {
|
|||
Class<InvalidParameterValueException> expectedInvalidParameterValueException = InvalidParameterValueException.class;
|
||||
Class<CloudRuntimeException> expectedCloudRuntimeException = CloudRuntimeException.class;
|
||||
|
||||
private Map<ConfigKey, Object> originalConfigValues = new HashMap<>();
|
||||
|
||||
|
||||
private void updateDefaultConfigValue(final ConfigKey configKey, final Object o, boolean revert) {
|
||||
try {
|
||||
final String name = "_defaultValue";
|
||||
Field f = ConfigKey.class.getDeclaredField(name);
|
||||
f.setAccessible(true);
|
||||
String stringVal = String.valueOf(o);
|
||||
if (!revert) {
|
||||
originalConfigValues.put(configKey, f.get(configKey));
|
||||
}
|
||||
f.set(configKey, stringVal);
|
||||
} catch (IllegalAccessException | NoSuchFieldException e) {
|
||||
Assert.fail("Failed to mock config " + configKey.key() + " value due to " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeTest() {
|
||||
userVmManagerImpl.resourceLimitService = resourceLimitMgr;
|
||||
|
|
@ -496,6 +517,9 @@ public class UserVmManagerImplTest {
|
|||
public void afterTest() {
|
||||
CallContext.unregister();
|
||||
unmanagedVMsManagerMockedStatic.close();
|
||||
for (Map.Entry<ConfigKey, Object> entry : originalConfigValues.entrySet()) {
|
||||
updateDefaultConfigValue(entry.getKey(), entry.getValue(), true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -4178,4 +4202,49 @@ public class UserVmManagerImplTest {
|
|||
verify(userVmDao, times(1)).releaseFromLockTable(vmId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateVmExtraConfigCleansUpWhenCleanupFlagIsTrue() {
|
||||
UserVmVO userVm = mock(UserVmVO.class);
|
||||
when(userVm.getUuid()).thenReturn("test-uuid");
|
||||
when(userVm.getId()).thenReturn(1L);
|
||||
|
||||
userVmManagerImpl.updateVmExtraConfig(userVm, "someConfig", true);
|
||||
|
||||
verify(vmInstanceDetailsDao, times(1)).removeDetailsWithPrefix(1L, ApiConstants.EXTRA_CONFIG);
|
||||
verifyNoMoreInteractions(vmInstanceDetailsDao);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateVmExtraConfigAddsConfigWhenValidAndEnabled() {
|
||||
UserVmVO userVm = mock(UserVmVO.class);
|
||||
when(userVm.getUuid()).thenReturn("test-uuid");
|
||||
when(userVm.getAccountId()).thenReturn(1L);
|
||||
when(userVm.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
|
||||
doNothing().when(userVmManagerImpl).persistExtraConfigKvm(anyString(), eq(userVm));
|
||||
updateDefaultConfigValue(UserVmManagerImpl.EnableAdditionalVmConfig, true, false);
|
||||
|
||||
userVmManagerImpl.updateVmExtraConfig(userVm, "validConfig", false);
|
||||
|
||||
verify(vmInstanceDetailsDao, never()).removeDetailsWithPrefix(anyLong(), anyString());
|
||||
verify(userVmManagerImpl, times(1)).addExtraConfig(userVm, "validConfig");
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void updateVmExtraConfigThrowsExceptionWhenConfigDisabled() {
|
||||
UserVmVO userVm = mock(UserVmVO.class);
|
||||
when(userVm.getAccountId()).thenReturn(1L);
|
||||
updateDefaultConfigValue(UserVmManagerImpl.EnableAdditionalVmConfig, false, false);
|
||||
|
||||
userVmManagerImpl.updateVmExtraConfig(userVm, "validConfig", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateVmExtraConfigDoesNothingWhenExtraConfigIsBlank() {
|
||||
UserVmVO userVm = mock(UserVmVO.class);
|
||||
|
||||
userVmManagerImpl.updateVmExtraConfig(userVm, "", false);
|
||||
|
||||
verify(vmInstanceDetailsDao, never()).removeDetailsWithPrefix(anyLong(), anyString());
|
||||
verify(userVmManagerImpl, never()).addExtraConfig(any(UserVmVO.class), anyString());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -425,6 +425,8 @@ export default {
|
|||
}
|
||||
if (values.extraconfig && values.extraconfig.length > 0) {
|
||||
params.extraconfig = encodeURIComponent(values.extraconfig)
|
||||
} else if (this.combinedExtraConfig) {
|
||||
params.cleanupextraconfig = true
|
||||
}
|
||||
this.loading = true
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue