You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by nv...@apache.org on 2021/08/10 04:26:24 UTC
[cloudstack] branch main updated: server: Optional destination host
when migrate a vm (#4378)
This is an automated email from the ASF dual-hosted git repository.
nvazquez pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/main by this push:
new 846efdb server: Optional destination host when migrate a vm (#4378)
846efdb is described below
commit 846efdbfe4b218d71063f628449adda22a6f9902
Author: Wei Zhou <w....@global.leaseweb.com>
AuthorDate: Tue Aug 10 06:25:57 2021 +0200
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 <we...@apache.org>
---
.../org/apache/cloudstack/api/ApiConstants.java | 1 +
.../command/admin/systemvm/MigrateSystemVMCmd.java | 47 +++++++++----
.../api/command/admin/vm/MigrateVMCmd.java | 43 +++++++-----
.../main/java/com/cloud/vm/UserVmManagerImpl.java | 81 +++++++++++++++++-----
ui/public/locales/en.json | 1 +
ui/src/views/compute/MigrateWizard.vue | 17 +++--
6 files changed, 136 insertions(+), 54 deletions(-)
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index e3d681f..48386a4 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -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";
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/MigrateSystemVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/MigrateSystemVMCmd.java
index 50129a5..decc722 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/MigrateSystemVMCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/MigrateSystemVMCmd.java
@@ -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
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVMCmd.java
index 9f73ae5..2c68d86 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVMCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVMCmd.java
@@ -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) {
diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
index f18e7b3..00c8d98 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
@@ -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()));
+ }
+ }
}
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 6b3efc3..fab328d 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -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",
diff --git a/ui/src/views/compute/MigrateWizard.vue b/ui/src/views/compute/MigrateWizard.vue
index 1b82639..e0f390b 100644
--- a/ui/src/views/compute/MigrateWizard.vue
+++ b/ui/src/views/compute/MigrateWizard.vue
@@ -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,