Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/src/main/java/com/cloud/vm/VirtualMachineProfile.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public static class Param {
public static final Param PreserveNics = new Param("PreserveNics");
public static final Param ConsiderLastHost = new Param("ConsiderLastHost");
public static final Param ReturnAfterVolumePrepare = new Param("ReturnAfterVolumePrepare");
public static final Param ResetPasswordOnRestore = new Param("ResetPasswordOnRestore");

private String name;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ public class ApiConstants {
public static final String CURRENT_PASSWORD = "currentpassword";
public static final String SHOULD_UPDATE_PASSWORD = "update_passwd_on_host";
public static final String PASSWORD_ENABLED = "passwordenabled";
public static final String RESET_PASSWORD = "resetpassword";
public static final String SSHKEY_ENABLED = "sshkeyenabled";
public static final String PATH = "path";
public static final String PATH_READY = "pathready";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ public class CreateVMFromBackupCmd extends BaseDeployVMCmd {
@Parameter(name = ApiConstants.PRESERVE_IP, type = CommandType.BOOLEAN, description = "Use the same IP/MAC addresses as stored in the backup metadata. Works only if the original Instance is deleted and the IP/MAC address is available.")
private Boolean preserveIp;

@Parameter(name = ApiConstants.RESET_PASSWORD, type = CommandType.BOOLEAN,
description = "For a password enabled template, whether to generate a new password for the created Instance and return it in the response. " +
"If not specified, the zone setting `restore.vm.from.backup.reset.password` decides.", since = "4.22.1.0")
private Boolean resetPassword;

/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
Expand All @@ -90,6 +95,10 @@ public boolean getPreserveIp() {
return (preserveIp != null) ? preserveIp : false;
}

public Boolean getResetPassword() {
return resetPassword;
}

@Override
public void create() {
UserVm vm;
Expand Down
4 changes: 4 additions & 0 deletions server/src/main/java/com/cloud/vm/UserVmManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ public interface UserVmManager extends UserVmService {
ConfigKey<Boolean> AllowDifferentHostTagsOfferingsForVmScale = new ConfigKey<>("Advanced", Boolean.class, "allow.different.host.tags.offerings.for.vm.scale", "false",
"Enables/Disable allowing to change a VM offering to offerings with different host tags", true);

ConfigKey<Boolean> ResetPasswordOnRestoreFromBackup = new ConfigKey<Boolean>("Advanced", Boolean.class, "restore.vm.from.backup.reset.password", "true",
"For a password enabled template, whether to generate a new password and expose it in the API response when creating/restoring an Instance from a backup. " +
"Can be overridden per call with the createVMFromBackup API's resetpassword parameter.", true, ConfigKey.Scope.Zone);

static final int MAX_USER_DATA_LENGTH_BYTES = 2048;

public static final String CKS_NODE = "cksnode";
Expand Down
65 changes: 44 additions & 21 deletions server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -5801,7 +5801,8 @@ private Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVirtualMac
}

// Set parameters
Map<VirtualMachineProfile.Param, Object> params = null;
Map<VirtualMachineProfile.Param, Object> params = new HashMap<>();
params.putAll(additionalParams);
if (vm.isUpdateParameters()) {
_vmDao.loadDetails(vm);
String password = getCurrentVmPasswordOrDefineNewPassword(String.valueOf(additionalParams.getOrDefault(VirtualMachineProfile.Param.VmPassword, "")), vm, template);
Expand All @@ -5811,18 +5812,19 @@ private Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVirtualMac
// Check if an SSH key pair was selected for the instance and if so
// use it to encrypt & save the vm password
encryptAndStorePassword(vm, password);
params = createParameterInParameterMap(params, additionalParams, VirtualMachineProfile.Param.VmPassword, password);
// overwrite VmPassword
params = createParameterInParameterMap(params, VirtualMachineProfile.Param.VmPassword, password);
}

if (additionalParams.containsKey(VirtualMachineProfile.Param.BootIntoSetup)) {
if (!HypervisorType.VMware.equals(vm.getHypervisorType())) {
throw new InvalidParameterValueException(ApiConstants.BOOT_INTO_SETUP + " makes no sense for " + vm.getHypervisorType());
}

//overwrite BootIntoSetup
Object paramValue = additionalParams.get(VirtualMachineProfile.Param.BootIntoSetup);
if (logger.isTraceEnabled()) {
logger.trace("It was specified whether to enter setup mode: " + paramValue.toString());
}
params = createParameterInParameterMap(params, additionalParams, VirtualMachineProfile.Param.BootIntoSetup, paramValue);
logger.trace("It was specified whether to enter setup mode: {}", paramValue.toString());
params = createParameterInParameterMap(params, VirtualMachineProfile.Param.BootIntoSetup, paramValue);
}

VirtualMachineEntity vmEntity = _orchSrvc.getVirtualMachine(vm.getUuid());
Expand All @@ -5843,7 +5845,7 @@ private Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVirtualMac
vmEntity.deploy(reservationId, Long.toString(callerUser.getId()), params, deployOnGivenHost);

Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> vmParamPair = new Pair(vm, params);
if (vm.isUpdateParameters()) {
if (shouldClearUpdateParametersFlag(vm, additionalParams)) {
// this value is not being sent to the backend; need only for api
// display purposes
if (template.isEnablePassword()) {
Expand Down Expand Up @@ -5907,6 +5909,16 @@ public Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVirtualMach
}
}

/**
* False for a volume-prepare-only start that should still reset the password (isUpdateParameters must stay
* set for the real start that follows).
*/
boolean shouldClearUpdateParametersFlag(UserVmVO vm, Map<VirtualMachineProfile.Param, Object> additionalParams) {
boolean isVolumePrepareOnly = Boolean.TRUE.equals(additionalParams.get(VirtualMachineProfile.Param.ReturnAfterVolumePrepare));
boolean resetPasswordOnRestore = Boolean.TRUE.equals(additionalParams.get(VirtualMachineProfile.Param.ResetPasswordOnRestore));
return vm.isUpdateParameters() && !(isVolumePrepareOnly && resetPasswordOnRestore);
}

/**
* If the template is password enabled and the VM already has a password, returns it.
* If the template is password enabled and the VM does not have a password, sets the password to the password defined by the user and returns it. If no password is informed,
Expand Down Expand Up @@ -5940,20 +5952,18 @@ protected String getCurrentVmPasswordOrDefineNewPassword(String newPassword, Use
return password;
}

private Map<VirtualMachineProfile.Param, Object> createParameterInParameterMap(Map<VirtualMachineProfile.Param, Object> params, Map<VirtualMachineProfile.Param, Object> parameterMap, VirtualMachineProfile.Param parameter,
/**
* Create or overwrite a parameter in the list
* @param params the list of parameters
* @param parameter the parameter to create/overwrite
* @param parameterValue the value to give to the parameter
* @return the resulting updated list of parameters
*/
private Map<VirtualMachineProfile.Param, Object> createParameterInParameterMap(
Map<VirtualMachineProfile.Param, Object> params,
VirtualMachineProfile.Param parameter,
Object parameterValue) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("createParameterInParameterMap(%s, %s)", parameter, parameterValue));
}
if (params == null) {
if (logger.isTraceEnabled()) {
logger.trace("creating new Parameter map");
}
params = new HashMap<>();
if (parameterMap != null) {
params.putAll(parameterMap);
}
}
logger.trace("createParameterInParameterMap({}, {})", parameter, parameterValue);
params.put(parameter, parameterValue);
return params;
}
Expand Down Expand Up @@ -9420,7 +9430,8 @@ public ConfigKey<?>[] getConfigKeys() {
VmIpFetchThreadPoolMax, VmIpFetchTaskWorkers, AllowDeployVmIfGivenHostFails, EnableAdditionalVmConfig, DisplayVMOVFProperties,
KvmAdditionalConfigAllowList, XenServerAdditionalConfigAllowList, VmwareAdditionalConfigAllowList, DestroyRootVolumeOnVmDestruction,
EnforceStrictResourceLimitHostTagCheck, StrictHostTags, AllowUserForceStopVm, VmDistinctHostNameScope,
VmwareAdditionalDetailsFromOvaEnabled, VmwareAllowedAdditionalDetailsFromOva, AllowDifferentHostTagsOfferingsForVmScale};
VmwareAdditionalDetailsFromOvaEnabled, VmwareAllowedAdditionalDetailsFromOva, AllowDifferentHostTagsOfferingsForVmScale,
ResetPasswordOnRestoreFromBackup};
}

