Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
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;
Expand Down Expand Up @@ -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 ///////////////////////
/////////////////////////////////////////////////////
Expand All @@ -91,6 +98,10 @@ public Long getStorageId() {
return storageId;
}

public Boolean isAutoSelect() {
return BooleanUtils.isNotFalse(autoSelect);
}

/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
Expand Down Expand Up @@ -122,34 +133,40 @@ public String getEventDescription() {

@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) {
throw new InvalidParameterValueException("Unable to find the storage pool to migrate the VM");
}
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
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;
Expand Down Expand Up @@ -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,
Expand All @@ -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 ///////////////////////
/////////////////////////////////////////////////////
Expand All @@ -93,6 +100,10 @@ public Long getStoragePoolId() {
return storageId;
}

public Boolean isAutoSelect() {
return BooleanUtils.isNotFalse(autoSelect);
}

/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
Expand Down Expand Up @@ -132,10 +143,6 @@ public String getEventDescription() {

@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");
}
Expand All @@ -146,17 +153,6 @@ public void execute() {
}

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) {
Expand All @@ -165,13 +161,24 @@ public void execute() {
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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@weizhouapache just to check, auto select here picks the suitable host for VM migration. In the similar way, any possibility to pick suitable storage ? If so, can this API have both parameters: autoselecthost & autoselectstorage (only one of them should be true).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sureshanaparti
this pr works only if storage migraiotn is not required.
it can be improved in a new pr.

}

try {
VirtualMachine migratedVm = null;
if (getHostId() != null) {
if (getStoragePoolId() == null) {
Copy link
Contributor

@sureshanaparti sureshanaparti Mar 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ustcweizhou whenever host / storage pool is not specified in the cmd, instead of directly picking a suitable dest host to migrate to, I think it is better to confirm it (from the user) through a API cmd param (something like chooseHost / selectHost). If user is not OK to auto select host, then throw appropriate message.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sureshanaparti ok.
what do you think if we regard hostid=-1 as autoselect ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@weizhouapache host id in the cmd is UUID, so i think it is better to take input from another param when host id is null. If host id is not null, ignore auto selection.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sureshanaparti yes, I am currently working on the coding, same as you said.

ps: cloudstack supports projectid=-1, so it is feasible to support hostid=-1 but it might have big impact on other apis.

migratedVm = _userVmService.migrateVirtualMachine(getVirtualMachineId(), destinationHost);
} else if (getStoragePoolId() != null) {
} else {
migratedVm = _userVmService.vmStorageMigration(getVirtualMachineId(), destStoragePool);
}
if (migratedVm != null) {
Expand Down
81 changes: 65 additions & 16 deletions server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,13 @@
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;
Expand Down Expand Up @@ -977,8 +979,7 @@ private UserVm rebootVirtualMachine(long userId, long vmId, boolean enterSetup,
}

if (vm.getState() == State.Running && vm.getHostId() != null) {
collectVmDiskStatistics(vm);
collectVmNetworkStatistics(vm);
collectVmDiskAndNetworkStatistics(vm, State.Running);

if (forced) {
Host vmOnHost = _hostDao.findById(vm.getHostId());
Expand Down Expand Up @@ -5961,8 +5962,47 @@ public VirtualMachine migrateVirtualMachine(Long vmId, Host destinationHost) thr
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");
}

Expand All @@ -5983,7 +6023,7 @@ public VirtualMachine migrateVirtualMachine(Long vmId, Host destinationHost) thr
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());
Expand All @@ -6002,19 +6042,14 @@ public VirtualMachine migrateVirtualMachine(Long vmId, Host destinationHost) thr
+ " 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) {
Expand Down Expand Up @@ -7377,11 +7412,7 @@ else if (!answer.getResult()) {

@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
Expand Down Expand Up @@ -7724,4 +7755,22 @@ private Network getNetworkForOvfNetworkMapping(DataCenter zone, Account owner) t
}
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()));
}
}
}
1 change: 1 addition & 0 deletions ui/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1392,6 +1392,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",
Expand Down
17 changes: 12 additions & 5 deletions ui/src/views/compute/MigrateWizard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down Expand Up @@ -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}`)
Expand All @@ -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,
Expand Down