Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 6CFCD200B92 for ; Tue, 13 Sep 2016 14:11:06 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id 6B99B160AC6; Tue, 13 Sep 2016 12:11:06 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 6E6FE160AD3 for ; Tue, 13 Sep 2016 14:11:04 +0200 (CEST) Received: (qmail 35729 invoked by uid 500); 13 Sep 2016 12:11:03 -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 35478 invoked by uid 99); 13 Sep 2016 12:11:03 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 13 Sep 2016 12:11:03 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 33D22E00C4; Tue, 13 Sep 2016 12:11:03 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: rajani@apache.org To: commits@cloudstack.apache.org Date: Tue, 13 Sep 2016 12:11:04 -0000 Message-Id: <56f2846653064a0895ec3a7a9de816da@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [3/4] git commit: updated refs/heads/master to f21477a archived-at: Tue, 13 Sep 2016 12:11:06 -0000 Adding support for cross-cluster storage migration for managed storage when using XenServer Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/b508fb86 Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/b508fb86 Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/b508fb86 Branch: refs/heads/master Commit: b508fb8692eac1675a4597c9dfaef463304aecba Parents: 1d9735c Author: Mike Tutkowski Authored: Sat Aug 20 17:58:30 2016 -0600 Committer: Mike Tutkowski Committed: Mon Sep 12 07:39:13 2016 -0600 ---------------------------------------------------------------------- api/src/com/cloud/storage/StoragePool.java | 2 + .../api/MigrateWithStorageReceiveCommand.java | 11 +- .../agent/test/BackupSnapshotCommandTest.java | 3 + .../api/agent/test/CheckNetworkAnswerTest.java | 3 + .../api/agent/test/SnapshotCommandTest.java | 3 + .../api/storage/PrimaryDataStoreDriver.java | 7 + .../com/cloud/vm/VirtualMachineManagerImpl.java | 88 ++- .../storage/datastore/db/StoragePoolVO.java | 1 + ...MigrateWithStorageReceiveCommandWrapper.java | 13 +- .../CitrixCreateStoragePoolCommandWrapper.java | 31 +- .../CitrixDeleteStoragePoolCommandWrapper.java | 30 +- .../motion/XenServerStorageMotionStrategy.java | 222 +++++- .../xenbase/XenServer610WrapperTest.java | 6 +- .../driver/SolidFirePrimaryDataStoreDriver.java | 190 ++++- .../com/cloud/server/ManagementServerImpl.java | 23 +- .../plugins/solidfire/TestAddRemoveHosts.py | 58 +- .../plugins/solidfire/TestSnapshots.py | 580 +++++++++++---- .../solidfire/TestVMMigrationWithStorage.py | 697 +++++++++++++++++++ .../plugins/solidfire/TestVMSnapshots.py | 74 +- .../plugins/solidfire/TestVolumes.py | 548 +++++---------- .../plugins/solidfire/util/sf_util.py | 217 ++++++ 21 files changed, 2083 insertions(+), 724 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/api/src/com/cloud/storage/StoragePool.java ---------------------------------------------------------------------- diff --git a/api/src/com/cloud/storage/StoragePool.java b/api/src/com/cloud/storage/StoragePool.java index 8e03c33..3a2d3bd 100644 --- a/api/src/com/cloud/storage/StoragePool.java +++ b/api/src/com/cloud/storage/StoragePool.java @@ -104,4 +104,6 @@ public interface StoragePool extends Identity, InternalIdentity { boolean isInMaintenance(); Hypervisor.HypervisorType getHypervisor(); + + boolean isManaged(); } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/core/src/com/cloud/agent/api/MigrateWithStorageReceiveCommand.java ---------------------------------------------------------------------- diff --git a/core/src/com/cloud/agent/api/MigrateWithStorageReceiveCommand.java b/core/src/com/cloud/agent/api/MigrateWithStorageReceiveCommand.java index 66aecdb..3d413fc 100644 --- a/core/src/com/cloud/agent/api/MigrateWithStorageReceiveCommand.java +++ b/core/src/com/cloud/agent/api/MigrateWithStorageReceiveCommand.java @@ -21,26 +21,25 @@ package com.cloud.agent.api; import java.util.List; -import com.cloud.agent.api.to.StorageFilerTO; import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.agent.api.to.VolumeTO; import com.cloud.utils.Pair; public class MigrateWithStorageReceiveCommand extends Command { VirtualMachineTO vm; - List> volumeToFiler; + List> volumeToStorageUuid; - public MigrateWithStorageReceiveCommand(VirtualMachineTO vm, List> volumeToFiler) { + public MigrateWithStorageReceiveCommand(VirtualMachineTO vm, List> volumeToStorageUuid) { this.vm = vm; - this.volumeToFiler = volumeToFiler; + this.volumeToStorageUuid = volumeToStorageUuid; } public VirtualMachineTO getVirtualMachine() { return vm; } - public List> getVolumeToFiler() { - return volumeToFiler; + public List> getVolumeToStorageUuid() { + return volumeToStorageUuid; } @Override http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/core/test/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java ---------------------------------------------------------------------- diff --git a/core/test/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java b/core/test/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java index bdcda38..edc90aa 100644 --- a/core/test/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java +++ b/core/test/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java @@ -135,6 +135,9 @@ public class BackupSnapshotCommandTest { }; @Override + public boolean isManaged() { return false; } + + @Override public Long getPodId() { return 0L; } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/core/test/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java ---------------------------------------------------------------------- diff --git a/core/test/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java b/core/test/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java index d6f0bfc..4d49c99 100644 --- a/core/test/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java +++ b/core/test/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java @@ -174,6 +174,9 @@ public class CheckNetworkAnswerTest { }; @Override + public boolean isManaged() { return false; } + + @Override public Long getPodId() { return 0L; } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/core/test/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java ---------------------------------------------------------------------- diff --git a/core/test/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java b/core/test/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java index 629669a..576419a 100644 --- a/core/test/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java +++ b/core/test/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java @@ -136,6 +136,9 @@ public class SnapshotCommandTest { }; @Override + public boolean isManaged() { return false; } + + @Override public Long getPodId() { return 0L; } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java ---------------------------------------------------------------------- diff --git a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java index 6dcdf4f..8749589 100644 --- a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java +++ b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java @@ -25,6 +25,13 @@ import com.cloud.host.Host; import com.cloud.storage.StoragePool; public interface PrimaryDataStoreDriver extends DataStoreDriver { + String BASIC_CREATE = "basicCreate"; + String BASIC_DELETE = "basicDelete"; + String BASIC_DELETE_FAILURE = "basicDeleteFailure"; + String BASIC_GRANT_ACCESS = "basicGrantAccess"; + String BASIC_REVOKE_ACCESS = "basicRevokeAccess"; + String BASIC_IQN = "basicIqn"; + ChapInfo getChapInfo(DataObject dataObject); boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore); http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java ---------------------------------------------------------------------- diff --git a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java index 9523b92..a4c9889 100644 --- a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -2045,62 +2045,74 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac private Map getPoolListForVolumesForMigration(final VirtualMachineProfile profile, final Host host, final Map volumeToPool) { final List allVolumes = _volsDao.findUsableVolumesForInstance(profile.getId()); - final Map volumeToPoolObjectMap = new HashMap (); + final Map volumeToPoolObjectMap = new HashMap<>(); + for (final VolumeVO volume : allVolumes) { final Long poolId = volumeToPool.get(Long.valueOf(volume.getId())); - final StoragePoolVO pool = _storagePoolDao.findById(poolId); + final StoragePoolVO destPool = _storagePoolDao.findById(poolId); final StoragePoolVO currentPool = _storagePoolDao.findById(volume.getPoolId()); final DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); - if (pool != null) { + + if (destPool != 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()) { + if (_poolHostDao.findByPoolHost(destPool.getId(), host.getId()) == null || destPool.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 + + throw new CloudRuntimeException("Cannot migrate volume " + volume + " to storage pool " + destPool + " 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, the volume doesn't need to be migrated. + } else if (destPool.getId() == currentPool.getId()) { + // If the pool to migrate to is the same as current pool, the volume doesn't need to be migrated. } else { - volumeToPoolObjectMap.put(volume, pool); + volumeToPoolObjectMap.put(volume, destPool); } } else { - // Find a suitable pool for the volume. Call the storage pool allocator to find the list of pools. - final DiskProfile diskProfile = new DiskProfile(volume, diskOffering, profile.getHypervisorType()); - final DataCenterDeployment plan = new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), host.getClusterId(), host.getId(), null, null); - final ExcludeList avoid = new ExcludeList(); - boolean currentPoolAvailable = false; - - final List poolList = new ArrayList(); - for (final StoragePoolAllocator allocator : _storagePoolAllocators) { - final List poolListFromAllocator = allocator.allocateToPool(diskProfile, profile, plan, avoid, StoragePoolAllocator.RETURN_UPTO_ALL); - if (poolListFromAllocator != null && !poolListFromAllocator.isEmpty()) { - poolList.addAll(poolListFromAllocator); - } - } + if (currentPool.isManaged()) { + volumeToPoolObjectMap.put(volume, currentPool); + } else { + // Find a suitable pool for the volume. Call the storage pool allocator to find the list of pools. - 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. - final Iterator iter = poolList.iterator(); - while (iter.hasNext()) { - if (currentPool.getId() == iter.next().getId()) { - currentPoolAvailable = true; - break; + final DiskProfile diskProfile = new DiskProfile(volume, diskOffering, profile.getHypervisorType()); + final DataCenterDeployment plan = new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), host.getClusterId(), host.getId(), null, null); + + final List poolList = new ArrayList<>(); + final ExcludeList avoid = new ExcludeList(); + + for (final StoragePoolAllocator allocator : _storagePoolAllocators) { + final List poolListFromAllocator = allocator.allocateToPool(diskProfile, profile, plan, avoid, StoragePoolAllocator.RETURN_UPTO_ALL); + + if (poolListFromAllocator != null && !poolListFromAllocator.isEmpty()) { + poolList.addAll(poolListFromAllocator); } } - if (!currentPoolAvailable) { - volumeToPoolObjectMap.put(volume, _storagePoolDao.findByUuid(poolList.get(0).getUuid())); - } - } + boolean currentPoolAvailable = false; + 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 (!currentPoolAvailable && !volumeToPoolObjectMap.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); + final Iterator iter = poolList.iterator(); + + while (iter.hasNext()) { + if (currentPool.getId() == iter.next().getId()) { + currentPoolAvailable = true; + + break; + } + } + + if (!currentPoolAvailable) { + volumeToPoolObjectMap.put(volume, _storagePoolDao.findByUuid(poolList.get(0).getUuid())); + } + } + + if (!currentPoolAvailable && !volumeToPoolObjectMap.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); + } } } } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/engine/schema/src/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java ---------------------------------------------------------------------- diff --git a/engine/schema/src/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java b/engine/schema/src/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java index ad2ad41..24fcaa0 100644 --- a/engine/schema/src/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java +++ b/engine/schema/src/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java @@ -231,6 +231,7 @@ public class StoragePoolVO implements StoragePool { this.managed = managed; } + @Override public boolean isManaged() { return managed; } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageReceiveCommandWrapper.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageReceiveCommandWrapper.java b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageReceiveCommandWrapper.java index 046a425..fdcb7b5 100644 --- a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageReceiveCommandWrapper.java +++ b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageReceiveCommandWrapper.java @@ -31,7 +31,6 @@ import com.cloud.agent.api.Answer; import com.cloud.agent.api.MigrateWithStorageReceiveAnswer; import com.cloud.agent.api.MigrateWithStorageReceiveCommand; import com.cloud.agent.api.to.NicTO; -import com.cloud.agent.api.to.StorageFilerTO; import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.agent.api.to.VolumeTO; import com.cloud.hypervisor.xenserver.resource.XenServer610Resource; @@ -56,7 +55,7 @@ public final class XenServer610MigrateWithStorageReceiveCommandWrapper extends C public Answer execute(final MigrateWithStorageReceiveCommand command, final XenServer610Resource xenServer610Resource) { final Connection connection = xenServer610Resource.getConnection(); final VirtualMachineTO vmSpec = command.getVirtualMachine(); - final List> volumeToFiler = command.getVolumeToFiler(); + final List> volumeToStorageUuid = command.getVolumeToStorageUuid(); try { // In a cluster management server setup, the migrate with storage receive and send @@ -69,10 +68,12 @@ public final class XenServer610MigrateWithStorageReceiveCommandWrapper extends C // storage send command execution. Gson gson = new Gson(); // Get a map of all the SRs to which the vdis will be migrated. - final List> volumeToSr = new ArrayList>(); - for (final Pair entry : volumeToFiler) { - final StorageFilerTO storageFiler = entry.second(); - final SR sr = xenServer610Resource.getStorageRepository(connection, storageFiler.getUuid()); + final List> volumeToSr = new ArrayList<>(); + + for (final Pair entry : volumeToStorageUuid) { + final String storageUuid = entry.second(); + final SR sr = xenServer610Resource.getStorageRepository(connection, storageUuid); + volumeToSr.add(new Pair(entry.first(), sr)); } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCreateStoragePoolCommandWrapper.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCreateStoragePoolCommandWrapper.java b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCreateStoragePoolCommandWrapper.java index bed417f..7b2a599 100644 --- a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCreateStoragePoolCommandWrapper.java +++ b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCreateStoragePoolCommandWrapper.java @@ -19,6 +19,8 @@ package com.cloud.hypervisor.xenserver.resource.wrapper.xenbase; +import java.util.Map; + import org.apache.log4j.Logger; import com.cloud.agent.api.Answer; @@ -39,20 +41,35 @@ public final class CitrixCreateStoragePoolCommandWrapper extends CommandWrapper< public Answer execute(final CreateStoragePoolCommand command, final CitrixResourceBase citrixResourceBase) { final Connection conn = citrixResourceBase.getConnection(); final StorageFilerTO pool = command.getPool(); + try { - if (pool.getType() == StoragePoolType.NetworkFilesystem) { - citrixResourceBase.getNfsSR(conn, Long.toString(pool.getId()), pool.getUuid(), pool.getHost(), pool.getPath(), pool.toString()); - } else if (pool.getType() == StoragePoolType.IscsiLUN) { - citrixResourceBase.getIscsiSR(conn, pool.getUuid(), pool.getHost(), pool.getPath(), null, null, false); - } else if (pool.getType() == StoragePoolType.PreSetup) { - } else { - return new Answer(command, false, "The pool type: " + pool.getType().name() + " is not supported."); + if (command.getCreateDatastore()) { + Map details = command.getDetails(); + + String srNameLabel = details.get(CreateStoragePoolCommand.DATASTORE_NAME); + String storageHost = details.get(CreateStoragePoolCommand.STORAGE_HOST); + String iqn = details.get(CreateStoragePoolCommand.IQN); + + citrixResourceBase.getIscsiSR(conn, srNameLabel, storageHost, iqn, null, null, false); } + else { + if (pool.getType() == StoragePoolType.NetworkFilesystem) { + citrixResourceBase.getNfsSR(conn, Long.toString(pool.getId()), pool.getUuid(), pool.getHost(), pool.getPath(), pool.toString()); + } else if (pool.getType() == StoragePoolType.IscsiLUN) { + citrixResourceBase.getIscsiSR(conn, pool.getUuid(), pool.getHost(), pool.getPath(), null, null, false); + } else if (pool.getType() == StoragePoolType.PreSetup) { + } else { + return new Answer(command, false, "The pool type: " + pool.getType().name() + " is not supported."); + } + } + return new Answer(command, true, "success"); } catch (final Exception e) { final String msg = "Catch Exception " + e.getClass().getName() + ", create StoragePool failed due to " + e.toString() + " on host:" + citrixResourceBase.getHost().getUuid() + " pool: " + pool.getHost() + pool.getPath(); + s_logger.warn(msg, e); + return new Answer(command, false, msg); } } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixDeleteStoragePoolCommandWrapper.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixDeleteStoragePoolCommandWrapper.java b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixDeleteStoragePoolCommandWrapper.java index a9ae680..c93dd90 100644 --- a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixDeleteStoragePoolCommandWrapper.java +++ b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixDeleteStoragePoolCommandWrapper.java @@ -19,6 +19,8 @@ package com.cloud.hypervisor.xenserver.resource.wrapper.xenbase; +import java.util.Map; + import org.apache.log4j.Logger; import com.cloud.agent.api.Answer; @@ -32,22 +34,40 @@ import com.xensource.xenapi.SR; @ResourceWrapper(handles = DeleteStoragePoolCommand.class) public final class CitrixDeleteStoragePoolCommandWrapper extends CommandWrapper { - private static final Logger s_logger = Logger.getLogger(CitrixDeleteStoragePoolCommandWrapper.class); @Override public Answer execute(final DeleteStoragePoolCommand command, final CitrixResourceBase citrixResourceBase) { final Connection conn = citrixResourceBase.getConnection(); final StorageFilerTO poolTO = command.getPool(); + try { - final SR sr = citrixResourceBase.getStorageRepository(conn, poolTO.getUuid()); + final SR sr; + + // getRemoveDatastore being true indicates we are using managed storage and need to pull the SR name out of a Map + // instead of pulling it out using getUuid of the StorageFilerTO instance. + if (command.getRemoveDatastore()) { + Map details = command.getDetails(); + + String srNameLabel = details.get(DeleteStoragePoolCommand.DATASTORE_NAME); + + sr = citrixResourceBase.getStorageRepository(conn, srNameLabel); + } + else { + sr = citrixResourceBase.getStorageRepository(conn, poolTO.getUuid()); + } + citrixResourceBase.removeSR(conn, sr); + final Answer answer = new Answer(command, true, "success"); + return answer; } catch (final Exception e) { - final String msg = "DeleteStoragePoolCommand XenAPIException:" + e.getMessage() + " host:" + citrixResourceBase.getHost().getUuid() + " pool: " + poolTO.getHost() - + poolTO.getPath(); - s_logger.warn(msg, e); + final String msg = "DeleteStoragePoolCommand XenAPIException:" + e.getMessage() + " host:" + citrixResourceBase.getHost().getUuid() + + " pool: " + poolTO.getHost() + poolTO.getPath(); + + s_logger.error(msg, e); + return new Answer(command, false, msg); } } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/plugins/hypervisors/xenserver/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/xenserver/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java b/plugins/hypervisors/xenserver/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java index 7de96b0..2409b6e 100644 --- a/plugins/hypervisors/xenserver/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java +++ b/plugins/hypervisors/xenserver/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.storage.motion; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -28,6 +29,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy; 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.PrimaryDataStoreDriver; import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; @@ -39,6 +41,8 @@ import org.springframework.stereotype.Component; import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CreateStoragePoolCommand; +import com.cloud.agent.api.DeleteStoragePoolCommand; import com.cloud.agent.api.MigrateWithStorageAnswer; import com.cloud.agent.api.MigrateWithStorageCommand; import com.cloud.agent.api.MigrateWithStorageCompleteAnswer; @@ -56,9 +60,12 @@ import com.cloud.host.Host; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.storage.StoragePool; import com.cloud.storage.VolumeVO; +import com.cloud.storage.VolumeDetailVO; import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.dao.VolumeDetailsDao; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.Pair; +import com.cloud.utils.StringUtils; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.dao.VMInstanceDao; @@ -74,6 +81,8 @@ public class XenServerStorageMotionStrategy implements DataMotionStrategy { @Inject PrimaryDataStoreDao storagePoolDao; @Inject + private VolumeDetailsDao volumeDetailsDao; + @Inject VMInstanceDao instanceDao; @Override @@ -120,25 +129,175 @@ public class XenServerStorageMotionStrategy implements DataMotionStrategy { callback.complete(result); } + private String getBasicIqn(long volumeId) { + VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volumeId, PrimaryDataStoreDriver.BASIC_IQN); + + return volumeDetail.getValue(); + } + + /** + * Tell the underlying storage plug-in to create a new volume, put it in the VAG of the destination cluster, and + * send a command to the destination cluster to create an SR and to attach to the SR from all hosts in the cluster. + */ + private String handleManagedVolumePreMigration(VolumeInfo volumeInfo, StoragePool storagePool, Host destHost) { + final PrimaryDataStoreDriver pdsd = (PrimaryDataStoreDriver)volumeInfo.getDataStore().getDriver(); + + VolumeDetailVO volumeDetailVo = new VolumeDetailVO(volumeInfo.getId(), PrimaryDataStoreDriver.BASIC_CREATE, Boolean.TRUE.toString(), false); + + volumeDetailsDao.persist(volumeDetailVo); + + pdsd.createAsync(volumeInfo.getDataStore(), volumeInfo, null); + + volumeDetailVo = new VolumeDetailVO(volumeInfo.getId(), PrimaryDataStoreDriver.BASIC_GRANT_ACCESS, Boolean.TRUE.toString(), false); + + volumeDetailsDao.persist(volumeDetailVo); + + pdsd.grantAccess(volumeInfo, destHost, volumeInfo.getDataStore()); + + final Map details = new HashMap<>(); + + final String iqn = getBasicIqn(volumeInfo.getId()); + + details.put(CreateStoragePoolCommand.DATASTORE_NAME, iqn); + + details.put(CreateStoragePoolCommand.IQN, iqn); + + details.put(CreateStoragePoolCommand.STORAGE_HOST, storagePool.getHostAddress()); + + details.put(CreateStoragePoolCommand.STORAGE_PORT, String.valueOf(storagePool.getPort())); + + final CreateStoragePoolCommand cmd = new CreateStoragePoolCommand(true, storagePool); + + cmd.setDetails(details); + cmd.setCreateDatastore(true); + + final Answer answer = agentMgr.easySend(destHost.getId(), cmd); + + if (answer == null || !answer.getResult()) { + String errMsg = "Error interacting with host (related to CreateStoragePoolCommand)" + + (StringUtils.isNotBlank(answer.getDetails()) ? ": " + answer.getDetails() : ""); + + s_logger.error(errMsg); + + throw new CloudRuntimeException(errMsg); + } + + return iqn; + } + + private void handleManagedVolumePostMigration(VolumeInfo volumeInfo, Host srcHost, VolumeObjectTO volumeTO) { + final Map details = new HashMap<>(); + + details.put(DeleteStoragePoolCommand.DATASTORE_NAME, volumeInfo.get_iScsiName()); + + final DeleteStoragePoolCommand cmd = new DeleteStoragePoolCommand(); + + cmd.setDetails(details); + cmd.setRemoveDatastore(true); + + final Answer answer = agentMgr.easySend(srcHost.getId(), cmd); + + if (answer == null || !answer.getResult()) { + String errMsg = "Error interacting with host (related to DeleteStoragePoolCommand)" + + (StringUtils.isNotBlank(answer.getDetails()) ? ": " + answer.getDetails() : ""); + + s_logger.error(errMsg); + + throw new CloudRuntimeException(errMsg); + } + + final PrimaryDataStoreDriver pdsd = (PrimaryDataStoreDriver)volumeInfo.getDataStore().getDriver(); + + pdsd.revokeAccess(volumeInfo, srcHost, volumeInfo.getDataStore()); + + VolumeDetailVO volumeDetailVo = new VolumeDetailVO(volumeInfo.getId(), PrimaryDataStoreDriver.BASIC_DELETE, Boolean.TRUE.toString(), false); + + volumeDetailsDao.persist(volumeDetailVo); + + pdsd.deleteAsync(volumeInfo.getDataStore(), volumeInfo, null); + + VolumeVO volumeVO = volDao.findById(volumeInfo.getId()); + + volumeVO.setPath(volumeTO.getPath()); + + volDao.update(volumeVO.getId(), volumeVO); + } + + private void handleManagedVolumesAfterFailedMigration(Map volumeToPool, Host destHost) { + for (Map.Entry entry : volumeToPool.entrySet()) { + VolumeInfo volumeInfo = entry.getKey(); + StoragePool storagePool = storagePoolDao.findById(volumeInfo.getPoolId()); + + if (storagePool.isManaged()) { + final Map details = new HashMap<>(); + + details.put(DeleteStoragePoolCommand.DATASTORE_NAME, getBasicIqn(volumeInfo.getId())); + + final DeleteStoragePoolCommand cmd = new DeleteStoragePoolCommand(); + + cmd.setDetails(details); + cmd.setRemoveDatastore(true); + + final Answer answer = agentMgr.easySend(destHost.getId(), cmd); + + if (answer == null || !answer.getResult()) { + String errMsg = "Error interacting with host (related to handleManagedVolumesAfterFailedMigration)" + + (StringUtils.isNotBlank(answer.getDetails()) ? ": " + answer.getDetails() : ""); + + s_logger.error(errMsg); + + // no need to throw an exception here as the calling code is responsible for doing so + // regardless of the success or lack thereof concerning this method + return; + } + + final PrimaryDataStoreDriver pdsd = (PrimaryDataStoreDriver)volumeInfo.getDataStore().getDriver(); + + VolumeDetailVO volumeDetailVo = new VolumeDetailVO(volumeInfo.getId(), PrimaryDataStoreDriver.BASIC_REVOKE_ACCESS, Boolean.TRUE.toString(), false); + + volumeDetailsDao.persist(volumeDetailVo); + + pdsd.revokeAccess(volumeInfo, destHost, volumeInfo.getDataStore()); + + volumeDetailVo = new VolumeDetailVO(volumeInfo.getId(), PrimaryDataStoreDriver.BASIC_DELETE_FAILURE, Boolean.TRUE.toString(), false); + + volumeDetailsDao.persist(volumeDetailVo); + + pdsd.deleteAsync(volumeInfo.getDataStore(), volumeInfo, null); + } + } + } + private Answer migrateVmWithVolumesAcrossCluster(VMInstanceVO vm, VirtualMachineTO to, Host srcHost, Host destHost, Map volumeToPool) throws AgentUnavailableException { + // Initiate migration of a virtual machine with its volumes. - // Initiate migration of a virtual machine with it's volumes. try { - List> volumeToFilerto = new ArrayList>(); + List> volumeToStorageUuid = new ArrayList<>(); + 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.add(new Pair(volumeTo, filerTo)); + VolumeInfo volumeInfo = entry.getKey(); + StoragePool storagePool = storagePoolDao.findById(volumeInfo.getPoolId()); + VolumeTO volumeTo = new VolumeTO(volumeInfo, storagePool); + + if (storagePool.isManaged()) { + String iqn = handleManagedVolumePreMigration(volumeInfo, storagePool, destHost); + + volumeToStorageUuid.add(new Pair<>(volumeTo, iqn)); + } + else { + volumeToStorageUuid.add(new Pair<>(volumeTo, ((StoragePool)entry.getValue()).getPath())); + } } // 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); + + MigrateWithStorageReceiveCommand receiveCmd = new MigrateWithStorageReceiveCommand(to, volumeToStorageUuid); 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); @@ -150,16 +309,22 @@ public class XenServerStorageMotionStrategy implements DataMotionStrategy { MigrateWithStorageSendCommand sendCmd = new MigrateWithStorageSendCommand(to, receiveAnswer.getVolumeToSr(), receiveAnswer.getNicToNetwork(), receiveAnswer.getToken()); MigrateWithStorageSendAnswer sendAnswer = (MigrateWithStorageSendAnswer)agentMgr.send(srcHost.getId(), sendCmd); + if (sendAnswer == null) { + handleManagedVolumesAfterFailedMigration(volumeToPool, destHost); + 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()) { + handleManagedVolumesAfterFailedMigration(volumeToPool, destHost); + 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); } 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); @@ -168,7 +333,7 @@ public class XenServerStorageMotionStrategy implements DataMotionStrategy { throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost); } else { // Update the volume details after migration. - updateVolumePathsAfterMigration(volumeToPool, answer.getVolumeTos()); + updateVolumePathsAfterMigration(volumeToPool, answer.getVolumeTos(), srcHost); } return answer; @@ -181,7 +346,7 @@ public class XenServerStorageMotionStrategy implements DataMotionStrategy { 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. + // Initiate migration of a virtual machine with its volumes. try { List> volumeToFilerto = new ArrayList>(); for (Map.Entry entry : volumeToPool.entrySet()) { @@ -201,7 +366,7 @@ public class XenServerStorageMotionStrategy implements DataMotionStrategy { 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()); + updateVolumePathsAfterMigration(volumeToPool, answer.getVolumeTos(), srcHost); } return answer; @@ -211,28 +376,39 @@ public class XenServerStorageMotionStrategy implements DataMotionStrategy { } } - private void updateVolumePathsAfterMigration(Map volumeToPool, List volumeTos) { + private void updateVolumePathsAfterMigration(Map volumeToPool, List volumeTos, Host srcHost) { for (Map.Entry entry : volumeToPool.entrySet()) { + VolumeInfo volumeInfo = entry.getKey(); + StoragePool storagePool = (StoragePool)entry.getValue(); + boolean updated = false; - VolumeInfo volume = entry.getKey(); - StoragePool pool = (StoragePool)entry.getValue(); + for (VolumeObjectTO 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); + if (volumeInfo.getId() == volumeTo.getId()) { + if (storagePool.isManaged()) { + handleManagedVolumePostMigration(volumeInfo, srcHost, volumeTo); + } + else { + VolumeVO volumeVO = volDao.findById(volumeInfo.getId()); + Long oldPoolId = volumeVO.getPoolId(); + + volumeVO.setPath(volumeTo.getPath()); + volumeVO.setFolder(storagePool.getPath()); + volumeVO.setPodId(storagePool.getPodId()); + volumeVO.setPoolId(storagePool.getId()); + volumeVO.setLastPoolId(oldPoolId); + + volDao.update(volumeInfo.getId(), volumeVO); + } + updated = true; + break; } } if (!updated) { - s_logger.error("Volume path wasn't updated for volume " + volume + " after it was migrated."); + s_logger.error("The volume path wasn't updated for volume '" + volumeInfo + "' after it was migrated."); } } } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/plugins/hypervisors/xenserver/test/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer610WrapperTest.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/xenserver/test/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer610WrapperTest.java b/plugins/hypervisors/xenserver/test/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer610WrapperTest.java index f294af1..8fa68f5 100644 --- a/plugins/hypervisors/xenserver/test/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer610WrapperTest.java +++ b/plugins/hypervisors/xenserver/test/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer610WrapperTest.java @@ -204,9 +204,9 @@ public class XenServer610WrapperTest { final StorageFilerTO storage1 = Mockito.mock(StorageFilerTO.class); final StorageFilerTO storage2 = Mockito.mock(StorageFilerTO.class); - final List> volumeToFiler = new ArrayList>(); - volumeToFiler.add(new Pair(vol1, storage1)); - volumeToFiler.add(new Pair(vol2, storage2)); + final List> volumeToFiler = new ArrayList<>(); + volumeToFiler.add(new Pair<>(vol1, storage1.getPath())); + volumeToFiler.add(new Pair<>(vol2, storage2.getPath())); final NicTO nicTO1 = Mockito.mock(NicTO.class); final NicTO nicTO2 = Mockito.mock(NicTO.class); http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java ---------------------------------------------------------------------- diff --git a/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java b/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java index af969e1..ccc1bdc 100644 --- a/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java +++ b/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java @@ -94,6 +94,8 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver { private static final long MIN_IOPS_FOR_SNAPSHOT_VOLUME = 100L; private static final long MAX_IOPS_FOR_SNAPSHOT_VOLUME = 20000L; + private static final String BASIC_SF_ID = "basicSfId"; + @Inject private AccountDao accountDao; @Inject private AccountDetailsDao accountDetailsDao; @Inject private ClusterDao clusterDao; @@ -153,7 +155,7 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver { Preconditions.checkArgument(host != null, "'host' should not be 'null'"); Preconditions.checkArgument(dataStore != null, "'dataStore' should not be 'null'"); - long sfVolumeId = getSolidFireVolumeId(dataObject); + long sfVolumeId = getSolidFireVolumeId(dataObject, true); long clusterId = host.getClusterId(); long storagePoolId = dataStore.getId(); @@ -215,7 +217,7 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver { return; } - long sfVolumeId = getSolidFireVolumeId(dataObject); + long sfVolumeId = getSolidFireVolumeId(dataObject, false); long clusterId = host.getClusterId(); long storagePoolId = dataStore.getId(); @@ -252,9 +254,31 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver { } } - private long getSolidFireVolumeId(DataObject dataObject) { + private long getSolidFireVolumeId(DataObject dataObject, boolean grantAccess) { if (dataObject.getType() == DataObjectType.VOLUME) { - return Long.parseLong(((VolumeInfo)dataObject).getFolder()); + final VolumeInfo volumeInfo = (VolumeInfo)dataObject; + final long volumeId = volumeInfo.getId(); + + if (grantAccess && isBasicGrantAccess(volumeId)) { + volumeDetailsDao.removeDetail(volumeInfo.getId(), BASIC_GRANT_ACCESS); + + final Long sfVolumeId = getBasicSfVolumeId(volumeId); + + Preconditions.checkNotNull(sfVolumeId, "'sfVolumeId' should not be 'null' (basic grant access)."); + + return sfVolumeId; + } + else if (!grantAccess && isBasicRevokeAccess(volumeId)) { + volumeDetailsDao.removeDetail(volumeInfo.getId(), BASIC_REVOKE_ACCESS); + + final Long sfVolumeId = getBasicSfVolumeId(volumeId); + + Preconditions.checkNotNull(sfVolumeId, "'sfVolumeId' should not be 'null' (basic revoke access)."); + + return sfVolumeId; + } + + return Long.parseLong(volumeInfo.getFolder()); } if (dataObject.getType() == DataObjectType.SNAPSHOT) { @@ -271,7 +295,7 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver { return getVolumeIdFrom_iScsiPath(((TemplateInfo)dataObject).getInstallPath()); } - throw new CloudRuntimeException("Invalid DataObjectType (" + dataObject.getType() + ") passed to getSolidFireVolumeId(DataObject)"); + throw new CloudRuntimeException("Invalid DataObjectType (" + dataObject.getType() + ") passed to getSolidFireVolumeId(DataObject, boolean)"); } private long getVolumeIdFrom_iScsiPath(String iScsiPath) { @@ -313,10 +337,11 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver { private SolidFireUtil.SolidFireVolume createSolidFireVolume(SolidFireUtil.SolidFireConnection sfConnection, DataObject dataObject, long sfAccountId) { long storagePoolId = dataObject.getDataStore().getId(); - Long minIops = null; - Long maxIops = null; - Long volumeSize = dataObject.getSize(); - String volumeName = null; + + final Long minIops; + final Long maxIops; + final Long volumeSize; + final String volumeName; final Map mapAttributes; @@ -647,6 +672,58 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver { snapshotDetailsDao.remove(snapshotDetails.getId()); } + private Long getBasicSfVolumeId(long volumeId) { + VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volumeId, BASIC_SF_ID); + + if (volumeDetail != null && volumeDetail.getValue() != null) { + return new Long(volumeDetail.getValue()); + } + + return null; + } + + private String getBasicIqn(long volumeId) { + VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volumeId, BASIC_IQN); + + if (volumeDetail != null && volumeDetail.getValue() != null) { + return volumeDetail.getValue(); + } + + return null; + } + + // If isBasicCreate returns true, this means the calling code simply wants us to create a SolidFire volume with specified + // characteristics. We do not update the cloud.volumes table with this info. + private boolean isBasicCreate(long volumeId) { + return getBooleanValueFromVolumeDetails(volumeId, BASIC_CREATE); + } + + private boolean isBasicDelete(long volumeId) { + return getBooleanValueFromVolumeDetails(volumeId, BASIC_DELETE); + } + + private boolean isBasicDeleteFailure(long volumeId) { + return getBooleanValueFromVolumeDetails(volumeId, BASIC_DELETE_FAILURE); + } + + private boolean isBasicGrantAccess(long volumeId) { + return getBooleanValueFromVolumeDetails(volumeId, BASIC_GRANT_ACCESS); + } + + private boolean isBasicRevokeAccess(long volumeId) { + return getBooleanValueFromVolumeDetails(volumeId, BASIC_REVOKE_ACCESS); + } + + private boolean getBooleanValueFromVolumeDetails(long volumeId, String name) { + VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volumeId, name); + + if (volumeDetail != null && volumeDetail.getValue() != null) { + return Boolean.parseBoolean(volumeDetail.getValue()); + } + + return false; + } + private long getCsIdForCloning(long volumeId, String cloneOf) { VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volumeId, cloneOf); @@ -788,11 +865,13 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver { LOGGER.error(errMsg); } - CommandResult result = new CommandResult(); + if (callback != null) { + CommandResult result = new CommandResult(); - result.setResult(errMsg); + result.setResult(errMsg); - callback.complete(result); + callback.complete(result); + } } @Override @@ -950,19 +1029,43 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver { snapshotDetailsDao.persist(snapshotDetail); } + private void addBasicCreateInfoToVolumeDetails(long volumeId, SolidFireUtil.SolidFireVolume sfVolume) { + VolumeDetailVO volumeDetailVo = new VolumeDetailVO(volumeId, BASIC_SF_ID, String.valueOf(sfVolume.getId()), false); + + volumeDetailsDao.persist(volumeDetailVo); + + volumeDetailVo = new VolumeDetailVO(volumeId, BASIC_IQN, sfVolume.getIqn(), false); + + volumeDetailsDao.persist(volumeDetailVo); + } + private String createVolume(VolumeInfo volumeInfo, long storagePoolId) { - verifySufficientBytesForStoragePool(volumeInfo, storagePoolId); - verifySufficientIopsForStoragePool(volumeInfo.getMinIops() != null ? volumeInfo.getMinIops() : getDefaultMinIops(storagePoolId), storagePoolId); + boolean isBasicCreate = isBasicCreate(volumeInfo.getId()); + + if (!isBasicCreate) { + verifySufficientBytesForStoragePool(volumeInfo, storagePoolId); + verifySufficientIopsForStoragePool(volumeInfo.getMinIops() != null ? volumeInfo.getMinIops() : getDefaultMinIops(storagePoolId), storagePoolId); + } SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, storagePoolDetailsDao); long sfAccountId = getCreateSolidFireAccountId(sfConnection, volumeInfo.getAccountId(), storagePoolId); + SolidFireUtil.SolidFireVolume sfVolume; + + if (isBasicCreate) { + sfVolume = createSolidFireVolume(sfConnection, volumeInfo, sfAccountId); + + volumeDetailsDao.removeDetail(volumeInfo.getId(), BASIC_CREATE); + + addBasicCreateInfoToVolumeDetails(volumeInfo.getId(), sfVolume); + + return sfVolume.getIqn(); + } + long csSnapshotId = getCsIdForCloning(volumeInfo.getId(), "cloneOfSnapshot"); long csTemplateId = getCsIdForCloning(volumeInfo.getId(), "cloneOfTemplate"); - SolidFireUtil.SolidFireVolume sfVolume; - if (csSnapshotId > 0) { // We are supposed to create a clone of the underlying volume or snapshot that supports the CloudStack snapshot. sfVolume = createClone(sfConnection, csSnapshotId, volumeInfo, sfAccountId, storagePoolId, DataObjectType.SNAPSHOT); @@ -1083,23 +1186,66 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver { return iqn; } + private void performBasicDelete(SolidFireUtil.SolidFireConnection sfConnection, long volumeId) { + Long sfVolumeId = getBasicSfVolumeId(volumeId); + + Preconditions.checkNotNull(sfVolumeId, "'sfVolumeId' should not be 'null'."); + + String iqn = getBasicIqn(volumeId); + + Preconditions.checkNotNull(iqn, "'iqn' should not be 'null'."); + + VolumeVO volumeVO = volumeDao.findById(volumeId); + + SolidFireUtil.deleteSolidFireVolume(sfConnection, Long.parseLong(volumeVO.getFolder())); + + volumeVO.setFolder(String.valueOf(sfVolumeId)); + volumeVO.set_iScsiName(iqn); + + volumeDao.update(volumeId, volumeVO); + + volumeDetailsDao.removeDetail(volumeId, BASIC_SF_ID); + volumeDetailsDao.removeDetail(volumeId, BASIC_IQN); + volumeDetailsDao.removeDetail(volumeId, BASIC_DELETE); + } + + private void performBasicDeleteFailure(SolidFireUtil.SolidFireConnection sfConnection, long volumeId) { + Long sfVolumeId = getBasicSfVolumeId(volumeId); + + Preconditions.checkNotNull(sfVolumeId, "'sfVolumeId' should not be 'null'."); + + SolidFireUtil.deleteSolidFireVolume(sfConnection, sfVolumeId); + + volumeDetailsDao.removeDetail(volumeId, BASIC_SF_ID); + volumeDetailsDao.removeDetail(volumeId, BASIC_IQN); + volumeDetailsDao.removeDetail(volumeId, BASIC_DELETE_FAILURE); + } + private void deleteVolume(VolumeInfo volumeInfo, long storagePoolId) { try { long volumeId = volumeInfo.getId(); SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, storagePoolDetailsDao); - deleteSolidFireVolume(sfConnection, volumeInfo); + if (isBasicDelete(volumeId)) { + performBasicDelete(sfConnection, volumeId); + } + else if (isBasicDeleteFailure(volumeId)) { + performBasicDeleteFailure(sfConnection, volumeId); + } + else { + deleteSolidFireVolume(sfConnection, volumeInfo); - volumeDetailsDao.removeDetails(volumeId); + volumeDetailsDao.removeDetails(volumeId); - StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId); + StoragePoolVO storagePool = storagePoolDao.findById(storagePoolId); - long usedBytes = getUsedBytes(storagePool, volumeId); + long usedBytes = getUsedBytes(storagePool, volumeId); - storagePool.setUsedBytes(usedBytes < 0 ? 0 : usedBytes); + storagePool.setUsedBytes(usedBytes < 0 ? 0 : usedBytes); - storagePoolDao.update(storagePoolId, storagePool); + storagePoolDao.update(storagePoolId, storagePool); + } } catch (Exception ex) { LOGGER.debug(SolidFireUtil.LOG_PREFIX + "Failed to delete SolidFire volume. CloudStack volume ID: " + volumeInfo.getId(), ex); http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/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 60b44d7..82f8030 100644 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -1205,12 +1205,15 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe srcHost.getHypervisorType(), srcHost.getHypervisorVersion()); allHosts = allHostsPair.first(); allHosts.remove(srcHost); + for (final VolumeVO volume : volumes) { - final Long volClusterId = _poolDao.findById(volume.getPoolId()).getClusterId(); - // only check for volume which are not in zone wide primary store, as only those may require storage motion - if (volClusterId != null) { - for (final Iterator iterator = allHosts.iterator(); iterator.hasNext();) { - final Host host = iterator.next(); + final StoragePool storagePool = _poolDao.findById(volume.getPoolId()); + final Long volClusterId = storagePool.getClusterId(); + + for (final Iterator iterator = allHosts.iterator(); iterator.hasNext();) { + final Host host = iterator.next(); + + if (volClusterId != null) { if (!host.getClusterId().equals(volClusterId) || usesLocal) { if (hasSuitablePoolsForVolume(volume, host, vmProfile)) { requiresStorageMotion.put(host, true); @@ -1219,8 +1222,16 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe } } } + else { + if (storagePool.isManaged()) { + if (srcHost.getClusterId() != host.getClusterId()) { + requiresStorageMotion.put(host, true); + } + } + } } } + plan = new DataCenterDeployment(srcHost.getDataCenterId(), null, null, null, null, null); } else { final Long cluster = srcHost.getClusterId(); @@ -1249,7 +1260,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe } for (final HostAllocator allocator : hostAllocators) { - if (canMigrateWithStorage) { + 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); http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b508fb86/test/integration/plugins/solidfire/TestAddRemoveHosts.py ---------------------------------------------------------------------- diff --git a/test/integration/plugins/solidfire/TestAddRemoveHosts.py b/test/integration/plugins/solidfire/TestAddRemoveHosts.py index 518d022..a13c61a 100644 --- a/test/integration/plugins/solidfire/TestAddRemoveHosts.py +++ b/test/integration/plugins/solidfire/TestAddRemoveHosts.py @@ -21,6 +21,8 @@ import SignedAPICall import time import XenAPI +from util import sf_util + # All tests inherit from cloudstackTestCase from marvin.cloudstackTestCase import cloudstackTestCase @@ -37,6 +39,15 @@ from marvin.lib.utils import cleanup_resources from solidfire import solidfire_element_api as sf_api +# Prerequisites: +# Only one zone +# Only one pod +# Only one cluster (two hosts with another added/removed during the tests) +# +# Running the tests: +# Set a breakpoint on each test after the first one. When the breakpoint is hit, reset the third +# host to a snapshot state and re-start it. Once it's up and running, run the test code. + class TestData: account = "account" @@ -238,7 +249,7 @@ class TestAddRemoveHosts(cloudstackTestCase): try: cleanup_resources(cls.apiClient, cls._cleanup) - cls._purge_solidfire_volumes() + sf_util.purge_solidfire_volumes(cls.sf_client) except Exception as e: logging.debug("Exception in tearDownClass(cls): %s" % e) @@ -286,7 +297,7 @@ class TestAddRemoveHosts(cloudstackTestCase): root_volume = self._get_root_volume(self.virtual_machine) - sf_iscsi_name = self._get_iqn(root_volume) + sf_iscsi_name = sf_util.get_iqn(self.cs_api, root_volume, self) self._perform_add_remove_host(primary_storage.id, sf_iscsi_name) @@ -342,7 +353,7 @@ class TestAddRemoveHosts(cloudstackTestCase): root_volume = self._get_root_volume(self.virtual_machine) - sf_iscsi_name = self._get_iqn(root_volume) + sf_iscsi_name = sf_util.get_iqn(self.cs_api, root_volume, self) primarystorage2 = self.testdata[TestData.primaryStorage2] @@ -596,19 +607,6 @@ class TestAddRemoveHosts(cloudstackTestCase): self.assert_(False, "Unable to locate the ROOT volume of the VM with the following ID: " + str(vm.id)) - def _get_iqn(self, volume): - # Get volume IQN - sf_iscsi_name_request = {'volumeid': volume.id} - # put this commented line back once PR 1403 is in - # sf_iscsi_name_result = self.cs_api.getVolumeiScsiName(sf_iscsi_name_request) - sf_iscsi_name_result = self.cs_api.getSolidFireVolumeIscsiName(sf_iscsi_name_request) - # sf_iscsi_name = sf_iscsi_name_result['apivolumeiscsiname']['volumeiScsiName'] - sf_iscsi_name = sf_iscsi_name_result['apisolidfirevolumeiscsiname']['solidFireVolumeIscsiName'] - - self._check_iscsi_name(sf_iscsi_name) - - return sf_iscsi_name - def _get_iqn_2(self, primary_storage): sql_query = "Select path From storage_pool Where uuid = '" + str(primary_storage.id) + "'" @@ -617,13 +615,6 @@ class TestAddRemoveHosts(cloudstackTestCase): return sql_result[0][0] - def _check_iscsi_name(self, sf_iscsi_name): - self.assertEqual( - sf_iscsi_name[0], - "/", - "The iSCSI name needs to start with a forward slash." - ) - def _get_host_iscsi_iqns(self): hosts = self.xen_session.xenapi.host.get_all() @@ -687,24 +678,3 @@ class TestAddRemoveHosts(cloudstackTestCase): for host_iscsi_iqn in host_iscsi_iqns: # an error should occur if host_iscsi_iqn is not in sf_vag_initiators sf_vag_initiators.index(host_iscsi_iqn) - - def _check_list(self, in_list, expected_size_of_list, err_msg): - self.assertEqual( - isinstance(in_list, list), - True, - "'in_list' is not a list." - ) - - self.assertEqual( - len(in_list), - expected_size_of_list, - err_msg - ) - - @classmethod - def _purge_solidfire_volumes(cls): - deleted_volumes = cls.sf_client.list_deleted_volumes() - - for deleted_volume in deleted_volumes: - cls.sf_client.purge_deleted_volume(deleted_volume['volumeID']) -