mirror of https://github.com/apache/cloudstack.git
server,api,ui: host auto-select for migrateVirtualMachineWithVolume (#7554)
Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
This commit is contained in:
parent
cb4e35f44b
commit
0466bf9d47
|
|
@ -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 ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -144,31 +150,39 @@ public class MigrateVirtualMachineWithVolumeCmd extends BaseAsyncCmd {
|
|||
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));
|
||||
}
|
||||
|
||||
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.",
|
||||
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());
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -40,13 +34,21 @@ import org.mockito.InjectMocks;
|
|||
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;
|
||||
|
|
@ -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();
|
||||
|
|
@ -117,33 +121,35 @@ public class MigrateVirtualMachineWithVolumeCmdTest {
|
|||
}
|
||||
|
||||
@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();
|
||||
|
|
@ -154,15 +160,22 @@ public class MigrateVirtualMachineWithVolumeCmdTest {
|
|||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
@ -176,11 +189,11 @@ public class MigrateVirtualMachineWithVolumeCmdTest {
|
|||
@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();
|
||||
|
|
@ -194,11 +207,11 @@ public class MigrateVirtualMachineWithVolumeCmdTest {
|
|||
@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);
|
||||
|
||||
|
|
@ -211,11 +224,11 @@ public class MigrateVirtualMachineWithVolumeCmdTest {
|
|||
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));
|
||||
|
||||
|
|
|
|||
|
|
@ -6347,22 +6347,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
}
|
||||
|
||||
public boolean isVMUsingLocalStorage(VMInstanceVO vm) {
|
||||
boolean usesLocalStorage = false;
|
||||
|
||||
List<VolumeVO> volumes = _volsDao.findByInstance(vm.getId());
|
||||
for (VolumeVO vol : volumes) {
|
||||
DiskOfferingVO diskOffering = _diskOfferingDao.findById(vol.getDiskOfferingId());
|
||||
if (diskOffering.isUseLocalStorage()) {
|
||||
usesLocalStorage = true;
|
||||
break;
|
||||
}
|
||||
StoragePoolVO storagePool = _storagePoolDao.findById(vol.getPoolId());
|
||||
if (storagePool.isLocal()) {
|
||||
usesLocalStorage = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return usesLocalStorage;
|
||||
return isAnyVmVolumeUsingLocalStorage(volumes);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -6421,7 +6407,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
|
||||
DeployDestination dest = null;
|
||||
if (destinationHost == null) {
|
||||
dest = chooseVmMigrationDestination(vm, srcHost);
|
||||
dest = chooseVmMigrationDestination(vm, srcHost, null);
|
||||
} else {
|
||||
dest = checkVmMigrationDestination(vm, srcHost, destinationHost);
|
||||
}
|
||||
|
|
@ -6436,7 +6422,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
return findMigratedVm(vm.getId(), vm.getType());
|
||||
}
|
||||
|
||||
private DeployDestination chooseVmMigrationDestination(VMInstanceVO vm, Host srcHost) {
|
||||
private DeployDestination chooseVmMigrationDestination(VMInstanceVO vm, Host srcHost, Long poolId) {
|
||||
vm.setLastHostId(null); // Last host does not have higher priority in vm migration
|
||||
final ServiceOfferingVO offering = serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId());
|
||||
final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm, null, offering, null, null);
|
||||
|
|
@ -6444,7 +6430,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
final Host host = _hostDao.findById(srcHostId);
|
||||
ExcludeList excludes = new ExcludeList();
|
||||
excludes.addHost(srcHostId);
|
||||
final DataCenterDeployment plan = _itMgr.getMigrationDeployment(vm, host, null, excludes);
|
||||
final DataCenterDeployment plan = _itMgr.getMigrationDeployment(vm, host, poolId, excludes);
|
||||
try {
|
||||
return _planningMgr.planDeployment(profile, plan, excludes, null);
|
||||
} catch (final AffinityConflictException e2) {
|
||||
|
|
@ -6744,8 +6730,21 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
return implicitPlannerUsed;
|
||||
}
|
||||
|
||||
private boolean isVmVolumesOnZoneWideStore(VMInstanceVO vm) {
|
||||
final List<VolumeVO> volumes = _volsDao.findCreatedByInstance(vm.getId());
|
||||
protected boolean isAnyVmVolumeUsingLocalStorage(final List<VolumeVO> volumes) {
|
||||
for (VolumeVO vol : volumes) {
|
||||
DiskOfferingVO diskOffering = _diskOfferingDao.findById(vol.getDiskOfferingId());
|
||||
if (diskOffering.isUseLocalStorage()) {
|
||||
return true;
|
||||
}
|
||||
StoragePoolVO storagePool = _storagePoolDao.findById(vol.getPoolId());
|
||||
if (storagePool.isLocal()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isAllVmVolumesOnZoneWideStore(final List<VolumeVO> volumes) {
|
||||
if (CollectionUtils.isEmpty(volumes)) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -6769,6 +6768,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
throw new InvalidParameterValueException("Cannot migrate VM, host with ID: " + srcHostId + " for VM not found");
|
||||
}
|
||||
|
||||
if (destinationHost == null) {
|
||||
return new Pair<>(srcHost, null);
|
||||
}
|
||||
|
||||
// Check if source and destination hosts are valid and migrating to same host
|
||||
if (destinationHost.getId() == srcHostId) {
|
||||
throw new InvalidParameterValueException(String.format("Cannot migrate VM as it is already present on host %s (ID: %s), please specify valid destination host to migrate the VM",
|
||||
|
|
@ -6888,6 +6891,25 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
return volToPoolObjectMap;
|
||||
}
|
||||
|
||||
protected boolean isVmCanBeMigratedWithoutStorage(Host srcHost, Host destinationHost, List<VolumeVO> volumes,
|
||||
Map<String, String> volumeToPool) {
|
||||
return !isAnyVmVolumeUsingLocalStorage(volumes) &&
|
||||
MapUtils.isEmpty(volumeToPool) && destinationHost != null
|
||||
&& (destinationHost.getClusterId().equals(srcHost.getClusterId()) || isAllVmVolumesOnZoneWideStore(volumes));
|
||||
}
|
||||
|
||||
protected Host chooseVmMigrationDestinationUsingVolumePoolMap(VMInstanceVO vm, Host srcHost, Map<Long, Long> volToPoolObjectMap) {
|
||||
Long poolId = null;
|
||||
if (MapUtils.isNotEmpty(volToPoolObjectMap)) {
|
||||
poolId = new ArrayList<>(volToPoolObjectMap.values()).get(0);
|
||||
}
|
||||
DeployDestination deployDestination = chooseVmMigrationDestination(vm, srcHost, poolId);
|
||||
if (deployDestination == null || deployDestination.getHost() == null) {
|
||||
throw new CloudRuntimeException("Unable to find suitable destination to migrate VM " + vm.getInstanceName());
|
||||
}
|
||||
return deployDestination.getHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_MIGRATE, eventDescription = "migrating VM", async = true)
|
||||
public VirtualMachine migrateVirtualMachineWithVolume(Long vmId, Host destinationHost, Map<String, String> volumeToPool) throws ResourceUnavailableException,
|
||||
|
|
@ -6932,8 +6954,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
Pair<Host, Host> sourceDestinationHosts = getHostsForMigrateVmWithStorage(vm, destinationHost);
|
||||
Host srcHost = sourceDestinationHosts.first();
|
||||
|
||||
if (!isVMUsingLocalStorage(vm) && MapUtils.isEmpty(volumeToPool)
|
||||
&& (destinationHost.getClusterId().equals(srcHost.getClusterId()) || isVmVolumesOnZoneWideStore(vm))){
|
||||
final List<VolumeVO> volumes = _volsDao.findCreatedByInstance(vm.getId());
|
||||
if (isVmCanBeMigratedWithoutStorage(srcHost, destinationHost, volumes, volumeToPool)) {
|
||||
// If volumes do not have to be migrated
|
||||
// call migrateVirtualMachine for non-user VMs else throw exception
|
||||
if (!VirtualMachine.Type.User.equals(vm.getType())) {
|
||||
|
|
@ -6945,6 +6967,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
|
||||
Map<Long, Long> volToPoolObjectMap = getVolumePoolMappingForMigrateVmWithStorage(vm, volumeToPool);
|
||||
|
||||
if (destinationHost == null) {
|
||||
destinationHost = chooseVmMigrationDestinationUsingVolumePoolMap(vm, srcHost, volToPoolObjectMap);
|
||||
}
|
||||
|
||||
checkHostsDedication(vm, srcHost.getId(), destinationHost.getId());
|
||||
|
||||
_itMgr.migrateWithStorage(vm.getUuid(), srcHost.getId(), destinationHost.getId(), volToPoolObjectMap);
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@
|
|||
// under the License.
|
||||
package com.cloud.vm;
|
||||
|
||||
import com.cloud.storage.Volume;
|
||||
import com.cloud.storage.dao.VolumeDao;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
|
@ -38,18 +36,15 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.template.VirtualMachineTemplate;
|
||||
import com.cloud.user.UserData;
|
||||
import com.cloud.user.UserDataVO;
|
||||
import com.cloud.user.dao.UserDataDao;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import org.apache.cloudstack.api.BaseCmd.HTTPMethod;
|
||||
import org.apache.cloudstack.api.command.user.vm.ResetVMUserDataCmd;
|
||||
import org.apache.cloudstack.api.command.user.vm.DeployVMCmd;
|
||||
import org.apache.cloudstack.api.command.user.vm.ResetVMUserDataCmd;
|
||||
import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
|
||||
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
|
|
@ -67,11 +62,19 @@ import com.cloud.configuration.Resource;
|
|||
import com.cloud.dc.DataCenter;
|
||||
import com.cloud.dc.DataCenterVO;
|
||||
import com.cloud.dc.dao.DataCenterDao;
|
||||
import com.cloud.deploy.DataCenterDeployment;
|
||||
import com.cloud.deploy.DeployDestination;
|
||||
import com.cloud.deploy.DeploymentPlanner;
|
||||
import com.cloud.deploy.DeploymentPlanningManager;
|
||||
import com.cloud.exception.InsufficientAddressCapacityException;
|
||||
import com.cloud.exception.InsufficientCapacityException;
|
||||
import com.cloud.exception.InsufficientServerCapacityException;
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
import com.cloud.exception.ResourceUnavailableException;
|
||||
import com.cloud.host.Host;
|
||||
import com.cloud.host.HostVO;
|
||||
import com.cloud.host.dao.HostDao;
|
||||
import com.cloud.hypervisor.Hypervisor;
|
||||
import com.cloud.network.NetworkModel;
|
||||
import com.cloud.network.dao.NetworkDao;
|
||||
|
|
@ -81,21 +84,30 @@ import com.cloud.service.ServiceOfferingVO;
|
|||
import com.cloud.service.dao.ServiceOfferingDao;
|
||||
import com.cloud.storage.DiskOfferingVO;
|
||||
import com.cloud.storage.GuestOSVO;
|
||||
import com.cloud.storage.ScopeType;
|
||||
import com.cloud.storage.VMTemplateVO;
|
||||
import com.cloud.storage.Volume;
|
||||
import com.cloud.storage.VolumeApiService;
|
||||
import com.cloud.storage.VolumeVO;
|
||||
import com.cloud.storage.dao.DiskOfferingDao;
|
||||
import com.cloud.storage.dao.GuestOSDao;
|
||||
import com.cloud.storage.dao.VMTemplateDao;
|
||||
import com.cloud.storage.dao.VolumeDao;
|
||||
import com.cloud.template.VirtualMachineTemplate;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.user.AccountManager;
|
||||
import com.cloud.user.AccountService;
|
||||
import com.cloud.user.AccountVO;
|
||||
import com.cloud.user.ResourceLimitService;
|
||||
import com.cloud.user.UserData;
|
||||
import com.cloud.user.UserDataVO;
|
||||
import com.cloud.user.UserVO;
|
||||
import com.cloud.user.dao.AccountDao;
|
||||
import com.cloud.user.dao.UserDataDao;
|
||||
import com.cloud.uservm.UserVm;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.db.EntityManager;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.vm.dao.NicDao;
|
||||
import com.cloud.vm.dao.UserVmDao;
|
||||
import com.cloud.vm.dao.UserVmDetailsDao;
|
||||
|
|
@ -181,6 +193,18 @@ public class UserVmManagerImplTest {
|
|||
@Mock
|
||||
UserDataDao userDataDao;
|
||||
|
||||
@Mock
|
||||
PrimaryDataStoreDao primaryDataStoreDao;
|
||||
|
||||
@Mock
|
||||
VirtualMachineManager virtualMachineManager;
|
||||
|
||||
@Mock
|
||||
DeploymentPlanningManager planningManager;
|
||||
|
||||
@Mock
|
||||
HostDao hostDao;
|
||||
|
||||
@Mock
|
||||
private VolumeVO volumeVOMock;
|
||||
|
||||
|
|
@ -912,4 +936,122 @@ public class UserVmManagerImplTest {
|
|||
|
||||
userVmManagerImpl.createVirtualMachine(deployVMCmd);
|
||||
}
|
||||
|
||||
private List<VolumeVO> mockVolumesForIsAnyVmVolumeUsingLocalStorageTest(int localVolumes, int nonLocalVolumes) {
|
||||
List<VolumeVO> volumes = new ArrayList<>();
|
||||
for (int i=0; i< localVolumes + nonLocalVolumes; ++i) {
|
||||
VolumeVO vol = Mockito.mock(VolumeVO.class);
|
||||
long index = i + 1;
|
||||
Mockito.when(vol.getDiskOfferingId()).thenReturn(index);
|
||||
Mockito.when(vol.getPoolId()).thenReturn(index);
|
||||
DiskOfferingVO diskOffering = Mockito.mock(DiskOfferingVO.class);
|
||||
Mockito.when(diskOfferingDao.findById(index)).thenReturn(diskOffering);
|
||||
StoragePoolVO storagePool = Mockito.mock(StoragePoolVO.class);
|
||||
Mockito.when(primaryDataStoreDao.findById(index)).thenReturn(storagePool);
|
||||
if (i < localVolumes) {
|
||||
if ((localVolumes + nonLocalVolumes) % 2 == 0) {
|
||||
Mockito.when(diskOffering.isUseLocalStorage()).thenReturn(true);
|
||||
} else {
|
||||
|
||||
Mockito.when(diskOffering.isUseLocalStorage()).thenReturn(false);
|
||||
Mockito.when(storagePool.isLocal()).thenReturn(true);
|
||||
}
|
||||
} else {
|
||||
Mockito.when(diskOffering.isUseLocalStorage()).thenReturn(false);
|
||||
Mockito.when(storagePool.isLocal()).thenReturn(false);
|
||||
}
|
||||
volumes.add(vol);
|
||||
}
|
||||
return volumes;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsAnyVmVolumeUsingLocalStorage() {
|
||||
Assert.assertTrue(userVmManagerImpl.isAnyVmVolumeUsingLocalStorage(mockVolumesForIsAnyVmVolumeUsingLocalStorageTest(1, 0)));
|
||||
Assert.assertTrue(userVmManagerImpl.isAnyVmVolumeUsingLocalStorage(mockVolumesForIsAnyVmVolumeUsingLocalStorageTest(2, 0)));
|
||||
Assert.assertTrue(userVmManagerImpl.isAnyVmVolumeUsingLocalStorage(mockVolumesForIsAnyVmVolumeUsingLocalStorageTest(1, 1)));
|
||||
Assert.assertFalse(userVmManagerImpl.isAnyVmVolumeUsingLocalStorage(mockVolumesForIsAnyVmVolumeUsingLocalStorageTest(0, 2)));
|
||||
Assert.assertFalse(userVmManagerImpl.isAnyVmVolumeUsingLocalStorage(mockVolumesForIsAnyVmVolumeUsingLocalStorageTest(0, 0)));
|
||||
}
|
||||
|
||||
private List<VolumeVO> mockVolumesForIsAllVmVolumesOnZoneWideStore(int nullPoolIdVolumes, int nullPoolVolumes, int zoneVolumes, int nonZoneVolumes) {
|
||||
List<VolumeVO> volumes = new ArrayList<>();
|
||||
for (int i=0; i< nullPoolIdVolumes + nullPoolVolumes + zoneVolumes + nonZoneVolumes; ++i) {
|
||||
VolumeVO vol = Mockito.mock(VolumeVO.class);
|
||||
volumes.add(vol);
|
||||
if (i < nullPoolIdVolumes) {
|
||||
Mockito.when(vol.getPoolId()).thenReturn(null);
|
||||
continue;
|
||||
}
|
||||
long index = i + 1;
|
||||
Mockito.when(vol.getPoolId()).thenReturn(index);
|
||||
if (i < nullPoolVolumes) {
|
||||
Mockito.when(primaryDataStoreDao.findById(index)).thenReturn(null);
|
||||
continue;
|
||||
}
|
||||
StoragePoolVO storagePool = Mockito.mock(StoragePoolVO.class);
|
||||
Mockito.when(primaryDataStoreDao.findById(index)).thenReturn(storagePool);
|
||||
if (i < zoneVolumes) {
|
||||
Mockito.when(storagePool.getScope()).thenReturn(ScopeType.ZONE);
|
||||
} else {
|
||||
Mockito.when(storagePool.getScope()).thenReturn(ScopeType.CLUSTER);
|
||||
}
|
||||
}
|
||||
return volumes;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsAllVmVolumesOnZoneWideStoreCombinations() {
|
||||
Assert.assertTrue(userVmManagerImpl.isAllVmVolumesOnZoneWideStore(mockVolumesForIsAllVmVolumesOnZoneWideStore(0, 0, 1, 0)));
|
||||
Assert.assertTrue(userVmManagerImpl.isAllVmVolumesOnZoneWideStore(mockVolumesForIsAllVmVolumesOnZoneWideStore(0, 0, 2, 0)));
|
||||
Assert.assertFalse(userVmManagerImpl.isAllVmVolumesOnZoneWideStore(mockVolumesForIsAllVmVolumesOnZoneWideStore(0, 0, 1, 1)));
|
||||
Assert.assertFalse(userVmManagerImpl.isAllVmVolumesOnZoneWideStore(mockVolumesForIsAllVmVolumesOnZoneWideStore(0, 0, 0, 0)));
|
||||
Assert.assertFalse(userVmManagerImpl.isAllVmVolumesOnZoneWideStore(mockVolumesForIsAllVmVolumesOnZoneWideStore(1, 0, 1, 1)));
|
||||
Assert.assertFalse(userVmManagerImpl.isAllVmVolumesOnZoneWideStore(mockVolumesForIsAllVmVolumesOnZoneWideStore(0, 1, 1, 1)));
|
||||
}
|
||||
|
||||
private Pair<VMInstanceVO, Host> mockObjectsForChooseVmMigrationDestinationUsingVolumePoolMapTest(boolean nullPlan, Host destinationHost) {
|
||||
VMInstanceVO vm = Mockito.mock(VMInstanceVO.class);
|
||||
Mockito.when(vm.getId()).thenReturn(1L);
|
||||
Mockito.when(vm.getServiceOfferingId()).thenReturn(1L);
|
||||
Host host = Mockito.mock(Host.class);
|
||||
Mockito.when(host.getId()).thenReturn(1L);
|
||||
Mockito.when(hostDao.findById(1L)).thenReturn(Mockito.mock(HostVO.class));
|
||||
Mockito.when(virtualMachineManager.getMigrationDeployment(Mockito.any(VirtualMachine.class),
|
||||
Mockito.any(Host.class), Mockito.nullable(Long.class),
|
||||
Mockito.any(DeploymentPlanner.ExcludeList.class)))
|
||||
.thenReturn(Mockito.mock(DataCenterDeployment.class));
|
||||
if (!nullPlan) {
|
||||
try {
|
||||
DeployDestination destination = Mockito.mock(DeployDestination.class);
|
||||
Mockito.when(destination.getHost()).thenReturn(destinationHost);
|
||||
Mockito.when(planningManager.planDeployment(Mockito.any(VirtualMachineProfile.class),
|
||||
Mockito.any(DataCenterDeployment.class), Mockito.any(DeploymentPlanner.ExcludeList.class),
|
||||
Mockito.nullable(DeploymentPlanner.class)))
|
||||
.thenReturn(destination);
|
||||
} catch (InsufficientServerCapacityException e) {
|
||||
Assert.fail("Failed to mock DeployDestination");
|
||||
}
|
||||
}
|
||||
return new Pair<>(vm, host);
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testChooseVmMigrationDestinationUsingVolumePoolMapNullDestination() {
|
||||
Pair<VMInstanceVO, Host> pair = mockObjectsForChooseVmMigrationDestinationUsingVolumePoolMapTest(true, null);
|
||||
userVmManagerImpl.chooseVmMigrationDestinationUsingVolumePoolMap(pair.first(), pair.second(), null);
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testChooseVmMigrationDestinationUsingVolumePoolMapNullHost() {
|
||||
Pair<VMInstanceVO, Host> pair = mockObjectsForChooseVmMigrationDestinationUsingVolumePoolMapTest(false, null);
|
||||
userVmManagerImpl.chooseVmMigrationDestinationUsingVolumePoolMap(pair.first(), pair.second(), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChooseVmMigrationDestinationUsingVolumePoolMapValid() {
|
||||
Host destinationHost = Mockito.mock(Host.class);
|
||||
Pair<VMInstanceVO, Host> pair = mockObjectsForChooseVmMigrationDestinationUsingVolumePoolMapTest(false, destinationHost);
|
||||
Assert.assertEquals(destinationHost, userVmManagerImpl.chooseVmMigrationDestinationUsingVolumePoolMap(pair.first(), pair.second(), null));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,7 +188,8 @@ export default {
|
|||
}
|
||||
],
|
||||
migrateWithStorage: false,
|
||||
volumeToPoolSelection: []
|
||||
volumeToPoolSelection: [],
|
||||
volumes: []
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
|
@ -248,6 +249,7 @@ export default {
|
|||
handleSelectedHostChange (host) {
|
||||
if (host.id === -1) {
|
||||
this.migrateWithStorage = false
|
||||
this.fetchVolumes()
|
||||
}
|
||||
this.selectedHost = host
|
||||
this.selectedVolumeForStoragePoolSelection = {}
|
||||
|
|
@ -259,6 +261,31 @@ export default {
|
|||
handleVolumeToPoolChange (volumeToPool) {
|
||||
this.volumeToPoolSelection = volumeToPool
|
||||
},
|
||||
fetchVolumes () {
|
||||
this.loading = true
|
||||
this.volumes = []
|
||||
api('listVolumes', {
|
||||
listAll: true,
|
||||
virtualmachineid: this.resource.id
|
||||
}).then(response => {
|
||||
this.volumes = response.listvolumesresponse.volume
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
requiresStorageMigration () {
|
||||
if (this.selectedHost.requiresStorageMotion || this.volumeToPoolSelection.length > 0) {
|
||||
return true
|
||||
}
|
||||
if (this.selectedHost.id === -1 && this.volumes && this.volumes.length > 0) {
|
||||
for (var volume of this.volumes) {
|
||||
if (volume.storagetype === 'local') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
handleKeyboardSubmit () {
|
||||
if (this.selectedHost.id) {
|
||||
this.submitForm()
|
||||
|
|
@ -271,7 +298,7 @@ export default {
|
|||
if (this.loading) return
|
||||
this.loading = true
|
||||
const migrateApi = this.isUserVm
|
||||
? (this.selectedHost.requiresStorageMotion || this.volumeToPoolSelection.length > 0)
|
||||
? this.requiresStorageMigration()
|
||||
? 'migrateVirtualMachineWithVolume'
|
||||
: 'migrateVirtualMachine'
|
||||
: 'migrateSystemVm'
|
||||
|
|
|
|||
Loading…
Reference in New Issue