Return-Path: X-Original-To: apmail-cloudstack-commits-archive@www.apache.org Delivered-To: apmail-cloudstack-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 33F8310F1B for ; Fri, 19 Apr 2013 11:28:27 +0000 (UTC) Received: (qmail 55982 invoked by uid 500); 19 Apr 2013 11:28:21 -0000 Delivered-To: apmail-cloudstack-commits-archive@cloudstack.apache.org Received: (qmail 55911 invoked by uid 500); 19 Apr 2013 11:28:21 -0000 Mailing-List: contact commits-help@cloudstack.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cloudstack.apache.org Delivered-To: mailing list commits@cloudstack.apache.org Received: (qmail 52712 invoked by uid 99); 19 Apr 2013 11:28:16 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 19 Apr 2013 11:28:16 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id F3A753248DE; Fri, 19 Apr 2013 11:28:15 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: tsp@apache.org To: commits@cloudstack.apache.org Date: Fri, 19 Apr 2013 11:28:41 -0000 Message-Id: <743eb91501eb42daba85c5769f503c88@git.apache.org> In-Reply-To: <92333337c91046f4b7601ac7d3a64bd4@git.apache.org> References: <92333337c91046f4b7601ac7d3a64bd4@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [27/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/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/MockStorageMotionStrategy.java ---------------------------------------------------------------------- diff --git a/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/MockStorageMotionStrategy.java b/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/MockStorageMotionStrategy.java index b619ee9..a84f308 100644 --- a/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/MockStorageMotionStrategy.java +++ b/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/MockStorageMotionStrategy.java @@ -18,11 +18,18 @@ */ package org.apache.cloudstack.storage.test; +import java.util.Map; + import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; import org.apache.cloudstack.framework.async.AsyncCompletionCallback; import org.apache.cloudstack.storage.motion.DataMotionStrategy; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.host.Host; + public class MockStorageMotionStrategy implements DataMotionStrategy { @Override @@ -32,6 +39,11 @@ public class MockStorageMotionStrategy implements DataMotionStrategy { } @Override + public boolean canHandle(Map volumeMap, Host srcHost, Host destHost) { + return true; + } + + @Override public Void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback callback) { CopyCommandResult result = new CopyCommandResult("something", null); @@ -39,4 +51,11 @@ public class MockStorageMotionStrategy implements DataMotionStrategy { return null; } + @Override + public Void copyAsync(Map volumeMap, VirtualMachineTO vmTo, Host srcHost, Host destHost, + AsyncCompletionCallback callback) { + CopyCommandResult result = new CopyCommandResult("something", null); + callback.complete(result); + return null; + } } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/engine/storage/src/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java ---------------------------------------------------------------------- diff --git a/engine/storage/src/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java b/engine/storage/src/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java index 3602bb1..ad9238a 100644 --- a/engine/storage/src/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java +++ b/engine/storage/src/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java @@ -20,12 +20,14 @@ package org.apache.cloudstack.storage.motion; import java.util.Date; import java.util.List; +import java.util.Map; import javax.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectType; +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.DataStoreRole; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; @@ -36,6 +38,7 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; +import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; import com.cloud.agent.api.BackupSnapshotAnswer; import com.cloud.agent.api.BackupSnapshotCommand; @@ -47,15 +50,21 @@ import com.cloud.agent.api.CreateVolumeFromSnapshotCommand; import com.cloud.agent.api.UpgradeSnapshotCommand; import com.cloud.agent.api.storage.CopyVolumeAnswer; import com.cloud.agent.api.storage.CopyVolumeCommand; +import com.cloud.agent.api.storage.MigrateVolumeAnswer; +import com.cloud.agent.api.storage.MigrateVolumeCommand; import com.cloud.agent.api.storage.CreateAnswer; import com.cloud.agent.api.storage.CreateCommand; import com.cloud.agent.api.storage.CreatePrivateTemplateAnswer; import com.cloud.agent.api.to.S3TO; import com.cloud.agent.api.to.StorageFilerTO; import com.cloud.agent.api.to.SwiftTO; +import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.configuration.Config; import com.cloud.configuration.dao.ConfigurationDao; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.StorageUnavailableException; +import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.storage.DiskOfferingVO; @@ -86,6 +95,8 @@ import com.cloud.utils.db.DB; import com.cloud.utils.db.Transaction; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.DiskProfile; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.dao.VMInstanceDao; @Component public class AncientDataMotionStrategy implements DataMotionStrategy { @@ -102,8 +113,12 @@ public class AncientDataMotionStrategy implements DataMotionStrategy { @Inject StorageManager storageMgr; @Inject + AgentManager agentMgr; + @Inject VolumeDao volDao; @Inject + VMInstanceDao instanceDao; + @Inject VMTemplateDao templateDao; @Inject SnapshotManager snapshotMgr; @@ -130,6 +145,11 @@ public class AncientDataMotionStrategy implements DataMotionStrategy { return true; } + @Override + public boolean canHandle(Map volumeMap, Host srcHost, Host destHost) { + return false; + } + @DB protected Answer copyVolumeFromImage(DataObject srcData, DataObject destData) { String value = configDao.getValue(Config.RecreateSystemVmEnabled.key()); @@ -393,6 +413,53 @@ public class AncientDataMotionStrategy implements DataMotionStrategy { return cvAnswer; } + protected Answer migrateVolumeToPool(DataObject srcData, DataStore destStore) { + VolumeInfo volume = (VolumeInfo)srcData; + Long instanceId = volume.getInstanceId(); + StoragePool destPool = (StoragePool)this.dataStoreMgr.getDataStore(destStore.getId(), DataStoreRole.Primary); + MigrateVolumeAnswer answer = null; + VMInstanceVO vmInstance = null; + if (instanceId != null) { + vmInstance = instanceDao.findById(instanceId); + } + + Long hostId = null; + if (vmInstance != null) { + hostId = vmInstance.getHostId(); + } + + try { + if (hostId != null) { + MigrateVolumeCommand command = new MigrateVolumeCommand(volume.getId(), volume.getPath(), destPool); + answer = (MigrateVolumeAnswer) this.agentMgr.send(hostId, command); + } + } catch (OperationTimedoutException e) { + s_logger.error("Operation timed out on storage motion for volume " + volume, e); + throw new CloudRuntimeException("Failed to live migrate volume " + volume + " to storage pool " + + destPool, e); + } catch (AgentUnavailableException e) { + s_logger.error("Agent unavailable exception while doing storage motion for volume " + volume, e); + throw new CloudRuntimeException("Failed to live migrate volume " + volume + " to storage pool " + + destPool, e); + } + + if (answer == null || !answer.getResult()) { + throw new CloudRuntimeException("Failed to migrate volume " + volume + " to storage pool " + destPool); + } else { + // Update the volume details after migration. + VolumeVO volumeVo = this.volDao.findById(volume.getId()); + Long oldPoolId = volume.getPoolId(); + volumeVo.setPath(answer.getVolumePath()); + volumeVo.setFolder(destPool.getPath()); + volumeVo.setPodId(destPool.getPodId()); + volumeVo.setPoolId(destPool.getId()); + volumeVo.setLastPoolId(oldPoolId); + this.volDao.update(volume.getId(), volumeVo); + } + + return answer; + } + @Override public Void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback callback) { @@ -419,7 +486,12 @@ public class AncientDataMotionStrategy implements DataMotionStrategy { answer = cloneVolume(srcData, destData); } else if (destData.getType() == DataObjectType.VOLUME && srcData.getType() == DataObjectType.VOLUME && srcData.getDataStore().getRole() == DataStoreRole.Primary) { - answer = copyVolumeBetweenPools(srcData, destData); + if (srcData.getId() == destData.getId()) { + // The volume has to be migrated across storage pools. + answer = migrateVolumeToPool(srcData, destData.getDataStore()); + } else { + answer = copyVolumeBetweenPools(srcData, destData); + } } else if (srcData.getType() == DataObjectType.SNAPSHOT && destData.getType() == DataObjectType.SNAPSHOT) { answer = copySnapshot(srcData, destData); @@ -435,6 +507,16 @@ public class AncientDataMotionStrategy implements DataMotionStrategy { return null; } + @Override + public Void copyAsync(Map volumeMap, VirtualMachineTO vmTo, Host srcHost, Host destHost, + AsyncCompletionCallback callback) { + CopyCommandResult result = new CopyCommandResult(null, null); + result.setResult("Unsupported operation requested for copying data."); + callback.complete(result); + + return null; + } + @DB protected Answer createTemplateFromSnashot(DataObject srcData, DataObject destData) { http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/engine/storage/src/org/apache/cloudstack/storage/motion/DataMotionService.java ---------------------------------------------------------------------- diff --git a/engine/storage/src/org/apache/cloudstack/storage/motion/DataMotionService.java b/engine/storage/src/org/apache/cloudstack/storage/motion/DataMotionService.java index db36f64..5ecbcb3 100644 --- a/engine/storage/src/org/apache/cloudstack/storage/motion/DataMotionService.java +++ b/engine/storage/src/org/apache/cloudstack/storage/motion/DataMotionService.java @@ -18,11 +18,20 @@ */ package org.apache.cloudstack.storage.motion; +import java.util.Map; + import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; import org.apache.cloudstack.framework.async.AsyncCompletionCallback; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.host.Host; + public interface DataMotionService { public void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback callback); + public void copyAsync(Map volumeMap, VirtualMachineTO vmTo, + Host srcHost, Host destHost, AsyncCompletionCallback callback); } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/engine/storage/src/org/apache/cloudstack/storage/motion/DataMotionServiceImpl.java ---------------------------------------------------------------------- diff --git a/engine/storage/src/org/apache/cloudstack/storage/motion/DataMotionServiceImpl.java b/engine/storage/src/org/apache/cloudstack/storage/motion/DataMotionServiceImpl.java index 343140f..b74e10c 100644 --- a/engine/storage/src/org/apache/cloudstack/storage/motion/DataMotionServiceImpl.java +++ b/engine/storage/src/org/apache/cloudstack/storage/motion/DataMotionServiceImpl.java @@ -19,14 +19,19 @@ package org.apache.cloudstack.storage.motion; import java.util.List; +import java.util.Map; import javax.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; import org.apache.cloudstack.framework.async.AsyncCompletionCallback; import org.springframework.stereotype.Component; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.host.Host; import com.cloud.utils.exception.CloudRuntimeException; @Component @@ -58,4 +63,15 @@ public class DataMotionServiceImpl implements DataMotionService { throw new CloudRuntimeException("can't find strategy to move data"); } + @Override + public void copyAsync(Map volumeMap, VirtualMachineTO vmTo, + Host srcHost, Host destHost, AsyncCompletionCallback callback) { + for (DataMotionStrategy strategy : strategies) { + if (strategy.canHandle(volumeMap, srcHost, destHost)) { + strategy.copyAsync(volumeMap, vmTo, srcHost, destHost, callback); + return; + } + } + throw new CloudRuntimeException("can't find strategy to move data"); + } } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/engine/storage/src/org/apache/cloudstack/storage/motion/DataMotionStrategy.java ---------------------------------------------------------------------- diff --git a/engine/storage/src/org/apache/cloudstack/storage/motion/DataMotionStrategy.java b/engine/storage/src/org/apache/cloudstack/storage/motion/DataMotionStrategy.java index ba40c6d..e3859b4 100644 --- a/engine/storage/src/org/apache/cloudstack/storage/motion/DataMotionStrategy.java +++ b/engine/storage/src/org/apache/cloudstack/storage/motion/DataMotionStrategy.java @@ -18,13 +18,23 @@ */ package org.apache.cloudstack.storage.motion; +import java.util.Map; + import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; import org.apache.cloudstack.framework.async.AsyncCompletionCallback; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.host.Host; + public interface DataMotionStrategy { public boolean canHandle(DataObject srcData, DataObject destData); + public boolean canHandle(Map volumeMap, Host srcHost, Host destHost); public Void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback callback); + public Void copyAsync(Map volumeMap, VirtualMachineTO vmTo, Host srcHost, Host destHost, + AsyncCompletionCallback callback); } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeObject.java ---------------------------------------------------------------------- diff --git a/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeObject.java b/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeObject.java index ceadb25..ea31be3 100644 --- a/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeObject.java +++ b/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeObject.java @@ -176,6 +176,8 @@ public class VolumeObject implements VolumeInfo { volEvent = Volume.Event.CreateRequested; } else if (event == ObjectInDataStoreStateMachine.Event.CopyingRequested) { volEvent = Volume.Event.CopyRequested; + } else if (event == ObjectInDataStoreStateMachine.Event.MigrationRequested) { + volEvent = Volume.Event.MigrationRequested; } } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java ---------------------------------------------------------------------- diff --git a/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java b/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java index 32e7d27..e3526de 100644 --- a/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java +++ b/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java @@ -18,6 +18,10 @@ */ package org.apache.cloudstack.storage.volume; +import java.util.Map; +import java.util.List; +import java.util.ArrayList; + import javax.inject.Inject; import org.apache.cloudstack.engine.cloud.entity.api.VolumeEntity; @@ -27,6 +31,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService.VolumeApiResult; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; @@ -40,11 +45,14 @@ import org.apache.cloudstack.storage.datastore.DataObjectManager; import org.apache.cloudstack.storage.datastore.ObjectInDataStoreManager; import org.apache.cloudstack.storage.datastore.PrimaryDataStore; import org.apache.cloudstack.storage.datastore.PrimaryDataStoreProviderManager; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.motion.DataMotionService; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; +import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.exception.ConcurrentOperationException; +import com.cloud.host.Host; import com.cloud.storage.StoragePool; import com.cloud.storage.Volume; import com.cloud.storage.Volume.Type; @@ -561,7 +569,163 @@ public class VolumeServiceImpl implements VolumeService { return null; } - + + private class MigrateVolumeContext extends AsyncRpcConext { + final VolumeInfo srcVolume; + final VolumeInfo destVolume; + final DataStore destStore; + final AsyncCallFuture future; + /** + * @param callback + */ + public MigrateVolumeContext(AsyncCompletionCallback callback, AsyncCallFuture future, + VolumeInfo srcVolume, VolumeInfo destVolume, DataStore destStore) { + super(callback); + this.srcVolume = srcVolume; + this.destVolume = destVolume; + this.destStore = destStore; + this.future = future; + } + } + + @Override + public AsyncCallFuture migrateVolume(VolumeInfo srcVolume, DataStore destStore) { + AsyncCallFuture future = new AsyncCallFuture(); + VolumeApiResult res = new VolumeApiResult(srcVolume); + try { + if (!this.snapshotMgr.canOperateOnVolume(srcVolume)) { + s_logger.debug("Snapshots are being created on this volume. This volume cannot be migrated now."); + res.setResult("Snapshots are being created on this volume. This volume cannot be migrated now."); + future.complete(res); + return future; + } + + VolumeInfo destVolume = this.volFactory.getVolume(srcVolume.getId(), destStore); + srcVolume.processEvent(Event.MigrationRequested); + MigrateVolumeContext context = new MigrateVolumeContext(null, future, + srcVolume, destVolume, destStore); + AsyncCallbackDispatcher caller = AsyncCallbackDispatcher.create(this); + caller.setCallback(caller.getTarget().migrateVolumeCallBack(null, null)).setContext(context); + this.motionSrv.copyAsync(srcVolume, destVolume, caller); + } catch (Exception e) { + s_logger.debug("Failed to copy volume", e); + res.setResult(e.toString()); + future.complete(res); + } + return future; + } + + protected Void migrateVolumeCallBack(AsyncCallbackDispatcher callback, + MigrateVolumeContext context) { + VolumeInfo srcVolume = context.srcVolume; + VolumeInfo destVolume = context.destVolume; + CopyCommandResult result = callback.getResult(); + AsyncCallFuture future = context.future; + VolumeApiResult res = new VolumeApiResult(srcVolume); + try { + if (result.isFailed()) { + res.setResult(result.getResult()); + srcVolume.processEvent(Event.OperationFailed); + future.complete(res); + } else { + srcVolume.processEvent(Event.OperationSuccessed); + future.complete(res); + } + } catch (Exception e) { + s_logger.error("Failed to process copy volume callback", e); + res.setResult(e.toString()); + future.complete(res); + } + + return null; + } + + private class MigrateVmWithVolumesContext extends AsyncRpcConext { + final Map volumeToPool; + final AsyncCallFuture future; + /** + * @param callback + */ + public MigrateVmWithVolumesContext(AsyncCompletionCallback callback, AsyncCallFuture future, + Map volumeToPool) { + super(callback); + this.volumeToPool = volumeToPool; + this.future = future; + } + } + + @Override + public AsyncCallFuture migrateVolumes(Map volumeMap, VirtualMachineTO vmTo, + Host srcHost, Host destHost) { + AsyncCallFuture future = new AsyncCallFuture(); + CommandResult res = new CommandResult(); + try { + // Check to make sure there are no snapshot operations on a volume and + // put it in the migrating state. + List volumesMigrating = new ArrayList(); + for (Map.Entry entry : volumeMap.entrySet()) { + VolumeInfo volume = entry.getKey(); + if (!this.snapshotMgr.canOperateOnVolume(volume)) { + s_logger.debug("Snapshots are being created on a volume. Volumes cannot be migrated now."); + res.setResult("Snapshots are being created on a volume. Volumes cannot be migrated now."); + future.complete(res); + + // All the volumes that are already in migrating state need to be put back in ready state. + for (VolumeInfo volumeMigrating : volumesMigrating) { + volumeMigrating.processEvent(Event.OperationFailed); + } + return future; + } else { + volume.processEvent(Event.MigrationRequested); + volumesMigrating.add(volume); + } + } + + MigrateVmWithVolumesContext context = new MigrateVmWithVolumesContext(null, + future, volumeMap); + AsyncCallbackDispatcher caller = AsyncCallbackDispatcher.create(this); + caller.setCallback(caller.getTarget().migrateVmWithVolumesCallBack(null, null)).setContext(context); + this.motionSrv.copyAsync(volumeMap, vmTo, srcHost, destHost, caller); + + } catch (Exception e) { + s_logger.debug("Failed to copy volume", e); + res.setResult(e.toString()); + future.complete(res); + } + + return future; + } + + protected Void migrateVmWithVolumesCallBack(AsyncCallbackDispatcher callback, + MigrateVmWithVolumesContext context) { + Map volumeToPool = context.volumeToPool; + CopyCommandResult result = callback.getResult(); + AsyncCallFuture future = context.future; + CommandResult res = new CommandResult(); + try { + if (result.isFailed()) { + res.setResult(result.getResult()); + for (Map.Entry entry : volumeToPool.entrySet()) { + VolumeInfo volume = entry.getKey(); + volume.processEvent(Event.OperationFailed); + } + future.complete(res); + } else { + for (Map.Entry entry : volumeToPool.entrySet()) { + VolumeInfo volume = entry.getKey(); + volume.processEvent(Event.OperationSuccessed); + } + future.complete(res); + } + } catch (Exception e) { + s_logger.error("Failed to process copy volume callback", e); + res.setResult(e.toString()); + future.complete(res); + } + + return null; + } + @Override public AsyncCallFuture registerVolume(VolumeInfo volume, DataStore store) { http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/plugins/host-allocators/random/src/com/cloud/agent/manager/allocator/impl/RandomAllocator.java ---------------------------------------------------------------------- diff --git a/plugins/host-allocators/random/src/com/cloud/agent/manager/allocator/impl/RandomAllocator.java b/plugins/host-allocators/random/src/com/cloud/agent/manager/allocator/impl/RandomAllocator.java index a672efd..8243f3a 100755 --- a/plugins/host-allocators/random/src/com/cloud/agent/manager/allocator/impl/RandomAllocator.java +++ b/plugins/host-allocators/random/src/com/cloud/agent/manager/allocator/impl/RandomAllocator.java @@ -55,6 +55,62 @@ public class RandomAllocator extends AdapterBase implements HostAllocator { @Override public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, + ExcludeList avoid, List hosts, int returnUpTo, boolean considerReservedCapacity) { + long dcId = plan.getDataCenterId(); + Long podId = plan.getPodId(); + Long clusterId = plan.getClusterId(); + ServiceOffering offering = vmProfile.getServiceOffering(); + List suitableHosts = new ArrayList(); + + if (type == Host.Type.Storage) { + return suitableHosts; + } + + String hostTag = offering.getHostTag(); + if(hostTag != null){ + s_logger.debug("Looking for hosts in dc: " + dcId + " pod:" + podId + " cluster:" + clusterId + + " having host tag:" + hostTag); + }else{ + s_logger.debug("Looking for hosts in dc: " + dcId + " pod:" + podId + " cluster:" + clusterId); + } + + // list all computing hosts, regardless of whether they support routing...it's random after all + if(hostTag != null){ + hosts.retainAll(_hostDao.listByHostTag(type, clusterId, podId, dcId, hostTag)); + }else{ + hosts.retainAll(_resourceMgr.listAllUpAndEnabledHosts(type, clusterId, podId, dcId)); + } + + s_logger.debug("Random Allocator found " + hosts.size() + " hosts"); + if (hosts.size() == 0) { + return suitableHosts; + } + + Collections.shuffle(hosts); + for (Host host : hosts) { + if(suitableHosts.size() == returnUpTo){ + break; + } + + if (!avoid.shouldAvoid(host)) { + suitableHosts.add(host); + } else { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Host name: " + host.getName() + ", hostId: "+ host.getId() +" is in avoid set, " + + "skipping this and trying other available hosts"); + } + } + } + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Random Host Allocator returning "+suitableHosts.size() +" suitable hosts"); + } + + return suitableHosts; + } + + @Override + public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo, boolean considerReservedCapacity) { long dcId = plan.getDataCenterId(); http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java index 4ef583a..46ae35a 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java @@ -3358,7 +3358,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe vm.setMemoryLimits(conn, maxMemsize, maxMemsize, minMemsize, maxMemsize); } - private void waitForTask(Connection c, Task task, long pollInterval, long timeout) throws XenAPIException, XmlRpcException { + protected void waitForTask(Connection c, Task task, long pollInterval, long timeout) throws XenAPIException, XmlRpcException { long beginTime = System.currentTimeMillis(); while (task.getStatus(c) == Types.TaskStatusType.PENDING) { try { @@ -3374,7 +3374,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe } } - private void checkForSuccess(Connection c, Task task) throws XenAPIException, XmlRpcException { + protected void checkForSuccess(Connection c, Task task) throws XenAPIException, XmlRpcException { if (task.getStatus(c) == Types.TaskStatusType.SUCCESS) { return; } else { http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java index d64e173..96a90a6 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java @@ -132,6 +132,7 @@ public class XenServer56FP1Resource extends XenServer56Resource { record.affinity = host; record.otherConfig.remove("disks"); record.otherConfig.remove("default_template"); + record.otherConfig.remove("mac_seed"); record.isATemplate = false; record.nameLabel = vmSpec.getName(); record.actionsAfterCrash = Types.OnCrashBehaviour.DESTROY; http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer610Resource.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer610Resource.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer610Resource.java index 8d267b1..bb31136 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer610Resource.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer610Resource.java @@ -20,6 +20,9 @@ package com.cloud.hypervisor.xen.resource; import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; import javax.ejb.Local; @@ -28,7 +31,34 @@ import org.apache.log4j.Logger; import com.cloud.resource.ServerResource; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.script.Script; - +import com.cloud.vm.VirtualMachine.State; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.storage.MigrateVolumeAnswer; +import com.cloud.agent.api.storage.MigrateVolumeCommand; +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.to.StorageFilerTO; +import com.cloud.network.Networks.TrafficType; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.agent.api.to.VolumeTO; +import com.cloud.agent.api.to.NicTO; +import com.xensource.xenapi.Connection; +import com.xensource.xenapi.Host; +import com.xensource.xenapi.Network; +import com.xensource.xenapi.SR; +import com.xensource.xenapi.Task; +import com.xensource.xenapi.Types; +import com.xensource.xenapi.VBD; +import com.xensource.xenapi.VDI; +import com.xensource.xenapi.VIF; +import com.xensource.xenapi.VM; @Local(value=ServerResource.class) public class XenServer610Resource extends XenServer56FP1Resource { @@ -55,4 +85,331 @@ public class XenServer610Resource extends XenServer56FP1Resource { files.add(file); return files; } + + @Override + public Answer executeRequest(Command cmd) { + if (cmd instanceof MigrateWithStorageCommand) { + return execute((MigrateWithStorageCommand) cmd); + } else if (cmd instanceof MigrateWithStorageReceiveCommand) { + return execute((MigrateWithStorageReceiveCommand) cmd); + } else if (cmd instanceof MigrateWithStorageSendCommand) { + return execute((MigrateWithStorageSendCommand) cmd); + } else if (cmd instanceof MigrateWithStorageCompleteCommand) { + return execute((MigrateWithStorageCompleteCommand) cmd); + } else if (cmd instanceof MigrateVolumeCommand) { + return execute((MigrateVolumeCommand) cmd); + } else { + return super.executeRequest(cmd); + } + } + + private List getUpdatedVolumePathsOfMigratedVm(Connection connection, VM migratedVm, + VolumeTO[] volumes) throws CloudRuntimeException { + List volumeToList = new ArrayList(); + + try { + // Volume paths would have changed. Return that information. + Set vbds = migratedVm.getVBDs(connection); + Map deviceIdToVdiMap = new HashMap(); + // get vdi:vbdr to a map + for (VBD vbd : vbds) { + VBD.Record vbdr = vbd.getRecord(connection); + if (vbdr.type == Types.VbdType.DISK) { + VDI vdi = vbdr.VDI; + deviceIdToVdiMap.put(vbdr.userdevice, vdi); + } + } + + for (VolumeTO volumeTo : volumes) { + Long deviceId = volumeTo.getDeviceId(); + VDI vdi = deviceIdToVdiMap.get(deviceId.toString()); + volumeTo.setPath(vdi.getUuid(connection)); + volumeToList.add(volumeTo); + } + } catch (Exception e) { + s_logger.error("Unable to get the updated VDI paths of the migrated vm " + e.toString(), e); + throw new CloudRuntimeException("Unable to get the updated VDI paths of the migrated vm " + e.toString(), e); + } + + return volumeToList; + } + + protected MigrateWithStorageAnswer execute(MigrateWithStorageCommand cmd) { + Connection connection = getConnection(); + VirtualMachineTO vmSpec = cmd.getVirtualMachine(); + Map volumeToFiler = cmd.getVolumeToFiler(); + final String vmName = vmSpec.getName(); + State state = s_vms.getState(_cluster, vmName); + Task task = null; + + synchronized (_cluster.intern()) { + s_vms.put(_cluster, _name, vmName, State.Stopping); + } + + try { + prepareISO(connection, vmSpec.getName()); + Map other = new HashMap(); + other.put("live", "true"); + Network networkForSm = getNativeNetworkForTraffic(connection, TrafficType.Storage, null).getNetwork(); + Host host = Host.getByUuid(connection, _host.uuid); + Map token = host.migrateReceive(connection, networkForSm, other); + + // Get the vm to migrate. + Set vms = VM.getByNameLabel(connection, vmSpec.getName()); + VM vmToMigrate = vms.iterator().next(); + + // Create the vif map. The vm stays in the same cluster so we have to pass an empty vif map. + Map vifMap = new HashMap(); + Map vdiMap = new HashMap(); + for (Map.Entry entry : volumeToFiler.entrySet()) { + vdiMap.put(getVDIbyUuid(connection, entry.getKey().getPath()), + getStorageRepository(connection, entry.getValue().getUuid())); + } + + // Check migration with storage is possible. + task = vmToMigrate.assertCanMigrateAsync(connection, token, true, vdiMap, vifMap, other); + try { + // poll every 1 seconds + long timeout = (_migratewait) * 1000L; + waitForTask(connection, task, 1000, timeout); + checkForSuccess(connection, task); + } catch (Types.HandleInvalid e) { + s_logger.error("Error while checking if vm " + vmName + " can be migrated to the destination host " + + host, e); + throw new CloudRuntimeException("Error while checking if vm " + vmName + " can be migrated to the " + + "destination host " + host, e); + } + + // Migrate now. + task = vmToMigrate.migrateSendAsync(connection, token, true, vdiMap, vifMap, other); + try { + // poll every 1 seconds. + long timeout = (_migratewait) * 1000L; + waitForTask(connection, task, 1000, timeout); + checkForSuccess(connection, task); + } catch (Types.HandleInvalid e) { + s_logger.error("Error while migrating vm " + vmName + " to the destination host " + host, e); + throw new CloudRuntimeException("Error while migrating vm " + vmName + " to the destination host " + + host, e); + } + + // Volume paths would have changed. Return that information. + List volumeToList = getUpdatedVolumePathsOfMigratedVm(connection, vmToMigrate, vmSpec.getDisks()); + vmToMigrate.setAffinity(connection, host); + state = State.Stopping; + + return new MigrateWithStorageAnswer(cmd, volumeToList); + } catch (Exception e) { + s_logger.warn("Catch Exception " + e.getClass().getName() + ". Storage motion failed due to " + + e.toString(), e); + return new MigrateWithStorageAnswer(cmd, e); + } finally { + if (task != null) { + try { + task.destroy(connection); + } catch (Exception e) { + s_logger.debug("Unable to destroy task " + task.toString() + " on host " + _host.uuid +" due to " + + e.toString()); + } + } + + synchronized (_cluster.intern()) { + s_vms.put(_cluster, _name, vmName, state); + } + } + } + + protected MigrateWithStorageReceiveAnswer execute(MigrateWithStorageReceiveCommand cmd) { + Connection connection = getConnection(); + VirtualMachineTO vmSpec = cmd.getVirtualMachine(); + Map volumeToFiler = cmd.getVolumeToFiler(); + + try { + // Get a map of all the SRs to which the vdis will be migrated. + Map volumeToSr = new HashMap(); + for (Map.Entry entry : volumeToFiler.entrySet()) { + SR sr = getStorageRepository(connection, entry.getValue().getUuid()); + volumeToSr.put(entry.getKey(), sr); + } + + // Get the list of networks to which the vifs will attach. + Map nicToNetwork = new HashMap(); + for (NicTO nicTo : vmSpec.getNics()) { + Network network = getNetwork(connection, nicTo); + nicToNetwork.put(nicTo, network); + } + + Map other = new HashMap(); + other.put("live", "true"); + Network network = getNativeNetworkForTraffic(connection, TrafficType.Storage, null).getNetwork(); + Host host = Host.getByUuid(connection, _host.uuid); + Map token = host.migrateReceive(connection, network, other); + + return new MigrateWithStorageReceiveAnswer(cmd, volumeToSr, nicToNetwork, token); + } catch (CloudRuntimeException e) { + s_logger.error("Migration of vm " + vmSpec.getName() + " with storage failed due to " + e.toString(), e); + return new MigrateWithStorageReceiveAnswer(cmd, e); + } catch (Exception e) { + s_logger.error("Migration of vm " + vmSpec.getName() + " with storage failed due to " + e.toString(), e); + return new MigrateWithStorageReceiveAnswer(cmd, e); + } + } + + protected MigrateWithStorageSendAnswer execute(MigrateWithStorageSendCommand cmd) { + Connection connection = getConnection(); + VirtualMachineTO vmSpec = cmd.getVirtualMachine(); + Map volumeToSr = cmd.getVolumeToSr(); + Map nicToNetwork = cmd.getNicToNetwork(); + Map token = cmd.getToken(); + final String vmName = vmSpec.getName(); + State state = s_vms.getState(_cluster, vmName); + Set volumeToSet = null; + boolean migrated = false; + Task task = null; + + synchronized (_cluster.intern()) { + s_vms.put(_cluster, _name, vmName, State.Stopping); + } + + try { + Set vms = VM.getByNameLabel(connection, vmSpec.getName()); + VM vmToMigrate = vms.iterator().next(); + Map other = new HashMap(); + other.put("live", "true"); + + // Create the vdi map which tells what volumes of the vm need to go on which sr on the destination. + Map vdiMap = new HashMap(); + for (Map.Entry entry : volumeToSr.entrySet()) { + if (entry.getValue() instanceof SR) { + SR sr = (SR)entry.getValue(); + VDI vdi = getVDIbyUuid(connection, entry.getKey().getPath()); + vdiMap.put(vdi, sr); + } else { + throw new CloudRuntimeException("The object " + entry.getValue() + " passed is not of type SR."); + } + } + + // Create the vif map. + Map vifMap = new HashMap(); + for (Map.Entry entry : nicToNetwork.entrySet()) { + if (entry.getValue() instanceof Network) { + Network network = (Network)entry.getValue(); + VIF vif = getVifByMac(connection, vmToMigrate, entry.getKey().getMac()); + vifMap.put(vif, network); + } else { + throw new CloudRuntimeException("The object " + entry.getValue() + " passed is not of type Network."); + } + } + + // Check migration with storage is possible. + task = vmToMigrate.assertCanMigrateAsync(connection, token, true, vdiMap, vifMap, other); + try { + // poll every 1 seconds. + long timeout = (_migratewait) * 1000L; + waitForTask(connection, task, 1000, timeout); + checkForSuccess(connection, task); + } catch (Types.HandleInvalid e) { + s_logger.error("Error while checking if vm " + vmName + " can be migrated.", e); + throw new CloudRuntimeException("Error while checking if vm " + vmName + " can be migrated.", e); + } + + // Migrate now. + task = vmToMigrate.migrateSendAsync(connection, token, true, vdiMap, vifMap, other); + try { + // poll every 1 seconds. + long timeout = (_migratewait) * 1000L; + waitForTask(connection, task, 1000, timeout); + checkForSuccess(connection, task); + } catch (Types.HandleInvalid e) { + s_logger.error("Error while migrating vm " + vmName, e); + throw new CloudRuntimeException("Error while migrating vm " + vmName, e); + } + + migrated = true; + return new MigrateWithStorageSendAnswer(cmd, volumeToSet); + } catch (CloudRuntimeException e) { + s_logger.error("Migration of vm " + vmName + " with storage failed due to " + e.toString(), e); + return new MigrateWithStorageSendAnswer(cmd, e); + } catch (Exception e) { + s_logger.error("Migration of vm " + vmName + " with storage failed due to " + e.toString(), e); + return new MigrateWithStorageSendAnswer(cmd, e); + } finally { + if (task != null) { + try { + task.destroy(connection); + } catch (Exception e) { + s_logger.debug("Unable to destroy task " + task.toString() + " on host " + _host.uuid +" due to " + + e.toString()); + } + } + + // Keep cluster/vm sync happy. + synchronized (_cluster.intern()) { + if (migrated) { + s_vms.remove(_cluster, _name, vmName); + } else { + s_vms.put(_cluster, _name, vmName, state); + } + } + } + } + + protected MigrateWithStorageCompleteAnswer execute(MigrateWithStorageCompleteCommand cmd) { + Connection connection = getConnection(); + VirtualMachineTO vmSpec = cmd.getVirtualMachine(); + + try { + Host host = Host.getByUuid(connection, _host.uuid); + Set vms = VM.getByNameLabel(connection, vmSpec.getName()); + VM migratedVm = vms.iterator().next(); + + // Check the vm is present on the new host. + if (migratedVm == null) { + throw new CloudRuntimeException("Couldn't find the migrated vm " + vmSpec.getName() + + " on the destination host."); + } + + // Volume paths would have changed. Return that information. + List volumeToSet = getUpdatedVolumePathsOfMigratedVm(connection, migratedVm, vmSpec.getDisks()); + migratedVm.setAffinity(connection, host); + + synchronized (_cluster.intern()) { + s_vms.put(_cluster, _name, vmSpec.getName(), State.Running); + } + + return new MigrateWithStorageCompleteAnswer(cmd, volumeToSet); + } catch (CloudRuntimeException e) { + s_logger.error("Migration of vm " + vmSpec.getName() + " with storage failed due to " + e.toString(), e); + return new MigrateWithStorageCompleteAnswer(cmd, e); + } catch (Exception e) { + s_logger.error("Migration of vm " + vmSpec.getName() + " with storage failed due to " + e.toString(), e); + return new MigrateWithStorageCompleteAnswer(cmd, e); + } + } + + protected MigrateVolumeAnswer execute(MigrateVolumeCommand cmd) { + Connection connection = getConnection(); + String volumeUUID = cmd.getVolumePath(); + StorageFilerTO poolTO = cmd.getPool(); + + try { + SR destinationPool = getStorageRepository(connection, poolTO.getUuid()); + VDI srcVolume = getVDIbyUuid(connection, volumeUUID); + Map other = new HashMap(); + other.put("live", "true"); + + // Live migrate the vdi across pool. + Task task = srcVolume.poolMigrateAsync(connection, destinationPool, other); + long timeout = (_migratewait) * 1000L; + waitForTask(connection, task, 1000, timeout); + checkForSuccess(connection, task); + VDI dvdi = Types.toVDI(task, connection); + + return new MigrateVolumeAnswer(cmd, true, null, dvdi.getUuid(connection)); + } catch (Exception e) { + String msg = "Catch Exception " + e.getClass().getName() + " due to " + e.toString(); + s_logger.error(msg, e); + return new MigrateVolumeAnswer(cmd, false, msg, null); + } + } } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/plugins/hypervisors/xen/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/xen/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java b/plugins/hypervisors/xen/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java new file mode 100644 index 0000000..353f2b5 --- /dev/null +++ b/plugins/hypervisors/xen/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java @@ -0,0 +1,239 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cloudstack.storage.motion; + +import java.util.HashMap; +import java.util.Map; +import java.util.List; + +import javax.inject.Inject; + +import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; +import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.framework.async.AsyncCompletionCallback; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.MigrateWithStorageAnswer; +import com.cloud.agent.api.MigrateWithStorageCommand; +import com.cloud.agent.api.MigrateWithStorageCompleteAnswer; +import com.cloud.agent.api.MigrateWithStorageCompleteCommand; +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.to.StorageFilerTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.agent.api.to.VolumeTO; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.host.Host; +import com.cloud.storage.StoragePool; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.dao.VMInstanceDao; + +@Component +public class XenServerStorageMotionStrategy implements DataMotionStrategy { + private static final Logger s_logger = Logger.getLogger(XenServerStorageMotionStrategy.class); + @Inject AgentManager agentMgr; + @Inject VolumeDao volDao; + @Inject VolumeDataFactory volFactory; + @Inject PrimaryDataStoreDao storagePoolDao; + @Inject VMInstanceDao instanceDao; + + @Override + public boolean canHandle(DataObject srcData, DataObject destData) { + return false; + } + + @Override + public boolean canHandle(Map volumeMap, Host srcHost, Host destHost) { + return true; + } + + @Override + public Void copyAsync(DataObject srcData, DataObject destData, + AsyncCompletionCallback callback) { + CopyCommandResult result = new CopyCommandResult(null, null); + result.setResult("Unsupported operation requested for copying data."); + callback.complete(result); + + return null; + } + + @Override + public Void copyAsync(Map volumeMap, VirtualMachineTO vmTo, Host srcHost, Host destHost, + AsyncCompletionCallback callback) { + Answer answer = null; + String errMsg = null; + try { + VMInstanceVO instance = instanceDao.findById(vmTo.getId()); + if (instance != null) { + if (srcHost.getClusterId() == destHost.getClusterId()) { + answer = migrateVmWithVolumesWithinCluster(instance, vmTo, srcHost, destHost, volumeMap); + } else { + answer = migrateVmWithVolumesAcrossCluster(instance, vmTo, srcHost, destHost, volumeMap); + } + } else { + throw new CloudRuntimeException("Unsupported operation requested for moving data."); + } + } catch (Exception e) { + s_logger.error("copy failed", e); + errMsg = e.toString(); + } + + CopyCommandResult result = new CopyCommandResult(null, answer); + result.setResult(errMsg); + callback.complete(result); + return null; + } + + private Answer migrateVmWithVolumesAcrossCluster(VMInstanceVO vm, VirtualMachineTO to, Host srcHost, + Host destHost, Map volumeToPool) throws AgentUnavailableException { + + // Initiate migration of a virtual machine with it's volumes. + try { + Map volumeToFilerto = new HashMap(); + for (Map.Entry entry : volumeToPool.entrySet()) { + VolumeInfo volume = entry.getKey(); + VolumeTO volumeTo = new VolumeTO(volume, storagePoolDao.findById(volume.getPoolId())); + StorageFilerTO filerTo = new StorageFilerTO((StoragePool)entry.getValue()); + volumeToFilerto.put(volumeTo, filerTo); + } + + // Migration across cluster needs to be done in three phases. + // 1. Send a migrate receive command to the destination host so that it is ready to receive a vm. + // 2. Send a migrate send command to the source host. This actually migrates the vm to the destination. + // 3. Complete the process. Update the volume details. + MigrateWithStorageReceiveCommand receiveCmd = new MigrateWithStorageReceiveCommand(to, volumeToFilerto); + MigrateWithStorageReceiveAnswer receiveAnswer = (MigrateWithStorageReceiveAnswer) agentMgr.send( + destHost.getId(), receiveCmd); + if (receiveAnswer == null) { + s_logger.error("Migration with storage of vm " + vm+ " to host " + destHost + " failed."); + throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost); + } else if (!receiveAnswer.getResult()) { + s_logger.error("Migration with storage of vm " + vm+ " failed. Details: " + receiveAnswer.getDetails()); + throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost + + ". " + receiveAnswer.getDetails()); + } + + MigrateWithStorageSendCommand sendCmd = new MigrateWithStorageSendCommand(to, receiveAnswer.getVolumeToSr(), + receiveAnswer.getNicToNetwork(), receiveAnswer.getToken()); + MigrateWithStorageSendAnswer sendAnswer = (MigrateWithStorageSendAnswer) agentMgr.send( + srcHost.getId(), sendCmd); + if (sendAnswer == null) { + s_logger.error("Migration with storage of vm " + vm+ " to host " + destHost + " failed."); + throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost); + } else if (!sendAnswer.getResult()) { + s_logger.error("Migration with storage of vm " + vm+ " failed. Details: " + sendAnswer.getDetails()); + throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost + + ". " + sendAnswer.getDetails()); + } + + MigrateWithStorageCompleteCommand command = new MigrateWithStorageCompleteCommand(to); + MigrateWithStorageCompleteAnswer answer = (MigrateWithStorageCompleteAnswer) agentMgr.send( + destHost.getId(), command); + if (answer == null) { + s_logger.error("Migration with storage of vm " + vm + " failed."); + throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost); + } else if (!answer.getResult()) { + s_logger.error("Migration with storage of vm " + vm+ " failed. Details: " + answer.getDetails()); + throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost + + ". " + answer.getDetails()); + } else { + // Update the volume details after migration. + updateVolumePathsAfterMigration(volumeToPool, answer.getVolumeTos()); + } + + return answer; + } catch (OperationTimedoutException e) { + s_logger.error("Error while migrating vm " + vm + " to host " + destHost, e); + throw new AgentUnavailableException("Operation timed out on storage motion for " + vm, destHost.getId()); + } + } + + private Answer migrateVmWithVolumesWithinCluster(VMInstanceVO vm, VirtualMachineTO to, Host srcHost, + Host destHost, Map volumeToPool) throws AgentUnavailableException { + + // Initiate migration of a virtual machine with it's volumes. + try { + Map volumeToFilerto = new HashMap(); + for (Map.Entry entry : volumeToPool.entrySet()) { + VolumeInfo volume = entry.getKey(); + VolumeTO volumeTo = new VolumeTO(volume, storagePoolDao.findById(volume.getPoolId())); + StorageFilerTO filerTo = new StorageFilerTO((StoragePool)entry.getValue()); + volumeToFilerto.put(volumeTo, filerTo); + } + + MigrateWithStorageCommand command = new MigrateWithStorageCommand(to, volumeToFilerto); + MigrateWithStorageAnswer answer = (MigrateWithStorageAnswer) agentMgr.send(destHost.getId(), command); + if (answer == null) { + s_logger.error("Migration with storage of vm " + vm + " failed."); + throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost); + } else if (!answer.getResult()) { + s_logger.error("Migration with storage of vm " + vm+ " failed. Details: " + answer.getDetails()); + throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost + + ". " + answer.getDetails()); + } else { + // Update the volume details after migration. + updateVolumePathsAfterMigration(volumeToPool, answer.getVolumeTos()); + } + + return answer; + } catch (OperationTimedoutException e) { + s_logger.error("Error while migrating vm " + vm + " to host " + destHost, e); + throw new AgentUnavailableException("Operation timed out on storage motion for " + vm, destHost.getId()); + } + } + + private void updateVolumePathsAfterMigration(Map volumeToPool, List volumeTos) { + for (Map.Entry entry : volumeToPool.entrySet()) { + boolean updated = false; + VolumeInfo volume = entry.getKey(); + StoragePool pool = (StoragePool)entry.getValue(); + for (VolumeTO volumeTo : volumeTos) { + if (volume.getId() == volumeTo.getId()) { + VolumeVO volumeVO = volDao.findById(volume.getId()); + Long oldPoolId = volumeVO.getPoolId(); + volumeVO.setPath(volumeTo.getPath()); + volumeVO.setFolder(pool.getPath()); + volumeVO.setPodId(pool.getPodId()); + volumeVO.setPoolId(pool.getId()); + volumeVO.setLastPoolId(oldPoolId); + volDao.update(volume.getId(), volumeVO); + updated = true; + break; + } + } + + if (!updated) { + s_logger.error("Volume path wasn't updated for volume " + volume + " after it was migrated."); + } + } + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/src/com/cloud/agent/manager/allocator/HostAllocator.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/agent/manager/allocator/HostAllocator.java b/server/src/com/cloud/agent/manager/allocator/HostAllocator.java index 60027e7..6700f22 100755 --- a/server/src/com/cloud/agent/manager/allocator/HostAllocator.java +++ b/server/src/com/cloud/agent/manager/allocator/HostAllocator.java @@ -21,6 +21,7 @@ import java.util.List; import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.host.Host; +import com.cloud.host.HostVO; import com.cloud.host.Host.Type; import com.cloud.offering.ServiceOffering; import com.cloud.utils.component.Adapter; @@ -63,8 +64,22 @@ public interface HostAllocator extends Adapter { **/ public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo, boolean considerReservedCapacity); - - - public static int RETURN_UPTO_ALL = -1; - + + /** + * Determines which physical hosts are suitable to + * allocate the guest virtual machines on + * + * @param VirtualMachineProfile vmProfile + * @param DeploymentPlan plan + * @param GuestType type + * @param ExcludeList avoid + * @param List hosts + * @param int returnUpTo (use -1 to return all possible hosts) + * @param boolean considerReservedCapacity (default should be true, set to false if host capacity calculation should not look at reserved capacity) + * @return List List of hosts that are suitable for VM allocation + **/ + public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, List hosts, int returnUpTo, boolean considerReservedCapacity); + + public static int RETURN_UPTO_ALL = -1; + } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/src/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java b/server/src/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java index 0091e43..b54b1c1 100755 --- a/server/src/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java +++ b/server/src/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java @@ -172,6 +172,53 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator { return allocateTo(plan, offering, template, avoid, clusterHosts, returnUpTo, considerReservedCapacity, account); } + @Override + public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, + Type type, ExcludeList avoid, List hosts, int returnUpTo, boolean considerReservedCapacity) { + long dcId = plan.getDataCenterId(); + Long podId = plan.getPodId(); + Long clusterId = plan.getClusterId(); + ServiceOffering offering = vmProfile.getServiceOffering(); + VMTemplateVO template = (VMTemplateVO)vmProfile.getTemplate(); + Account account = vmProfile.getOwner(); + List suitableHosts = new ArrayList(); + + if (type == Host.Type.Storage) { + // FirstFitAllocator should be used for user VMs only since it won't care whether the host is capable of + // routing or not. + return suitableHosts; + } + + String hostTagOnOffering = offering.getHostTag(); + String hostTagOnTemplate = template.getTemplateTag(); + boolean hasSvcOfferingTag = hostTagOnOffering != null ? true : false; + boolean hasTemplateTag = hostTagOnTemplate != null ? true : false; + + String haVmTag = (String)vmProfile.getParameter(VirtualMachineProfile.Param.HaTag); + if (haVmTag != null) { + hosts.retainAll(_hostDao.listByHostTag(type, clusterId, podId, dcId, haVmTag)); + } else { + if (hostTagOnOffering == null && hostTagOnTemplate == null){ + hosts.retainAll(_resourceMgr.listAllUpAndEnabledNonHAHosts(type, clusterId, podId, dcId)); + } else { + if (hasSvcOfferingTag) { + hosts.retainAll(_hostDao.listByHostTag(type, clusterId, podId, dcId, hostTagOnOffering)); + } + + if (hasTemplateTag) { + hosts.retainAll(_hostDao.listByHostTag(type, clusterId, podId, dcId, hostTagOnTemplate)); + } + } + } + + if (!hosts.isEmpty()) { + suitableHosts = allocateTo(plan, offering, template, avoid, hosts, returnUpTo, considerReservedCapacity, + account); + } + + return suitableHosts; + } + protected List allocateTo(DeploymentPlan plan, ServiceOffering offering, VMTemplateVO template, ExcludeList avoid, List hosts, int returnUpTo, boolean considerReservedCapacity, Account account) { if (_allocationAlgorithm.equals("random") || _allocationAlgorithm.equals("userconcentratedpod_random")) { // Shuffle this so that we don't check the hosts in the same order. http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/src/com/cloud/agent/manager/allocator/impl/TestingAllocator.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/agent/manager/allocator/impl/TestingAllocator.java b/server/src/com/cloud/agent/manager/allocator/impl/TestingAllocator.java index 90bd956..890c047 100755 --- a/server/src/com/cloud/agent/manager/allocator/impl/TestingAllocator.java +++ b/server/src/com/cloud/agent/manager/allocator/impl/TestingAllocator.java @@ -29,6 +29,7 @@ import com.cloud.agent.manager.allocator.HostAllocator; import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.host.Host; +import com.cloud.host.HostVO; import com.cloud.host.Host.Type; import com.cloud.host.dao.HostDao; import com.cloud.offering.ServiceOffering; @@ -52,6 +53,12 @@ public class TestingAllocator extends AdapterBase implements HostAllocator { @Override public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, + ExcludeList avoid, List hosts, int returnUpTo, boolean considerReservedCapacity) { + return allocateTo(vmProfile, plan, type, avoid, returnUpTo, considerReservedCapacity); + } + + @Override + public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo, boolean considerReservedCapacity) { List availableHosts = new ArrayList(); Host host = null; http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/src/com/cloud/api/ApiDBUtils.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/api/ApiDBUtils.java b/server/src/com/cloud/api/ApiDBUtils.java index 303f328..c60af27 100755 --- a/server/src/com/cloud/api/ApiDBUtils.java +++ b/server/src/com/cloud/api/ApiDBUtils.java @@ -36,6 +36,7 @@ import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainRouterResponse; import org.apache.cloudstack.api.response.EventResponse; import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.HostForMigrationResponse; import org.apache.cloudstack.api.response.InstanceGroupResponse; import org.apache.cloudstack.api.response.ProjectAccountResponse; import org.apache.cloudstack.api.response.ProjectInvitationResponse; @@ -43,6 +44,7 @@ import org.apache.cloudstack.api.response.ProjectResponse; import org.apache.cloudstack.api.response.ResourceTagResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.StoragePoolForMigrationResponse; import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.UserResponse; import org.apache.cloudstack.api.response.UserVmResponse; @@ -1518,6 +1520,14 @@ public class ApiDBUtils { return _hostJoinDao.setHostResponse(vrData, vr); } + public static HostForMigrationResponse newHostForMigrationResponse(HostJoinVO vr, EnumSet details) { + return _hostJoinDao.newHostForMigrationResponse(vr, details); + } + + public static HostForMigrationResponse fillHostForMigrationDetails(HostForMigrationResponse vrData, HostJoinVO vr) { + return _hostJoinDao.setHostForMigrationResponse(vrData, vr); + } + public static List newHostView(Host vr){ return _hostJoinDao.newHostView(vr); } @@ -1543,6 +1553,15 @@ public class ApiDBUtils { return _poolJoinDao.setStoragePoolResponse(vrData, vr); } + public static StoragePoolForMigrationResponse newStoragePoolForMigrationResponse(StoragePoolJoinVO vr) { + return _poolJoinDao.newStoragePoolForMigrationResponse(vr); + } + + public static StoragePoolForMigrationResponse fillStoragePoolForMigrationDetails(StoragePoolForMigrationResponse + vrData, StoragePoolJoinVO vr){ + return _poolJoinDao.setStoragePoolForMigrationResponse(vrData, vr); + } + public static List newStoragePoolView(StoragePool vr){ return _poolJoinDao.newStoragePoolView(vr); } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/src/com/cloud/api/ApiResponseHelper.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/api/ApiResponseHelper.java b/server/src/com/cloud/api/ApiResponseHelper.java index 7629e5e..a7d6165 100755 --- a/server/src/com/cloud/api/ApiResponseHelper.java +++ b/server/src/com/cloud/api/ApiResponseHelper.java @@ -65,6 +65,7 @@ import org.apache.cloudstack.api.response.FirewallRuleResponse; import org.apache.cloudstack.api.response.GlobalLoadBalancerResponse; import org.apache.cloudstack.api.response.GuestOSResponse; import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.HostForMigrationResponse; import org.apache.cloudstack.api.response.HypervisorCapabilitiesResponse; import org.apache.cloudstack.api.response.IPAddressResponse; import org.apache.cloudstack.api.response.InstanceGroupResponse; @@ -105,6 +106,7 @@ import org.apache.cloudstack.api.response.SnapshotResponse; import org.apache.cloudstack.api.response.SnapshotScheduleResponse; import org.apache.cloudstack.api.response.StaticRouteResponse; import org.apache.cloudstack.api.response.StorageNetworkIpRangeResponse; +import org.apache.cloudstack.api.response.StoragePoolForMigrationResponse; import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.SwiftResponse; import org.apache.cloudstack.api.response.SystemVmInstanceResponse; @@ -511,6 +513,20 @@ public class ApiResponseHelper implements ResponseGenerator { } @Override + public HostForMigrationResponse createHostForMigrationResponse(Host host) { + return createHostForMigrationResponse(host, EnumSet.of(HostDetails.all)); + } + + @Override + public HostForMigrationResponse createHostForMigrationResponse(Host host, EnumSet details) { + List viewHosts = ApiDBUtils.newHostView(host); + List listHosts = ViewResponseHelper.createHostForMigrationResponse(details, + viewHosts.toArray(new HostJoinVO[viewHosts.size()])); + assert listHosts != null && listHosts.size() == 1 : "There should be one host returned"; + return listHosts.get(0); + } + + @Override public SwiftResponse createSwiftResponse(Swift swift) { SwiftResponse swiftResponse = new SwiftResponse(); swiftResponse.setId(swift.getUuid()); @@ -908,16 +924,21 @@ public class ApiResponseHelper implements ResponseGenerator { } - - @Override public StoragePoolResponse createStoragePoolResponse(StoragePool pool) { List viewPools = ApiDBUtils.newStoragePoolView(pool); List listPools = ViewResponseHelper.createStoragePoolResponse(viewPools.toArray(new StoragePoolJoinVO[viewPools.size()])); assert listPools != null && listPools.size() == 1 : "There should be one storage pool returned"; return listPools.get(0); + } - + @Override + public StoragePoolForMigrationResponse createStoragePoolForMigrationResponse(StoragePool pool) { + List viewPools = ApiDBUtils.newStoragePoolView(pool); + List listPools = ViewResponseHelper.createStoragePoolForMigrationResponse( + viewPools.toArray(new StoragePoolJoinVO[viewPools.size()])); + assert listPools != null && listPools.size() == 1 : "There should be one storage pool returned"; + return listPools.get(0); } @Override http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/src/com/cloud/api/query/ViewResponseHelper.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/api/query/ViewResponseHelper.java b/server/src/com/cloud/api/query/ViewResponseHelper.java index dc2727e..827ae7b 100644 --- a/server/src/com/cloud/api/query/ViewResponseHelper.java +++ b/server/src/com/cloud/api/query/ViewResponseHelper.java @@ -30,6 +30,7 @@ import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainRouterResponse; import org.apache.cloudstack.api.response.EventResponse; import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.HostForMigrationResponse; import org.apache.cloudstack.api.response.InstanceGroupResponse; import org.apache.cloudstack.api.response.ProjectAccountResponse; import org.apache.cloudstack.api.response.ProjectInvitationResponse; @@ -38,6 +39,7 @@ import org.apache.cloudstack.api.response.ResourceTagResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.cloudstack.api.response.StoragePoolResponse; +import org.apache.cloudstack.api.response.StoragePoolForMigrationResponse; import org.apache.cloudstack.api.response.UserResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.VolumeResponse; @@ -230,6 +232,24 @@ public class ViewResponseHelper { return new ArrayList(vrDataList.values()); } + public static List createHostForMigrationResponse(EnumSet details, + HostJoinVO... hosts) { + Hashtable vrDataList = new Hashtable(); + // Initialise the vrdatalist with the input data + for (HostJoinVO vr : hosts) { + HostForMigrationResponse vrData = vrDataList.get(vr.getId()); + if ( vrData == null ) { + // first time encountering this vm + vrData = ApiDBUtils.newHostForMigrationResponse(vr, details); + } else { + // update tags + vrData = ApiDBUtils.fillHostForMigrationDetails(vrData, vr); + } + vrDataList.put(vr.getId(), vrData); + } + return new ArrayList(vrDataList.values()); + } + public static List createVolumeResponse(VolumeJoinVO... volumes) { Hashtable vrDataList = new Hashtable(); for (VolumeJoinVO vr : volumes) { @@ -265,6 +285,23 @@ public class ViewResponseHelper { return new ArrayList(vrDataList.values()); } + public static List createStoragePoolForMigrationResponse(StoragePoolJoinVO... pools) { + Hashtable vrDataList = new Hashtable(); + // Initialise the vrdatalist with the input data + for (StoragePoolJoinVO vr : pools) { + StoragePoolForMigrationResponse vrData = vrDataList.get(vr.getId()); + if ( vrData == null ) { + // first time encountering this vm + vrData = ApiDBUtils.newStoragePoolForMigrationResponse(vr); + } else { + // update tags + vrData = ApiDBUtils.fillStoragePoolForMigrationDetails(vrData, vr); + } + vrDataList.put(vr.getId(), vrData); + } + return new ArrayList(vrDataList.values()); + } + public static List createAccountResponse(AccountJoinVO... accounts) { List respList = new ArrayList(); http://git-wip-us.apache.org/repos/asf/cloudstack/blob/21ce3bef/server/src/com/cloud/api/query/dao/HostJoinDao.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/api/query/dao/HostJoinDao.java b/server/src/com/cloud/api/query/dao/HostJoinDao.java index 1a21299..f526ca3 100644 --- a/server/src/com/cloud/api/query/dao/HostJoinDao.java +++ b/server/src/com/cloud/api/query/dao/HostJoinDao.java @@ -21,6 +21,7 @@ import java.util.List; import org.apache.cloudstack.api.ApiConstants.HostDetails; import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.HostForMigrationResponse; import com.cloud.api.query.vo.HostJoinVO; import com.cloud.host.Host; import com.cloud.utils.db.GenericDao; @@ -31,6 +32,10 @@ public interface HostJoinDao extends GenericDao { HostResponse setHostResponse(HostResponse response, HostJoinVO host); + HostForMigrationResponse newHostForMigrationResponse(HostJoinVO host, EnumSet details); + + HostForMigrationResponse setHostForMigrationResponse(HostForMigrationResponse response, HostJoinVO host); + List newHostView(Host group); List searchByIds(Long... ids);