You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ts...@apache.org on 2013/04/19 13:28:40 UTC
[26/35] Storage motion for Xenserver changes: 1. Implemented Api
findStoragePoolsForMigration. Added a new response objects to list storage
pools available for migration. 2. Updated migrateVolume api for allowing
migrating volumes of running vms. These c
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/src/com/cloud/api/query/dao/HostJoinDaoImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/query/dao/HostJoinDaoImpl.java b/server/src/com/cloud/api/query/dao/HostJoinDaoImpl.java
index 1adff40..7796529 100644
--- a/server/src/com/cloud/api/query/dao/HostJoinDaoImpl.java
+++ b/server/src/com/cloud/api/query/dao/HostJoinDaoImpl.java
@@ -29,6 +29,7 @@ import javax.inject.Inject;
import org.apache.cloudstack.api.ApiConstants.HostDetails;
import org.apache.cloudstack.api.response.HostResponse;
+import org.apache.cloudstack.api.response.HostForMigrationResponse;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
@@ -190,10 +191,6 @@ public class HostJoinDaoImpl extends GenericDaoBase<HostJoinVO, Long> implements
}
-
-
-
-
@Override
public HostResponse setHostResponse(HostResponse response, HostJoinVO host) {
String tag = host.getTag();
@@ -208,7 +205,137 @@ public class HostJoinDaoImpl extends GenericDaoBase<HostJoinVO, Long> implements
return response;
}
+ @Override
+ public HostForMigrationResponse newHostForMigrationResponse(HostJoinVO host, EnumSet<HostDetails> details) {
+ HostForMigrationResponse hostResponse = new HostForMigrationResponse();
+ hostResponse.setId(host.getUuid());
+ hostResponse.setCapabilities(host.getCapabilities());
+ hostResponse.setClusterId(host.getClusterUuid());
+ hostResponse.setCpuNumber(host.getCpus());
+ hostResponse.setZoneId(host.getZoneUuid());
+ hostResponse.setDisconnectedOn(host.getDisconnectedOn());
+ hostResponse.setHypervisor(host.getHypervisorType());
+ hostResponse.setHostType(host.getType());
+ hostResponse.setLastPinged(new Date(host.getLastPinged()));
+ hostResponse.setManagementServerId(host.getManagementServerId());
+ hostResponse.setName(host.getName());
+ hostResponse.setPodId(host.getPodUuid());
+ hostResponse.setRemoved(host.getRemoved());
+ hostResponse.setCpuSpeed(host.getSpeed());
+ hostResponse.setState(host.getStatus());
+ hostResponse.setIpAddress(host.getPrivateIpAddress());
+ hostResponse.setVersion(host.getVersion());
+ hostResponse.setCreated(host.getCreated());
+
+ if (details.contains(HostDetails.all) || details.contains(HostDetails.capacity)
+ || details.contains(HostDetails.stats) || details.contains(HostDetails.events)) {
+ hostResponse.setOsCategoryId(host.getOsCategoryUuid());
+ hostResponse.setOsCategoryName(host.getOsCategoryName());
+ hostResponse.setZoneName(host.getZoneName());
+ hostResponse.setPodName(host.getPodName());
+ if ( host.getClusterId() > 0) {
+ hostResponse.setClusterName(host.getClusterName());
+ hostResponse.setClusterType(host.getClusterType().toString());
+ }
+ }
+
+ DecimalFormat decimalFormat = new DecimalFormat("#.##");
+ if (host.getType() == Host.Type.Routing) {
+ if (details.contains(HostDetails.all) || details.contains(HostDetails.capacity)) {
+ // set allocated capacities
+ Long mem = host.getMemReservedCapacity() + host.getMemUsedCapacity();
+ Long cpu = host.getCpuReservedCapacity() + host.getCpuReservedCapacity();
+
+ hostResponse.setMemoryAllocated(mem);
+ hostResponse.setMemoryTotal(host.getTotalMemory());
+
+ String hostTags = host.getTag();
+ hostResponse.setHostTags(host.getTag());
+
+ String haTag = ApiDBUtils.getHaTag();
+ if (haTag != null && !haTag.isEmpty() && hostTags != null && !hostTags.isEmpty()) {
+ if (haTag.equalsIgnoreCase(hostTags)) {
+ hostResponse.setHaHost(true);
+ } else {
+ hostResponse.setHaHost(false);
+ }
+ } else {
+ hostResponse.setHaHost(false);
+ }
+
+ hostResponse.setHypervisorVersion(host.getHypervisorVersion());
+
+ String cpuAlloc = decimalFormat.format(((float) cpu / (float) (host.getCpus() * host.getSpeed())) * 100f) + "%";
+ hostResponse.setCpuAllocated(cpuAlloc);
+ String cpuWithOverprovisioning = new Float(host.getCpus() * host.getSpeed() * ApiDBUtils.getCpuOverprovisioningFactor()).toString();
+ hostResponse.setCpuWithOverprovisioning(cpuWithOverprovisioning);
+ }
+
+ if (details.contains(HostDetails.all) || details.contains(HostDetails.stats)) {
+ // set CPU/RAM/Network stats
+ String cpuUsed = null;
+ HostStats hostStats = ApiDBUtils.getHostStatistics(host.getId());
+ if (hostStats != null) {
+ float cpuUtil = (float) hostStats.getCpuUtilization();
+ cpuUsed = decimalFormat.format(cpuUtil) + "%";
+ hostResponse.setCpuUsed(cpuUsed);
+ hostResponse.setMemoryUsed((new Double(hostStats.getUsedMemory())).longValue());
+ hostResponse.setNetworkKbsRead((new Double(hostStats.getNetworkReadKBs())).longValue());
+ hostResponse.setNetworkKbsWrite((new Double(hostStats.getNetworkWriteKBs())).longValue());
+
+ }
+ }
+
+ } else if (host.getType() == Host.Type.SecondaryStorage) {
+ StorageStats secStorageStats = ApiDBUtils.getSecondaryStorageStatistics(host.getId());
+ if (secStorageStats != null) {
+ hostResponse.setDiskSizeTotal(secStorageStats.getCapacityBytes());
+ hostResponse.setDiskSizeAllocated(secStorageStats.getByteUsed());
+ }
+ }
+
+ hostResponse.setLocalStorageActive(ApiDBUtils.isLocalStorageActiveOnHost(host.getId()));
+
+ if (details.contains(HostDetails.all) || details.contains(HostDetails.events)) {
+ Set<com.cloud.host.Status.Event> possibleEvents = host.getStatus().getPossibleEvents();
+ if ((possibleEvents != null) && !possibleEvents.isEmpty()) {
+ String events = "";
+ Iterator<com.cloud.host.Status.Event> iter = possibleEvents.iterator();
+ while (iter.hasNext()) {
+ com.cloud.host.Status.Event event = iter.next();
+ events += event.toString();
+ if (iter.hasNext()) {
+ events += "; ";
+ }
+ }
+ hostResponse.setEvents(events);
+ }
+ }
+
+ hostResponse.setResourceState(host.getResourceState().toString());
+
+ // set async job
+ hostResponse.setJobId(host.getJobUuid());
+ hostResponse.setJobStatus(host.getJobStatus());
+
+ hostResponse.setObjectName("host");
+
+ return hostResponse;
+ }
+
+ @Override
+ public HostForMigrationResponse setHostForMigrationResponse(HostForMigrationResponse response, HostJoinVO host) {
+ String tag = host.getTag();
+ if (tag != null) {
+ if (response.getHostTags() != null && response.getHostTags().length() > 0) {
+ response.setHostTags(response.getHostTags() + "," + tag);
+ } else {
+ response.setHostTags(tag);
+ }
+ }
+ return response;
+ }
@Override
public List<HostJoinVO> newHostView(Host host) {
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/src/com/cloud/api/query/dao/StoragePoolJoinDao.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/query/dao/StoragePoolJoinDao.java b/server/src/com/cloud/api/query/dao/StoragePoolJoinDao.java
index bbb0242..b7e467f 100644
--- a/server/src/com/cloud/api/query/dao/StoragePoolJoinDao.java
+++ b/server/src/com/cloud/api/query/dao/StoragePoolJoinDao.java
@@ -18,6 +18,7 @@ package com.cloud.api.query.dao;
import java.util.List;
+import org.apache.cloudstack.api.response.StoragePoolForMigrationResponse;
import org.apache.cloudstack.api.response.StoragePoolResponse;
import com.cloud.api.query.vo.StoragePoolJoinVO;
@@ -30,6 +31,11 @@ public interface StoragePoolJoinDao extends GenericDao<StoragePoolJoinVO, Long>
StoragePoolResponse setStoragePoolResponse(StoragePoolResponse response, StoragePoolJoinVO host);
+ StoragePoolForMigrationResponse newStoragePoolForMigrationResponse(StoragePoolJoinVO host);
+
+ StoragePoolForMigrationResponse setStoragePoolForMigrationResponse(StoragePoolForMigrationResponse response,
+ StoragePoolJoinVO host);
+
List<StoragePoolJoinVO> newStoragePoolView(StoragePool group);
List<StoragePoolJoinVO> searchByIds(Long... spIds);
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/src/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java b/server/src/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java
index 58968df..34b88ba 100644
--- a/server/src/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java
+++ b/server/src/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java
@@ -22,6 +22,7 @@ import java.util.List;
import javax.ejb.Local;
import javax.inject.Inject;
+import org.apache.cloudstack.api.response.StoragePoolForMigrationResponse;
import org.apache.cloudstack.api.response.StoragePoolResponse;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
@@ -108,10 +109,6 @@ public class StoragePoolJoinDaoImpl extends GenericDaoBase<StoragePoolJoinVO, Lo
return poolResponse;
}
-
-
-
-
@Override
public StoragePoolResponse setStoragePoolResponse(StoragePoolResponse response, StoragePoolJoinVO sp) {
String tag = sp.getTag();
@@ -126,7 +123,61 @@ public class StoragePoolJoinDaoImpl extends GenericDaoBase<StoragePoolJoinVO, Lo
return response;
}
+ @Override
+ public StoragePoolForMigrationResponse newStoragePoolForMigrationResponse(StoragePoolJoinVO pool) {
+ StoragePoolForMigrationResponse poolResponse = new StoragePoolForMigrationResponse();
+ poolResponse.setId(pool.getUuid());
+ poolResponse.setName(pool.getName());
+ poolResponse.setState(pool.getStatus());
+ poolResponse.setPath(pool.getPath());
+ poolResponse.setIpAddress(pool.getHostAddress());
+ poolResponse.setZoneId(pool.getZoneUuid());
+ poolResponse.setZoneName(pool.getZoneName());
+ if (pool.getPoolType() != null) {
+ poolResponse.setType(pool.getPoolType().toString());
+ }
+ poolResponse.setPodId(pool.getPodUuid());
+ poolResponse.setPodName(pool.getPodName());
+ poolResponse.setCreated(pool.getCreated());
+ poolResponse.setScope(pool.getScope().toString());
+
+ long allocatedSize = pool.getUsedCapacity() + pool.getReservedCapacity();
+ poolResponse.setDiskSizeTotal(pool.getCapacityBytes());
+ poolResponse.setDiskSizeAllocated(allocatedSize);
+
+ //TODO: StatsCollector does not persist data
+ StorageStats stats = ApiDBUtils.getStoragePoolStatistics(pool.getId());
+ if (stats != null) {
+ Long used = stats.getByteUsed();
+ poolResponse.setDiskSizeUsed(used);
+ }
+
+ poolResponse.setClusterId(pool.getClusterUuid());
+ poolResponse.setClusterName(pool.getClusterName());
+ poolResponse.setTags(pool.getTag());
+
+ // set async job
+ poolResponse.setJobId(pool.getJobUuid());
+ poolResponse.setJobStatus(pool.getJobStatus());
+
+ poolResponse.setObjectName("storagepool");
+ return poolResponse;
+ }
+
+ @Override
+ public StoragePoolForMigrationResponse setStoragePoolForMigrationResponse(StoragePoolForMigrationResponse response,
+ StoragePoolJoinVO sp) {
+ String tag = sp.getTag();
+ if (tag != null) {
+ if ( response.getTags() != null && response.getTags().length() > 0){
+ response.setTags(response.getTags() + "," + tag);
+ } else {
+ response.setTags(tag);
+ }
+ }
+ return response;
+ }
@Override
public List<StoragePoolJoinVO> newStoragePoolView(StoragePool host) {
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/src/com/cloud/server/ManagementServerImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java
index db8db8a..16127a2 100755
--- a/server/src/com/cloud/server/ManagementServerImpl.java
+++ b/server/src/com/cloud/server/ManagementServerImpl.java
@@ -214,6 +214,7 @@ import org.apache.cloudstack.api.command.admin.vlan.DeleteVlanIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.ListVlanIpRangesCmd;
import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd;
import org.apache.cloudstack.api.command.admin.vm.MigrateVMCmd;
+import org.apache.cloudstack.api.command.admin.vm.MigrateVirtualMachineWithVolumeCmd;
import org.apache.cloudstack.api.command.admin.vm.RecoverVMCmd;
import org.apache.cloudstack.api.command.admin.zone.CreateZoneCmd;
import org.apache.cloudstack.api.command.admin.zone.DeleteZoneCmd;
@@ -256,6 +257,7 @@ import org.apache.cloudstack.api.command.user.vmsnapshot.ListVMSnapshotCmd;
import org.apache.cloudstack.api.command.user.vmsnapshot.RevertToSnapshotCmd;
import org.apache.cloudstack.api.command.user.zone.ListZonesByCmd;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
+import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
@@ -329,6 +331,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
@Inject
private StorageManager _storageMgr;
@Inject
+ private VolumeManager _volumeMgr;
+ @Inject
private VirtualMachineManager _itMgr;
@Inject
private HostPodDao _hostPodDao;
@@ -352,10 +356,10 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
private LoadBalancerDao _loadbalancerDao;
@Inject
private HypervisorCapabilitiesDao _hypervisorCapabilitiesDao;
-
private List<HostAllocator> _hostAllocators;
-
- @Inject
+ @Inject
+ private List<StoragePoolAllocator> _storagePoolAllocators;
+ @Inject
private ConfigurationManager _configMgr;
@Inject
private ResourceTagDao _resourceTagDao;
@@ -679,12 +683,14 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
Object resourceState = cmd.getResourceState();
Object haHosts = cmd.getHaHost();
- Pair<List<HostVO>, Integer> result = searchForServers(cmd.getStartIndex(), cmd.getPageSizeVal(), name, type, state, zoneId, pod, cluster, id, keyword, resourceState, haHosts);
+ Pair<List<HostVO>, Integer> result = searchForServers(cmd.getStartIndex(), cmd.getPageSizeVal(), name, type,
+ state, zoneId, pod, cluster, id, keyword, resourceState, haHosts, null, null);
return new Pair<List<? extends Host>, Integer>(result.first(), result.second());
}
@Override
- public Pair<Pair<List<? extends Host>, Integer>, List<? extends Host>> listHostsForMigrationOfVM(Long vmId, Long startIndex, Long pageSize) {
+ public Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>>
+ listHostsForMigrationOfVM(Long vmId, Long startIndex, Long pageSize) {
// access check - only root admin can migrate VM
Account caller = UserContext.current().getCaller();
if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN) {
@@ -700,12 +706,13 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
ex.addProxyObject(vm, vmId, "vmId");
throw ex;
}
- // business logic
+
if (vm.getState() != State.Running) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("VM is not Running, unable to migrate the vm" + vm);
}
- InvalidParameterValueException ex = new InvalidParameterValueException("VM is not Running, unable to migrate the vm with specified id");
+ InvalidParameterValueException ex = new InvalidParameterValueException("VM is not Running, unable to" +
+ " migrate the vm with specified id");
ex.addProxyObject(vm, vmId, "vmId");
throw ex;
}
@@ -715,17 +722,11 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
if (s_logger.isDebugEnabled()) {
s_logger.debug(vm + " is not XenServer/VMware/KVM/OVM, cannot migrate this VM.");
}
- throw new InvalidParameterValueException("Unsupported Hypervisor Type for VM migration, we support XenServer/VMware/KVM only");
- }
- ServiceOfferingVO svcOffering = _offeringsDao.findById(vm.getServiceOfferingId());
- if (svcOffering.getUseLocalStorage()) {
- if (s_logger.isDebugEnabled()) {
- s_logger.debug(vm + " is using Local Storage, cannot migrate this VM.");
- }
- throw new InvalidParameterValueException("Unsupported operation, VM uses Local storage, cannot migrate");
+ throw new InvalidParameterValueException("Unsupported Hypervisor Type for VM migration, we support " +
+ "XenServer/VMware/KVM/Ovm only");
}
+
long srcHostId = vm.getHostId();
- // why is this not HostVO?
Host srcHost = _hostDao.findById(srcHostId);
if (srcHost == null) {
if (s_logger.isDebugEnabled()) {
@@ -737,32 +738,73 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
ex.addProxyObject(vm, vmId, "vmId");
throw ex;
}
- Long cluster = srcHost.getClusterId();
- Type hostType = srcHost.getType();
- if (s_logger.isDebugEnabled()) {
- s_logger.debug("Searching for all hosts in cluster: " + cluster + " for migrating VM " + vm);
- }
-
- Pair<List<HostVO>, Integer> allHostsInClusterPair = searchForServers(startIndex, pageSize, null, hostType, null, null, null, cluster, null, null, null, null);
- // filter out the current host
- List<HostVO> allHostsInCluster = allHostsInClusterPair.first();
- allHostsInCluster.remove(srcHost);
- Pair<List<? extends Host>, Integer> otherHostsInCluster = new Pair<List <? extends Host>, Integer>(allHostsInCluster, new Integer(allHostsInClusterPair.second().intValue()-1));
+ // Check if the vm can be migrated with storage.
+ boolean canMigrateWithStorage = false;
+ HypervisorCapabilitiesVO capabilities = _hypervisorCapabilitiesDao.findByHypervisorTypeAndVersion(
+ srcHost.getHypervisorType(), srcHost.getHypervisorVersion());
+ if (capabilities != null) {
+ canMigrateWithStorage = capabilities.isStorageMotionSupported();
+ }
- if (s_logger.isDebugEnabled()) {
- s_logger.debug("Other Hosts in this cluster: " + allHostsInCluster);
+ // Check if the vm is using any disks on local storage.
+ VirtualMachineProfile<VMInstanceVO> vmProfile = new VirtualMachineProfileImpl<VMInstanceVO>(vm);
+ List<VolumeVO> volumes = _volumeDao.findCreatedByInstance(vmProfile.getId());
+ boolean usesLocal = false;
+ for (VolumeVO volume : volumes) {
+ DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId());
+ DiskProfile diskProfile = new DiskProfile(volume, diskOffering, vmProfile.getHypervisorType());
+ if (diskProfile.useLocalStorage()) {
+ usesLocal = true;
+ break;
+ }
}
- if (s_logger.isDebugEnabled()) {
- s_logger.debug("Calling HostAllocators to search for hosts in cluster: " + cluster + " having enough capacity and suitable for migration");
+ if (!canMigrateWithStorage && usesLocal) {
+ throw new InvalidParameterValueException("Unsupported operation, VM uses Local storage, cannot migrate");
}
- List<Host> suitableHosts = new ArrayList<Host>();
+ Type hostType = srcHost.getType();
+ Pair<List<HostVO>, Integer> allHostsPair = null;
+ List<HostVO> allHosts = null;
+ Map<Host, Boolean> requiresStorageMotion = new HashMap<Host, Boolean>();
+ DataCenterDeployment plan = null;
+ if (canMigrateWithStorage) {
+ allHostsPair = searchForServers(startIndex, pageSize, null, hostType, null, srcHost.getDataCenterId(), null,
+ null, null, null, null, null, srcHost.getHypervisorType(), srcHost.getHypervisorVersion());
+ allHosts = allHostsPair.first();
+ allHosts.remove(srcHost);
+
+ // Check if the host has storage pools for all the volumes of the vm to be migrated.
+ for (Host host : allHosts) {
+ Map<Volume, List<StoragePool>> volumePools = findSuitablePoolsForVolumes(vmProfile, host);
+ if (volumePools.isEmpty()) {
+ allHosts.remove(host);
+ } else {
+ if (host.getClusterId() != srcHost.getClusterId() || usesLocal) {
+ requiresStorageMotion.put(host, true);
+ }
+ }
+ }
- VirtualMachineProfile<VMInstanceVO> vmProfile = new VirtualMachineProfileImpl<VMInstanceVO>(vm);
+ plan = new DataCenterDeployment(srcHost.getDataCenterId(), null, null, null, null, null);
+ } else {
+ Long cluster = srcHost.getClusterId();
+ if (s_logger.isDebugEnabled()) {
+ s_logger.debug("Searching for all hosts in cluster " + cluster + " for migrating VM " + vm);
+ }
+ allHostsPair = searchForServers(startIndex, pageSize, null, hostType, null, null, null, cluster, null, null,
+ null, null, null, null);
+ // Filter out the current host.
+ allHosts = allHostsPair.first();
+ allHosts.remove(srcHost);
+ plan = new DataCenterDeployment(srcHost.getDataCenterId(), srcHost.getPodId(), srcHost.getClusterId(),
+ null, null, null);
+ }
- DataCenterDeployment plan = new DataCenterDeployment(srcHost.getDataCenterId(), srcHost.getPodId(), srcHost.getClusterId(), null, null, null);
+ Pair<List<? extends Host>, Integer> otherHosts = new Pair<List <? extends Host>, Integer>(allHosts,
+ new Integer(allHosts.size()));
+ List<Host> suitableHosts = new ArrayList<Host>();
ExcludeList excludes = new ExcludeList();
excludes.addHost(srcHostId);
@@ -776,25 +818,174 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
}
for (HostAllocator allocator : _hostAllocators) {
- suitableHosts = allocator.allocateTo(vmProfile, plan, Host.Type.Routing, excludes, HostAllocator.RETURN_UPTO_ALL, false);
+ if (canMigrateWithStorage) {
+ suitableHosts = allocator.allocateTo(vmProfile, plan, Host.Type.Routing, excludes, allHosts,
+ HostAllocator.RETURN_UPTO_ALL, false);
+ } else {
+ suitableHosts = allocator.allocateTo(vmProfile, plan, Host.Type.Routing, excludes,
+ HostAllocator.RETURN_UPTO_ALL, false);
+ }
+
if (suitableHosts != null && !suitableHosts.isEmpty()) {
break;
}
}
- if (suitableHosts.isEmpty()) {
- s_logger.debug("No suitable hosts found");
- } else {
- if (s_logger.isDebugEnabled()) {
+ if (s_logger.isDebugEnabled()) {
+ if (suitableHosts.isEmpty()) {
+ s_logger.debug("No suitable hosts found");
+ } else {
s_logger.debug("Hosts having capacity and suitable for migration: " + suitableHosts);
}
}
- return new Pair<Pair<List<? extends Host>, Integer>, List<? extends Host>>(otherHostsInCluster, suitableHosts);
+ return new Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>> (otherHosts,
+ suitableHosts, requiresStorageMotion);
+ }
+
+ private Map<Volume, List<StoragePool>> findSuitablePoolsForVolumes(VirtualMachineProfile<VMInstanceVO> vmProfile,
+ Host host) {
+ List<VolumeVO> volumes = _volumeDao.findCreatedByInstance(vmProfile.getId());
+ Map<Volume, List<StoragePool>> suitableVolumeStoragePools = new HashMap<Volume, List<StoragePool>>();
+
+ // For each volume find list of suitable storage pools by calling the allocators
+ for (VolumeVO volume : volumes) {
+ DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId());
+ DiskProfile diskProfile = new DiskProfile(volume, diskOffering, vmProfile.getHypervisorType());
+ DataCenterDeployment plan = new DataCenterDeployment(host.getDataCenterId(), host.getPodId(),
+ host.getClusterId(), host.getId(), null, null);
+ ExcludeList avoid = new ExcludeList();
+
+ boolean foundPools = false;
+ for (StoragePoolAllocator allocator : _storagePoolAllocators) {
+ List<StoragePool> poolList = allocator.allocateToPool(diskProfile, vmProfile, plan, avoid,
+ StoragePoolAllocator.RETURN_UPTO_ALL);
+ if (poolList != null && !poolList.isEmpty()) {
+ suitableVolumeStoragePools.put(volume, poolList);
+ foundPools = true;
+ break;
+ }
+ }
+
+ if (!foundPools) {
+ suitableVolumeStoragePools.clear();
+ break;
+ }
+ }
+
+ return suitableVolumeStoragePools;
+ }
+
+ @Override
+ public Pair<List<? extends StoragePool>, List<? extends StoragePool>> listStoragePoolsForMigrationOfVolume(Long volumeId) {
+ // Access check - only root administrator can migrate volumes.
+ Account caller = UserContext.current().getCaller();
+ if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN) {
+ if (s_logger.isDebugEnabled()) {
+ s_logger.debug("Caller is not a root admin, permission denied to migrate the volume");
+ }
+ throw new PermissionDeniedException("No permission to migrate volume, only root admin can migrate a volume");
+ }
+
+ VolumeVO volume = _volumeDao.findById(volumeId);
+ if (volume == null) {
+ InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find volume with" +
+ " specified id.");
+ ex.addProxyObject(volume, volumeId, "volumeId");
+ throw ex;
+ }
+
+ // Volume must be attached to an instance for live migration.
+ List<StoragePool> allPools = new ArrayList<StoragePool>();
+ List<StoragePool> suitablePools = new ArrayList<StoragePool>();
+ Long instanceId = volume.getInstanceId();
+ VMInstanceVO vm = null;
+ if (instanceId != null) {
+ vm = _vmInstanceDao.findById(instanceId);
+ }
+
+ // Check that the VM is in correct state.
+ if (vm == null || vm.getState() != State.Running) {
+ s_logger.info("Volume " + volume + " isn't attached to any running vm. Only volumes attached to a running" +
+ " VM can be migrated.");
+ return new Pair<List<? extends StoragePool>, List<? extends StoragePool>>(allPools, suitablePools);
+ }
+
+ // Volume must be in Ready state to be migrated.
+ if (!Volume.State.Ready.equals(volume.getState())) {
+ s_logger.info("Volume " + volume + " must be in ready state for migration.");
+ return new Pair<List<? extends StoragePool>, List<? extends StoragePool>>(allPools, suitablePools);
+ }
+
+ if (!_volumeMgr.volumeOnSharedStoragePool(volume)) {
+ s_logger.info("Volume " + volume + " is on local storage. It cannot be migrated to another pool.");
+ return new Pair<List<? extends StoragePool>, List<? extends StoragePool>>(allPools, suitablePools);
+ }
+
+ // Check if the underlying hypervisor supports storage motion.
+ boolean storageMotionSupported = false;
+ Long hostId = vm.getHostId();
+ if (hostId != null) {
+ HostVO host = _hostDao.findById(hostId);
+ HypervisorCapabilitiesVO capabilities = null;
+ if (host != null) {
+ capabilities = _hypervisorCapabilitiesDao.findByHypervisorTypeAndVersion(host.getHypervisorType(),
+ host.getHypervisorVersion());
+ } else {
+ s_logger.error("Details of the host on which the vm " + vm + ", to which volume "+ volume + " is "
+ + "attached, couldn't be retrieved.");
+ }
+
+ if (capabilities != null) {
+ storageMotionSupported = capabilities.isStorageMotionSupported();
+ } else {
+ s_logger.error("Capabilities for host " + host + " couldn't be retrieved.");
+ }
+ }
+
+ if (storageMotionSupported) {
+ // Source pool of the volume.
+ StoragePoolVO srcVolumePool = _poolDao.findById(volume.getPoolId());
+
+ // Get all the pools available. Only shared pools are considered because only a volume on a shared pools
+ // can be live migrated while the virtual machine stays on the same host.
+ List<StoragePoolVO> storagePools = _poolDao.findPoolsByTags(volume.getDataCenterId(),
+ volume.getPodId(), srcVolumePool.getClusterId(), null);
+ storagePools.remove(srcVolumePool);
+ for (StoragePoolVO pool : storagePools) {
+ if (pool.isShared()) {
+ allPools.add((StoragePool)this.dataStoreMgr.getPrimaryDataStore(pool.getId()));
+ }
+ }
+
+ // Get all the suitable pools.
+ // Exclude the current pool from the list of pools to which the volume can be migrated.
+ ExcludeList avoid = new ExcludeList();
+ avoid.addPool(srcVolumePool.getId());
+
+ // Volume stays in the same cluster after migration.
+ DataCenterDeployment plan = new DataCenterDeployment(volume.getDataCenterId(), volume.getPodId(),
+ srcVolumePool.getClusterId(), null, null, null);
+ VirtualMachineProfile<VMInstanceVO> profile = new VirtualMachineProfileImpl<VMInstanceVO>(vm);
+
+ DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId());
+ DiskProfile diskProfile = new DiskProfile(volume, diskOffering, profile.getHypervisorType());
+
+ // Call the storage pool allocator to find the list of storage pools.
+ for (StoragePoolAllocator allocator : _storagePoolAllocators) {
+ List<StoragePool> pools = allocator.allocateToPool(diskProfile, profile, plan, avoid,
+ StoragePoolAllocator.RETURN_UPTO_ALL);
+ if (pools != null && !pools.isEmpty()) {
+ suitablePools.addAll(pools);
+ break;
+ }
+ }
+ }
+ return new Pair<List<? extends StoragePool>, List<? extends StoragePool>>(allPools, suitablePools);
}
private Pair<List<HostVO>, Integer> searchForServers(Long startIndex, Long pageSize, Object name, Object type, Object state, Object zone, Object pod, Object cluster, Object id, Object keyword,
- Object resourceState, Object haHosts) {
+ Object resourceState, Object haHosts, Object hypervisorType, Object hypervisorVersion) {
Filter searchFilter = new Filter(HostVO.class, "id", Boolean.TRUE, startIndex, pageSize);
SearchBuilder<HostVO> sb = _hostDao.createSearchBuilder();
@@ -806,6 +997,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
sb.and("podId", sb.entity().getPodId(), SearchCriteria.Op.EQ);
sb.and("clusterId", sb.entity().getClusterId(), SearchCriteria.Op.EQ);
sb.and("resourceState", sb.entity().getResourceState(), SearchCriteria.Op.EQ);
+ sb.and("hypervisorType", sb.entity().getHypervisorType(), SearchCriteria.Op.EQ);
+ sb.and("hypervisorVersion", sb.entity().getHypervisorVersion(), SearchCriteria.Op.EQ);
String haTag = _haMgr.getHaTag();
SearchBuilder<HostTagVO> hostTagSearch = null;
@@ -855,6 +1048,12 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
if (cluster != null) {
sc.setParameters("clusterId", cluster);
}
+ if (hypervisorType != null) {
+ sc.setParameters("hypervisorType", hypervisorType);
+ }
+ if (hypervisorVersion != null) {
+ sc.setParameters("hypervisorVersion", hypervisorVersion);
+ }
if (resourceState != null) {
sc.setParameters("resourceState", resourceState);
@@ -1969,6 +2168,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(CancelMaintenanceCmd.class);
cmdList.add(DeleteHostCmd.class);
cmdList.add(ListHostsCmd.class);
+ cmdList.add(FindHostsForMigrationCmd.class);
cmdList.add(PrepareForMaintenanceCmd.class);
cmdList.add(ReconnectHostCmd.class);
cmdList.add(UpdateHostCmd.class);
@@ -2025,6 +2225,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(DeletePoolCmd.class);
cmdList.add(ListS3sCmd.class);
cmdList.add(ListStoragePoolsCmd.class);
+ cmdList.add(FindStoragePoolsForMigrationCmd.class);
cmdList.add(PreparePrimaryStorageForMaintenanceCmd.class);
cmdList.add(UpdateStoragePoolCmd.class);
cmdList.add(AddSwiftCmd.class);
@@ -2064,6 +2265,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(ReleasePublicIpRangeCmd.class);
cmdList.add(AssignVMCmd.class);
cmdList.add(MigrateVMCmd.class);
+ cmdList.add(MigrateVirtualMachineWithVolumeCmd.class);
cmdList.add(RecoverVMCmd.class);
cmdList.add(CreatePrivateGatewayCmd.class);
cmdList.add(CreateVPCOfferingCmd.class);
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/src/com/cloud/storage/VolumeManager.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/storage/VolumeManager.java b/server/src/com/cloud/storage/VolumeManager.java
index 2101038..d198e5d 100644
--- a/server/src/com/cloud/storage/VolumeManager.java
+++ b/server/src/com/cloud/storage/VolumeManager.java
@@ -18,6 +18,8 @@
*/
package com.cloud.storage;
+import java.util.Map;
+
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
@@ -25,12 +27,15 @@ import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.UploadVolumeCmd;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import com.cloud.agent.api.to.VirtualMachineTO;
import com.cloud.deploy.DeployDestination;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientStorageCapacityException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.StorageUnavailableException;
+import com.cloud.host.Host;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.storage.Volume.Type;
import com.cloud.user.Account;
@@ -80,6 +85,9 @@ public interface VolumeManager extends VolumeApiService {
Volume migrateVolume(MigrateVolumeCmd cmd);
+ <T extends VMInstanceVO> void migrateVolumes(T vm, VirtualMachineTO vmTo, Host srcHost, Host destHost,
+ Map<VolumeVO, StoragePoolVO> volumeToPool);
+
boolean storageMigration(
VirtualMachineProfile<? extends VirtualMachine> vm,
StoragePool destPool);
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/src/com/cloud/storage/VolumeManagerImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/storage/VolumeManagerImpl.java b/server/src/com/cloud/storage/VolumeManagerImpl.java
index 1e8edaf..e57d393 100644
--- a/server/src/com/cloud/storage/VolumeManagerImpl.java
+++ b/server/src/com/cloud/storage/VolumeManagerImpl.java
@@ -28,6 +28,7 @@ import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.HashMap;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
@@ -42,6 +43,7 @@ import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.UploadVolumeCmd;
+import org.apache.cloudstack.engine.subsystem.api.storage.CommandResult;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager;
@@ -68,6 +70,7 @@ import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.AttachVolumeAnswer;
import com.cloud.agent.api.AttachVolumeCommand;
+import com.cloud.agent.api.to.VirtualMachineTO;
import com.cloud.agent.api.to.VolumeTO;
import com.cloud.alert.AlertManager;
import com.cloud.api.ApiDBUtils;
@@ -102,11 +105,13 @@ import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.StorageUnavailableException;
+import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.hypervisor.HypervisorGuruManager;
import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
+import com.cloud.hypervisor.HypervisorCapabilitiesVO;
import com.cloud.network.NetworkModel;
import com.cloud.org.Grouping;
import com.cloud.resource.ResourceManager;
@@ -2003,7 +2008,7 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager {
public Volume migrateVolume(MigrateVolumeCmd cmd) {
Long volumeId = cmd.getVolumeId();
Long storagePoolId = cmd.getStoragePoolId();
-
+
VolumeVO vol = _volsDao.findById(volumeId);
if (vol == null) {
throw new InvalidParameterValueException(
@@ -2015,9 +2020,39 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager {
"Volume must be in ready state");
}
- if (vol.getInstanceId() != null) {
- throw new InvalidParameterValueException(
- "Volume needs to be dettached from VM");
+ boolean liveMigrateVolume = false;
+ Long instanceId = vol.getInstanceId();
+ VMInstanceVO vm = null;
+ if (instanceId != null) {
+ vm = _vmInstanceDao.findById(instanceId);
+ }
+
+ if (vm != null && vm.getState() == State.Running) {
+ // Check if the underlying hypervisor supports storage motion.
+ Long hostId = vm.getHostId();
+ if (hostId != null) {
+ HostVO host = _hostDao.findById(hostId);
+ HypervisorCapabilitiesVO capabilities = null;
+ if (host != null) {
+ capabilities = _hypervisorCapabilitiesDao.findByHypervisorTypeAndVersion(host.getHypervisorType(),
+ host.getHypervisorVersion());
+ }
+
+ if (capabilities != null) {
+ liveMigrateVolume = capabilities.isStorageMotionSupported();
+ }
+ }
+ }
+
+ // If the disk is not attached to any VM then it can be moved. Otherwise, it needs to be attached to a vm
+ // running on a hypervisor that supports storage motion so that it be be migrated.
+ if (instanceId != null && !liveMigrateVolume) {
+ throw new InvalidParameterValueException("Volume needs to be detached from VM");
+ }
+
+ if (liveMigrateVolume && !cmd.isLiveMigrate()) {
+ throw new InvalidParameterValueException("The volume " + vol + "is attached to a vm and for migrating it " +
+ "the parameter livemigrate should be specified");
}
StoragePool destPool = (StoragePool)this.dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary);
@@ -2032,12 +2067,15 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager {
"Migration of volume from local storage pool is not supported");
}
- Volume newVol = migrateVolume(vol, destPool);
+ Volume newVol = null;
+ if (liveMigrateVolume) {
+ newVol = liveMigrateVolume(vol, destPool);
+ } else {
+ newVol = migrateVolume(vol, destPool);
+ }
return newVol;
}
-
-
@DB
protected Volume migrateVolume(Volume volume, StoragePool destPool) {
VolumeInfo vol = this.volFactory.getVolume(volume.getId());
@@ -2058,6 +2096,66 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager {
}
}
+ @DB
+ protected Volume liveMigrateVolume(Volume volume, StoragePool destPool) {
+ VolumeInfo vol = this.volFactory.getVolume(volume.getId());
+ AsyncCallFuture<VolumeApiResult> future = this.volService.migrateVolume(vol, (DataStore)destPool);
+ try {
+ VolumeApiResult result = future.get();
+ if (result.isFailed()) {
+ s_logger.debug("migrate volume failed:" + result.getResult());
+ return null;
+ }
+ return result.getVolume();
+ } catch (InterruptedException e) {
+ s_logger.debug("migrate volume failed", e);
+ return null;
+ } catch (ExecutionException e) {
+ s_logger.debug("migrate volume failed", e);
+ return null;
+ }
+ }
+
+ @Override
+ public <T extends VMInstanceVO> void migrateVolumes(T vm, VirtualMachineTO vmTo, Host srcHost, Host destHost,
+ Map<VolumeVO, StoragePoolVO> volumeToPool) {
+ // Check if all the vms being migrated belong to the vm.
+ // Check if the storage pool is of the right type.
+ // Create a VolumeInfo to DataStore map too.
+ Map<VolumeInfo, DataStore> volumeMap = new HashMap<VolumeInfo, DataStore>();
+ for (Map.Entry<VolumeVO, StoragePoolVO> entry : volumeToPool.entrySet()) {
+ VolumeVO volume = entry.getKey();
+ StoragePoolVO storagePool = entry.getValue();
+ StoragePool destPool = (StoragePool)this.dataStoreMgr.getDataStore(storagePool.getId(),
+ DataStoreRole.Primary);
+
+ if (volume.getInstanceId() != vm.getId()) {
+ throw new CloudRuntimeException("Volume " + volume + " that has to be migrated doesn't belong to the" +
+ " instance " + vm);
+ }
+
+ if (destPool == null) {
+ throw new CloudRuntimeException("Failed to find the destination storage pool " + storagePool.getId());
+ }
+
+ volumeMap.put(this.volFactory.getVolume(volume.getId()), (DataStore)destPool);
+ }
+
+ AsyncCallFuture<CommandResult> future = this.volService.migrateVolumes(volumeMap, vmTo, srcHost, destHost);
+ try {
+ CommandResult result = future.get();
+ if (result.isFailed()) {
+ s_logger.debug("Failed to migrated vm " + vm + " along with its volumes. " + result.getResult());
+ throw new CloudRuntimeException("Failed to migrated vm " + vm + " along with its volumes. " +
+ result.getResult());
+ }
+ } catch (InterruptedException e) {
+ s_logger.debug("Failed to migrated vm " + vm + " along with its volumes.", e);
+ } catch (ExecutionException e) {
+ s_logger.debug("Failed to migrated vm " + vm + " along with its volumes.", e);
+ }
+ }
+
@Override
public boolean storageMigration(
VirtualMachineProfile<? extends VirtualMachine> vm,
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/src/com/cloud/vm/UserVmManagerImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java
index 3ecdf42..bc6237f 100755
--- a/server/src/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/com/cloud/vm/UserVmManagerImpl.java
@@ -46,6 +46,7 @@ import org.apache.cloudstack.api.command.user.vmgroup.DeleteVMGroupCmd;
import org.apache.cloudstack.engine.cloud.entity.api.VirtualMachineEntity;
import org.apache.cloudstack.engine.service.api.OrchestrationService;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
@@ -108,6 +109,7 @@ import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.hypervisor.HypervisorCapabilitiesVO;
import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
import com.cloud.network.Network;
import com.cloud.network.Network.IpAddresses;
@@ -3502,6 +3504,127 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use
return migratedVm;
}
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VM_MIGRATE, eventDescription = "migrating VM", async = true)
+ public VirtualMachine migrateVirtualMachineWithVolume(Long vmId, Host destinationHost,
+ Map<String, String> volumeToPool) throws ResourceUnavailableException, ConcurrentOperationException,
+ ManagementServerException, VirtualMachineMigrationException {
+ // Access check - only root administrator can migrate VM.
+ Account caller = UserContext.current().getCaller();
+ if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN) {
+ if (s_logger.isDebugEnabled()) {
+ s_logger.debug("Caller is not a root admin, permission denied to migrate the VM");
+ }
+ throw new PermissionDeniedException("No permission to migrate VM, Only Root Admin can migrate a VM!");
+ }
+
+ VMInstanceVO vm = _vmInstanceDao.findById(vmId);
+ if (vm == null) {
+ throw new InvalidParameterValueException("Unable to find the vm by id " + vmId);
+ }
+
+ if (vm.getState() != State.Running) {
+ if (s_logger.isDebugEnabled()) {
+ s_logger.debug("VM is not Running, unable to migrate the vm " + vm);
+ }
+ CloudRuntimeException ex = new CloudRuntimeException("VM is not Running, unable to migrate the vm with" +
+ " specified id");
+ ex.addProxyObject(vm, vmId, "vmId");
+ throw ex;
+ }
+
+ if (!vm.getHypervisorType().equals(HypervisorType.XenServer) &&
+ !vm.getHypervisorType().equals(HypervisorType.VMware) &&
+ !vm.getHypervisorType().equals(HypervisorType.KVM) &&
+ !vm.getHypervisorType().equals(HypervisorType.Ovm)) {
+ throw new InvalidParameterValueException("Unsupported hypervisor type for vm migration, we support" +
+ " XenServer/VMware/KVM only");
+ }
+
+ long srcHostId = vm.getHostId();
+ Host srcHost = _resourceMgr.getHost(srcHostId);
+ // Check if src and destination hosts are valid and migrating to same host
+ if (destinationHost.getId() == srcHostId) {
+ throw new InvalidParameterValueException("Cannot migrate VM, VM is already present on this host, please" +
+ " specify valid destination host to migrate the VM");
+ }
+
+ // Check if the source and destination hosts are of the same type and support storage motion.
+ if (!(srcHost.getHypervisorType().equals(destinationHost.getHypervisorType()) &&
+ srcHost.getHypervisorVersion().equals(destinationHost.getHypervisorVersion()))) {
+ throw new CloudRuntimeException("The source and destination hosts are not of the same type and version. " +
+ "Source hypervisor type and version: " + srcHost.getHypervisorType().toString() + " " +
+ srcHost.getHypervisorVersion() + ", Destination hypervisor type and version: " +
+ destinationHost.getHypervisorType().toString() + " " + destinationHost.getHypervisorVersion());
+ }
+
+ HypervisorCapabilitiesVO capabilities = _hypervisorCapabilitiesDao.findByHypervisorTypeAndVersion(
+ srcHost.getHypervisorType(), srcHost.getHypervisorVersion());
+ if (!capabilities.isStorageMotionSupported()) {
+ throw new CloudRuntimeException("Migration with storage isn't supported on hypervisor " +
+ srcHost.getHypervisorType() + " of version " + srcHost.getHypervisorVersion());
+ }
+
+ // Check if destination host is up.
+ if (destinationHost.getStatus() != com.cloud.host.Status.Up ||
+ destinationHost.getResourceState() != ResourceState.Enabled){
+ throw new CloudRuntimeException("Cannot migrate VM, destination host is not in correct state, has " +
+ "status: " + destinationHost.getStatus() + ", state: " + destinationHost.getResourceState());
+ }
+
+ List<VolumeVO> vmVolumes = _volsDao.findUsableVolumesForInstance(vm.getId());
+ Map<VolumeVO, StoragePoolVO> volToPoolObjectMap = new HashMap<VolumeVO, StoragePoolVO>();
+ if (!isVMUsingLocalStorage(vm) && destinationHost.getClusterId() == srcHost.getClusterId()) {
+ if (volumeToPool.isEmpty()) {
+ // If the destination host is in the same cluster and volumes do not have to be migrated across pools
+ // then fail the call. migrateVirtualMachine api should have been used.
+ throw new InvalidParameterValueException("Migration of the vm " + vm + "from host " + srcHost +
+ " to destination host " + destinationHost + " doesn't involve migrating the volumes.");
+ }
+ }
+
+ if (!volumeToPool.isEmpty()) {
+ // Check if all the volumes and pools passed as parameters are valid.
+ for (Map.Entry<String, String> entry : volumeToPool.entrySet()) {
+ VolumeVO volume = _volsDao.findByUuid(entry.getKey());
+ StoragePoolVO pool = _storagePoolDao.findByUuid(entry.getValue());
+ if (volume == null) {
+ throw new InvalidParameterValueException("There is no volume present with the given id " +
+ entry.getKey());
+ } else if (pool == null) {
+ throw new InvalidParameterValueException("There is no storage pool present with the given id " +
+ entry.getValue());
+ } else {
+ // Verify the volume given belongs to the vm.
+ if (!vmVolumes.contains(volume)) {
+ throw new InvalidParameterValueException("There volume " + volume + " doesn't belong to " +
+ "the virtual machine "+ vm + " that has to be migrated");
+ }
+ volToPoolObjectMap.put(volume, pool);
+ }
+ }
+ }
+
+ // Check if all the volumes are in the correct state.
+ for (VolumeVO volume : vmVolumes) {
+ if (volume.getState() != Volume.State.Ready) {
+ throw new CloudRuntimeException("Volume " + volume + " of the VM is not in Ready state. Cannot " +
+ "migrate the vm with its volumes.");
+ }
+ }
+
+ // Check max guest vm limit for the destinationHost.
+ HostVO destinationHostVO = _hostDao.findById(destinationHost.getId());
+ if(_capacityMgr.checkIfHostReachMaxGuestLimit(destinationHostVO)){
+ throw new VirtualMachineMigrationException("Host name: " + destinationHost.getName() + ", hostId: " +
+ destinationHost.getId() + " already has max running vms (count includes system VMs). Cannot" +
+ " migrate to this host");
+ }
+
+ VMInstanceVO migratedVm = _itMgr.migrateWithStorage(vm, srcHostId, destinationHost.getId(), volToPoolObjectMap);
+ return migratedVm;
+ }
+
@DB
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_MOVE, eventDescription = "move VM to another user", async = false)
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/src/com/cloud/vm/VirtualMachineManager.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/vm/VirtualMachineManager.java b/server/src/com/cloud/vm/VirtualMachineManager.java
index 4a30d97..ea9f7bb 100644
--- a/server/src/com/cloud/vm/VirtualMachineManager.java
+++ b/server/src/com/cloud/vm/VirtualMachineManager.java
@@ -20,6 +20,7 @@ import java.net.URI;
import java.util.List;
import java.util.Map;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import com.cloud.agent.api.to.NicTO;
import com.cloud.agent.api.to.VirtualMachineTO;
@@ -41,6 +42,7 @@ import com.cloud.service.ServiceOfferingVO;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.StoragePool;
import com.cloud.storage.VMTemplateVO;
+import com.cloud.storage.VolumeVO;
import com.cloud.user.Account;
import com.cloud.user.User;
import com.cloud.utils.Pair;
@@ -109,6 +111,8 @@ public interface VirtualMachineManager extends Manager {
<T extends VMInstanceVO> T migrate(T vm, long srcHostId, DeployDestination dest) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException;
+ <T extends VMInstanceVO> T migrateWithStorage(T vm, long srcId, long destId, Map<VolumeVO, StoragePoolVO> volumeToPool) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException;
+
<T extends VMInstanceVO> T reboot(T vm, Map<VirtualMachineProfile.Param, Object> params, User caller, Account account) throws InsufficientCapacityException, ResourceUnavailableException;
<T extends VMInstanceVO> T advanceReboot(T vm, Map<VirtualMachineProfile.Param, Object> params, User caller, Account account) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException, OperationTimedoutException;
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/src/com/cloud/vm/VirtualMachineManagerImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java
index 19f4005..2ecece2 100755
--- a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java
+++ b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java
@@ -38,7 +38,12 @@ import javax.naming.ConfigurationException;
import com.cloud.capacity.CapacityManager;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
+import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
+import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import com.cloud.dc.*;
import com.cloud.agent.api.*;
@@ -50,6 +55,8 @@ import com.cloud.agent.Listener;
import com.cloud.agent.api.StartupRoutingCommand.VmState;
import com.cloud.agent.api.to.NicTO;
import com.cloud.agent.api.to.VirtualMachineTO;
+import com.cloud.agent.api.to.StorageFilerTO;
+import com.cloud.agent.api.to.VolumeTO;
import com.cloud.agent.manager.Commands;
import com.cloud.agent.manager.allocator.HostAllocator;
import com.cloud.alert.AlertManager;
@@ -57,6 +64,7 @@ import com.cloud.cluster.ClusterManager;
import com.cloud.configuration.Config;
import com.cloud.configuration.ConfigurationManager;
import com.cloud.configuration.dao.ConfigurationDao;
+import com.cloud.dc.dao.ClusterDao;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.HostPodVO;
@@ -111,10 +119,13 @@ import com.cloud.storage.Volume;
import com.cloud.storage.Volume.Type;
import com.cloud.storage.VolumeManager;
import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.GuestOSCategoryDao;
import com.cloud.storage.dao.GuestOSDao;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.StoragePoolHostDao;
+import com.cloud.storage.snapshot.SnapshotManager;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.User;
@@ -164,6 +175,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
@Inject
protected ServiceOfferingDao _offeringDao;
@Inject
+ protected DiskOfferingDao _diskOfferingDao;
+ @Inject
protected VMTemplateDao _templateDao;
@Inject
protected UserDao _userDao;
@@ -202,13 +215,19 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
@Inject
protected DataCenterDao _dcDao;
@Inject
+ protected ClusterDao _clusterDao;
+ @Inject
protected PrimaryDataStoreDao _storagePoolDao;
@Inject
protected HypervisorGuruManager _hvGuruMgr;
@Inject
protected NetworkDao _networkDao;
@Inject
+ protected StoragePoolHostDao _poolHostDao;
+ @Inject
protected VMSnapshotDao _vmSnapshotDao;
+ @Inject
+ protected VolumeDataFactory volFactory;
protected List<DeploymentPlanner> _planners;
public List<DeploymentPlanner> getPlanners() {
@@ -226,10 +245,16 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
this._hostAllocators = _hostAllocators;
}
- @Inject
+ @Inject
+ protected List<StoragePoolAllocator> _storagePoolAllocators;
+
+ @Inject
protected ResourceManager _resourceMgr;
@Inject
+ protected SnapshotManager _snapshotMgr;
+
+ @Inject
protected VMSnapshotManager _vmSnapshotMgr = null;
@Inject
protected ClusterDetailsDao _clusterDetailsDao;
@@ -1427,6 +1452,189 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
}
}
+ private Map<VolumeVO, StoragePoolVO> getPoolListForVolumesForMigration(VirtualMachineProfile<VMInstanceVO> profile,
+ Host host, Map<VolumeVO, StoragePoolVO> volumeToPool) {
+ List<VolumeVO> allVolumes = _volsDao.findUsableVolumesForInstance(profile.getId());
+ for (VolumeVO volume : allVolumes) {
+ StoragePoolVO pool = volumeToPool.get(volume);
+ DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId());
+ StoragePoolVO currentPool = _storagePoolDao.findById(volume.getPoolId());
+ if (pool != null) {
+ // Check if pool is accessible from the destination host and disk offering with which the volume was
+ // created is compliant with the pool type.
+ if (_poolHostDao.findByPoolHost(pool.getId(), host.getId()) == null ||
+ pool.isLocal() != diskOffering.getUseLocalStorage()) {
+ // Cannot find a pool for the volume. Throw an exception.
+ throw new CloudRuntimeException("Cannot migrate volume " + volume + " to storage pool " + pool +
+ " while migrating vm to host " + host + ". Either the pool is not accessible from the " +
+ "host or because of the offering with which the volume is created it cannot be placed on " +
+ "the given pool.");
+ } else if (pool.getId() == currentPool.getId()){
+ // If the pool to migrate too is the same as current pool, remove the volume from the list of
+ // volumes to be migrated.
+ volumeToPool.remove(volume);
+ }
+ } else {
+ // Find a suitable pool for the volume. Call the storage pool allocator to find the list of pools.
+ DiskProfile diskProfile = new DiskProfile(volume, diskOffering, profile.getHypervisorType());
+ DataCenterDeployment plan = new DataCenterDeployment(host.getDataCenterId(), host.getPodId(),
+ host.getClusterId(), host.getId(), null, null);
+ ExcludeList avoid = new ExcludeList();
+ boolean currentPoolAvailable = false;
+
+ for (StoragePoolAllocator allocator : _storagePoolAllocators) {
+ List<StoragePool> poolList = allocator.allocateToPool(diskProfile, profile, plan, avoid,
+ StoragePoolAllocator.RETURN_UPTO_ALL);
+ if (poolList != null && !poolList.isEmpty()) {
+ // Volume needs to be migrated. Pick the first pool from the list. Add a mapping to migrate the
+ // volume to a pool only if it is required; that is the current pool on which the volume resides
+ // is not available on the destination host.
+ if (poolList.contains(currentPool)) {
+ currentPoolAvailable = true;
+ } else {
+ volumeToPool.put(volume, _storagePoolDao.findByUuid(poolList.get(0).getUuid()));
+ }
+
+ break;
+ }
+ }
+
+ if (!currentPoolAvailable && !volumeToPool.containsKey(volume)) {
+ // Cannot find a pool for the volume. Throw an exception.
+ throw new CloudRuntimeException("Cannot find a storage pool which is available for volume " +
+ volume + " while migrating virtual machine " + profile.getVirtualMachine() + " to host " +
+ host);
+ }
+ }
+ }
+
+ return volumeToPool;
+ }
+
+ private <T extends VMInstanceVO> void moveVmToMigratingState(T vm, Long hostId, ItWorkVO work)
+ throws ConcurrentOperationException {
+ // Put the vm in migrating state.
+ try {
+ if (!changeState(vm, Event.MigrationRequested, hostId, work, Step.Migrating)) {
+ s_logger.info("Migration cancelled because state has changed: " + vm);
+ throw new ConcurrentOperationException("Migration cancelled because state has changed: " + vm);
+ }
+ } catch (NoTransitionException e) {
+ s_logger.info("Migration cancelled because " + e.getMessage());
+ throw new ConcurrentOperationException("Migration cancelled because " + e.getMessage());
+ }
+ }
+
+ private <T extends VMInstanceVO> void moveVmOutofMigratingStateOnSuccess(T vm, Long hostId, ItWorkVO work)
+ throws ConcurrentOperationException {
+ // Put the vm in running state.
+ try {
+ if (!changeState(vm, Event.OperationSucceeded, hostId, work, Step.Started)) {
+ s_logger.error("Unable to change the state for " + vm);
+ throw new ConcurrentOperationException("Unable to change the state for " + vm);
+ }
+ } catch (NoTransitionException e) {
+ s_logger.error("Unable to change state due to " + e.getMessage());
+ throw new ConcurrentOperationException("Unable to change state due to " + e.getMessage());
+ }
+ }
+
+ @Override
+ public <T extends VMInstanceVO> T migrateWithStorage(T vm, long srcHostId, long destHostId,
+ Map<VolumeVO, StoragePoolVO> volumeToPool) throws ResourceUnavailableException, ConcurrentOperationException,
+ ManagementServerException, VirtualMachineMigrationException {
+
+ HostVO srcHost = _hostDao.findById(srcHostId);
+ HostVO destHost = _hostDao.findById(destHostId);
+ VirtualMachineGuru<T> vmGuru = getVmGuru(vm);
+
+ DataCenterVO dc = _dcDao.findById(destHost.getDataCenterId());
+ HostPodVO pod = _podDao.findById(destHost.getPodId());
+ Cluster cluster = _clusterDao.findById(destHost.getClusterId());
+ DeployDestination destination = new DeployDestination(dc, pod, cluster, destHost);
+
+ // Create a map of which volume should go in which storage pool.
+ long vmId = vm.getId();
+ vm = vmGuru.findById(vmId);
+ VirtualMachineProfile<VMInstanceVO> profile = new VirtualMachineProfileImpl<VMInstanceVO>(vm);
+ volumeToPool = getPoolListForVolumesForMigration(profile, destHost, volumeToPool);
+
+ // If none of the volumes have to be migrated, fail the call. Administrator needs to make a call for migrating
+ // a vm and not migrating a vm with storage.
+ if (volumeToPool.isEmpty()) {
+ throw new InvalidParameterValueException("Migration of the vm " + vm + "from host " + srcHost +
+ " to destination host " + destHost + " doesn't involve migrating the volumes.");
+ }
+
+ short alertType = AlertManager.ALERT_TYPE_USERVM_MIGRATE;
+ if (VirtualMachine.Type.DomainRouter.equals(vm.getType())) {
+ alertType = AlertManager.ALERT_TYPE_DOMAIN_ROUTER_MIGRATE;
+ } else if (VirtualMachine.Type.ConsoleProxy.equals(vm.getType())) {
+ alertType = AlertManager.ALERT_TYPE_CONSOLE_PROXY_MIGRATE;
+ }
+
+ _networkMgr.prepareNicForMigration(profile, destination);
+ this.volumeMgr.prepareForMigration(profile, destination);
+ HypervisorGuru hvGuru = _hvGuruMgr.getGuru(vm.getHypervisorType());
+ VirtualMachineTO to = hvGuru.implement(profile);
+
+ ItWorkVO work = new ItWorkVO(UUID.randomUUID().toString(), _nodeId, State.Migrating, vm.getType(), vm.getId());
+ work.setStep(Step.Prepare);
+ work.setResourceType(ItWorkVO.ResourceType.Host);
+ work.setResourceId(destHostId);
+ work = _workDao.persist(work);
+
+ // Put the vm in migrating state.
+ vm.setLastHostId(srcHostId);
+ moveVmToMigratingState(vm, destHostId, work);
+
+ boolean migrated = false;
+ try {
+ // Migrate the vm and its volume.
+ this.volumeMgr.migrateVolumes(vm, to, srcHost, destHost, volumeToPool);
+
+ // Put the vm back to running state.
+ moveVmOutofMigratingStateOnSuccess(vm, destHost.getId(), work);
+
+ try {
+ if (!checkVmOnHost(vm, destHostId)) {
+ s_logger.error("Vm not found on destination host. Unable to complete migration for " + vm);
+ try {
+ _agentMgr.send(srcHostId, new Commands(cleanup(vm.getInstanceName())), null);
+ } catch (AgentUnavailableException e) {
+ s_logger.error("AgentUnavailableException while cleanup on source host: " + srcHostId);
+ }
+ cleanup(vmGuru, new VirtualMachineProfileImpl<T>(vm), work, Event.AgentReportStopped, true,
+ _accountMgr.getSystemUser(), _accountMgr.getSystemAccount());
+ return null;
+ }
+ } catch (OperationTimedoutException e) {
+ s_logger.warn("Error while checking the vm " + vm + " is on host " + destHost, e);
+ }
+
+ migrated = true;
+ return vm;
+ } finally {
+ if (!migrated) {
+ s_logger.info("Migration was unsuccessful. Cleaning up: " + vm);
+ _alertMgr.sendAlert(alertType, srcHost.getDataCenterId(), srcHost.getPodId(), "Unable to migrate vm " +
+ vm.getInstanceName() + " from host " + srcHost.getName() + " in zone " + dc.getName() +
+ " and pod " + dc.getName(), "Migrate Command failed. Please check logs.");
+ try {
+ _agentMgr.send(destHostId, new Commands(cleanup(vm.getInstanceName())), null);
+ stateTransitTo(vm, Event.OperationFailed, srcHostId);
+ } catch (AgentUnavailableException e) {
+ s_logger.warn("Looks like the destination Host is unavailable for cleanup.", e);
+ } catch (NoTransitionException e) {
+ s_logger.error("Error while transitioning vm from migrating to running state.", e);
+ }
+ }
+
+ work.setStep(Step.Done);
+ _workDao.update(work.getId(), work);
+ }
+ }
+
@Override
public VirtualMachineTO toVmTO(VirtualMachineProfile<? extends VMInstanceVO> profile) {
HypervisorGuru hvGuru = _hvGuruMgr.getGuru(profile.getVirtualMachine().getHypervisorType());
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/test/com/cloud/vm/MockUserVmManagerImpl.java
----------------------------------------------------------------------
diff --git a/server/test/com/cloud/vm/MockUserVmManagerImpl.java b/server/test/com/cloud/vm/MockUserVmManagerImpl.java
index fd826d9..0d0a8f4 100644
--- a/server/test/com/cloud/vm/MockUserVmManagerImpl.java
+++ b/server/test/com/cloud/vm/MockUserVmManagerImpl.java
@@ -367,6 +367,14 @@ public class MockUserVmManagerImpl extends ManagerBase implements UserVmManager,
}
@Override
+ public VirtualMachine migrateVirtualMachineWithVolume(Long vmId, Host destinationHost, Map<String, String> volumeToPool)
+ throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException,
+ VirtualMachineMigrationException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
public UserVm moveVMToUser(AssignVMCmd moveUserVMCmd)
throws ResourceAllocationException, ConcurrentOperationException,
ResourceUnavailableException, InsufficientCapacityException {
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/test/com/cloud/vm/MockVirtualMachineManagerImpl.java
----------------------------------------------------------------------
diff --git a/server/test/com/cloud/vm/MockVirtualMachineManagerImpl.java b/server/test/com/cloud/vm/MockVirtualMachineManagerImpl.java
index 4917e77..94ddea6 100755
--- a/server/test/com/cloud/vm/MockVirtualMachineManagerImpl.java
+++ b/server/test/com/cloud/vm/MockVirtualMachineManagerImpl.java
@@ -23,6 +23,7 @@ import java.util.Map;
import javax.ejb.Local;
import javax.naming.ConfigurationException;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.springframework.stereotype.Component;
import com.cloud.agent.api.to.NicTO;
@@ -45,6 +46,7 @@ import com.cloud.service.ServiceOfferingVO;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.StoragePool;
import com.cloud.storage.VMTemplateVO;
+import com.cloud.storage.VolumeVO;
import com.cloud.user.Account;
import com.cloud.user.User;
import com.cloud.utils.Pair;
@@ -143,6 +145,14 @@ public class MockVirtualMachineManagerImpl extends ManagerBase implements Virtua
}
@Override
+ public <T extends VMInstanceVO> T migrateWithStorage(T vm, long srcHostId, long destHostId,
+ Map<VolumeVO, StoragePoolVO> volumeToPool) throws ResourceUnavailableException,
+ ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
public VMInstanceVO findByIdAndType(Type type, long vmId) {
// TODO Auto-generated method stub
return null;
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/test/com/cloud/vm/VirtualMachineManagerImplTest.java
----------------------------------------------------------------------
diff --git a/server/test/com/cloud/vm/VirtualMachineManagerImplTest.java b/server/test/com/cloud/vm/VirtualMachineManagerImplTest.java
index 322f051..dd51e74 100644
--- a/server/test/com/cloud/vm/VirtualMachineManagerImplTest.java
+++ b/server/test/com/cloud/vm/VirtualMachineManagerImplTest.java
@@ -31,6 +31,41 @@ import com.cloud.service.ServiceOfferingVO;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.VolumeManager;
import com.cloud.storage.VolumeVO;
+import com.cloud.agent.api.PrepareForMigrationAnswer;
+import com.cloud.agent.api.PrepareForMigrationCommand;
+import com.cloud.agent.api.MigrateWithStorageAnswer;
+import com.cloud.agent.api.MigrateWithStorageCommand;
+import com.cloud.agent.api.MigrateWithStorageReceiveAnswer;
+import com.cloud.agent.api.MigrateWithStorageReceiveCommand;
+import com.cloud.agent.api.MigrateWithStorageSendAnswer;
+import com.cloud.agent.api.MigrateWithStorageSendCommand;
+import com.cloud.agent.api.MigrateWithStorageCompleteAnswer;
+import com.cloud.agent.api.MigrateWithStorageCompleteCommand;
+import com.cloud.agent.api.CheckVirtualMachineAnswer;
+import com.cloud.agent.api.CheckVirtualMachineCommand;
+import com.cloud.capacity.CapacityManager;
+import com.cloud.configuration.ConfigurationManager;
+import com.cloud.configuration.dao.ConfigurationDao;
+import com.cloud.dc.dao.ClusterDao;
+import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.dc.dao.HostPodDao;
+import com.cloud.exception.ConcurrentOperationException;
+import com.cloud.exception.ManagementServerException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.exception.VirtualMachineMigrationException;
+import com.cloud.exception.OperationTimedoutException;
+import com.cloud.host.HostVO;
+import com.cloud.host.dao.HostDao;
+import com.cloud.hypervisor.HypervisorGuru;
+import com.cloud.hypervisor.HypervisorGuruManager;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.network.NetworkManager;
+import com.cloud.storage.DiskOfferingVO;
+import com.cloud.storage.StoragePoolHostVO;
+import com.cloud.storage.VolumeManager;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.DiskOfferingDao;
+import com.cloud.storage.dao.StoragePoolHostDao;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.user.*;
@@ -41,20 +76,33 @@ import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.api.command.user.vm.RestoreVMCmd;
import org.apache.cloudstack.api.command.user.vm.ScaleVMCmd;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.dao.UserVmDao;
+import com.cloud.vm.dao.VMInstanceDao;
+import com.cloud.vm.snapshot.VMSnapshotManager;
+import com.cloud.vm.VirtualMachine.Event;
+import com.cloud.vm.VirtualMachine.State;
+
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+
import org.junit.Test;
import org.junit.Before;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
+import static org.mockito.Matchers.anyLong;
import static org.mockito.Mockito.*;
-
import java.lang.reflect.Field;
import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
public class VirtualMachineManagerImplTest {
-
@Spy VirtualMachineManagerImpl _vmMgr = new VirtualMachineManagerImpl();
@Mock
VolumeManager _storageMgr;
@@ -106,6 +154,25 @@ public class VirtualMachineManagerImplTest {
List<VolumeVO> _rootVols;
@Mock
ItWorkVO _work;
+
+ @Mock ClusterDao _clusterDao;
+ @Mock HostPodDao _podDao;
+ @Mock DataCenterDao _dcDao;
+ @Mock DiskOfferingDao _diskOfferingDao;
+ @Mock PrimaryDataStoreDao _storagePoolDao;
+ @Mock StoragePoolHostDao _poolHostDao;
+ @Mock NetworkManager _networkMgr;
+ @Mock HypervisorGuruManager _hvGuruMgr;
+ @Mock VMSnapshotManager _vmSnapshotMgr;
+
+ // Mock objects for vm migration with storage test.
+ @Mock DiskOfferingVO _diskOfferingMock;
+ @Mock StoragePoolVO _srcStoragePoolMock;
+ @Mock StoragePoolVO _destStoragePoolMock;
+ @Mock HostVO _srcHostMock;
+ @Mock HostVO _destHostMock;
+ @Mock Map<VolumeVO, StoragePoolVO> _volumeToPoolMock;
+
@Before
public void setup(){
MockitoAnnotations.initMocks(this);
@@ -122,6 +189,16 @@ public class VirtualMachineManagerImplTest {
_vmMgr._nodeId = 1L;
_vmMgr._workDao = _workDao;
_vmMgr._agentMgr = _agentMgr;
+ _vmMgr._podDao = _podDao;
+ _vmMgr._clusterDao = _clusterDao;
+ _vmMgr._dcDao = _dcDao;
+ _vmMgr._diskOfferingDao = _diskOfferingDao;
+ _vmMgr._storagePoolDao = _storagePoolDao;
+ _vmMgr._poolHostDao= _poolHostDao;
+ _vmMgr._networkMgr = _networkMgr;
+ _vmMgr._hvGuruMgr = _hvGuruMgr;
+ _vmMgr._vmSnapshotMgr = _vmSnapshotMgr;
+ _vmMgr._vmDao = _vmInstanceDao;
when(_vmMock.getId()).thenReturn(314l);
when(_vmInstance.getId()).thenReturn(1L);
@@ -204,5 +281,155 @@ public class VirtualMachineManagerImplTest {
return serviceOffering;
}
+ private void initializeMockConfigForMigratingVmWithVolumes() throws OperationTimedoutException,
+ ResourceUnavailableException {
+
+ // Mock the source and destination hosts.
+ when(_srcHostMock.getId()).thenReturn(5L);
+ when(_destHostMock.getId()).thenReturn(6L);
+ when(_hostDao.findById(5L)).thenReturn(_srcHostMock);
+ when(_hostDao.findById(6L)).thenReturn(_destHostMock);
+
+ // Mock the vm being migrated.
+ when(_vmMock.getId()).thenReturn(1L);
+ when(_vmMock.getHypervisorType()).thenReturn(HypervisorType.XenServer);
+ when(_vmMock.getState()).thenReturn(State.Running).thenReturn(State.Running).thenReturn(State.Migrating)
+ .thenReturn(State.Migrating);
+ when(_vmMock.getHostId()).thenReturn(5L);
+ when(_vmInstance.getId()).thenReturn(1L);
+ when(_vmInstance.getServiceOfferingId()).thenReturn(2L);
+ when(_vmInstance.getInstanceName()).thenReturn("myVm");
+ when(_vmInstance.getHostId()).thenReturn(5L);
+ when(_vmInstance.getType()).thenReturn(VirtualMachine.Type.User);
+ when(_vmInstance.getState()).thenReturn(State.Running).thenReturn(State.Running).thenReturn(State.Migrating)
+ .thenReturn(State.Migrating);
+
+ // Mock the work item.
+ when(_workDao.persist(any(ItWorkVO.class))).thenReturn(_work);
+ when(_workDao.update("1", _work)).thenReturn(true);
+ when(_work.getId()).thenReturn("1");
+ doNothing().when(_work).setStep(ItWorkVO.Step.Done);
+
+ // Mock the vm guru and the user vm object that gets returned.
+ _vmMgr._vmGurus = new HashMap<VirtualMachine.Type, VirtualMachineGuru<? extends VMInstanceVO>>();
+ UserVmManagerImpl userVmManager = mock(UserVmManagerImpl.class);
+ _vmMgr.registerGuru(VirtualMachine.Type.User, userVmManager);
+ when(userVmManager.findById(anyLong())).thenReturn(_vmMock);
+
+ // Mock the iteration over all the volumes of an instance.
+ Iterator<VolumeVO> volumeIterator = mock(Iterator.class);
+ when(_volsDao.findUsableVolumesForInstance(anyLong())).thenReturn(_rootVols);
+ when(_rootVols.iterator()).thenReturn(volumeIterator);
+ when(volumeIterator.hasNext()).thenReturn(true, false);
+ when(volumeIterator.next()).thenReturn(_volumeMock);
+
+ // Mock the disk offering and pool objects for a volume.
+ when(_volumeMock.getDiskOfferingId()).thenReturn(5L);
+ when(_volumeMock.getPoolId()).thenReturn(200L);
+ when(_diskOfferingDao.findById(anyLong())).thenReturn(_diskOfferingMock);
+ when(_storagePoolDao.findById(anyLong())).thenReturn(_srcStoragePoolMock);
+
+ // Mock the volume to pool mapping.
+ when(_volumeToPoolMock.get(_volumeMock)).thenReturn(_destStoragePoolMock);
+ when(_destStoragePoolMock.getId()).thenReturn(201L);
+ when(_srcStoragePoolMock.getId()).thenReturn(200L);
+ when(_destStoragePoolMock.isLocal()).thenReturn(false);
+ when(_diskOfferingMock.getUseLocalStorage()).thenReturn(false);
+ when(_poolHostDao.findByPoolHost(anyLong(), anyLong())).thenReturn(mock(StoragePoolHostVO.class));
+
+ // Mock hypervisor guru.
+ HypervisorGuru guruMock = mock(HypervisorGuru.class);
+ when(_hvGuruMgr.getGuru(HypervisorType.XenServer)).thenReturn(guruMock);
+
+ when(_srcHostMock.getClusterId()).thenReturn(3L);
+ when(_destHostMock.getClusterId()).thenReturn(3L);
+
+ // Mock the commands and answers to the agent.
+ PrepareForMigrationAnswer prepAnswerMock = mock(PrepareForMigrationAnswer.class);
+ when(prepAnswerMock.getResult()).thenReturn(true);
+ when(_agentMgr.send(anyLong(), isA(PrepareForMigrationCommand.class))).thenReturn(prepAnswerMock);
+
+ MigrateWithStorageAnswer migAnswerMock = mock(MigrateWithStorageAnswer.class);
+ when(migAnswerMock.getResult()).thenReturn(true);
+ when(_agentMgr.send(anyLong(), isA(MigrateWithStorageCommand.class))).thenReturn(migAnswerMock);
+
+ MigrateWithStorageReceiveAnswer migRecAnswerMock = mock(MigrateWithStorageReceiveAnswer.class);
+ when(migRecAnswerMock.getResult()).thenReturn(true);
+ when(_agentMgr.send(anyLong(), isA(MigrateWithStorageReceiveCommand.class))).thenReturn(migRecAnswerMock);
+
+ MigrateWithStorageSendAnswer migSendAnswerMock = mock(MigrateWithStorageSendAnswer.class);
+ when(migSendAnswerMock.getResult()).thenReturn(true);
+ when(_agentMgr.send(anyLong(), isA(MigrateWithStorageSendCommand.class))).thenReturn(migSendAnswerMock);
+
+ MigrateWithStorageCompleteAnswer migCompleteAnswerMock = mock(MigrateWithStorageCompleteAnswer.class);
+ when(migCompleteAnswerMock.getResult()).thenReturn(true);
+ when(_agentMgr.send(anyLong(), isA(MigrateWithStorageCompleteCommand.class))).thenReturn(migCompleteAnswerMock);
+
+ CheckVirtualMachineAnswer checkVmAnswerMock = mock(CheckVirtualMachineAnswer.class);
+ when(checkVmAnswerMock.getResult()).thenReturn(true);
+ when(checkVmAnswerMock.getState()).thenReturn(State.Running);
+ when(_agentMgr.send(anyLong(), isA(CheckVirtualMachineCommand.class))).thenReturn(checkVmAnswerMock);
+
+ // Mock the state transitions of vm.
+ Pair<Long, Long> opaqueMock = new Pair<Long, Long> (_vmMock.getHostId(), _destHostMock.getId());
+ when(_vmSnapshotMgr.hasActiveVMSnapshotTasks(anyLong())).thenReturn(false);
+ when(_vmInstanceDao.updateState(State.Running, Event.MigrationRequested, State.Migrating, _vmMock, opaqueMock))
+ .thenReturn(true);
+ when(_vmInstanceDao.updateState(State.Migrating, Event.OperationSucceeded, State.Running, _vmMock, opaqueMock))
+ .thenReturn(true);
+ }
+
+ // Check migration of a vm with its volumes within a cluster.
+ @Test
+ public void testMigrateWithVolumeWithinCluster() throws ResourceUnavailableException, ConcurrentOperationException,
+ ManagementServerException, VirtualMachineMigrationException, OperationTimedoutException {
+ initializeMockConfigForMigratingVmWithVolumes();
+ when(_srcHostMock.getClusterId()).thenReturn(3L);
+ when(_destHostMock.getClusterId()).thenReturn(3L);
+
+ _vmMgr.migrateWithStorage(_vmInstance, _srcHostMock.getId(), _destHostMock.getId(), _volumeToPoolMock);
+ }
+
+ // Check migration of a vm with its volumes across a cluster.
+ @Test
+ public void testMigrateWithVolumeAcrossCluster() throws ResourceUnavailableException, ConcurrentOperationException,
+ ManagementServerException, VirtualMachineMigrationException, OperationTimedoutException {
+
+ initializeMockConfigForMigratingVmWithVolumes();
+ when(_srcHostMock.getClusterId()).thenReturn(3L);
+ when(_destHostMock.getClusterId()).thenReturn(4L);
+
+ _vmMgr.migrateWithStorage(_vmInstance, _srcHostMock.getId(), _destHostMock.getId(), _volumeToPoolMock);
+ }
+
+ // Check migration of a vm fails when src and destination pool are not of same type; that is, one is shared and
+ // other is local.
+ @Test(expected=CloudRuntimeException.class)
+ public void testMigrateWithVolumeFail1() throws ResourceUnavailableException, ConcurrentOperationException,
+ ManagementServerException, VirtualMachineMigrationException, OperationTimedoutException {
+
+ initializeMockConfigForMigratingVmWithVolumes();
+ when(_srcHostMock.getClusterId()).thenReturn(3L);
+ when(_destHostMock.getClusterId()).thenReturn(3L);
+
+ when(_destStoragePoolMock.isLocal()).thenReturn(true);
+ when(_diskOfferingMock.getUseLocalStorage()).thenReturn(false);
+
+ _vmMgr.migrateWithStorage(_vmInstance, _srcHostMock.getId(), _destHostMock.getId(), _volumeToPoolMock);
+ }
+
+ // Check migration of a vm fails when vm is not in Running state.
+ @Test(expected=ConcurrentOperationException.class)
+ public void testMigrateWithVolumeFail2() throws ResourceUnavailableException, ConcurrentOperationException,
+ ManagementServerException, VirtualMachineMigrationException, OperationTimedoutException {
+
+ initializeMockConfigForMigratingVmWithVolumes();
+ when(_srcHostMock.getClusterId()).thenReturn(3L);
+ when(_destHostMock.getClusterId()).thenReturn(3L);
+
+ when(_vmMock.getState()).thenReturn(State.Stopped);
+
+ _vmMgr.migrateWithStorage(_vmInstance, _srcHostMock.getId(), _destHostMock.getId(), _volumeToPoolMock);
+ }
}