This is an automated email from the ASF dual-hosted git repository.

DaanHoogland pushed a commit to branch 4.22
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.22 by this push:
     new 6434ff2c62a make sure virtual machine params exist (#12771)
6434ff2c62a is described below

commit 6434ff2c62a29b2ba5c38a0f87ca61f10e32d9e4
Author: dahn <[email protected]>
AuthorDate: Wed Sep 2 10:18:03 2026 +0200

    make sure virtual machine params exist (#12771)
    
    Co-authored-by: Daan Hoogland <[email protected]>
    Co-authored-by: Suresh Kumar Anaparti <[email protected]>
---
 .../java/com/cloud/vm/VirtualMachineProfile.java   |   1 +
 .../org/apache/cloudstack/api/ApiConstants.java    |   1 +
 .../api/command/user/vm/CreateVMFromBackupCmd.java |   9 ++
 .../src/main/java/com/cloud/vm/UserVmManager.java  |   4 +
 .../main/java/com/cloud/vm/UserVmManagerImpl.java  |  65 ++++++++-----
 .../java/com/cloud/vm/UserVmManagerImplTest.java   | 101 +++++++++++++++++++++
 6 files changed, 160 insertions(+), 21 deletions(-)

diff --git a/api/src/main/java/com/cloud/vm/VirtualMachineProfile.java 
b/api/src/main/java/com/cloud/vm/VirtualMachineProfile.java
index 5c78d6bedd6..c91f5b73651 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 interface VirtualMachineProfile {
         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 a8ff00c40ff..91beefde509 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 6192bfb4540..7390d4d2da7 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 class CreateVMFromBackupCmd extends BaseDeployVMCmd {
         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 38cb6d2db46..7882668ccbb 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<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";
diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java 
b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
index 95c00c66067..ec87e941933 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
@@ -5819,7 +5819,8 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         }
 
         // 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);
@@ -5829,18 +5830,19 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
             // 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());
@@ -5861,7 +5863,7 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         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()) {
@@ -5925,6 +5927,16 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         }
     }
 
+    /**
+     * 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,
@@ -5958,20 +5970,18 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         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;
     }
@@ -9453,7 +9463,8 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
                 VmIpFetchThreadPoolMax, VmIpFetchTaskWorkers, 
AllowDeployVmIfGivenHostFails, EnableAdditionalVmConfig, DisplayVMOVFProperties,
                 KvmAdditionalConfigAllowList, 
XenServerAdditionalConfigAllowList, VmwareAdditionalConfigAllowList, 
DestroyRootVolumeOnVmDestruction,
                 EnforceStrictResourceLimitHostTagCheck, StrictHostTags, 
AllowUserForceStopVm, VmDistinctHostNameScope,
-                VmwareAdditionalDetailsFromOvaEnabled, 
VmwareAllowedAdditionalDetailsFromOva, 
AllowDifferentHostTagsOfferingsForVmScale};
+                VmwareAdditionalDetailsFromOvaEnabled, 
VmwareAllowedAdditionalDetailsFromOva, 
AllowDifferentHostTagsOfferingsForVmScale,
+                ResetPasswordOnRestoreFromBackup};
     }
 
     @Override
@@ -9838,6 +9849,17 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         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();
@@ -9845,6 +9867,7 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         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;
diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java 
b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
index 70302b22e79..4b26402ffbe 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.times;
 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;
@@ -84,6 +85,7 @@ import org.apache.cloudstack.backup.dao.BackupDao;
 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.engine.orchestration.service.VolumeOrchestrationService;
 import org.apache.cloudstack.resourcelimit.Reserver;
 import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
@@ -1404,6 +1406,105 @@ public class UserVmManagerImplTest {
         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,

Reply via email to