Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ public class MigrateVirtualMachineWithVolumeCmd extends BaseAsyncCmd {
"<1b331390-59f2-4796-9993-bf11c6e76225>&migrateto[2].pool=<41fdb564-9d3b-447d-88ed-7628f7640cbc>")
private Map migrateVolumeTo;

@Parameter(name = ApiConstants.AUTO_SELECT,
since = "4.19.0",
type = CommandType.BOOLEAN,
description = "Automatically select a destination host for a running instance, if hostId is not specified. false by default")
private Boolean autoSelect;

/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
Expand Down Expand Up @@ -144,31 +150,39 @@ public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.VirtualMachine;
}

private Host getDestinationHost() {
if (getHostId() == null) {
return null;
}
Host destinationHost = _resourceService.getHost(getHostId());
// OfflineVmwareMigration: destination host would have to not be a required parameter for stopped VMs
if (destinationHost == null) {
s_logger.error(String.format("Unable to find the host with ID [%s].", getHostId()));
throw new InvalidParameterValueException("Unable to find the specified host to migrate the VM.");
}
return destinationHost;
}

@Override
public void execute() {
if (hostId == null && MapUtils.isEmpty(migrateVolumeTo)) {
throw new InvalidParameterValueException(String.format("Either %s or %s must be passed for migrating the VM.", ApiConstants.HOST_ID, ApiConstants.MIGRATE_TO));
if (hostId == null && MapUtils.isEmpty(migrateVolumeTo) && !Boolean.TRUE.equals(autoSelect)) {
throw new InvalidParameterValueException(String.format("Either %s or %s must be passed or %s must be true for migrating the VM.", ApiConstants.HOST_ID, ApiConstants.MIGRATE_TO, ApiConstants.AUTO_SELECT));
}

VirtualMachine virtualMachine = _userVmService.getVm(getVirtualMachineId());
if (!VirtualMachine.State.Running.equals(virtualMachine.getState()) && hostId != null) {
if (!VirtualMachine.State.Running.equals(virtualMachine.getState()) && (hostId != null || Boolean.TRUE.equals(autoSelect))) {
throw new InvalidParameterValueException(String.format("%s is not in the Running state to migrate it to the new host.", virtualMachine));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not in scope, but I think this meesage is misleading a bit:

Suggested change
throw new InvalidParameterValueException(String.format("%s is not in the Running state to migrate it to the new host.", virtualMachine));
throw new InvalidParameterValueException(String.format("%s is not in the Running state. It must be running to migrate to a new host.", virtualMachine));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this as it can be addressed separately

}

if (!VirtualMachine.State.Stopped.equals(virtualMachine.getState()) && hostId == null) {
throw new InvalidParameterValueException(String.format("%s is not in the Stopped state to migrate, use the %s parameter to migrate it to a new host.",
virtualMachine, ApiConstants.HOST_ID));
if (!VirtualMachine.State.Stopped.equals(virtualMachine.getState()) && hostId == null && Boolean.FALSE.equals(autoSelect)) {
throw new InvalidParameterValueException(String.format("%s is not in the Stopped state to migrate, use the %s or %s parameter to migrate it to a new host.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, not really in scope:

Suggested change
throw new InvalidParameterValueException(String.format("%s is not in the Stopped state to migrate, use the %s or %s parameter to migrate it to a new host.",
throw new InvalidParameterValueException(String.format("%s is not in the Stopped. To migrate it, use the %s or %s parameter to select a new host.",

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this as it can be addressed separately

virtualMachine, ApiConstants.HOST_ID,ApiConstants.AUTO_SELECT));
}

try {
VirtualMachine migratedVm = null;
if (hostId != null) {
Host destinationHost = _resourceService.getHost(getHostId());
// OfflineVmwareMigration: destination host would have to not be a required parameter for stopped VMs
if (destinationHost == null) {
s_logger.error(String.format("Unable to find the host with ID [%s].", getHostId()));
throw new InvalidParameterValueException("Unable to find the specified host to migrate the VM.");
}
if (getHostId() != null || Boolean.TRUE.equals(autoSelect)) {
Host destinationHost = getDestinationHost();
migratedVm = _userVmService.migrateVirtualMachineWithVolume(getVirtualMachineId(), destinationHost, getVolumeToPool());
} else if (MapUtils.isNotEmpty(migrateVolumeTo)) {
migratedVm = _userVmService.vmStorageMigration(getVirtualMachineId(), getVolumeToPool());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,10 @@
// under the License.
package org.apache.cloudstack.api.command.admin.vm;

import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ManagementServerException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.exception.VirtualMachineMigrationException;
import com.cloud.host.Host;
import com.cloud.resource.ResourceService;
import com.cloud.uservm.UserVm;
import com.cloud.utils.db.UUIDManager;
import com.cloud.vm.UserVmService;
import com.cloud.vm.VirtualMachine;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ResponseGenerator;
import org.apache.cloudstack.api.ResponseObject;
Expand All @@ -40,13 +34,21 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.powermock.modules.junit4.PowerMockRunner;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.List;
import java.util.Map;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ManagementServerException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.exception.VirtualMachineMigrationException;
import com.cloud.host.Host;
import com.cloud.resource.ResourceService;
import com.cloud.uservm.UserVm;
import com.cloud.utils.db.UUIDManager;
import com.cloud.vm.UserVmService;
import com.cloud.vm.VirtualMachine;

@RunWith(PowerMockRunner.class)
@RunWith(MockitoJUnitRunner.class)
public class MigrateVirtualMachineWithVolumeCmdTest {
@Mock
UserVmService userVmServiceMock;
Expand All @@ -68,44 +70,46 @@ public class MigrateVirtualMachineWithVolumeCmdTest {

@Spy
@InjectMocks
MigrateVirtualMachineWithVolumeCmd cmdSpy = new MigrateVirtualMachineWithVolumeCmd();
MigrateVirtualMachineWithVolumeCmd cmdSpy;

private Long hostId = 1L;
private Long virtualMachineUuid = 1L;
private Long virtualMachineId = 1L;
private String virtualMachineName = "VM-name";
private Map<String, String> migrateVolumeTo = Map.of("key","value");
private Map<String, String> migrateVolumeTo = null;
private SystemVmResponse systemVmResponse = new SystemVmResponse();
private UserVmResponse userVmResponse = new UserVmResponse();

@Before
public void setup() {
Mockito.when(cmdSpy.getVirtualMachineId()).thenReturn(virtualMachineUuid);
Mockito.when(cmdSpy.getHostId()).thenReturn(hostId);
Mockito.when(cmdSpy.getVolumeToPool()).thenReturn(migrateVolumeTo);
public void setUp() throws Exception {
ReflectionTestUtils.setField(cmdSpy, "virtualMachineId", virtualMachineId);
migrateVolumeTo = new HashMap<>();
migrateVolumeTo.put("volume", "abc");
migrateVolumeTo.put("pool", "xyz");
}

@Test
public void executeTestHostIdIsNullAndMigrateVolumeToIsNullThrowsInvalidParameterValueException(){
public void executeTestRequiredArgsNullThrowsInvalidParameterValueException() {
ReflectionTestUtils.setField(cmdSpy, "hostId", null);
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", null);
ReflectionTestUtils.setField(cmdSpy, "autoSelect", null);

try {
cmdSpy.execute();
} catch (Exception e) {
Assert.assertEquals(InvalidParameterValueException.class, e.getClass());
String expected = String.format("Either %s or %s must be passed for migrating the VM.", ApiConstants.HOST_ID, ApiConstants.MIGRATE_TO);
String expected = String.format("Either %s or %s must be passed or %s must be true for migrating the VM.", ApiConstants.HOST_ID, ApiConstants.MIGRATE_TO, ApiConstants.AUTO_SELECT);
Assert.assertEquals(expected , e.getMessage());
}
}

@Test
public void executeTestVMIsStoppedAndHostIdIsNotNullThrowsInvalidParameterValueException(){
public void executeTestVMIsStoppedAndHostIdIsNotNullThrowsInvalidParameterValueException() {
ReflectionTestUtils.setField(cmdSpy, "hostId", hostId);
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);

Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Stopped);
Mockito.when(virtualMachineMock.toString()).thenReturn(String.format("VM [uuid: %s, name: %s]", virtualMachineUuid, virtualMachineName));
Mockito.when(virtualMachineMock.toString()).thenReturn(String.format("VM [uuid: %s, name: %s]", virtualMachineId, virtualMachineName));

try {
cmdSpy.execute();
Expand All @@ -117,33 +121,35 @@ public void executeTestVMIsStoppedAndHostIdIsNotNullThrowsInvalidParameterValueE
}

@Test
public void executeTestVMIsRunningAndHostIdIsNullThrowsInvalidParameterValueException(){
public void executeTestVMIsRunningHostIdIsNullAndAutoSelectIsFalseThrowsInvalidParameterValueException() {
ReflectionTestUtils.setField(cmdSpy, "hostId", null);
ReflectionTestUtils.setField(cmdSpy, "autoSelect", false);
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);

Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Running);
Mockito.when(virtualMachineMock.toString()).thenReturn(String.format("VM [uuid: %s, name: %s]", virtualMachineUuid, virtualMachineName));
Mockito.when(virtualMachineMock.toString()).thenReturn(String.format("VM [uuid: %s, name: %s]", virtualMachineId, virtualMachineName));

try {
cmdSpy.execute();
} catch (Exception e) {
Assert.assertEquals(InvalidParameterValueException.class, e.getClass());
String expected = String.format("%s is not in the Stopped state to migrate, use the %s parameter to migrate it to a new host.", virtualMachineMock,
ApiConstants.HOST_ID);
String expected = String.format("%s is not in the Stopped state to migrate, use the %s or %s parameter to migrate it to a new host.", virtualMachineMock,
ApiConstants.HOST_ID, ApiConstants.AUTO_SELECT);
Assert.assertEquals(expected , e.getMessage());
}
}

@Test
public void executeTestHostIdIsNullThrowsInvalidParameterValueException(){
public void executeTestHostIdIsNullThrowsInvalidParameterValueException() {
ReflectionTestUtils.setField(cmdSpy, "virtualMachineId", virtualMachineId);
ReflectionTestUtils.setField(cmdSpy, "hostId", hostId);
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
ReflectionTestUtils.setField(cmdSpy, "autoSelect", false);

Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Running);
Mockito.when(resourceServiceMock.getHost(Mockito.anyLong())).thenReturn(null);
Mockito.when(uuidManagerMock.getUuid(Host.class, virtualMachineUuid)).thenReturn(virtualMachineUuid.toString());

try {
cmdSpy.execute();
Expand All @@ -154,15 +160,22 @@ public void executeTestHostIdIsNullThrowsInvalidParameterValueException(){
}
}

private Map getMockedMigrateVolumeToApiCmdParam() {
Map<String, String> migrateVolumeTo = new HashMap<>();
migrateVolumeTo.put("volume", "abc");
migrateVolumeTo.put("pool", "xyz");
return Map.of("", migrateVolumeTo);
}

@Test
public void executeTestHostIsNotNullMigratedVMIsNullThrowsServerApiException() throws ManagementServerException, ResourceUnavailableException, VirtualMachineMigrationException {
ReflectionTestUtils.setField(cmdSpy, "hostId", hostId);
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", getMockedMigrateVolumeToApiCmdParam());

Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Running);
Mockito.when(resourceServiceMock.getHost(Mockito.anyLong())).thenReturn(hostMock);
Mockito.when(userVmServiceMock.migrateVirtualMachineWithVolume(virtualMachineUuid, hostMock, migrateVolumeTo)).thenReturn(null);
Mockito.when(resourceServiceMock.getHost(hostId)).thenReturn(hostMock);
Mockito.when(userVmServiceMock.migrateVirtualMachineWithVolume(Mockito.anyLong(), Mockito.any(), Mockito.anyMap())).thenReturn(null);

try {
cmdSpy.execute();
Expand All @@ -176,11 +189,11 @@ public void executeTestHostIsNotNullMigratedVMIsNullThrowsServerApiException() t
@Test
public void executeTestHostIsNullMigratedVMIsNullThrowsServerApiException() {
ReflectionTestUtils.setField(cmdSpy, "hostId", null);
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", getMockedMigrateVolumeToApiCmdParam());

Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Stopped);
Mockito.when(userVmServiceMock.vmStorageMigration(virtualMachineUuid, migrateVolumeTo)).thenReturn(null);
Mockito.when(userVmServiceMock.vmStorageMigration(Mockito.anyLong(), Mockito.anyMap())).thenReturn(null);

try {
cmdSpy.execute();
Expand All @@ -194,11 +207,11 @@ public void executeTestHostIsNullMigratedVMIsNullThrowsServerApiException() {
@Test
public void executeTestSystemVMMigratedWithSuccess() {
ReflectionTestUtils.setField(cmdSpy, "hostId", null);
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", getMockedMigrateVolumeToApiCmdParam());

Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Stopped);
Mockito.when(userVmServiceMock.vmStorageMigration(virtualMachineUuid, migrateVolumeTo)).thenReturn(virtualMachineMock);
Mockito.when(userVmServiceMock.vmStorageMigration(Mockito.anyLong(), Mockito.anyMap())).thenReturn(virtualMachineMock);
Mockito.when(virtualMachineMock.getType()).thenReturn(VirtualMachine.Type.ConsoleProxy);
Mockito.when(responseGeneratorMock.createSystemVmResponse(virtualMachineMock)).thenReturn(systemVmResponse);

Expand All @@ -211,11 +224,11 @@ public void executeTestSystemVMMigratedWithSuccess() {
public void executeTestUserVMMigratedWithSuccess() {
UserVm userVmMock = Mockito.mock(UserVm.class);
ReflectionTestUtils.setField(cmdSpy, "hostId", null);
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", getMockedMigrateVolumeToApiCmdParam());

Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(userVmMock);
Mockito.when(userVmMock.getState()).thenReturn(VirtualMachine.State.Stopped);
Mockito.when(userVmServiceMock.vmStorageMigration(virtualMachineUuid, migrateVolumeTo)).thenReturn(userVmMock);
Mockito.when(userVmServiceMock.vmStorageMigration(Mockito.anyLong(), Mockito.anyMap())).thenReturn(userVmMock);
Mockito.when(userVmMock.getType()).thenReturn(VirtualMachine.Type.User);
Mockito.when(responseGeneratorMock.createUserVmResponse(ResponseObject.ResponseView.Full, "virtualmachine", userVmMock)).thenReturn(List.of(userVmResponse));

Expand Down
Loading