@Override
Expand Down Expand Up @@ -9805,13 +9816,25 @@ public UserVm allocateVMFromBackup(CreateVMFromBackupCmd cmd) throws Insufficien
return vm;
}

/**
* The cmd's resetpassword parameter, if set; otherwise the zone's ResetPasswordOnRestoreFromBackup setting.
*/
boolean isResetPasswordOnRestoreFromBackup(CreateVMFromBackupCmd cmd) {
if (cmd.getResetPassword() != null) {
return cmd.getResetPassword();
}
UserVmVO vm = _vmDao.findById(cmd.getEntityId());
return ResetPasswordOnRestoreFromBackup.valueIn(vm.getDataCenterId());
}

@Override
public UserVm restoreVMFromBackup(CreateVMFromBackupCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException {
long vmId = cmd.getEntityId();
UserVm vm;
Map<Long, DiskOffering> diskOfferingMap = cmd.getDataDiskTemplateToDiskOfferingMap();
Map<VirtualMachineProfile.Param, Object> additonalParams = new HashMap<>();
additonalParams.put(VirtualMachineProfile.Param.ReturnAfterVolumePrepare, true);
additonalParams.put(VirtualMachineProfile.Param.ResetPasswordOnRestore, isResetPasswordOnRestoreFromBackup(cmd));

try {
Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> vmParamPair = null;
Expand Down
101 changes: 101 additions & 0 deletions server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
Expand Down Expand Up @@ -88,6 +89,7 @@
import org.apache.cloudstack.backup.dao.BackupScheduleDao;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.resourcelimit.Reserver;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
Expand Down Expand Up @@ -1400,6 +1402,105 @@ public void getCurrentVmPasswordOrDefineNewPasswordTestUserDefinedPasswordReturn
Assert.assertEquals(expected, userVmVoMock.getPassword());
}

private void overrideDefaultConfigValue(final ConfigKey configKey, final String value) throws IllegalAccessException, NoSuchFieldException {
final Field f = ConfigKey.class.getDeclaredField("_defaultValue");
f.setAccessible(true);
f.set(configKey, value);
}

@Test
public void shouldClearUpdateParametersFlagTestVmDoesNotHaveParametersToUpdateReturnFalse() {
Mockito.doReturn(false).when(userVmVoMock).isUpdateParameters();

boolean result = userVmManagerImpl.shouldClearUpdateParametersFlag(userVmVoMock, new HashMap<>());

Assert.assertFalse(result);
}

@Test
public void shouldClearUpdateParametersFlagTestRegularStartReturnTrue() {
Mockito.doReturn(true).when(userVmVoMock).isUpdateParameters();

boolean result = userVmManagerImpl.shouldClearUpdateParametersFlag(userVmVoMock, new HashMap<>());

Assert.assertTrue(result);
}

@Test
public void shouldClearUpdateParametersFlagTestVolumePrepareOnlyWithoutPasswordResetReturnTrue() {
Mockito.doReturn(true).when(userVmVoMock).isUpdateParameters();
Map<VirtualMachineProfile.Param, Object> additionalParams = new HashMap<>();
additionalParams.put(VirtualMachineProfile.Param.ReturnAfterVolumePrepare, true);

boolean result = userVmManagerImpl.shouldClearUpdateParametersFlag(userVmVoMock, additionalParams);

Assert.assertTrue(result);
}

@Test
public void shouldClearUpdateParametersFlagTestVolumePrepareOnlyWithPasswordResetReturnFalse() {
Mockito.doReturn(true).when(userVmVoMock).isUpdateParameters();
Map<VirtualMachineProfile.Param, Object> additionalParams = new HashMap<>();
additionalParams.put(VirtualMachineProfile.Param.ReturnAfterVolumePrepare, true);
additionalParams.put(VirtualMachineProfile.Param.ResetPasswordOnRestore, true);

boolean result = userVmManagerImpl.shouldClearUpdateParametersFlag(userVmVoMock, additionalParams);

Assert.assertFalse(result);
}

@Test
public void isResetPasswordOnRestoreFromBackupTestCmdOverrideTrueIgnoresZoneSetting() throws IllegalAccessException, NoSuchFieldException {
overrideDefaultConfigValue(UserVmManager.ResetPasswordOnRestoreFromBackup, "false");
CreateVMFromBackupCmd cmd = mock(CreateVMFromBackupCmd.class);
when(cmd.getResetPassword()).thenReturn(true);

boolean result = userVmManagerImpl.isResetPasswordOnRestoreFromBackup(cmd);

Assert.assertTrue(result);
overrideDefaultConfigValue(UserVmManager.ResetPasswordOnRestoreFromBackup, "true");
}

@Test
public void isResetPasswordOnRestoreFromBackupTestCmdOverrideFalseIgnoresZoneSetting() throws IllegalAccessException, NoSuchFieldException {
overrideDefaultConfigValue(UserVmManager.ResetPasswordOnRestoreFromBackup, "true");
CreateVMFromBackupCmd cmd = mock(CreateVMFromBackupCmd.class);
when(cmd.getResetPassword()).thenReturn(false);

boolean result = userVmManagerImpl.isResetPasswordOnRestoreFromBackup(cmd);

Assert.assertFalse(result);
}

@Test
public void isResetPasswordOnRestoreFromBackupTestNoCmdOverrideFallsBackToZoneSettingTrue() throws IllegalAccessException, NoSuchFieldException {
overrideDefaultConfigValue(UserVmManager.ResetPasswordOnRestoreFromBackup, "true");
CreateVMFromBackupCmd cmd = mock(CreateVMFromBackupCmd.class);
when(cmd.getResetPassword()).thenReturn(null);
when(cmd.getEntityId()).thenReturn(vmId);
when(userVmDao.findById(vmId)).thenReturn(userVmVoMock);
Mockito.doReturn(1L).when(userVmVoMock).getDataCenterId();

boolean result = userVmManagerImpl.isResetPasswordOnRestoreFromBackup(cmd);

Assert.assertTrue(result);
}

@Test
public void isResetPasswordOnRestoreFromBackupTestNoCmdOverrideFallsBackToZoneSettingFalse() throws IllegalAccessException, NoSuchFieldException {
overrideDefaultConfigValue(UserVmManager.ResetPasswordOnRestoreFromBackup, "false");
CreateVMFromBackupCmd cmd = mock(CreateVMFromBackupCmd.class);
when(cmd.getResetPassword()).thenReturn(null);
when(cmd.getEntityId()).thenReturn(vmId);
when(userVmDao.findById(vmId)).thenReturn(userVmVoMock);
Mockito.doReturn(1L).when(userVmVoMock).getDataCenterId();

boolean result = userVmManagerImpl.isResetPasswordOnRestoreFromBackup(cmd);

Assert.assertFalse(result);
overrideDefaultConfigValue(UserVmManager.ResetPasswordOnRestoreFromBackup, "true");
}

@Test
public void testSetVmRequiredFieldsForImportNotImport() {
userVmManagerImpl.setVmRequiredFieldsForImport(false, userVmVoMock, _dcMock,
Expand Down
Loading