Skip to content

Commit d6e8b53

Browse files
authored
vmware: vm migration improvements (#4385)
- Fixes inter-cluster migration of VMs - Allows migration of stopped VM with disks attached to different and suitable pools - Improves inter-cluster detached volume migration - Allows inter-cluster migration (clusters of same Pod) for system VMs, VRs on VMware - Allows storage migration for stopped system VMs, VRs on VMware within same Pod if StoragePool cluster scopetype Linked Primate PR: apache/cloudstack-primate#789 [Changes merged in this PR after new UI merge] Documentation PR: apache/cloudstack-documentation#170 Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
1 parent 0cca854 commit d6e8b53

File tree

34 files changed

+1896
-704
lines changed

34 files changed

+1896
-704
lines changed

api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.cloud.agent.api.to.VirtualMachineTO;
2828
import com.cloud.hypervisor.Hypervisor.HypervisorType;
2929
import com.cloud.storage.StoragePool;
30+
import com.cloud.storage.Volume;
3031
import com.cloud.utils.Pair;
3132
import com.cloud.utils.component.Adapter;
3233
import com.cloud.vm.NicProfile;
@@ -99,5 +100,5 @@ boolean attachRestoredVolumeToVirtualMachine(long zoneId, String location, Backu
99100
* @param destination the primary storage pool to migrate to
100101
* @return a list of commands to perform for a successful migration
101102
*/
102-
List<Command> finalizeMigrate(VirtualMachine vm, StoragePool destination);
103+
List<Command> finalizeMigrate(VirtualMachine vm, Map<Volume, StoragePool> volumeToPool);
103104
}

api/src/main/java/com/cloud/vm/UserVmService.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,8 @@ UserVm moveVMToUser(AssignVMCmd moveUserVMCmd) throws ResourceAllocationExceptio
488488

489489
VirtualMachine vmStorageMigration(Long vmId, StoragePool destPool);
490490

491+
VirtualMachine vmStorageMigration(Long vmId, Map<String, String> volumeToPool);
492+
491493
UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException;
492494

493495
UserVm restoreVirtualMachine(Account caller, long vmId, Long newTemplateId) throws InsufficientCapacityException, ResourceUnavailableException;

api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/MigrateSystemVMCmd.java

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
// under the License.
1717
package org.apache.cloudstack.api.command.admin.systemvm;
1818

19-
import org.apache.log4j.Logger;
19+
import java.util.HashMap;
2020

2121
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
2222
import org.apache.cloudstack.api.ACL;
@@ -27,8 +27,10 @@
2727
import org.apache.cloudstack.api.Parameter;
2828
import org.apache.cloudstack.api.ServerApiException;
2929
import org.apache.cloudstack.api.response.HostResponse;
30+
import org.apache.cloudstack.api.response.StoragePoolResponse;
3031
import org.apache.cloudstack.api.response.SystemVmResponse;
3132
import org.apache.cloudstack.context.CallContext;
33+
import org.apache.log4j.Logger;
3234

3335
import com.cloud.event.EventTypes;
3436
import com.cloud.exception.ConcurrentOperationException;
@@ -37,6 +39,7 @@
3739
import com.cloud.exception.ResourceUnavailableException;
3840
import com.cloud.exception.VirtualMachineMigrationException;
3941
import com.cloud.host.Host;
42+
import com.cloud.storage.StoragePool;
4043
import com.cloud.user.Account;
4144
import com.cloud.vm.VirtualMachine;
4245

@@ -54,7 +57,6 @@ public class MigrateSystemVMCmd extends BaseAsyncCmd {
5457
@Parameter(name = ApiConstants.HOST_ID,
5558
type = CommandType.UUID,
5659
entityType = HostResponse.class,
57-
required = true,
5860
description = "destination Host ID to migrate VM to")
5961
private Long hostId;
6062

@@ -66,6 +68,13 @@ public class MigrateSystemVMCmd extends BaseAsyncCmd {
6668
description = "the ID of the virtual machine")
6769
private Long virtualMachineId;
6870

71+
@Parameter(name = ApiConstants.STORAGE_ID,
72+
since = "4.16.0",
73+
type = CommandType.UUID,
74+
entityType = StoragePoolResponse.class,
75+
description = "Destination storage pool ID to migrate VM volumes to. Required for migrating the root disk volume")
76+
private Long storageId;
77+
6978
/////////////////////////////////////////////////////
7079
/////////////////// Accessors ///////////////////////
7180
/////////////////////////////////////////////////////
@@ -78,6 +87,10 @@ public Long getVirtualMachineId() {
7887
return virtualMachineId;
7988
}
8089

90+
public Long getStorageId() {
91+
return storageId;
92+
}
93+
8194
/////////////////////////////////////////////////////
8295
/////////////// API Implementation///////////////////
8396
/////////////////////////////////////////////////////
@@ -109,15 +122,35 @@ public String getEventDescription() {
109122

110123
@Override
111124
public void execute() {
125+
if (getHostId() == null && getStorageId() == null) {
126+
throw new InvalidParameterValueException("Either hostId or storageId must be specified");
127+
}
112128

113-
Host destinationHost = _resourceService.getHost(getHostId());
114-
if (destinationHost == null) {
115-
throw new InvalidParameterValueException("Unable to find the host to migrate the VM, host id=" + getHostId());
129+
if (getHostId() != null && getStorageId() != null) {
130+
throw new InvalidParameterValueException("Only one of hostId and storageId can be specified");
116131
}
117132
try {
118-
CallContext.current().setEventDetails("VM Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getVirtualMachineId()) + " to host Id: " + this._uuidMgr.getUuid(Host.class, getHostId()));
119133
//FIXME : Should not be calling UserVmService to migrate all types of VMs - need a generic VM layer
120-
VirtualMachine migratedVm = _userVmService.migrateVirtualMachine(getVirtualMachineId(), destinationHost);
134+
VirtualMachine migratedVm = null;
135+
if (getHostId() != null) {
136+
Host destinationHost = _resourceService.getHost(getHostId());
137+
if (destinationHost == null) {
138+
throw new InvalidParameterValueException("Unable to find the host to migrate the VM, host id=" + getHostId());
139+
}
140+
if (destinationHost.getType() != Host.Type.Routing) {
141+
throw new InvalidParameterValueException("The specified host(" + destinationHost.getName() + ") is not suitable to migrate the VM, please specify another one");
142+
}
143+
CallContext.current().setEventDetails("VM Id: " + getVirtualMachineId() + " to host Id: " + getHostId());
144+
migratedVm = _userVmService.migrateVirtualMachineWithVolume(getVirtualMachineId(), destinationHost, new HashMap<String, String>());
145+
} else if (getStorageId() != null) {
146+
// OfflineMigration performed when this parameter is specified
147+
StoragePool destStoragePool = _storageService.getStoragePool(getStorageId());
148+
if (destStoragePool == null) {
149+
throw new InvalidParameterValueException("Unable to find the storage pool to migrate the VM");
150+
}
151+
CallContext.current().setEventDetails("VM Id: " + getVirtualMachineId() + " to storage pool Id: " + getStorageId());
152+
migratedVm = _userVmService.vmStorageMigration(getVirtualMachineId(), destStoragePool);
153+
}
121154
if (migratedVm != null) {
122155
// return the generic system VM instance response
123156
SystemVmResponse response = _responseGenerator.createSystemVmResponse(migratedVm);

api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVirtualMachineWithVolumeCmd.java

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
import java.util.Iterator;
2222
import java.util.Map;
2323

24-
import org.apache.log4j.Logger;
25-
2624
import org.apache.cloudstack.api.APICommand;
2725
import org.apache.cloudstack.api.ApiConstants;
2826
import org.apache.cloudstack.api.ApiErrorCode;
@@ -32,6 +30,8 @@
3230
import org.apache.cloudstack.api.ServerApiException;
3331
import org.apache.cloudstack.api.response.HostResponse;
3432
import org.apache.cloudstack.api.response.UserVmResponse;
33+
import org.apache.commons.collections.MapUtils;
34+
import org.apache.log4j.Logger;
3535

3636
import com.cloud.event.EventTypes;
3737
import com.cloud.exception.ConcurrentOperationException;
@@ -61,7 +61,6 @@ public class MigrateVirtualMachineWithVolumeCmd extends BaseAsyncCmd {
6161
@Parameter(name = ApiConstants.HOST_ID,
6262
type = CommandType.UUID,
6363
entityType = HostResponse.class,
64-
required = true,
6564
description = "Destination Host ID to migrate VM to.")
6665
private Long hostId;
6766

@@ -97,7 +96,7 @@ public Long getVirtualMachineId() {
9796

9897
public Map<String, String> getVolumeToPool() {
9998
Map<String, String> volumeToPoolMap = new HashMap<String, String>();
100-
if (migrateVolumeTo != null && !migrateVolumeTo.isEmpty()) {
99+
if (MapUtils.isNotEmpty(migrateVolumeTo)) {
101100
Collection<?> allValues = migrateVolumeTo.values();
102101
Iterator<?> iter = allValues.iterator();
103102
while (iter.hasNext()) {
@@ -141,19 +140,35 @@ public String getEventDescription() {
141140

142141
@Override
143142
public void execute() {
143+
if (hostId == null && MapUtils.isEmpty(migrateVolumeTo)) {
144+
throw new InvalidParameterValueException(String.format("Either %s or %s must be passed for migrating the VM", ApiConstants.HOST_ID, ApiConstants.MIGRATE_TO));
145+
}
146+
144147
UserVm userVm = _userVmService.getUserVm(getVirtualMachineId());
145148
if (userVm == null) {
146149
throw new InvalidParameterValueException("Unable to find the VM by id=" + getVirtualMachineId());
147150
}
148151

149-
Host destinationHost = _resourceService.getHost(getHostId());
150-
// OfflineVmwareMigration: destination host would have to not be a required parameter for stopped VMs
151-
if (destinationHost == null) {
152-
throw new InvalidParameterValueException("Unable to find the host to migrate the VM, host id =" + getHostId());
152+
if (!VirtualMachine.State.Running.equals(userVm.getState()) && hostId != null) {
153+
throw new InvalidParameterValueException(String.format("VM ID: %s is not in Running state to migrate it to new host", userVm.getUuid()));
154+
}
155+
156+
if (!VirtualMachine.State.Stopped.equals(userVm.getState()) && hostId == null) {
157+
throw new InvalidParameterValueException(String.format("VM ID: %s is not in Stopped state to migrate, use %s parameter to migrate it to a new host", userVm.getUuid(), ApiConstants.HOST_ID));
153158
}
154159

155160
try {
156-
VirtualMachine migratedVm = _userVmService.migrateVirtualMachineWithVolume(getVirtualMachineId(), destinationHost, getVolumeToPool());
161+
VirtualMachine migratedVm = null;
162+
if (hostId != null) {
163+
Host destinationHost = _resourceService.getHost(getHostId());
164+
// OfflineVmwareMigration: destination host would have to not be a required parameter for stopped VMs
165+
if (destinationHost == null) {
166+
throw new InvalidParameterValueException("Unable to find the host to migrate the VM, host id =" + getHostId());
167+
}
168+
migratedVm = _userVmService.migrateVirtualMachineWithVolume(getVirtualMachineId(), destinationHost, getVolumeToPool());
169+
} else if (MapUtils.isNotEmpty(migrateVolumeTo)) {
170+
migratedVm = _userVmService.vmStorageMigration(getVirtualMachineId(), getVolumeToPool());
171+
}
157172
if (migratedVm != null) {
158173
UserVmResponse response = _responseGenerator.createUserVmResponse(ResponseView.Full, "virtualmachine", (UserVm)migratedVm).get(0);
159174
response.setResponseName(getCommandName());

core/src/main/java/com/cloud/agent/api/MigrateVmToPoolCommand.java

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,48 +18,51 @@
1818
//
1919
package com.cloud.agent.api;
2020

21-
import com.cloud.agent.api.to.VolumeTO;
21+
import java.util.List;
2222

23-
import java.util.Collection;
23+
import com.cloud.agent.api.to.StorageFilerTO;
24+
import com.cloud.agent.api.to.VolumeTO;
25+
import com.cloud.utils.Pair;
2426

2527
/**
2628
* used to tell the agent to migrate a vm to a different primary storage pool.
2729
* It is for now only implemented on Vmware and is supposed to work irrespective of whether the VM is started or not.
2830
*
2931
*/
3032
public class MigrateVmToPoolCommand extends Command {
31-
private Collection<VolumeTO> volumes;
3233
private String vmName;
33-
private String destinationPool;
3434
private boolean executeInSequence = false;
35+
private List<Pair<VolumeTO, StorageFilerTO>> volumeToFilerAsList;
36+
private String hostGuidInTargetCluster;
3537

3638
protected MigrateVmToPoolCommand() {
3739
}
3840

3941
/**
4042
*
4143
* @param vmName the name of the VM to migrate
42-
* @param volumes used to supply feedback on vmware generated names
43-
* @param destinationPool the primary storage pool to migrate the VM to
44+
* @param volumeToFilerTo the volume to primary storage pool map to migrate the VM to
45+
* @param hostGuidInTargetCluster GUID of host in target cluster when migrating across clusters
4446
* @param executeInSequence
4547
*/
46-
public MigrateVmToPoolCommand(String vmName, Collection<VolumeTO> volumes, String destinationPool, boolean executeInSequence) {
48+
public MigrateVmToPoolCommand(String vmName, List<Pair<VolumeTO, StorageFilerTO>> volumeToFilerTo,
49+
String hostGuidInTargetCluster, boolean executeInSequence) {
4750
this.vmName = vmName;
48-
this.volumes = volumes;
49-
this.destinationPool = destinationPool;
51+
this.hostGuidInTargetCluster = hostGuidInTargetCluster;
52+
this.volumeToFilerAsList = volumeToFilerTo;
5053
this.executeInSequence = executeInSequence;
5154
}
5255

53-
public Collection<VolumeTO> getVolumes() {
54-
return volumes;
56+
public String getVmName() {
57+
return vmName;
5558
}
5659

57-
public String getDestinationPool() {
58-
return destinationPool;
60+
public List<Pair<VolumeTO, StorageFilerTO>> getVolumeToFilerAsList() {
61+
return volumeToFilerAsList;
5962
}
6063

61-
public String getVmName() {
62-
return vmName;
64+
public String getHostGuidInTargetCluster() {
65+
return hostGuidInTargetCluster;
6366
}
6467

6568
@Override

core/src/main/java/com/cloud/agent/api/storage/MigrateVolumeCommand.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public class MigrateVolumeCommand extends Command {
3434
StorageFilerTO sourcePool;
3535
String attachedVmName;
3636
Volume.Type volumeType;
37+
String hostGuidInTargetCluster;
3738

3839
private DataTO srcData;
3940
private DataTO destData;
@@ -68,6 +69,11 @@ public MigrateVolumeCommand(DataTO srcData, DataTO destData, Map<String, String>
6869
setWait(timeout);
6970
}
7071

72+
public MigrateVolumeCommand(long volumeId, String volumePath, StoragePool sourcePool, StoragePool targetPool, String targetClusterHost) {
73+
this(volumeId, volumePath, sourcePool, targetPool);
74+
this.hostGuidInTargetCluster = targetClusterHost;
75+
}
76+
7177
@Override
7278
public boolean executeInSequence() {
7379
return true;
@@ -125,7 +131,11 @@ public Map<String, String> getDestDetails() {
125131
return destDetails;
126132
}
127133

134+
public String getHostGuidInTargetCluster() {
135+
return hostGuidInTargetCluster;
136+
}
137+
128138
public int getWaitInMillSeconds() {
129139
return getWait() * 1000;
130140
}
131-
}
141+
}

core/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@
1919

2020
package org.apache.cloudstack.storage.to;
2121

22-
import com.cloud.storage.MigrationOptions;
2322
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
2423

2524
import com.cloud.agent.api.to.DataObjectType;
2625
import com.cloud.agent.api.to.DataStoreTO;
2726
import com.cloud.agent.api.to.DataTO;
2827
import com.cloud.hypervisor.Hypervisor;
2928
import com.cloud.offering.DiskOffering.DiskCacheMode;
29+
import com.cloud.storage.MigrationOptions;
3030
import com.cloud.storage.Storage;
3131
import com.cloud.storage.Volume;
3232

@@ -62,6 +62,7 @@ public class VolumeObjectTO implements DataTO {
6262
private Hypervisor.HypervisorType hypervisorType;
6363
private MigrationOptions migrationOptions;
6464
private boolean directDownload;
65+
private String dataStoreUuid;
6566
private boolean deployAsIs;
6667
private String updatedDataStoreUUID;
6768
private String vSphereStoragePolicyId;
@@ -319,6 +320,14 @@ public boolean isDirectDownload() {
319320
return directDownload;
320321
}
321322

323+
public String getDataStoreUuid() {
324+
return dataStoreUuid;
325+
}
326+
327+
public void setDataStoreUuid(String dataStoreUuid) {
328+
this.dataStoreUuid = dataStoreUuid;
329+
}
330+
322331
public boolean isDeployAsIs() {
323332
return deployAsIs;
324333
}

engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
import com.cloud.offering.DiskOffering;
4141
import com.cloud.offering.DiskOfferingInfo;
4242
import com.cloud.offering.ServiceOffering;
43-
import com.cloud.storage.StoragePool;
4443
import com.cloud.template.VirtualMachineTemplate;
4544
import com.cloud.user.Account;
4645
import com.cloud.uservm.UserVm;
@@ -167,7 +166,7 @@ void advanceReboot(String vmUuid, Map<VirtualMachineProfile.Param, Object> param
167166

168167
VirtualMachine findById(long vmId);
169168

170-
void storageMigration(String vmUuid, StoragePool storagePoolId);
169+
void storageMigration(String vmUuid, Map<Long, Long> volumeToPool);
171170

172171
/**
173172
* @param vmInstance

engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Lon
112112

113113
void migrateVolumes(VirtualMachine vm, VirtualMachineTO vmTo, Host srcHost, Host destHost, Map<Volume, StoragePool> volumeToPool);
114114

115-
boolean storageMigration(VirtualMachineProfile vm, StoragePool destPool) throws StorageUnavailableException;
115+
boolean storageMigration(VirtualMachineProfile vm, Map<Volume, StoragePool> volumeToPool) throws StorageUnavailableException;
116116

117117
void prepareForMigration(VirtualMachineProfile vm, DeployDestination dest);
118118

0 commit comments

Comments
 (0)