From ffa892891f758f3c55ce8316d9ee6a75aa30b6ed Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Mon, 9 Mar 2026 12:48:36 +0100 Subject: [PATCH 1/5] make sure virtual machine params exist --- server/src/main/java/com/cloud/vm/UserVmManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index f36c851e5bb3..9f6e21b3a57a 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -5795,7 +5795,7 @@ public Pair> startVirtualMach } // Set parameters - Map params = null; + Map params = new HashMap<>(); if (vm.isUpdateParameters()) { _vmDao.loadDetails(vm); From 242b743c34128009233e54d43f1e82dc3720717d Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Mon, 9 Mar 2026 13:32:03 +0100 Subject: [PATCH 2/5] cleanup --- .../java/com/cloud/vm/UserVmManagerImpl.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 9f6e21b3a57a..ea2d00e85fa0 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -5796,6 +5796,7 @@ public Pair> startVirtualMach // Set parameters Map params = new HashMap<>(); + params.putAll(additionalParams); if (vm.isUpdateParameters()) { _vmDao.loadDetails(vm); @@ -5809,18 +5810,19 @@ public Pair> startVirtualMach // 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()); @@ -5888,20 +5890,18 @@ protected String getCurrentVmPasswordOrDefineNewPassword(String newPassword, Use return password; } - private Map createParameterInParameterMap(Map params, Map parameterMap, VirtualMachineProfile.Param parameter, + /** + * Create or overwrite a parameter in the list + * @param params the list of parameters + * @param parameter the parameter to creat/overwrite + * @param parameterValue the value to give to the parameter + * @return the resulting updated list of parameters + */ + private Map createParameterInParameterMap( + Map 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; } From 200a961a6c1ae40c474c26fed0c1580c153ff075 Mon Sep 17 00:00:00 2001 From: dahn Date: Fri, 17 Apr 2026 09:14:10 +0200 Subject: [PATCH 3/5] Apply suggestions from code review Co-authored-by: Suresh Kumar Anaparti --- server/src/main/java/com/cloud/vm/UserVmManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 14cc8a085961..54517276b674 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -5945,7 +5945,7 @@ protected String getCurrentVmPasswordOrDefineNewPassword(String newPassword, Use /** * Create or overwrite a parameter in the list * @param params the list of parameters - * @param parameter the parameter to creat/overwrite + * @param parameter the parameter to create/overwrite * @param parameterValue the value to give to the parameter * @return the resulting updated list of parameters */ From 4e39107275cf8429caa77f2695a85076dc1190bc Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Fri, 17 Apr 2026 10:32:52 +0200 Subject: [PATCH 4/5] revert superfluent addition --- server/src/main/java/com/cloud/vm/UserVmManagerImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 54517276b674..9b1299b5bef8 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -5813,7 +5813,7 @@ private Pair> startVirtualMac // use it to encrypt & save the vm password encryptAndStorePassword(vm, password); // overwrite VmPassword - params = createParameterInParameterMap(params, additionalParams, VirtualMachineProfile.Param.VmPassword, password); + params = createParameterInParameterMap(params, VirtualMachineProfile.Param.VmPassword, password); } if (additionalParams.containsKey(VirtualMachineProfile.Param.BootIntoSetup)) { @@ -5824,7 +5824,7 @@ private Pair> startVirtualMac //overwrite BootIntoSetup Object paramValue = additionalParams.get(VirtualMachineProfile.Param.BootIntoSetup); logger.trace("It was specified whether to enter setup mode: {}", paramValue.toString()); - params = createParameterInParameterMap(params, additionalParams, VirtualMachineProfile.Param.BootIntoSetup, paramValue); + params = createParameterInParameterMap(params, VirtualMachineProfile.Param.BootIntoSetup, paramValue); } VirtualMachineEntity vmEntity = _orchSrvc.getVirtualMachine(vm.getUuid()); From 7746aa903abfc496abdd29cacf074c890149f551 Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Mon, 10 Aug 2026 15:33:56 +0200 Subject: [PATCH 5/5] pw reset configurability --- .../com/cloud/vm/VirtualMachineProfile.java | 1 + .../apache/cloudstack/api/ApiConstants.java | 1 + .../user/vm/CreateVMFromBackupCmd.java | 9 ++ .../main/java/com/cloud/vm/UserVmManager.java | 4 + .../java/com/cloud/vm/UserVmManagerImpl.java | 27 ++++- .../com/cloud/vm/UserVmManagerImplTest.java | 101 ++++++++++++++++++ 6 files changed, 141 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/cloud/vm/VirtualMachineProfile.java b/api/src/main/java/com/cloud/vm/VirtualMachineProfile.java index 5c78d6bedd64..c91f5b736514 100644 --- a/api/src/main/java/com/cloud/vm/VirtualMachineProfile.java +++ b/api/src/main/java/com/cloud/vm/VirtualMachineProfile.java @@ -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; diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 03b73834a94e..762da4ab5ff8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -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"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMFromBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMFromBackupCmd.java index 6192bfb4540a..7390d4d2da71 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMFromBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMFromBackupCmd.java @@ -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 /////////////////////// ///////////////////////////////////////////////////// @@ -90,6 +95,10 @@ public boolean getPreserveIp() { return (preserveIp != null) ? preserveIp : false; } + public Boolean getResetPassword() { + return resetPassword; + } + @Override public void create() { UserVm vm; diff --git a/server/src/main/java/com/cloud/vm/UserVmManager.java b/server/src/main/java/com/cloud/vm/UserVmManager.java index 38cb6d2db46b..7882668ccbbd 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManager.java +++ b/server/src/main/java/com/cloud/vm/UserVmManager.java @@ -111,6 +111,10 @@ public interface UserVmManager extends UserVmService { ConfigKey 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 ResetPasswordOnRestoreFromBackup = new ConfigKey("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"; diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 9b1299b5bef8..d6348e1ca5ac 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -5845,7 +5845,7 @@ private Pair> startVirtualMac vmEntity.deploy(reservationId, Long.toString(callerUser.getId()), params, deployOnGivenHost); Pair> 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()) { @@ -5909,6 +5909,16 @@ public Pair> 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 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, @@ -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 @@ -9805,6 +9816,17 @@ 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(); @@ -9812,6 +9834,7 @@ public UserVm restoreVMFromBackup(CreateVMFromBackupCmd cmd) throws ResourceUnav Map diskOfferingMap = cmd.getDataDiskTemplateToDiskOfferingMap(); Map additonalParams = new HashMap<>(); additonalParams.put(VirtualMachineProfile.Param.ReturnAfterVolumePrepare, true); + additonalParams.put(VirtualMachineProfile.Param.ResetPasswordOnRestore, isResetPasswordOnRestoreFromBackup(cmd)); try { Pair> vmParamPair = null; diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index cb15298fbf33..6cc1ef2337c3 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -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; @@ -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; @@ -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 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 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,