diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index c140a1e1c22..1cbe28f4dde 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -105,6 +105,10 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG @Parameter(name = ApiConstants.DISPLAY_NAME, type = CommandType.STRING, description = "an optional user generated name for the virtual machine") private String displayName; + @Parameter(name=ApiConstants.PASSWORD, type=CommandType.STRING, description="The password of the virtual machine. If null, a random password will be generated for the VM.", + since="4.19.0.0") + protected String password; + //Owner information @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "an optional account for the virtual machine. Must be used with domainId.") private String accountName; @@ -464,6 +468,10 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG return zoneId; } + public String getPassword() { + return password; + } + public List getNetworkIds() { if (MapUtils.isNotEmpty(vAppNetworks)) { if (CollectionUtils.isNotEmpty(networkIds) || ipAddress != null || getIp6Address() != null || MapUtils.isNotEmpty(ipToNetworkList)) { diff --git a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java index de8a3ff3c83..2f69ac6e9ba 100644 --- a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java +++ b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java @@ -1964,7 +1964,7 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage private boolean startNewVM(long vmId) { try { CallContext.current().setEventDetails("Vm Id: " + vmId); - userVmMgr.startVirtualMachine(vmId, null, null, null); + userVmMgr.startVirtualMachine(vmId, null, new HashMap<>(), null); } catch (final ResourceUnavailableException ex) { s_logger.warn("Exception: ", ex); throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage()); diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 23be3facd25..e4dc03a72fd 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -135,6 +135,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.log4j.Logger; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.w3c.dom.Document; @@ -1174,9 +1175,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir private UserVm forceRebootVirtualMachine(long vmId, long hostId, boolean enterSetup) { try { if (stopVirtualMachine(vmId, false) != null) { - Map params = null; + Map params = new HashMap<>(); if (enterSetup) { - params = new HashMap(); params.put(VirtualMachineProfile.Param.BootIntoSetup, Boolean.TRUE); } return startVirtualMachine(vmId, null, null, hostId, params, null, false).first(); @@ -4878,6 +4878,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir if (cmd.getBootIntoSetup() != null) { additionalParams.put(VirtualMachineProfile.Param.BootIntoSetup, cmd.getBootIntoSetup()); } + + if (StringUtils.isNotBlank(cmd.getPassword())) { + additionalParams.put(VirtualMachineProfile.Param.VmPassword, cmd.getPassword()); + } + return startVirtualMachine(vmId, podId, clusterId, hostId, diskOfferingMap, additionalParams, cmd.getDeploymentPlanner()); } @@ -5268,21 +5273,21 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } @Override - public Pair> startVirtualMachine(long vmId, Long hostId, Map additionalParams, String deploymentPlannerToUse) - throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + public Pair> startVirtualMachine(long vmId, Long hostId, @NotNull Map additionalParams, + String deploymentPlannerToUse) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { return startVirtualMachine(vmId, null, null, hostId, additionalParams, deploymentPlannerToUse); } @Override public Pair> startVirtualMachine(long vmId, Long podId, Long clusterId, Long hostId, - Map additionalParams, String deploymentPlannerToUse) + @NotNull Map additionalParams, String deploymentPlannerToUse) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { return startVirtualMachine(vmId, podId, clusterId, hostId, additionalParams, deploymentPlannerToUse, true); } @Override public Pair> startVirtualMachine(long vmId, Long podId, Long clusterId, Long hostId, - Map additionalParams, String deploymentPlannerToUse, boolean isExplicitHost) + @NotNull Map additionalParams, String deploymentPlannerToUse, boolean isExplicitHost) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { // Input validation final Account callerAccount = CallContext.current().getCallingAccount(); @@ -5381,15 +5386,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir // Check that the password was passed in and is valid template = _templateDao.findByIdIncludingRemoved(vm.getTemplateId()); - String password = "saved_password"; - if (template.isEnablePassword()) { - if (vm.getDetail("password") != null) { - password = DBEncryptionUtil.decrypt(vm.getDetail("password")); - } else { - password = _mgr.generateRandomPassword(); - vm.setPassword(password); - } - } + String password = getCurrentVmPasswordOrDefineNewPassword(String.valueOf(additionalParams.getOrDefault(VirtualMachineProfile.Param.VmPassword, "")), vm, template); if (!validPassword(password)) { throw new InvalidParameterValueException("A valid password for this virtual machine was not provided."); @@ -5402,7 +5399,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir params = createParameterInParameterMap(params, additionalParams, VirtualMachineProfile.Param.VmPassword, password); } - if(null != additionalParams && additionalParams.containsKey(VirtualMachineProfile.Param.BootIntoSetup)) { + 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()); } @@ -5445,6 +5442,39 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return vmParamPair; } + /** + * 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, + * sets it to a random password and returns it. + * If the template is not password enabled, returns saved_password. + * @param newPassword The new password informed by the user in order to set the password of the VM. + * @param vm The VM to retrieve the password from. + * @param template The template to be checked if the password is enabled or not. + * @return The password of the VM or saved_password. + */ + protected String getCurrentVmPasswordOrDefineNewPassword(String newPassword, UserVmVO vm, VMTemplateVO template) { + String password = "saved_password"; + + if (template.isEnablePassword()) { + if (vm.getDetail("password") != null) { + s_logger.debug(String.format("Decrypting VM [%s] current password.", vm)); + password = DBEncryptionUtil.decrypt(vm.getDetail("password")); + } else if (StringUtils.isNotBlank(newPassword)) { + s_logger.debug(String.format("A password for VM [%s] was informed. Setting VM password to value defined by user.", vm)); + password = newPassword; + vm.setPassword(password); + } else { + s_logger.debug(String.format("Setting VM [%s] password to a randomly generated password.", vm)); + password = _mgr.generateRandomPassword(); + vm.setPassword(password); + } + } else if (StringUtils.isNotBlank(newPassword)) { + s_logger.debug(String.format("A password was informed; however, the template [%s] is not password enabled. Ignoring the parameter.", template)); + } + + return password; + } + private Map createParameterInParameterMap(Map params, Map parameterMap, VirtualMachineProfile.Param parameter, Object parameterValue) { if (s_logger.isTraceEnabled()) { diff --git a/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java b/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java index 0c65f1a4694..aaf0f254d41 100644 --- a/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java +++ b/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java @@ -99,7 +99,6 @@ import com.cloud.vm.UserVmService; import com.cloud.vm.UserVmVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineManager; -import com.cloud.vm.VirtualMachineProfile; import com.cloud.vm.VmStats; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.UserVmDao; @@ -1495,8 +1494,6 @@ public class AutoScaleManagerImplTest { when(autoScaleVmGroupDao.updateState(vmGroupId, AutoScaleVmGroup.State.ENABLED, AutoScaleVmGroup.State.SCALING)).thenReturn(true); when(autoScaleVmGroupDao.updateState(vmGroupId, AutoScaleVmGroup.State.SCALING, AutoScaleVmGroup.State.ENABLED)).thenReturn(true); Mockito.doReturn(virtualMachineId).when(autoScaleManagerImplSpy).createNewVM(asVmGroupMock); - Pair> startVm = Mockito.mock(Pair.class); - when(userVmMgr.startVirtualMachine(virtualMachineId, null, null, null)).thenReturn(startVm); when(asVmGroupMock.getLoadBalancerId()).thenReturn(loadBalancerId); when(lbVmMapDao.listByLoadBalancerId(loadBalancerId)).thenReturn(Arrays.asList(loadBalancerVMMapMock)); diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index a58ce358dda..7886e70920c 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -38,6 +38,7 @@ import com.cloud.network.NetworkModel; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; import com.cloud.offering.ServiceOffering; +import com.cloud.server.ManagementService; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; @@ -213,6 +214,12 @@ public class UserVmManagerImplTest { @Mock AccountVO account; + @Mock + VMTemplateVO vmTemplateVoMock; + + @Mock + ManagementService managementServiceMock; + @Mock private ServiceOfferingVO serviceOffering; @@ -1068,4 +1075,58 @@ public class UserVmManagerImplTest { Mockito.verify(userVmDao).findById(vmId); Mockito.verify(userVmDao).update(vmId, userVmVoMock); } + + @Test + public void getCurrentVmPasswordOrDefineNewPasswordTestTemplateIsNotPasswordEnabledReturnPreDefinedString() { + String expected = "saved_password"; + + Mockito.doReturn(false).when(vmTemplateVoMock).isEnablePassword(); + + String result = userVmManagerImpl.getCurrentVmPasswordOrDefineNewPassword("", userVmVoMock, vmTemplateVoMock); + + Assert.assertEquals(expected, result); + } + + @Test + public void getCurrentVmPasswordOrDefineNewPasswordTestVmHasPasswordReturnCurrentPassword() { + String expected = "current_password"; + + Mockito.doReturn(true).when(vmTemplateVoMock).isEnablePassword(); + Mockito.doReturn(expected).when(userVmVoMock).getDetail("password"); + + String result = userVmManagerImpl.getCurrentVmPasswordOrDefineNewPassword("", userVmVoMock, vmTemplateVoMock); + + Assert.assertEquals(expected, result); + } + + @Test + public void getCurrentVmPasswordOrDefineNewPasswordTestUserDefinedPasswordReturnNewPasswordAndSetVmPassword() { + String expected = "new_password"; + + Mockito.doReturn(true).when(vmTemplateVoMock).isEnablePassword(); + Mockito.doReturn(null).when(userVmVoMock).getDetail("password"); + Mockito.doCallRealMethod().when(userVmVoMock).setPassword(Mockito.any()); + Mockito.doCallRealMethod().when(userVmVoMock).getPassword(); + + String result = userVmManagerImpl.getCurrentVmPasswordOrDefineNewPassword(expected, userVmVoMock, vmTemplateVoMock); + + Assert.assertEquals(expected, result); + Assert.assertEquals(expected, userVmVoMock.getPassword()); + } + + @Test + public void getCurrentVmPasswordOrDefineNewPasswordTestUserDefinedPasswordReturnRandomPasswordAndSetVmPassword() { + String expected = "random_password"; + + Mockito.doReturn(true).when(vmTemplateVoMock).isEnablePassword(); + Mockito.doReturn(null).when(userVmVoMock).getDetail("password"); + Mockito.doReturn(expected).when(managementServiceMock).generateRandomPassword(); + Mockito.doCallRealMethod().when(userVmVoMock).setPassword(Mockito.any()); + Mockito.doCallRealMethod().when(userVmVoMock).getPassword(); + + String result = userVmManagerImpl.getCurrentVmPasswordOrDefineNewPassword("", userVmVoMock, vmTemplateVoMock); + + Assert.assertEquals(expected, result); + Assert.assertEquals(expected, userVmVoMock.getPassword()); + } }