mirror of
https://github.com/apache/cloudstack.git
synced 2025-11-03 04:12:31 +01:00
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:
parent
d5015d7af1
commit
846efdbfe4
@ -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";
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user