server: Optional destination host when migrate a vm (#4378)

* server: Optional destination host when migrate a vm

* #4378: migrate systemvms/routers with optional host

* #4378: fix mistake

* #4378: fix issue when migrate systemvm

* #4378 add autoselect to migrate api commands

* #4378: more ui change

* #4378: add label label.migrate.auto.select

* #4378: add method chooseVmMigrationDestination

* #4378: fix vm migration wih storageid on vmware

* #4378: add method to collect vm disk/network statistics

* #4378: set autoSelect to default 'true'

* #4378: use BooleanUtils.isNotFalse

Co-authored-by: Wei Zhou <weizhou@apache.org>
This commit is contained in:
Wei Zhou 2021-08-10 06:25:57 +02:00 committed by GitHub
parent d5015d7af1
commit 846efdbfe4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 136 additions and 54 deletions

View File

@ -29,6 +29,7 @@ public class ApiConstants {
public static final String ANNOTATION = "annotation";
public static final String API_KEY = "apikey";
public static final String ASYNC_BACKUP = "asyncbackup";
public static final String AUTO_SELECT = "autoselect";
public static final String USER_API_KEY = "userapikey";
public static final String APPLIED = "applied";
public static final String LIST_LB_VMIPS = "lbvmips";

View File

@ -30,6 +30,7 @@ import org.apache.cloudstack.api.response.HostResponse;
import org.apache.cloudstack.api.response.StoragePoolResponse;
import org.apache.cloudstack.api.response.SystemVmResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.commons.lang.BooleanUtils;
import org.apache.log4j.Logger;
import com.cloud.event.EventTypes;
@ -75,6 +76,12 @@ public class MigrateSystemVMCmd extends BaseAsyncCmd {
description = "Destination storage pool ID to migrate VM volumes to. Required for migrating the root disk volume")
private Long storageId;
@Parameter(name = ApiConstants.AUTO_SELECT,
since = "4.16.0",
type = CommandType.BOOLEAN,
description = "Automatically select a destination host which do not require storage migration, if hostId and storageId are not specified. false by default")
private Boolean autoSelect;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -91,6 +98,10 @@ public class MigrateSystemVMCmd extends BaseAsyncCmd {
return storageId;
}
public Boolean isAutoSelect() {
return BooleanUtils.isNotFalse(autoSelect);
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@ -122,27 +133,14 @@ public class MigrateSystemVMCmd extends BaseAsyncCmd {
@Override
public void execute() {
if (getHostId() == null && getStorageId() == null) {
throw new InvalidParameterValueException("Either hostId or storageId must be specified");
}
if (getHostId() != null && getStorageId() != null) {
throw new InvalidParameterValueException("Only one of hostId and storageId can be specified");
}
try {
//FIXME : Should not be calling UserVmService to migrate all types of VMs - need a generic VM layer
VirtualMachine migratedVm = null;
if (getHostId() != null) {
Host destinationHost = _resourceService.getHost(getHostId());
if (destinationHost == null) {
throw new InvalidParameterValueException("Unable to find the host to migrate the VM, host id=" + getHostId());
}
if (destinationHost.getType() != Host.Type.Routing) {
throw new InvalidParameterValueException("The specified host(" + destinationHost.getName() + ") is not suitable to migrate the VM, please specify another one");
}
CallContext.current().setEventDetails("VM Id: " + getVirtualMachineId() + " to host Id: " + getHostId());
migratedVm = _userVmService.migrateVirtualMachineWithVolume(getVirtualMachineId(), destinationHost, new HashMap<String, String>());
} else if (getStorageId() != null) {
if (getStorageId() != null) {
// OfflineMigration performed when this parameter is specified
StoragePool destStoragePool = _storageService.getStoragePool(getStorageId());
if (destStoragePool == null) {
@ -150,6 +148,25 @@ public class MigrateSystemVMCmd extends BaseAsyncCmd {
}
CallContext.current().setEventDetails("VM Id: " + getVirtualMachineId() + " to storage pool Id: " + getStorageId());
migratedVm = _userVmService.vmStorageMigration(getVirtualMachineId(), destStoragePool);
} else {
Host destinationHost = null;
if (getHostId() != null) {
destinationHost =_resourceService.getHost(getHostId());
if (destinationHost == null) {
throw new InvalidParameterValueException("Unable to find the host to migrate the VM, host id=" + getHostId());
}
if (destinationHost.getType() != Host.Type.Routing) {
throw new InvalidParameterValueException("The specified host(" + destinationHost.getName() + ") is not suitable to migrate the VM, please specify another one");
}
} else if (! isAutoSelect()) {
throw new InvalidParameterValueException("Please specify a host or storage as destination, or pass 'autoselect=true' to automatically select a destination host which do not require storage migration");
}
CallContext.current().setEventDetails("VM Id: " + getVirtualMachineId() + " to host Id: " + getHostId());
if (destinationHost == null) {
migratedVm = _userVmService.migrateVirtualMachine(getVirtualMachineId(), null);
} else {
migratedVm = _userVmService.migrateVirtualMachineWithVolume(getVirtualMachineId(), destinationHost, new HashMap<String, String>());
}
}
if (migratedVm != null) {
// return the generic system VM instance response

View File

@ -29,6 +29,7 @@ import org.apache.cloudstack.api.response.HostResponse;
import org.apache.cloudstack.api.response.StoragePoolResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.commons.lang.BooleanUtils;
import com.cloud.event.EventTypes;
import com.cloud.exception.ConcurrentOperationException;
@ -60,7 +61,7 @@ public class MigrateVMCmd extends BaseAsyncCmd {
type = CommandType.UUID,
entityType = HostResponse.class,
required = false,
description = "Destination Host ID to migrate VM to. Required for live migrating a VM from host to host")
description = "Destination Host ID to migrate VM to.")
private Long hostId;
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
@ -77,6 +78,12 @@ public class MigrateVMCmd extends BaseAsyncCmd {
description = "Destination storage pool ID to migrate VM volumes to. Required for migrating the root disk volume")
private Long storageId;
@Parameter(name = ApiConstants.AUTO_SELECT,
since = "4.16.0",
type = CommandType.BOOLEAN,
description = "Automatically select a destination host which do not require storage migration, if hostId and storageId are not specified. false by default")
private Boolean autoSelect;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -93,6 +100,10 @@ public class MigrateVMCmd extends BaseAsyncCmd {
return storageId;
}
public Boolean isAutoSelect() {
return BooleanUtils.isNotFalse(autoSelect);
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@ -132,10 +143,6 @@ public class MigrateVMCmd extends BaseAsyncCmd {
@Override
public void execute() {
if (getHostId() == null && getStoragePoolId() == null) {
throw new InvalidParameterValueException("Either hostId or storageId must be specified");
}
if (getHostId() != null && getStoragePoolId() != null) {
throw new InvalidParameterValueException("Only one of hostId and storageId can be specified");
}
@ -146,17 +153,6 @@ public class MigrateVMCmd extends BaseAsyncCmd {
}
Host destinationHost = null;
if (getHostId() != null) {
destinationHost = _resourceService.getHost(getHostId());
if (destinationHost == null) {
throw new InvalidParameterValueException("Unable to find the host to migrate the VM, host id=" + getHostId());
}
if (destinationHost.getType() != Host.Type.Routing) {
throw new InvalidParameterValueException("The specified host(" + destinationHost.getName() + ") is not suitable to migrate the VM, please specify another one");
}
CallContext.current().setEventDetails("VM Id: " + getVirtualMachineId() + " to host Id: " + getHostId());
}
// OfflineMigration performed when this parameter is specified
StoragePool destStoragePool = null;
if (getStoragePoolId() != null) {
@ -165,13 +161,24 @@ public class MigrateVMCmd extends BaseAsyncCmd {
throw new InvalidParameterValueException("Unable to find the storage pool to migrate the VM");
}
CallContext.current().setEventDetails("VM Id: " + getVirtualMachineId() + " to storage pool Id: " + getStoragePoolId());
} else if (getHostId() != null) {
destinationHost = _resourceService.getHost(getHostId());
if (destinationHost == null) {
throw new InvalidParameterValueException("Unable to find the host to migrate the VM, host id=" + getHostId());
}
if (destinationHost.getType() != Host.Type.Routing) {
throw new InvalidParameterValueException("The specified host(" + destinationHost.getName() + ") is not suitable to migrate the VM, please specify another one");
}
CallContext.current().setEventDetails("VM Id: " + getVirtualMachineId() + " to host Id: " + getHostId());
} else if (! isAutoSelect()) {
throw new InvalidParameterValueException("Please specify a host or storage as destination, or pass 'autoselect=true' to automatically select a destination host which do not require storage migration");
}
try {
VirtualMachine migratedVm = null;
if (getHostId() != null) {
if (getStoragePoolId() == null) {
migratedVm = _userVmService.migrateVirtualMachine(getVirtualMachineId(), destinationHost);
} else if (getStoragePoolId() != null) {
} else {
migratedVm = _userVmService.vmStorageMigration(getVirtualMachineId(), destStoragePool);
}
if (migratedVm != null) {

View File

@ -189,11 +189,13 @@ import com.cloud.event.EventTypes;
import com.cloud.event.UsageEventUtils;
import com.cloud.event.UsageEventVO;
import com.cloud.event.dao.UsageEventDao;
import com.cloud.exception.AffinityConflictException;
import com.cloud.exception.AgentUnavailableException;
import com.cloud.exception.CloudException;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientAddressCapacityException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.InsufficientServerCapacityException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ManagementServerException;
import com.cloud.exception.OperationTimedoutException;
@ -980,8 +982,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
if (vm.getState() == State.Running && vm.getHostId() != null) {
collectVmDiskStatistics(vm);
collectVmNetworkStatistics(vm);
collectVmDiskAndNetworkStatistics(vm, State.Running);
if (forced) {
Host vmOnHost = _hostDao.findById(vm.getHostId());
@ -5970,8 +5971,47 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
throw new InvalidParameterValueException("Cannot migrate VM, host with id: " + srcHostId + " for VM not found");
}
DeployDestination dest = null;
if (destinationHost == null) {
dest = chooseVmMigrationDestination(vm, srcHost);
} else {
dest = checkVmMigrationDestination(vm, srcHost, destinationHost);
}
if (destinationHost.getId() == srcHostId) {
// If no suitable destination found then throw exception
if (dest == null) {
throw new CloudRuntimeException("Unable to find suitable destination to migrate VM " + vm.getInstanceName());
}
collectVmDiskAndNetworkStatistics(vmId, State.Running);
_itMgr.migrate(vm.getUuid(), srcHostId, dest);
return findMigratedVm(vm.getId(), vm.getType());
}
private DeployDestination chooseVmMigrationDestination(VMInstanceVO vm, Host srcHost) {
vm.setLastHostId(null); // Last host does not have higher priority in vm migration
final ServiceOfferingVO offering = _offeringDao.findById(vm.getId(), vm.getServiceOfferingId());
final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm, null, offering, null, null);
final Long srcHostId = srcHost.getId();
final Host host = _hostDao.findById(srcHostId);
final DataCenterDeployment plan = new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), host.getClusterId(), null, null, null);
ExcludeList excludes = new ExcludeList();
excludes.addHost(srcHostId);
try {
return _planningMgr.planDeployment(profile, plan, excludes, null);
} catch (final AffinityConflictException e2) {
s_logger.warn("Unable to create deployment, affinity rules associted to the VM conflict", e2);
throw new CloudRuntimeException("Unable to create deployment, affinity rules associted to the VM conflict");
} catch (final InsufficientServerCapacityException e3) {
throw new CloudRuntimeException("Unable to find a server to migrate the vm to");
}
}
private DeployDestination checkVmMigrationDestination(VMInstanceVO vm, Host srcHost, Host destinationHost) throws VirtualMachineMigrationException {
if (destinationHost == null) {
return null;
}
if (destinationHost.getId() == srcHost.getId()) {
throw new InvalidParameterValueException("Cannot migrate VM, VM is already present on this host, please specify valid destination host to migrate the VM");
}
@ -5992,7 +6032,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
throw new CloudRuntimeException("Cannot migrate VM, VM is DPDK enabled VM but destination host is not DPDK enabled");
}
checkHostsDedication(vm, srcHostId, destinationHost.getId());
checkHostsDedication(vm, srcHost.getId(), destinationHost.getId());
// call to core process
DataCenterVO dcVO = _dcDao.findById(destinationHost.getDataCenterId());
@ -6011,19 +6051,14 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
+ " already has max Running VMs(count includes system VMs), cannot migrate to this host");
}
//check if there are any ongoing volume snapshots on the volumes associated with the VM.
Long vmId = vm.getId();
s_logger.debug("Checking if there are any ongoing snapshots volumes associated with VM with ID " + vmId);
if (checkStatusOfVolumeSnapshots(vmId, null)) {
throw new CloudRuntimeException("There is/are unbacked up snapshot(s) on volume(s) attached to this VM, VM Migration is not permitted, please try again later.");
}
s_logger.debug("Found no ongoing snapshots on volumes associated with the vm with id " + vmId);
UserVmVO uservm = _vmDao.findById(vmId);
if (uservm != null) {
collectVmDiskStatistics(uservm);
collectVmNetworkStatistics(uservm);
}
_itMgr.migrate(vm.getUuid(), srcHostId, dest);
return findMigratedVm(vm.getId(), vm.getType());
return dest;
}
private boolean isOnSupportedHypevisorForMigration(VMInstanceVO vm) {
@ -7386,11 +7421,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
@Override
public void prepareStop(VirtualMachineProfile profile) {
UserVmVO vm = _vmDao.findById(profile.getId());
if (vm != null && vm.getState() == State.Stopping) {
collectVmDiskStatistics(vm);
collectVmNetworkStatistics(vm);
}
collectVmDiskAndNetworkStatistics(profile.getId(), State.Stopping);
}
@Override
@ -7733,4 +7764,22 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
return network;
}
private void collectVmDiskAndNetworkStatistics(Long vmId, State expectedState) {
UserVmVO uservm = _vmDao.findById(vmId);
if (uservm != null) {
collectVmDiskAndNetworkStatistics(uservm, expectedState);
} else {
s_logger.info(String.format("Skip collecting vm %s disk and network statistics as it is not user vm", uservm));
}
}
private void collectVmDiskAndNetworkStatistics(UserVm vm, State expectedState) {
if (expectedState == null || expectedState == vm.getState()) {
collectVmDiskStatistics(vm);
collectVmNetworkStatistics(vm);
} else {
s_logger.warn(String.format("Skip collecting vm %s disk and network statistics as the expected vm state is %s but actual state is %s", vm, expectedState, vm.getState()));
}
}
}

View File

@ -1393,6 +1393,7 @@
"label.metrics.network.write": "Write",
"label.metrics.num.cpu.cores": "Cores",
"label.migrate.allowed": "Migrate Allowed",
"label.migrate.auto.select": "AutoSelect",
"label.migrate.data.from.image.store": "Migrate Data from Image store",
"label.migrate.instance.to": "Migrate instance to",
"label.migrate.instance.to.host": "Migrate instance to another host",

View File

@ -46,7 +46,9 @@
v-else />
</div>
<div slot="memused" slot-scope="record">
{{ record.memoryused | byteToGigabyte }} GB
<span v-if="record.memoryused | byteToGigabyte">
{{ record.memoryused | byteToGigabyte }} GB
</span>
</div>
<div slot="memoryallocatedpercentage" slot-scope="record">
{{ record.memoryallocatedpercentage }}
@ -169,6 +171,12 @@ export default {
this.hosts.sort((a, b) => {
return b.suitableformigration - a.suitableformigration
})
for (const key in this.hosts) {
if (this.hosts[key].suitableformigration && !this.hosts[key].requiresstoragemigration) {
this.hosts.unshift({ id: -1, name: this.$t('label.migrate.auto.select'), suitableformigration: true, requiresstoragemigration: false })
break
}
}
this.totalCount = response.findhostsformigrationresponse.count
}).catch(error => {
this.$message.error(`${this.$t('message.load.host.failed')}: ${error}`)
@ -186,10 +194,9 @@ export default {
var migrateApi = isUserVm
? this.selectedHost.requiresStorageMotion ? 'migrateVirtualMachineWithVolume' : 'migrateVirtualMachine'
: 'migrateSystemVm'
api(migrateApi, {
hostid: this.selectedHost.id,
virtualmachineid: this.resource.id
}).then(response => {
var migrateParams = this.selectedHost.id === -1 ? { autoselect: true, virtualmachineid: this.resource.id }
: { hostid: this.selectedHost.id, virtualmachineid: this.resource.id }
api(migrateApi, migrateParams).then(response => {
const jobid = this.selectedHost.requiresStorageMotion ? response.migratevirtualmachinewithvolumeresponse.jobid : response.migratevirtualmachineresponse.jobid
this.$pollJob({
jobId: jobid,