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,