From 27b9ef484785bc00536acc61e5727a790a818e96 Mon Sep 17 00:00:00 2001 From: Slavka Peleva Date: Thu, 9 May 2024 09:55:04 +0300 Subject: [PATCH 1/5] Support of snapshot copy to different StorPool primary storage between zones Added support to copy a snapshot to another StorPool primary storage in different zones. --- .../com/cloud/storage/VolumeApiService.java | 4 +- .../apache/cloudstack/api/ApiConstants.java | 1 + .../user/snapshot/CopySnapshotCmd.java | 49 +- .../user/snapshot/CreateSnapshotCmd.java | 18 +- .../snapshot/CreateSnapshotPolicyCmd.java | 32 +- .../api/response/SnapshotPolicyResponse.java | 18 +- .../command/test/CreateSnapshotCmdTest.java | 4 +- .../api/storage/DataStoreCapabilities.java | 11 +- .../api/storage/SnapshotService.java | 2 + .../api/storage/SnapshotStrategy.java | 7 +- .../cloud/vm/VmWorkTakeVolumeSnapshot.java | 9 +- .../vm/VmWorkTakeVolumeSnapshotTest.java | 3 +- .../orchestration/VolumeOrchestrator.java | 169 +++---- .../resourcedetail/ResourceDetailsDao.java | 7 + .../ResourceDetailsDaoBase.java | 6 + .../datastore/db/SnapshotDataStoreDao.java | 14 +- .../db/SnapshotDataStoreDaoImpl.java | 53 +- .../snapshot/CephSnapshotStrategy.java | 4 + .../snapshot/DefaultSnapshotStrategy.java | 3 + .../snapshot/ScaleIOSnapshotStrategy.java | 4 + .../storage/snapshot/SnapshotServiceImpl.java | 78 ++- .../StorageSystemSnapshotStrategy.java | 5 +- .../StorPoolModifyStoragePoolAnswer.java | 12 +- .../StorPoolModifyStorageCommandWrapper.java | 30 +- .../StorPoolAbandonObjectsCollector.java | 132 ++++- .../StorPoolPrimaryDataStoreDriver.java | 252 +++++----- .../provider/StorPoolHostListener.java | 1 + .../datastore/util/StorPoolHelper.java | 61 ++- .../storage/datastore/util/StorPoolUtil.java | 45 ++ .../motion/StorPoolDataMotionStrategy.java | 113 +++-- .../StorPoolConfigurationManager.java | 6 +- .../snapshot/StorPoolSnapshotStrategy.java | 274 +++++++---- .../main/java/com/cloud/api/ApiDBUtils.java | 177 +++---- .../java/com/cloud/api/ApiResponseHelper.java | 454 +++++++++--------- .../com/cloud/api/query/QueryManagerImpl.java | 310 ++++++------ .../cloud/api/query/dao/SnapshotJoinDao.java | 11 +- .../api/query/dao/SnapshotJoinDaoImpl.java | 51 +- .../cloud/storage/CreateSnapshotPayload.java | 9 + .../cloud/storage/VolumeApiServiceImpl.java | 262 +++++----- .../storage/snapshot/SnapshotManagerImpl.java | 275 +++++++++-- .../cloud/template/TemplateManagerImpl.java | 216 +++++---- .../cloudstack/snapshot/SnapshotHelper.java | 81 ++-- .../storage/VolumeApiServiceImplTest.java | 6 +- .../snapshot/SnapshotManagerImplTest.java | 89 ++-- .../storage/snapshot/SnapshotManagerTest.java | 113 +++-- .../template/TemplateManagerImplTest.java | 8 + .../snapshot/SnapshotHelperTest.java | 59 ++- .../storpool/TestSnapshotCopyOnPrimary.py | 255 ++++++++++ test/integration/plugins/storpool/sp_util.py | 56 +++ tools/marvin/marvin/lib/base.py | 37 +- ui/src/views/storage/SnapshotZones.vue | 4 +- 51 files changed, 2502 insertions(+), 1398 deletions(-) create mode 100644 test/integration/plugins/storpool/TestSnapshotCopyOnPrimary.py diff --git a/api/src/main/java/com/cloud/storage/VolumeApiService.java b/api/src/main/java/com/cloud/storage/VolumeApiService.java index 6f4c7aa09e26..576bb74abb4c 100644 --- a/api/src/main/java/com/cloud/storage/VolumeApiService.java +++ b/api/src/main/java/com/cloud/storage/VolumeApiService.java @@ -112,10 +112,10 @@ public interface VolumeApiService { Volume detachVolumeFromVM(DetachVolumeCmd cmd); - Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup, Map tags, List zoneIds) + Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup, Map tags, List zoneIds, List poolIds) throws ResourceAllocationException; - Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, List zoneIds) throws ResourceAllocationException; + Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, List zoneIds, List poolIds) throws ResourceAllocationException; Volume updateVolume(long volumeId, String path, String state, Long storageId, Boolean displayVolume, Boolean deleteProtection, diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index bb16b0ff90de..77381daf85f2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -1042,6 +1042,7 @@ public class ApiConstants { public static final String ZONE_ID_LIST = "zoneids"; public static final String DESTINATION_ZONE_ID_LIST = "destzoneids"; + public static final String STORAGE_ID_LIST = "storageids"; public static final String ADMIN = "admin"; public static final String CHECKSUM_PARAMETER_PREFIX_DESCRIPTION = "The parameter containing the checksum will be considered a MD5sum if it is not prefixed\n" + " and just a plain ascii/utf8 representation of a hexadecimal string. If it is required to\n" diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java index 07973fcbfca5..b66714a09f1e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java @@ -17,9 +17,13 @@ package org.apache.cloudstack.api.command.user.snapshot; -import java.util.ArrayList; -import java.util.List; - +import com.cloud.dc.DataCenter; +import com.cloud.event.EventTypes; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.exception.StorageUnavailableException; +import com.cloud.storage.Snapshot; +import com.cloud.user.Account; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; @@ -31,26 +35,23 @@ import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.user.UserCmd; import org.apache.cloudstack.api.response.SnapshotResponse; +import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.context.CallContext; import org.apache.commons.collections.CollectionUtils; - -import com.cloud.dc.DataCenter; -import com.cloud.event.EventTypes; -import com.cloud.exception.ResourceAllocationException; -import com.cloud.exception.ResourceUnavailableException; -import com.cloud.exception.StorageUnavailableException; -import com.cloud.storage.Snapshot; -import com.cloud.user.Account; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.ArrayList; +import java.util.List; + @APICommand(name = "copySnapshot", description = "Copies a snapshot from one zone to another.", responseObject = SnapshotResponse.class, responseView = ResponseObject.ResponseView.Restricted, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) public class CopySnapshotCmd extends BaseAsyncCmd implements UserCmd { public static final Logger logger = LogManager.getLogger(CopySnapshotCmd.class.getName()); + private Snapshot snapshot; ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// @@ -84,6 +85,15 @@ public class CopySnapshotCmd extends BaseAsyncCmd implements UserCmd { "Do not specify destzoneid and destzoneids together, however one of them is required.") protected List destZoneIds; + @Parameter(name = ApiConstants.STORAGE_ID_LIST, + type=CommandType.LIST, + collectionType = CommandType.UUID, + entityType = StoragePoolResponse.class, + required = false, + description = "A comma-separated list of IDs of the storage pools in other zones in which the snapshot will be made available. " + + "The snapshot will always be made available in the zone in which the volume is present.") + protected List storagePoolIds; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -106,7 +116,11 @@ public List getDestinationZoneIds() { destIds.add(destZoneId); return destIds; } - return null; + return new ArrayList<>(); + } + + public List getStoragePoolIds() { + return storagePoolIds; } @Override @@ -152,7 +166,7 @@ public long getEntityOwnerId() { @Override public void execute() throws ResourceUnavailableException { try { - if (destZoneId == null && CollectionUtils.isEmpty(destZoneIds)) + if (destZoneId == null && CollectionUtils.isEmpty(destZoneIds) && CollectionUtils.isEmpty(storagePoolIds)) throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Either destzoneid or destzoneids parameters have to be specified."); @@ -161,7 +175,7 @@ public void execute() throws ResourceUnavailableException { "Both destzoneid and destzoneids cannot be specified at the same time."); CallContext.current().setEventDetails(getEventDescription()); - Snapshot snapshot = _snapshotService.copySnapshot(this); + snapshot = _snapshotService.copySnapshot(this); if (snapshot != null) { SnapshotResponse response = _queryService.listSnapshot(this); @@ -177,6 +191,13 @@ public void execute() throws ResourceUnavailableException { logger.warn("Exception: ", ex); throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, ex.getMessage()); } + } + + public Snapshot getSnapshot() { + return snapshot; + } + public void setSnapshot(Snapshot snapshot) { + this.snapshot = snapshot; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java index 3289ac2fe106..078f613d8b74 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java @@ -32,6 +32,7 @@ import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.SnapshotPolicyResponse; import org.apache.cloudstack.api.response.SnapshotResponse; +import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.commons.collections.MapUtils; @@ -99,6 +100,15 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd { since = "4.19.0") protected List zoneIds; + @Parameter(name = ApiConstants.STORAGE_ID_LIST, + type=CommandType.LIST, + collectionType = CommandType.UUID, + entityType = StoragePoolResponse.class, + description = "A comma-separated list of IDs of the storage pools in other zones in which the snapshot will be made available. " + + "The snapshot will always be made available in the zone in which the volume is present.", + since = "4.20.0") + protected List storagePoolIds; + private String syncObjectType = BaseAsyncCmd.snapshotHostSyncObject; // /////////////////////////////////////////////////// @@ -161,6 +171,10 @@ public List getZoneIds() { return zoneIds; } + public List getStoragePoolIds() { + return storagePoolIds; + } + // /////////////////////////////////////////////////// // ///////////// API Implementation/////////////////// // /////////////////////////////////////////////////// @@ -209,7 +223,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public void create() throws ResourceAllocationException { - Snapshot snapshot = _volumeService.allocSnapshot(getVolumeId(), getPolicyId(), getSnapshotName(), getLocationType(), getZoneIds()); + Snapshot snapshot = _volumeService.allocSnapshot(getVolumeId(), getPolicyId(), getSnapshotName(), getLocationType(), getZoneIds(), getStoragePoolIds()); if (snapshot != null) { setEntityId(snapshot.getId()); setEntityUuid(snapshot.getUuid()); @@ -223,7 +237,7 @@ public void execute() { Snapshot snapshot; try { snapshot = - _volumeService.takeSnapshot(getVolumeId(), getPolicyId(), getEntityId(), _accountService.getAccount(getEntityOwnerId()), getQuiescevm(), getLocationType(), getAsyncBackup(), getTags(), getZoneIds()); + _volumeService.takeSnapshot(getVolumeId(), getPolicyId(), getEntityId(), _accountService.getAccount(getEntityOwnerId()), getQuiescevm(), getLocationType(), getAsyncBackup(), getTags(), getZoneIds(), getStoragePoolIds()); if (snapshot != null) { SnapshotResponse response = _responseGenerator.createSnapshotResponse(snapshot); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmd.java index e30b897db2ea..0afeed364de4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmd.java @@ -16,11 +16,12 @@ // under the License. package org.apache.cloudstack.api.command.user.snapshot; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.projects.Project; +import com.cloud.storage.Volume; +import com.cloud.storage.snapshot.SnapshotPolicy; +import com.cloud.user.Account; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; @@ -30,16 +31,15 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.SnapshotPolicyResponse; +import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.commons.collections.MapUtils; -import com.cloud.exception.InvalidParameterValueException; -import com.cloud.exception.PermissionDeniedException; -import com.cloud.projects.Project; -import com.cloud.storage.Volume; -import com.cloud.storage.snapshot.SnapshotPolicy; -import com.cloud.user.Account; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @APICommand(name = "createSnapshotPolicy", description = "Creates a snapshot policy for the account.", responseObject = SnapshotPolicyResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -83,6 +83,14 @@ public class CreateSnapshotPolicyCmd extends BaseCmd { "The snapshots will always be made available in the zone in which the volume is present.") protected List zoneIds; + @Parameter(name = ApiConstants.STORAGE_ID_LIST, + type=CommandType.LIST, + collectionType = CommandType.UUID, + entityType = StoragePoolResponse.class, + description = "A comma-separated list of IDs of the storage pools in other zones in which the snapshot will be made available. " + + "The snapshot will always be made available in the zone in which the volume is present.", + since = "4.20.0") + protected List storagePoolIds; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -119,6 +127,8 @@ public List getZoneIds() { return zoneIds; } + public List getStoragePoolIds() { return storagePoolIds; } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/response/SnapshotPolicyResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/SnapshotPolicyResponse.java index bfa1cca1ca08..1425dcd160c1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/SnapshotPolicyResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/SnapshotPolicyResponse.java @@ -16,17 +16,16 @@ // under the License. package org.apache.cloudstack.api.response; -import java.util.LinkedHashSet; -import java.util.Set; - +import com.cloud.serializer.Param; +import com.cloud.storage.snapshot.SnapshotPolicy; +import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponseWithTagInformation; import org.apache.cloudstack.api.EntityReference; -import com.cloud.serializer.Param; -import com.cloud.storage.snapshot.SnapshotPolicy; -import com.google.gson.annotations.SerializedName; +import java.util.LinkedHashSet; +import java.util.Set; @EntityReference(value = SnapshotPolicy.class) public class SnapshotPolicyResponse extends BaseResponseWithTagInformation { @@ -62,9 +61,14 @@ public class SnapshotPolicyResponse extends BaseResponseWithTagInformation { @Param(description = "The list of zones in which snapshot backup is scheduled", responseObject = ZoneResponse.class, since = "4.19.0") protected Set zones; + @SerializedName(ApiConstants.STORAGE) + @Param(description = "The list of pools in which snapshot backup is scheduled", responseObject = StoragePoolResponse.class, since = "4.20.0") + protected Set storagePools; + public SnapshotPolicyResponse() { tags = new LinkedHashSet(); zones = new LinkedHashSet<>(); + storagePools = new LinkedHashSet<>(); } public String getId() { @@ -130,4 +134,6 @@ public void setTags(Set tags) { public void setZones(Set zones) { this.zones = zones; } + + public void setStoragePools(Set pools) { this.storagePools = pools; } } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/test/CreateSnapshotCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/test/CreateSnapshotCmdTest.java index 34baebe52574..699919741941 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/test/CreateSnapshotCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/test/CreateSnapshotCmdTest.java @@ -93,7 +93,7 @@ public void testCreateSuccess() { Snapshot snapshot = Mockito.mock(Snapshot.class); try { Mockito.when(volumeApiService.takeSnapshot(nullable(Long.class), nullable(Long.class), isNull(), - nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), nullable(Map.class), nullable(List.class))).thenReturn(snapshot); + nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), nullable(Map.class), nullable(List.class), nullable(List.class))).thenReturn(snapshot); } catch (Exception e) { Assert.fail("Received exception when success expected " + e.getMessage()); @@ -126,7 +126,7 @@ public void testCreateFailure() { try { Mockito.when(volumeApiService.takeSnapshot(nullable(Long.class), nullable(Long.class), nullable(Long.class), - nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), any(), Mockito.anyList())).thenReturn(null); + nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), any(), Mockito.anyList(), Mockito.anyList())).thenReturn(null); } catch (Exception e) { Assert.fail("Received exception when success expected " + e.getMessage()); } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java index f537d8f52028..8016d4cdc42c 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java @@ -40,5 +40,14 @@ public enum DataStoreCapabilities { /** * indicates that this driver supports reverting a volume to a snapshot state */ - CAN_REVERT_VOLUME_TO_SNAPSHOT + CAN_REVERT_VOLUME_TO_SNAPSHOT, + /** + * indicates that the driver supports copying snapshot between zones on pools of the same type + */ + CAN_COPY_SNAPSHOT_BETWEEN_ZONES, + /** + * indicates that the storage does not need to delete the snapshot when creating a volume/template from it + * and the setting `snapshot.backup.to.secondary` is enabled + */ + KEEP_SNAPSHOT_ON_PRIMARY_AND_BACKUP } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotService.java index d2e085fe90cf..03263c5f186f 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotService.java @@ -42,4 +42,6 @@ public interface SnapshotService { AsyncCallFuture copySnapshot(SnapshotInfo snapshot, String copyUrl, DataStore dataStore) throws ResourceUnavailableException; AsyncCallFuture queryCopySnapshot(SnapshotInfo snapshot) throws ResourceUnavailableException; + + AsyncCallFuture copySnapshot(SnapshotInfo sourceSnapshot, SnapshotInfo destSnapshot, SnapshotStrategy strategy); } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotStrategy.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotStrategy.java index f3aa8f52c932..43f411f75533 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotStrategy.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotStrategy.java @@ -16,12 +16,14 @@ // under the License. package org.apache.cloudstack.engine.subsystem.api.storage; + import com.cloud.storage.Snapshot; +import org.apache.cloudstack.framework.async.AsyncCompletionCallback; public interface SnapshotStrategy { enum SnapshotOperation { - TAKE, BACKUP, DELETE, REVERT + TAKE, BACKUP, DELETE, REVERT, COPY } SnapshotInfo takeSnapshot(SnapshotInfo snapshot); @@ -35,4 +37,7 @@ enum SnapshotOperation { StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op); void postSnapshotCreation(SnapshotInfo snapshot); + + default void copySnapshot(DataObject snapshotSource, DataObject snapshotDest, AsyncCompletionCallback caller) { + } } diff --git a/engine/components-api/src/main/java/com/cloud/vm/VmWorkTakeVolumeSnapshot.java b/engine/components-api/src/main/java/com/cloud/vm/VmWorkTakeVolumeSnapshot.java index 8474052be201..88d25441e0ac 100644 --- a/engine/components-api/src/main/java/com/cloud/vm/VmWorkTakeVolumeSnapshot.java +++ b/engine/components-api/src/main/java/com/cloud/vm/VmWorkTakeVolumeSnapshot.java @@ -30,12 +30,12 @@ public class VmWorkTakeVolumeSnapshot extends VmWork { private boolean quiesceVm; private Snapshot.LocationType locationType; private boolean asyncBackup; - + private List poolIds; private List zoneIds; public VmWorkTakeVolumeSnapshot(long userId, long accountId, long vmId, String handlerName, Long volumeId, Long policyId, Long snapshotId, boolean quiesceVm, Snapshot.LocationType locationType, - boolean asyncBackup, List zoneIds) { + boolean asyncBackup, List zoneIds, List poolIds) { super(userId, accountId, vmId, handlerName); this.volumeId = volumeId; this.policyId = policyId; @@ -44,6 +44,7 @@ public VmWorkTakeVolumeSnapshot(long userId, long accountId, long vmId, String h this.locationType = locationType; this.asyncBackup = asyncBackup; this.zoneIds = zoneIds; + this.poolIds = poolIds; } public Long getVolumeId() { @@ -71,4 +72,8 @@ public boolean isAsyncBackup() { public List getZoneIds() { return zoneIds; } + + public List getPoolIds() { + return poolIds; + } } diff --git a/engine/components-api/src/test/java/com/cloud/vm/VmWorkTakeVolumeSnapshotTest.java b/engine/components-api/src/test/java/com/cloud/vm/VmWorkTakeVolumeSnapshotTest.java index feb7ee46aec5..f80ba9580d5d 100644 --- a/engine/components-api/src/test/java/com/cloud/vm/VmWorkTakeVolumeSnapshotTest.java +++ b/engine/components-api/src/test/java/com/cloud/vm/VmWorkTakeVolumeSnapshotTest.java @@ -26,8 +26,9 @@ public class VmWorkTakeVolumeSnapshotTest { @Test public void testVmWorkTakeVolumeSnapshotZoneIds() { List zoneIds = List.of(10L, 20L); + List poolIds = List.of(10L, 20L); VmWorkTakeVolumeSnapshot work = new VmWorkTakeVolumeSnapshot(1L, 1L, 1L, "handler", - 1L, 1L, 1L, false, null, false, zoneIds); + 1L, 1L, 1L, false, null, false, zoneIds, poolIds); Assert.assertNotNull(work.getZoneIds()); Assert.assertEquals(zoneIds.size(), work.getZoneIds().size()); Assert.assertEquals(zoneIds.get(0), work.getZoneIds().get(0)); diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java index 36e281459492..07c7e4dc1e07 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@ -18,81 +18,6 @@ */ package org.apache.cloudstack.engine.orchestration; -import static com.cloud.storage.resource.StorageProcessor.REQUEST_TEMPLATE_RELOAD; -import static com.cloud.utils.NumbersUtil.toHumanReadableSize; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; - -import javax.inject.Inject; -import javax.naming.ConfigurationException; - -import com.cloud.exception.ResourceAllocationException; -import com.cloud.storage.DiskOfferingVO; -import com.cloud.storage.VMTemplateVO; -import com.cloud.storage.dao.VMTemplateDao; -import com.cloud.user.AccountManager; -import org.apache.cloudstack.api.ApiCommandResourceType; -import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy; -import org.apache.cloudstack.api.command.admin.vm.MigrateVMCmd; -import org.apache.cloudstack.api.command.admin.volume.MigrateVolumeCmdByAdmin; -import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; -import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; -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.DataStoreDriver; -import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; -import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; -import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService; -import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; -import org.apache.cloudstack.engine.subsystem.api.storage.StorageStrategyFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; -import org.apache.cloudstack.engine.subsystem.api.storage.TemplateService; -import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; -import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService; -import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService.VolumeApiResult; -import org.apache.cloudstack.framework.async.AsyncCallFuture; -import org.apache.cloudstack.framework.config.ConfigDepot; -import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.cloudstack.framework.config.Configurable; -import org.apache.cloudstack.framework.jobs.AsyncJobManager; -import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; -import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO; -import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; -import org.apache.cloudstack.secret.PassphraseVO; -import org.apache.cloudstack.secret.dao.PassphraseDao; -import org.apache.cloudstack.snapshot.SnapshotHelper; -import org.apache.cloudstack.storage.command.CommandResult; -import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; -import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; -import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.collections.MapUtils; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; -import org.jetbrains.annotations.Nullable; - import com.cloud.agent.api.to.DataTO; import com.cloud.agent.api.to.DatadiskTO; import com.cloud.agent.api.to.DiskTO; @@ -111,6 +36,7 @@ import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientStorageCapacityException; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.StorageAccessException; import com.cloud.exception.StorageUnavailableException; import com.cloud.host.Host; @@ -121,6 +47,7 @@ import com.cloud.offering.ServiceOffering; import com.cloud.org.Cluster; import com.cloud.storage.DataStoreRole; +import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.ScopeType; import com.cloud.storage.Snapshot; import com.cloud.storage.Storage; @@ -130,6 +57,7 @@ import com.cloud.storage.StoragePoolHostVO; import com.cloud.storage.StorageUtil; import com.cloud.storage.VMTemplateStorageResourceAssoc; +import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; import com.cloud.storage.Volume.Type; import com.cloud.storage.VolumeApiService; @@ -138,12 +66,14 @@ import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.storage.dao.SnapshotDao; import com.cloud.storage.dao.StoragePoolHostDao; +import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplateDetailsDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.dao.VolumeDetailsDao; import com.cloud.template.TemplateManager; import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; +import com.cloud.user.AccountManager; import com.cloud.user.ResourceLimitService; import com.cloud.uservm.UserVm; import com.cloud.utils.Pair; @@ -177,6 +107,75 @@ import com.cloud.vm.dao.UserVmCloneSettingDao; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.UserVmDetailsDao; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy; +import org.apache.cloudstack.api.command.admin.vm.MigrateVMCmd; +import org.apache.cloudstack.api.command.admin.volume.MigrateVolumeCmdByAdmin; +import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; +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.DataStoreCapabilities; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService; +import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; +import org.apache.cloudstack.engine.subsystem.api.storage.StorageStrategyFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateService; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService.VolumeApiResult; +import org.apache.cloudstack.framework.async.AsyncCallFuture; +import org.apache.cloudstack.framework.config.ConfigDepot; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.framework.jobs.AsyncJobManager; +import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; +import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO; +import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; +import org.apache.cloudstack.secret.PassphraseVO; +import org.apache.cloudstack.secret.dao.PassphraseDao; +import org.apache.cloudstack.snapshot.SnapshotHelper; +import org.apache.cloudstack.storage.command.CommandResult; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +import static com.cloud.storage.resource.StorageProcessor.REQUEST_TEMPLATE_RELOAD; +import static com.cloud.utils.NumbersUtil.toHumanReadableSize; public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrationService, Configurable { @@ -561,21 +560,29 @@ public VolumeInfo createVolumeFromSnapshot(Volume volume, Snapshot snapshot, Use } VolumeInfo vol = volFactory.getVolume(volume.getId()); + long zoneId = volume.getDataCenterId(); DataStore store = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary); - DataStoreRole dataStoreRole = snapshotHelper.getDataStoreRole(snapshot); - SnapshotInfo snapInfo = snapshotFactory.getSnapshotWithRoleAndZone(snapshot.getId(), dataStoreRole, volume.getDataCenterId()); - - boolean kvmSnapshotOnlyInPrimaryStorage = snapshotHelper.isKvmSnapshotOnlyInPrimaryStorage(snapshot, dataStoreRole); + DataStoreRole dataStoreRole = snapshotHelper.getDataStoreRole(snapshot, zoneId); + SnapshotInfo snapInfo = snapshotFactory.getSnapshotWithRoleAndZone(snapshot.getId(), dataStoreRole, zoneId); + boolean kvmSnapshotOnlyInPrimaryStorage = snapshotHelper.isKvmSnapshotOnlyInPrimaryStorage(snapshot, dataStoreRole, volume.getDataCenterId()); + boolean skipCopyToSecondary = false; + Map capabilities = snapInfo.getDataStore().getDriver().getCapabilities(); + boolean keepOnPrimary = MapUtils.isNotEmpty(capabilities) && capabilities.containsKey(DataStoreCapabilities.KEEP_SNAPSHOT_ON_PRIMARY_AND_BACKUP.toString()); + if (kvmSnapshotOnlyInPrimaryStorage && keepOnPrimary) { + skipCopyToSecondary = true; + } try { - snapInfo = snapshotHelper.backupSnapshotToSecondaryStorageIfNotExists(snapInfo, dataStoreRole, snapshot, kvmSnapshotOnlyInPrimaryStorage); + if (!skipCopyToSecondary) { + snapInfo = snapshotHelper.backupSnapshotToSecondaryStorageIfNotExists(snapInfo, dataStoreRole, snapshot, kvmSnapshotOnlyInPrimaryStorage); + } } catch (CloudRuntimeException e) { snapshotHelper.expungeTemporarySnapshot(kvmSnapshotOnlyInPrimaryStorage, snapInfo); throw e; } // don't try to perform a sync if the DataStoreRole of the snapshot is equal to DataStoreRole.Primary - if (!DataStoreRole.Primary.equals(dataStoreRole) || kvmSnapshotOnlyInPrimaryStorage) { + if (!DataStoreRole.Primary.equals(dataStoreRole) || (kvmSnapshotOnlyInPrimaryStorage && !skipCopyToSecondary)) { try { // sync snapshot to region store if necessary DataStore snapStore = snapInfo.getDataStore(); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java index 8f3d264da981..c24d627bca91 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java @@ -32,6 +32,13 @@ public interface ResourceDetailsDao extends GenericDao */ R findDetail(long resourceId, String name); + /** + * Find details by key + * @param key + * @return + */ + List findDetails(String key); + /** * Find details by resourceId and key * @param resourceId diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java index 4205a7823e44..88f0feae508f 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java @@ -53,6 +53,12 @@ public R findDetail(long resourceId, String name) { return findOneBy(sc); } + public List findDetails(String key) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("name", key); + return listBy(sc); + } + public List findDetails(long resourceId, String key) { SearchCriteria sc = AllFieldsSearch.create(); sc.setParameters("resourceId", resourceId); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDao.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDao.java index 4cd29b465eeb..7acc081611bf 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDao.java @@ -16,16 +16,15 @@ // under the License. package org.apache.cloudstack.storage.datastore.db; -import java.util.Date; -import java.util.List; - -import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore; -import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; - import com.cloud.storage.DataStoreRole; import com.cloud.storage.VMTemplateStorageResourceAssoc; import com.cloud.utils.db.GenericDao; import com.cloud.utils.fsm.StateDao; +import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; + +import java.util.Date; +import java.util.List; public interface SnapshotDataStoreDao extends GenericDao, StateDao { @@ -48,8 +47,11 @@ public interface SnapshotDataStoreDao extends GenericDao listBySnapshot(long snapshotId, DataStoreRole role); + List listBySnapshotId(long snapshotId); + List listReadyBySnapshot(long snapshotId, DataStoreRole role); + List listReadyBySnapshotId(long snapshotId); SnapshotDataStoreVO findBySourceSnapshot(long snapshotId, DataStoreRole role); List listDestroyed(long storeId); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java index 5bf67eb38819..cf8cfa05ff13 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java @@ -16,24 +16,6 @@ // under the License. package org.apache.cloudstack.storage.datastore.db; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; -import javax.naming.ConfigurationException; - -import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore; -import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; -import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event; -import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.State; -import org.apache.commons.collections.CollectionUtils; -import org.springframework.stereotype.Component; - import com.cloud.hypervisor.Hypervisor; import com.cloud.storage.DataStoreRole; import com.cloud.storage.SnapshotVO; @@ -46,6 +28,22 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.db.UpdateBuilder; +import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.State; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; @Component public class SnapshotDataStoreDaoImpl extends GenericDaoBase implements SnapshotDataStoreDao { @@ -67,6 +65,7 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase dataStoreAndInstallPathSearch; private SearchBuilder storeAndSnapshotIdsSearch; private SearchBuilder storeSnapshotDownloadStatusSearch; + private SearchBuilder searchBySnapshotId; protected static final List HYPERVISORS_SUPPORTING_SNAPSHOTS_CHAINING = List.of(Hypervisor.HypervisorType.XenServer); @@ -151,6 +150,10 @@ public boolean configure(String name, Map params) throws Configu storeSnapshotDownloadStatusSearch.and("downloadState", storeSnapshotDownloadStatusSearch.entity().getDownloadState(), SearchCriteria.Op.IN); storeSnapshotDownloadStatusSearch.done(); + searchBySnapshotId = createSearchBuilder(); + searchBySnapshotId.and(SNAPSHOT_ID, searchBySnapshotId.entity().getSnapshotId(), SearchCriteria.Op.EQ); + searchBySnapshotId.done(); + return true; } @@ -307,6 +310,13 @@ public List listBySnapshot(long snapshotId, DataStoreRole r return listBy(sc); } + @Override + public List listBySnapshotId(long snapshotId) { + SearchCriteria sc = searchBySnapshotId.create(); + sc.setParameters(SNAPSHOT_ID, snapshotId); + return listBy(sc); + } + @Override public List listReadyBySnapshot(long snapshotId, DataStoreRole role) { SearchCriteria sc = createSearchCriteriaBySnapshotIdAndStoreRole(snapshotId, role); @@ -314,6 +324,13 @@ public List listReadyBySnapshot(long snapshotId, DataStoreR return listBy(sc); } + @Override + public List listReadyBySnapshotId(long snapshotId) { + SearchCriteria sc = searchBySnapshotId.create(); + sc.setParameters(STATE, State.Ready); + return listBy(sc); + } + @Override public SnapshotDataStoreVO findBySourceSnapshot(long snapshotId, DataStoreRole role) { SearchCriteria sc = createSearchCriteriaBySnapshotIdAndStoreRole(snapshotId, role); diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategy.java index 04cca2e8f923..d9d028d4d085 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategy.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategy.java @@ -23,6 +23,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; @@ -46,6 +47,9 @@ public class CephSnapshotStrategy extends StorageSystemSnapshotStrategy { @Override public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op) { + if (SnapshotOperation.COPY.equals(op)) { + return StrategyPriority.CANT_HANDLE; + } long volumeId = snapshot.getVolumeId(); VolumeVO volumeVO = volumeDao.findByIdIncludingRemoved(volumeId); boolean baseVolumeExists = volumeVO.getRemoved() == null; diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategy.java index afc8be1e5f97..867d1c8ce105 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategy.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategy.java @@ -571,6 +571,9 @@ public void doInTransactionWithoutResult(TransactionStatus status) { @Override public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op) { + if (SnapshotOperation.COPY.equals(op)) { + return StrategyPriority.CANT_HANDLE; + } if (SnapshotOperation.REVERT.equals(op)) { long volumeId = snapshot.getVolumeId(); VolumeVO volumeVO = volumeDao.findById(volumeId); diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/ScaleIOSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/ScaleIOSnapshotStrategy.java index 0d48cb944aee..c1e38fc92512 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/ScaleIOSnapshotStrategy.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/ScaleIOSnapshotStrategy.java @@ -22,6 +22,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; @@ -44,6 +45,9 @@ public class ScaleIOSnapshotStrategy extends StorageSystemSnapshotStrategy { @Override public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op) { + if (SnapshotOperation.COPY.equals(op)) { + return StrategyPriority.CANT_HANDLE; + } long volumeId = snapshot.getVolumeId(); VolumeVO volumeVO = volumeDao.findByIdIncludingRemoved(volumeId); boolean baseVolumeExists = volumeVO.getRemoved() == null; diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java index dafc40e0674d..8de9942409a8 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java @@ -17,11 +17,24 @@ package org.apache.cloudstack.storage.snapshot; -import java.util.List; -import java.util.concurrent.ExecutionException; - -import javax.inject.Inject; - +import com.cloud.agent.api.Answer; +import com.cloud.configuration.Config; +import com.cloud.dc.DataCenter; +import com.cloud.event.EventTypes; +import com.cloud.event.UsageEventUtils; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.storage.CreateSnapshotPayload; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.Snapshot; +import com.cloud.storage.SnapshotVO; +import com.cloud.storage.dao.SnapshotDao; +import com.cloud.storage.dao.SnapshotDetailsDao; +import com.cloud.storage.template.TemplateConstants; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.NoTransitionException; import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionService; @@ -38,6 +51,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotResult; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy; import org.apache.cloudstack.engine.subsystem.api.storage.StorageCacheManager; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; @@ -57,27 +71,12 @@ import org.apache.cloudstack.storage.heuristics.HeuristicRuleHelper; import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; import org.apache.cloudstack.storage.to.SnapshotObjectTO; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; -import com.cloud.agent.api.Answer; -import com.cloud.configuration.Config; -import com.cloud.dc.DataCenter; -import com.cloud.event.EventTypes; -import com.cloud.event.UsageEventUtils; -import com.cloud.exception.ResourceUnavailableException; -import com.cloud.storage.CreateSnapshotPayload; -import com.cloud.storage.DataStoreRole; -import com.cloud.storage.Snapshot; -import com.cloud.storage.SnapshotVO; -import com.cloud.storage.dao.SnapshotDao; -import com.cloud.storage.dao.SnapshotDetailsDao; -import com.cloud.storage.template.TemplateConstants; -import com.cloud.utils.db.Transaction; -import com.cloud.utils.db.TransactionCallbackNoReturn; -import com.cloud.utils.db.TransactionStatus; -import com.cloud.utils.exception.CloudRuntimeException; -import com.cloud.utils.fsm.NoTransitionException; +import javax.inject.Inject; +import java.util.List; +import java.util.concurrent.ExecutionException; public class SnapshotServiceImpl implements SnapshotService { protected Logger logger = LogManager.getLogger(getClass()); @@ -782,4 +781,35 @@ public AsyncCallFuture queryCopySnapshot(SnapshotInfo snapshot) ep.sendMessageAsync(cmd, caller); return future; } + + public AsyncCallFuture copySnapshot(SnapshotInfo sourceSnapshot, SnapshotInfo destSnapshot, SnapshotStrategy strategy) { + try { + if (destSnapshot.getStatus() == ObjectInDataStoreStateMachine.State.Allocated) { + destSnapshot.processEvent(Event.CreateOnlyRequested); + } else if (sourceSnapshot.getStatus() == ObjectInDataStoreStateMachine.State.Ready) { + destSnapshot.processEvent(Event.CopyRequested); + } else { + logger.info(String.format("Cannot copy snapshot to another storage in different zone. It's not in the right state %s", sourceSnapshot.getStatus())); + sourceSnapshot.processEvent(Event.OperationFailed); + throw new CloudRuntimeException(String.format("Cannot copy snapshot to another storage in different zone. It's not in the right state %s", sourceSnapshot.getStatus())); + } + } catch (Exception e) { + logger.debug("Failed to change snapshot state: " + e.toString()); + sourceSnapshot.processEvent(Event.OperationFailed); + throw new CloudRuntimeException(e); + } + + AsyncCallFuture future = new AsyncCallFuture(); + try { + CopySnapshotContext context = new CopySnapshotContext<>(null, sourceSnapshot, destSnapshot, future); + AsyncCallbackDispatcher caller = AsyncCallbackDispatcher.create(this); + caller.setCallback(caller.getTarget().copySnapshotZoneAsyncCallback(null, null)).setContext(context); + strategy.copySnapshot(sourceSnapshot, destSnapshot, caller); + } catch (Exception e) { + logger.debug("Failed to take snapshot: " + destSnapshot.getId(), e); + destSnapshot.processEvent(Event.OperationFailed); + throw new CloudRuntimeException("Failed to copy snapshot" + destSnapshot.getId()); + } + return future; + } } diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java index 9838e41f8f6c..8b90e58124a3 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java @@ -38,6 +38,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation; import org.apache.cloudstack.storage.command.SnapshotAndCopyAnswer; import org.apache.cloudstack.storage.command.SnapshotAndCopyCommand; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; @@ -912,7 +913,9 @@ private boolean usingBackendSnapshotFor(long snapshotId) { @Override public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op) { Snapshot.LocationType locationType = snapshot.getLocationType(); - + if (SnapshotOperation.COPY.equals(op)) { + return StrategyPriority.CANT_HANDLE; + } // If the snapshot exists on Secondary Storage, we can't delete it. if (SnapshotOperation.DELETE.equals(op)) { if (Snapshot.LocationType.SECONDARY.equals(locationType)) { diff --git a/plugins/storage/volume/storpool/src/main/java/com/cloud/agent/api/storage/StorPoolModifyStoragePoolAnswer.java b/plugins/storage/volume/storpool/src/main/java/com/cloud/agent/api/storage/StorPoolModifyStoragePoolAnswer.java index 437e786f0f61..80b87a49acbd 100644 --- a/plugins/storage/volume/storpool/src/main/java/com/cloud/agent/api/storage/StorPoolModifyStoragePoolAnswer.java +++ b/plugins/storage/volume/storpool/src/main/java/com/cloud/agent/api/storage/StorPoolModifyStoragePoolAnswer.java @@ -36,14 +36,16 @@ public class StorPoolModifyStoragePoolAnswer extends Answer{ private List datastoreClusterChildren = new ArrayList<>(); private String clusterId; private String clientNodeId; + private String clusterLocation; - public StorPoolModifyStoragePoolAnswer(StorPoolModifyStoragePoolCommand cmd, long capacityBytes, long availableBytes, Map tInfo, String clusterId, String clientNodeId) { + public StorPoolModifyStoragePoolAnswer(StorPoolModifyStoragePoolCommand cmd, long capacityBytes, long availableBytes, Map tInfo, String clusterId, String clientNodeId, String clusterLocation) { super(cmd); result = true; poolInfo = new StoragePoolInfo(null, cmd.getPool().getHost(), cmd.getPool().getPath(), cmd.getLocalPath(), cmd.getPool().getType(), capacityBytes, availableBytes); templateInfo = tInfo; this.clusterId = clusterId; this.clientNodeId = clientNodeId; + this.clusterLocation = clusterLocation; } public StorPoolModifyStoragePoolAnswer(String errMsg) { @@ -101,4 +103,12 @@ public String getClientNodeId() { public void setClientNodeId(String clientNodeId) { this.clientNodeId = clientNodeId; } + + public String getClusterLocation() { + return clusterLocation; + } + + public void setClusterLocation(String clusterLocation) { + this.clusterLocation = clusterLocation; + } } diff --git a/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolModifyStorageCommandWrapper.java b/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolModifyStorageCommandWrapper.java index a44ff5473ae5..8d6dcff8aed7 100644 --- a/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolModifyStorageCommandWrapper.java +++ b/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolModifyStorageCommandWrapper.java @@ -24,6 +24,7 @@ import java.util.Map.Entry; import java.util.Set; +import org.apache.commons.lang3.StringUtils; import com.cloud.agent.api.Answer; import com.cloud.agent.api.storage.StorPoolModifyStoragePoolAnswer; @@ -38,7 +39,9 @@ import com.cloud.storage.template.TemplateProp; import com.cloud.utils.script.OutputInterpreter; import com.cloud.utils.script.Script; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.JsonParser; @ResourceWrapper(handles = StorPoolModifyStoragePoolCommand.class) @@ -51,6 +54,7 @@ public Answer execute(final StorPoolModifyStoragePoolCommand command, final Libv logger.debug(String.format("Could not get StorPool cluster id for a command [%s]", command.getClass())); return new Answer(command, false, "spNotFound"); } + String clusterLocation = getStorPoolClusterLocation(clusterId); try { String result = attachOrDetachVolume("attach", "volume", command.getVolumeName()); if (result != null) { @@ -66,7 +70,7 @@ public Answer execute(final StorPoolModifyStoragePoolCommand command, final Libv } final Map tInfo = new HashMap<>(); - return new StorPoolModifyStoragePoolAnswer(command, storagepool.getCapacity(), storagepool.getAvailable(), tInfo, clusterId, storagepool.getStorageNodeId()); + return new StorPoolModifyStoragePoolAnswer(command, storagepool.getCapacity(), storagepool.getAvailable(), tInfo, clusterId, storagepool.getStorageNodeId(), clusterLocation); } catch (Exception e) { logger.debug(String.format("Could not modify storage due to %s", e.getMessage())); return new Answer(command, e); @@ -118,4 +122,28 @@ public String attachOrDetachVolume(String command, String type, String volumeUui } return res; } + + private String getStorPoolClusterLocation(String clusterId) { + Script sc = new Script("storpool", 300000, logger); + sc.add("-j"); + sc.add("location"); + sc.add("list"); + + OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser(); + + String res = sc.execute(parser); + if (res == null) { + JsonObject jsonObj = new JsonParser().parse(parser.getLines()).getAsJsonObject(); + if (jsonObj.getAsJsonObject("data") != null) { + JsonArray arr = jsonObj.getAsJsonObject("data").getAsJsonArray("locations"); + for (JsonElement jsonElement : arr) { + JsonObject obj = jsonElement.getAsJsonObject(); + if (StringUtils.contains(clusterId, obj.get("id").getAsString())) { + return obj.get("name").getAsString(); + } + } + } + } + return null; + } } diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/collector/StorPoolAbandonObjectsCollector.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/collector/StorPoolAbandonObjectsCollector.java index 6258767921d2..1310cde72222 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/collector/StorPoolAbandonObjectsCollector.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/collector/StorPoolAbandonObjectsCollector.java @@ -19,28 +19,9 @@ package org.apache.cloudstack.storage.collector; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import javax.inject.Inject; - -import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.cloudstack.framework.config.Configurable; -import org.apache.cloudstack.managed.context.ManagedContextRunnable; -import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; -import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; -//import org.apache.cloudstack.storage.datastore.util.StorPoolHelper; -import org.apache.cloudstack.storage.datastore.util.StorPoolUtil; -import org.apache.commons.collections.CollectionUtils; - +import com.cloud.dc.dao.ClusterDao; +import com.cloud.storage.dao.SnapshotDetailsDao; +import com.cloud.storage.dao.SnapshotDetailsVO; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.db.DB; @@ -50,14 +31,43 @@ import com.cloud.utils.db.TransactionStatus; import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.datastore.util.StorPoolHelper; +import org.apache.cloudstack.storage.datastore.util.StorPoolUtil; +import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse; +import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc; +import org.apache.cloudstack.storage.snapshot.StorPoolSnapshotStrategy; +import org.apache.commons.collections.CollectionUtils; + +import javax.inject.Inject; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; public class StorPoolAbandonObjectsCollector extends ManagerBase implements Configurable { @Inject private PrimaryDataStoreDao storagePoolDao; @Inject private StoragePoolDetailsDao storagePoolDetailsDao; + @Inject + private SnapshotDetailsDao snapshotDetailsDao; + @Inject + private ClusterDao clusterDao; private ScheduledExecutorService _volumeTagsUpdateExecutor; + private ScheduledExecutorService snapshotRecoveryCheckExecutor; private static final String ABANDON_LOGGER = "/var/log/cloudstack/management/storpool-abandoned-objects"; @@ -69,6 +79,9 @@ public class StorPoolAbandonObjectsCollector extends ManagerBase implements Conf "storpool.snapshot.tags.checkup", "86400", "Minimal interval (in seconds) to check and report if StorPool snapshot exists in CloudStack snapshots database", false); + static final ConfigKey snapshotRecoveryFromRemoteCheck = new ConfigKey("Advanced", Integer.class, + "storpool.snapshot.recovery.from.remote.check", "300", + "Minimal interval (in seconds) to check and recover StorPool snapshot from remote", false); @Override public String getConfigComponentName() { @@ -77,7 +90,7 @@ public String getConfigComponentName() { @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] { volumeCheckupTagsInterval, snapshotCheckupTagsInterval }; + return new ConfigKey[] { volumeCheckupTagsInterval, snapshotCheckupTagsInterval, snapshotRecoveryFromRemoteCheck }; } @Override @@ -93,6 +106,8 @@ private void init() { } _volumeTagsUpdateExecutor = Executors.newScheduledThreadPool(2, new NamedThreadFactory("StorPoolAbandonObjectsCollector")); + snapshotRecoveryCheckExecutor = Executors.newScheduledThreadPool(1, + new NamedThreadFactory("StorPoolSnapshotRecoveryCheck")); if (volumeCheckupTagsInterval.value() > 0) { _volumeTagsUpdateExecutor.scheduleAtFixedRate(new StorPoolVolumesTagsUpdate(), @@ -102,6 +117,10 @@ private void init() { _volumeTagsUpdateExecutor.scheduleAtFixedRate(new StorPoolSnapshotsTagsUpdate(), snapshotCheckupTagsInterval.value(), snapshotCheckupTagsInterval.value(), TimeUnit.SECONDS); } + if (snapshotRecoveryFromRemoteCheck.value() > 0) { + snapshotRecoveryCheckExecutor.scheduleAtFixedRate(new StorPoolSnapshotRecoveryCheck(), + snapshotRecoveryFromRemoteCheck.value(), snapshotRecoveryFromRemoteCheck.value(), TimeUnit.SECONDS); + } } class StorPoolVolumesTagsUpdate extends ManagedContextRunnable { @@ -322,4 +341,71 @@ private Map getStorPoolNamesAndCsTag(JsonArray arr) { } return map; } + + class StorPoolSnapshotRecoveryCheck extends ManagedContextRunnable { + + @Override + protected void runInContext() { + List spPools = storagePoolDao.findPoolsByProvider(StorPoolUtil.SP_PROVIDER_NAME); + if (CollectionUtils.isEmpty(spPools)) { + return; + } + List snapshotDetails = snapshotDetailsDao.findDetails(StorPoolUtil.SP_RECOVERED_SNAPSHOT); + if (CollectionUtils.isEmpty(snapshotDetails)) { + return; + } + Map onePoolforZone = new HashMap<>(); + for (StoragePoolVO storagePoolVO : spPools) { + onePoolforZone.put(storagePoolVO.getDataCenterId(), storagePoolVO); + } + List recoveredSnapshots = new ArrayList<>(); + for (StoragePoolVO storagePool : onePoolforZone.values()) { + try { + logger.debug(String.format("Checking StorPool recovered snapshots for zone [%s]", + storagePool.getDataCenterId())); + SpConnectionDesc conn = StorPoolUtil.getSpConnection(storagePool.getUuid(), + storagePool.getId(), storagePoolDetailsDao, storagePoolDao); + JsonArray arr = StorPoolUtil.snapshotsList(conn); + List snapshots = snapshotsForRcovery(arr); + if (snapshots.isEmpty()) { + continue; + } + for (SnapshotDetailsVO snapshot : snapshotDetails) { + String name = snapshot.getValue().split(";")[0]; + String location = snapshot.getValue().split(";")[1]; + if (name == null || location == null) { + StorPoolUtil.spLog("Could not find name or location for the snapshot %s", snapshot.getValue()); + continue; + } + if (snapshots.contains(name)) { + Long clusterId = StorPoolHelper.findClusterIdByGlobalId(StorPoolUtil.getSnapshotClusterId(name, conn), clusterDao); + conn = StorPoolSnapshotStrategy.getSpConnectionDesc(conn, clusterId); + SpApiResponse resp = StorPoolUtil.snapshotUnexport(name, location, conn); + if (resp.getError() == null) { + recoveredSnapshots.add(snapshot.getId()); + } else { + logger.debug(String.format("Could not recover StorPool snapshot %s", resp.getError())); + } + } + } + } catch (Exception e) { + logger.debug(String.format("Could not collect StorPool recovered snapshots %s", e.getMessage())); + } + } + for (Long recoveredSnapshot : recoveredSnapshots) { + snapshotDetailsDao.remove(recoveredSnapshot); + } + } + } + + private static List snapshotsForRcovery(JsonArray arr) { + List snapshots = new ArrayList<>(); + for (int i = 0; i < arr.size(); i++) { + boolean recoveringFromRemote = arr.get(i).getAsJsonObject().get("recoveringFromRemote").getAsBoolean(); + if (!recoveringFromRemote) { + snapshots.add(arr.get(i).getAsJsonObject().get("name").getAsString()); + } + } + return snapshots; + } } diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java index 631186636ca2..365b5ffd4beb 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java @@ -18,57 +18,6 @@ */ package org.apache.cloudstack.storage.datastore.driver; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; - -import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; -import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; -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.DataStoreManager; -import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; -import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; -import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; -import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; -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.VolumeInfo; -import org.apache.cloudstack.framework.async.AsyncCompletionCallback; -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO; -import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; -import org.apache.cloudstack.storage.RemoteHostEndPoint; -import org.apache.cloudstack.storage.command.CommandResult; -import org.apache.cloudstack.storage.command.CopyCmdAnswer; -import org.apache.cloudstack.storage.command.CreateObjectAnswer; -import org.apache.cloudstack.storage.command.StorageSubSystemCommand; -import org.apache.cloudstack.storage.datastore.api.StorPoolSnapshotDef; -import org.apache.cloudstack.storage.datastore.api.StorPoolVolumeDef; -import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; -import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO; -import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; -import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; -import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; -import org.apache.cloudstack.storage.datastore.util.StorPoolHelper; -import org.apache.cloudstack.storage.datastore.util.StorPoolUtil; -import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse; -import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc; -import org.apache.cloudstack.storage.snapshot.StorPoolConfigurationManager; -import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; -import org.apache.cloudstack.storage.to.SnapshotObjectTO; -import org.apache.cloudstack.storage.to.TemplateObjectTO; -import org.apache.cloudstack.storage.to.VolumeObjectTO; -import org.apache.cloudstack.storage.volume.VolumeObject; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.collections4.MapUtils; - import com.cloud.agent.api.Answer; import com.cloud.agent.api.storage.ResizeVolumeAnswer; import com.cloud.agent.api.storage.StorPoolBackupSnapshotCommand; @@ -122,9 +71,60 @@ import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.dao.VMInstanceDao; +import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; +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.DataStoreCapabilities; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; +import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; +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.VolumeInfo; +import org.apache.cloudstack.framework.async.AsyncCompletionCallback; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO; +import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; +import org.apache.cloudstack.storage.RemoteHostEndPoint; +import org.apache.cloudstack.storage.command.CommandResult; +import org.apache.cloudstack.storage.command.CopyCmdAnswer; +import org.apache.cloudstack.storage.command.CreateObjectAnswer; +import org.apache.cloudstack.storage.command.StorageSubSystemCommand; +import org.apache.cloudstack.storage.datastore.api.StorPoolSnapshotDef; +import org.apache.cloudstack.storage.datastore.api.StorPoolVolumeDef; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; +import org.apache.cloudstack.storage.datastore.util.StorPoolHelper; +import org.apache.cloudstack.storage.datastore.util.StorPoolUtil; +import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse; +import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc; +import org.apache.cloudstack.storage.snapshot.StorPoolConfigurationManager; +import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; +import org.apache.cloudstack.storage.to.SnapshotObjectTO; +import org.apache.cloudstack.storage.to.TemplateObjectTO; +import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.apache.cloudstack.storage.volume.VolumeObject; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.MapUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import javax.inject.Inject; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { protected Logger logger = LogManager.getLogger(getClass()); @@ -184,7 +184,10 @@ private SnapshotDataStoreVO getSnapshotImageStoreRef(long snapshotId, long zoneI @Override public Map getCapabilities() { - return null; + Map mapCapabilities = new HashMap<>(); + mapCapabilities.put(DataStoreCapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES.toString(), Boolean.TRUE.toString()); + mapCapabilities.put(DataStoreCapabilities.KEEP_SNAPSHOT_ON_PRIMARY_AND_BACKUP.toString(), Boolean.TRUE.toString()); + return mapCapabilities; } @Override @@ -514,6 +517,15 @@ public void deleteAsync(DataStore dataStore, DataObject data, AsyncCompletionCal } catch (Exception e) { err = String.format("Could not delete volume due to %s", e.getMessage()); } + } else if (data.getType() == DataObjectType.SNAPSHOT) { + SnapshotInfo snapshot = (SnapshotInfo) data; + SpConnectionDesc conn = StorPoolUtil.getSpConnection(snapshot.getDataStore().getUuid(), snapshot.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao); + String name = StorPoolStorageAdaptor.getVolumeNameFromPath(snapshot.getPath(), true); + SpApiResponse resp = StorPoolUtil.snapshotDelete(name, conn); + if (resp.getError() != null) { + err = String.format("Failed to clean-up Storpool snapshot %s. Error: %s", name, resp.getError()); + StorPoolUtil.spLog(err); + } } else { err = String.format("Invalid DataObjectType \"%s\" passed to deleteAsync", data.getType()); } @@ -600,7 +612,22 @@ private void logDataObject(final String pref, DataObject data) { @Override public boolean canCopy(DataObject srcData, DataObject dstData) { - return true; + DataObjectType srcType = srcData.getType(); + DataObjectType dstType = dstData.getType(); + if (srcType == DataObjectType.SNAPSHOT && dstType == DataObjectType.VOLUME) { + return true; + } else if (srcType == DataObjectType.SNAPSHOT && dstType == DataObjectType.SNAPSHOT) { + return true; + } else if (srcType == DataObjectType.VOLUME && dstType == DataObjectType.TEMPLATE) { + return true; + } else if (srcType == DataObjectType.TEMPLATE && dstType == DataObjectType.TEMPLATE) { + return true; + } else if (srcType == DataObjectType.TEMPLATE && dstType == DataObjectType.VOLUME) { + return true; + } else if (srcType == DataObjectType.VOLUME && dstType == DataObjectType.VOLUME) { + return true; + } + return false; } @Override @@ -618,88 +645,77 @@ public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCal try { if (srcType == DataObjectType.SNAPSHOT && dstType == DataObjectType.VOLUME) { SnapshotInfo sinfo = (SnapshotInfo)srcData; - final String snapshotName = StorPoolHelper.getSnapshotName(srcData.getId(), srcData.getUuid(), snapshotDataStoreDao, snapshotDetailsDao); - VolumeInfo vinfo = (VolumeInfo)dstData; final String volumeName = vinfo.getUuid(); final Long size = vinfo.getSize(); SpConnectionDesc conn = StorPoolUtil.getSpConnection(vinfo.getDataStore().getUuid(), vinfo.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao); - SpApiResponse resp = StorPoolUtil.volumeCreate(volumeName, snapshotName, size, null, null, "volume", sinfo.getBaseVolume().getMaxIops(), conn); - if (resp.getError() == null) { - updateStoragePool(dstData.getDataStore().getId(), size); - VolumeObjectTO to = (VolumeObjectTO)dstData.getTO(); - to.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false))); - to.setSize(size); + if (sinfo.getDataStore().getRole().equals(DataStoreRole.Primary)) { + String snapshotName = StorPoolStorageAdaptor.getVolumeNameFromPath(((SnapshotInfo) srcData).getPath(), true); + SpApiResponse resp = StorPoolUtil.volumeCreate(volumeName, snapshotName, size, null, null, "volume", sinfo.getBaseVolume().getMaxIops(), conn); + if (resp.getError() == null) { + updateStoragePool(dstData.getDataStore().getId(), size); + + VolumeObjectTO to = (VolumeObjectTO) dstData.getTO(); + to.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false))); + to.setSize(size); - answer = new CopyCmdAnswer(to); - StorPoolUtil.spLog("Created volume=%s with uuid=%s from snapshot=%s with uuid=%s", StorPoolUtil.getNameFromResponse(resp, false), to.getUuid(), snapshotName, sinfo.getUuid()); - } else if (resp.getError().getName().equals("objectDoesNotExist")) { + answer = new CopyCmdAnswer(to); + StorPoolUtil.spLog("Created volume=%s with uuid=%s from snapshot=%s with uuid=%s", StorPoolUtil.getNameFromResponse(resp, false), to.getUuid(), snapshotName, sinfo.getUuid()); + } + } else if (sinfo.getDataStore().getRole().equals(DataStoreRole.Image)) { //check if snapshot is on secondary storage - StorPoolUtil.spLog("Snapshot %s does not exists on StorPool, will try to create a volume from a snapshot on secondary storage", snapshotName); + StorPoolUtil.spLog("Snapshot %s does not exists on StorPool, will try to create a volume from a snapshot on secondary storage", sinfo.getName()); SnapshotDataStoreVO snap = getSnapshotImageStoreRef(sinfo.getId(), vinfo.getDataCenterId()); - SnapshotDetailsVO snapshotDetail = snapshotDetailsDao.findDetail(sinfo.getId(), StorPoolUtil.SP_DELAY_DELETE); - if (snapshotDetail != null) { - err = String.format("Could not create volume from snapshot due to: %s", resp.getError()); - } else if (snap != null && StorPoolStorageAdaptor.getVolumeNameFromPath(snap.getInstallPath(), false) == null) { - resp = StorPoolUtil.volumeCreate(srcData.getUuid(), null, size, null, "no", "snapshot", sinfo.getBaseVolume().getMaxIops(), conn); - if (resp.getError() == null) { - VolumeObjectTO dstTO = (VolumeObjectTO) dstData.getTO(); - dstTO.setSize(size); - dstTO.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false))); - cmd = new StorPoolDownloadTemplateCommand(srcData.getTO(), dstTO, StorPoolHelper.getTimeout(StorPoolHelper.PrimaryStorageDownloadWait, configDao), VirtualMachineManager.ExecuteInSequence.value(), "volume"); + SpApiResponse resp = StorPoolUtil.volumeCreate(srcData.getUuid(), null, size, null, "no", "snapshot", sinfo.getBaseVolume().getMaxIops(), conn); + if (resp.getError() == null) { + VolumeObjectTO dstTO = (VolumeObjectTO) dstData.getTO(); + dstTO.setSize(size); + dstTO.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false))); + cmd = new StorPoolDownloadTemplateCommand(srcData.getTO(), dstTO, StorPoolHelper.getTimeout(StorPoolHelper.PrimaryStorageDownloadWait, configDao), VirtualMachineManager.ExecuteInSequence.value(), "volume"); - EndPoint ep = selector.select(srcData, dstData); - if (ep == null) { - err = "No remote endpoint to send command, check if host or ssvm is down?"; + EndPoint ep = selector.select(srcData, dstData); + if (ep == null) { + err = "No remote endpoint to send command, check if host or ssvm is down?"; + } else { + answer = ep.sendMessage(cmd); + } + if (answer != null && answer.getResult()) { + SpApiResponse resp2 = StorPoolUtil.volumeFreeze(StorPoolUtil.getNameFromResponse(resp, true), conn); + if (resp2.getError() != null) { + err = String.format("Could not freeze Storpool volume %s. Error: %s", srcData.getUuid(), resp2.getError()); } else { - answer = ep.sendMessage(cmd); - } + String name = StorPoolUtil.getNameFromResponse(resp, true); + + resp = StorPoolUtil.volumeCreate(volumeName, name, size, null, null, "volume", sinfo.getBaseVolume().getMaxIops(), conn); + if (resp.getError() == null) { + updateStoragePool(dstData.getDataStore().getId(), size); + + VolumeObjectTO to = (VolumeObjectTO) dstData.getTO(); + to.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false))); + to.setSize(size); + // successfully downloaded snapshot to primary storage + answer = new CopyCmdAnswer(to); + StorPoolUtil.spLog("Created volume=%s with uuid=%s from snapshot=%s with uuid=%s", name, to.getUuid(), sinfo.getName(), sinfo.getUuid()); - if (answer != null && answer.getResult()) { - SpApiResponse resp2 = StorPoolUtil.volumeFreeze(StorPoolUtil.getNameFromResponse(resp, true), conn); - if (resp2.getError() != null) { - err = String.format("Could not freeze Storpool volume %s. Error: %s", srcData.getUuid(), resp2.getError()); } else { - String name = StorPoolUtil.getNameFromResponse(resp, false); - SnapshotDetailsVO snapshotDetails = snapshotDetailsDao.findDetail(sinfo.getId(), sinfo.getUuid()); - if (snapshotDetails != null) { - StorPoolHelper.updateSnapshotDetailsValue(snapshotDetails.getId(), StorPoolUtil.devPath(name), "snapshot"); - }else { - StorPoolHelper.addSnapshotDetails(sinfo.getId(), sinfo.getUuid(), StorPoolUtil.devPath(name), snapshotDetailsDao); - } - resp = StorPoolUtil.volumeCreate(volumeName, StorPoolUtil.getNameFromResponse(resp, true), size, null, null, "volume", sinfo.getBaseVolume().getMaxIops(), conn); - if (resp.getError() == null) { - updateStoragePool(dstData.getDataStore().getId(), size); - - VolumeObjectTO to = (VolumeObjectTO) dstData.getTO(); - to.setPath(StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false))); - to.setSize(size); - // successfully downloaded snapshot to primary storage - answer = new CopyCmdAnswer(to); - StorPoolUtil.spLog("Created volume=%s with uuid=%s from snapshot=%s with uuid=%s", name, to.getUuid(), snapshotName, sinfo.getUuid()); - - } else { - err = String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, snapshotName, resp.getError()); - } + err = String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, sinfo.getName(), resp.getError()); } - } else { - err = answer != null ? answer.getDetails() : "Unknown error while downloading template. Null answer returned."; } } else { - err = String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, snapshotName, resp.getError()); + err = answer != null ? answer.getDetails() : "Unknown error while downloading template. Null answer returned."; } } else { - err = String.format("The snapshot %s does not exists neither on primary, neither on secondary storage. Cannot create volume from snapshot", snapshotName); + err = String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, sinfo.getName(), resp.getError()); } } else { - err = String.format("Could not create Storpool volume %s from snapshot %s. Error: %s", volumeName, snapshotName, resp.getError()); + err = String.format("The snapshot %s does not exists neither on primary, neither on secondary storage. Cannot create volume from snapshot", sinfo.getName()); } } else if (srcType == DataObjectType.SNAPSHOT && dstType == DataObjectType.SNAPSHOT) { SnapshotInfo sinfo = (SnapshotInfo)srcData; SnapshotDetailsVO snapshotDetail = snapshotDetailsDao.findDetail(sinfo.getId(), StorPoolUtil.SP_DELAY_DELETE); // bypass secondary storage - if (StorPoolConfigurationManager.BypassSecondaryStorage.value() || snapshotDetail != null) { + if (Boolean.FALSE.equals(SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value())) { SnapshotObjectTO snapshot = (SnapshotObjectTO) srcData.getTO(); answer = new CopyCmdAnswer(snapshot); } else { @@ -709,9 +725,9 @@ public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCal final String snapName = StorPoolStorageAdaptor.getVolumeNameFromPath(((SnapshotInfo) srcData).getPath(), true); SpConnectionDesc conn = StorPoolUtil.getSpConnection(srcData.getDataStore().getUuid(), srcData.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao); try { - Long clusterId = StorPoolHelper.findClusterIdByGlobalId(snapName, clusterDao); - EndPoint ep = clusterId != null ? RemoteHostEndPoint.getHypervisorHostEndPoint(StorPoolHelper.findHostByCluster(clusterId, hostDao)) : selector.select(srcData, dstData); - if (ep == null) { + Long clusterId = StorPoolHelper.findClusterIdByGlobalId(StorPoolUtil.getSnapshotClusterId(snapName, conn), clusterDao); + HostVO host = clusterId != null ? StorPoolHelper.findHostByCluster(clusterId, hostDao) : null; + EndPoint ep = host != null ? RemoteHostEndPoint.getHypervisorHostEndPoint(host) : selector.select(srcData, dstData); if (ep == null) { err = "No remote endpoint to send command, check if host or ssvm is down?"; } else { answer = ep.sendMessage(cmd); @@ -743,8 +759,7 @@ public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCal StorPoolHelper.getTimeout(StorPoolHelper.PrimaryStorageDownloadWait, configDao), VirtualMachineManager.ExecuteInSequence.value()); try { - Long clusterId = StorPoolHelper.findClusterIdByGlobalId(volumeName, clusterDao); - EndPoint ep2 = clusterId != null ? RemoteHostEndPoint.getHypervisorHostEndPoint(StorPoolHelper.findHostByCluster(clusterId, hostDao)) : selector.select(srcData, dstData); + EndPoint ep2 = selector.select(srcData, dstData); if (ep2 == null) { err = "No remote endpoint to send command, check if host or ssvm is down?"; } else { @@ -975,8 +990,9 @@ public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCal StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.copyAsnc command=%s ", cmd); try { - Long clusterId = StorPoolHelper.findClusterIdByGlobalId(snapshotName, clusterDao); - EndPoint ep = clusterId != null ? RemoteHostEndPoint.getHypervisorHostEndPoint(StorPoolHelper.findHostByCluster(clusterId, hostDao)) : selector.select(srcData, dstData); + Long clusterId = StorPoolHelper.findClusterIdByGlobalId(StorPoolUtil.getSnapshotClusterId(snapshotName, conn), clusterDao); + HostVO host = clusterId != null ? StorPoolHelper.findHostByCluster(clusterId, hostDao) : null; + EndPoint ep = host != null ? RemoteHostEndPoint.getHypervisorHostEndPoint(host) : selector.select(srcData, dstData); StorPoolUtil.spLog("selector.select(srcData, dstData) ", ep); if (ep == null) { ep = selector.select(dstData); diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/provider/StorPoolHostListener.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/provider/StorPoolHostListener.java index b696990c5336..da9cd791e66b 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/provider/StorPoolHostListener.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/provider/StorPoolHostListener.java @@ -172,6 +172,7 @@ public boolean hostConnect(long hostId, long poolId) throws StorageConflictExcep } StorPoolHelper.setSpClusterIdIfNeeded(hostId, mspAnswer.getClusterId(), clusterDao, hostDao, clusterDetailsDao); + StorPoolHelper.setLocationIfNeeded(pool, storagePoolDetailsDao, mspAnswer.getClusterLocation()); StorPoolUtil.spLog("Connection established between storage pool [%s] and host [%s]", poolVO.getName(), host.getName()); return true; diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolHelper.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolHelper.java index 3113ae8fdaaf..d78549337611 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolHelper.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolHelper.java @@ -19,26 +19,6 @@ package org.apache.cloudstack.storage.datastore.util; -import java.sql.PreparedStatement; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.cloudstack.framework.config.impl.ConfigurationVO; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; -import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO; -import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; -import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; -import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse; -import org.apache.cloudstack.storage.snapshot.StorPoolConfigurationManager; -import org.apache.cloudstack.storage.to.VolumeObjectTO; -import org.apache.commons.collections4.CollectionUtils; - import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterDetailsVO; import com.cloud.dc.ClusterVO; @@ -49,6 +29,7 @@ import com.cloud.server.ResourceTag; import com.cloud.server.ResourceTag.ResourceObjectType; import com.cloud.storage.DataStoreRole; +import com.cloud.storage.StoragePool; import com.cloud.storage.VMTemplateStoragePoolVO; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.SnapshotDetailsDao; @@ -64,6 +45,26 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.dao.VMInstanceDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.config.impl.ConfigurationVO; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; +import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse; +import org.apache.cloudstack.storage.snapshot.StorPoolConfigurationManager; +import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.sql.PreparedStatement; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; public class StorPoolHelper { @@ -218,6 +219,22 @@ public static void setSpClusterIdIfNeeded(long hostId, String clusterId, Cluster } } + public static void setLocationIfNeeded(StoragePool storagePool, StoragePoolDetailsDao storagePoolDetails, + String location) { + if (location == null) { + return; + } + StoragePoolDetailVO storagePoolDetailVO = storagePoolDetails.findDetail(storagePool.getId(), + StorPoolConfigurationManager.StorPoolClusterLocation.key()); + if (storagePoolDetailVO == null) { + storagePoolDetails.persist(new StoragePoolDetailVO(storagePool.getId(), + StorPoolConfigurationManager.StorPoolClusterLocation.key(), location, true)); + } else if (storagePoolDetailVO.getValue() == null || !storagePoolDetailVO.getValue().equals(location)) { + storagePoolDetailVO.setValue(location); + storagePoolDetails.update(storagePoolDetailVO.getId(), storagePoolDetailVO); + } + } + public static Long findClusterIdByGlobalId(String globalId, ClusterDao clusterDao) { List clusterVo = clusterDao.listAll(); if (clusterVo.size() == 1) { @@ -225,7 +242,7 @@ public static Long findClusterIdByGlobalId(String globalId, ClusterDao clusterDa return null; } for (ClusterVO clusterVO2 : clusterVo) { - if (globalId != null && StorPoolConfigurationManager.StorPoolClusterId.valueIn(clusterVO2.getId()) != null + if (globalId != null && StringUtils.isNotEmpty(StorPoolConfigurationManager.StorPoolClusterId.valueIn(clusterVO2.getId())) && globalId.contains(StorPoolConfigurationManager.StorPoolClusterId.valueIn(clusterVO2.getId()).toString())) { StorPoolUtil.spLog("Found cluster with id=%s for object with globalId=%s", clusterVO2.getId(), globalId); @@ -238,7 +255,7 @@ public static Long findClusterIdByGlobalId(String globalId, ClusterDao clusterDa public static HostVO findHostByCluster(Long clusterId, HostDao hostDao) { List host = hostDao.findByClusterId(clusterId); - return host != null ? host.get(0) : null; + return CollectionUtils.isNotEmpty(host) ? host.get(0) : null; } public static int getTimeout(String cfg, ConfigurationDao configDao) { diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java index 97f4e2fe155a..5f9618b8c03c 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java @@ -137,6 +137,9 @@ public static void spLog(String fmt, Object... args) { public static final String DELAY_DELETE = "delayDelete"; public static final String SP_TIER = "SP_QOSCLASS"; + public static final String SP_RECOVERED_SNAPSHOT = "SP_RECOVERED_SNAPSHOT"; + + public static final String SP_REMOTE_LOCATION = "SP_REMOTE_LOCATION"; public static enum StorpoolRights { RO("ro"), RW("rw"), DETACH("detach"); @@ -427,6 +430,14 @@ public static boolean snapshotExists(final String name, SpConnectionDesc conn) { return resp.getError() == null ? true : objectExists(resp.getError()); } + public static boolean snapshotRecovered(final String name, SpConnectionDesc conn) { + SpApiResponse resp = GET("Snapshot/" + name, conn); + JsonObject obj = resp.fullJson.getAsJsonObject(); + JsonObject data = obj.getAsJsonArray("data").get(0).getAsJsonObject(); + boolean recoveringFromRemote = data.getAsJsonPrimitive("recoveringFromRemote").getAsBoolean(); + return recoveringFromRemote; + } + public static JsonArray snapshotsList(SpConnectionDesc conn) { SpApiResponse resp = GET("MultiCluster/SnapshotsList", conn); JsonObject obj = resp.fullJson.getAsJsonObject(); @@ -662,6 +673,40 @@ public static SpApiResponse snapshotDelete(final String name, SpConnectionDesc c return resp.getError() == null ? POST("MultiCluster/SnapshotDelete/" + name, null, conn) : resp; } + public static SpApiResponse snapshotExport(String name, String location, SpConnectionDesc conn) { + Map json = new HashMap<>(); + json.put("snapshot", name); + json.put("location", location); + return POST("SnapshotExport", json, conn); + } + + public static SpApiResponse snapshotUnexport(String name, String location, SpConnectionDesc conn) { + Map json = new HashMap<>(); + json.put("snapshot", name); + json.put("location", location); + return POST("SnapshotUnexport", json, conn); + } + + public static String getSnapshotClusterId(String snapshotName, SpConnectionDesc conn) { + SpApiResponse resp = POST("MultiCluster/SnapshotUpdate/" + snapshotName, new HashMap<>(), conn); + JsonObject json = resp.fullJson.getAsJsonObject(); + return json.get("clusterId").getAsString(); + } + + public static SpApiResponse snapshotFromRemote(String name, String remoteLocation, String template, + SpConnectionDesc conn) { + Map json = new HashMap<>(); + json.put("remoteId", name); + json.put("remoteLocation", remoteLocation); + json.put("template", template); + json.put("name", ""); + return POST("SnapshotFromRemote", json, conn); + } + + public static SpApiResponse snapshotReconcile(String name, SpConnectionDesc conn) { + return POST("SnapshotReconcile/" + name, null, conn); + } + public static SpApiResponse detachAllForced(final String name, final boolean snapshot, SpConnectionDesc conn) { final String type = snapshot ? "snapshot" : "volume"; List> json = new ArrayList<>(); diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/motion/StorPoolDataMotionStrategy.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/motion/StorPoolDataMotionStrategy.java index 41e9676bb11f..d6855c93899c 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/motion/StorPoolDataMotionStrategy.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/motion/StorPoolDataMotionStrategy.java @@ -19,13 +19,42 @@ package org.apache.cloudstack.storage.motion; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; - +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.MigrateAnswer; +import com.cloud.agent.api.MigrateCommand; +import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo; +import com.cloud.agent.api.ModifyTargetsAnswer; +import com.cloud.agent.api.ModifyTargetsCommand; +import com.cloud.agent.api.PrepareForMigrationCommand; +import com.cloud.agent.api.storage.StorPoolBackupTemplateFromSnapshotCommand; +import com.cloud.agent.api.to.DataObjectType; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.StorageManager; +import com.cloud.storage.VMTemplateDetailVO; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.GuestOSCategoryDao; +import com.cloud.storage.dao.GuestOSDao; +import com.cloud.storage.dao.SnapshotDao; +import com.cloud.storage.dao.SnapshotDetailsDao; +import com.cloud.storage.dao.SnapshotDetailsVO; +import com.cloud.storage.dao.VMTemplateDetailsDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.dao.VMInstanceDao; 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; @@ -55,48 +84,18 @@ import org.apache.cloudstack.storage.datastore.util.StorPoolUtil; import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse; import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc; -import org.apache.cloudstack.storage.snapshot.StorPoolConfigurationManager; import org.apache.cloudstack.storage.to.SnapshotObjectTO; import org.apache.cloudstack.storage.to.TemplateObjectTO; import org.apache.commons.collections.MapUtils; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.stereotype.Component; -import com.cloud.agent.AgentManager; -import com.cloud.agent.api.Answer; -import com.cloud.agent.api.Command; -import com.cloud.agent.api.MigrateAnswer; -import com.cloud.agent.api.MigrateCommand; -import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo; -import com.cloud.agent.api.ModifyTargetsAnswer; -import com.cloud.agent.api.ModifyTargetsCommand; -import com.cloud.agent.api.PrepareForMigrationCommand; -import com.cloud.agent.api.storage.StorPoolBackupTemplateFromSnapshotCommand; -import com.cloud.agent.api.to.DataObjectType; -import com.cloud.agent.api.to.VirtualMachineTO; -import com.cloud.dc.dao.ClusterDao; -import com.cloud.exception.AgentUnavailableException; -import com.cloud.exception.OperationTimedoutException; -import com.cloud.host.Host; -import com.cloud.host.dao.HostDao; -import com.cloud.hypervisor.Hypervisor.HypervisorType; -import com.cloud.storage.Storage.ImageFormat; -import com.cloud.storage.StorageManager; -import com.cloud.storage.VMTemplateDetailVO; -import com.cloud.storage.Volume; -import com.cloud.storage.VolumeVO; -import com.cloud.storage.dao.GuestOSCategoryDao; -import com.cloud.storage.dao.GuestOSDao; -import com.cloud.storage.dao.SnapshotDao; -import com.cloud.storage.dao.SnapshotDetailsDao; -import com.cloud.storage.dao.SnapshotDetailsVO; -import com.cloud.storage.dao.VMTemplateDetailsDao; -import com.cloud.storage.dao.VolumeDao; -import com.cloud.utils.exception.CloudRuntimeException; -import com.cloud.vm.VMInstanceVO; -import com.cloud.vm.VirtualMachineManager; -import com.cloud.vm.dao.VMInstanceDao; +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @Component public class StorPoolDataMotionStrategy implements DataMotionStrategy { @@ -149,10 +148,13 @@ public class StorPoolDataMotionStrategy implements DataMotionStrategy { public StrategyPriority canHandle(DataObject srcData, DataObject destData) { DataObjectType srcType = srcData.getType(); DataObjectType dstType = destData.getType(); + if (srcType == DataObjectType.SNAPSHOT && dstType == DataObjectType.TEMPLATE) { SnapshotInfo sinfo = (SnapshotInfo) srcData; - VolumeInfo volume = sinfo.getBaseVolume(); - StoragePoolVO storagePool = _storagePool.findById(volume.getPoolId()); + if (!sinfo.getDataStore().getRole().equals(DataStoreRole.Primary)) { + return StrategyPriority.CANT_HANDLE; + } + StoragePoolVO storagePool = _storagePool.findById(sinfo.getDataStore().getId()); if (!storagePool.getStorageProviderName().equals(StorPoolUtil.SP_PROVIDER_NAME)) { return StrategyPriority.CANT_HANDLE; } @@ -163,7 +165,7 @@ public StrategyPriority canHandle(DataObject srcData, DataObject destData) { String snapshotName = StorPoolHelper.getSnapshotName(sinfo.getId(), sinfo.getUuid(), _snapshotStoreDao, _snapshotDetailsDao); StorPoolUtil.spLog("StorPoolDataMotionStrategy.canHandle snapshot name=%s", snapshotName); - if (snapshotName != null && StorPoolConfigurationManager.BypassSecondaryStorage.value()) { + if (snapshotName != null) { return StrategyPriority.HIGHEST; } } @@ -175,13 +177,12 @@ public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback callback) { SnapshotObjectTO snapshot = (SnapshotObjectTO) srcData.getTO(); TemplateObjectTO template = (TemplateObjectTO) destData.getTO(); - DataStore store = _dataStore.getDataStore(snapshot.getVolume().getDataStore().getUuid(), - snapshot.getVolume().getDataStore().getRole()); + DataStore store = _dataStore.getDataStore(snapshot.getDataStore().getUuid(), + snapshot.getDataStore().getRole()); SnapshotInfo sInfo = _snapshotDataFactory.getSnapshot(snapshot.getId(), store); - VolumeInfo vInfo = sInfo.getBaseVolume(); - SpConnectionDesc conn = StorPoolUtil.getSpConnection(vInfo.getDataStore().getUuid(), - vInfo.getDataStore().getId(), _storagePoolDetails, _storagePool); + SpConnectionDesc conn = StorPoolUtil.getSpConnection(sInfo.getDataStore().getUuid(), + sInfo.getDataStore().getId(), _storagePoolDetails, _storagePool); String name = template.getUuid(); String volumeName = ""; @@ -209,11 +210,9 @@ public void copyAsync(DataObject srcData, DataObject destData, Host destHost, // final String snapName = // StorpoolStorageAdaptor.getVolumeNameFromPath(((SnapshotInfo) // srcData).getPath(), true); - Long clusterId = StorPoolHelper.findClusterIdByGlobalId(parentName, _clusterDao); - EndPoint ep2 = clusterId != null - ? RemoteHostEndPoint - .getHypervisorHostEndPoint(StorPoolHelper.findHostByCluster(clusterId, _hostDao)) - : _selector.select(sInfo, destData); + Long clusterId = StorPoolHelper.findClusterIdByGlobalId(StorPoolUtil.getSnapshotClusterId(parentName, conn), _clusterDao); + HostVO host = clusterId != null ? StorPoolHelper.findHostByCluster(clusterId, _hostDao) : null; + EndPoint ep2 = host != null ? RemoteHostEndPoint.getHypervisorHostEndPoint(host) : _selector.select(srcData, destData); if (ep2 == null) { err = "No remote endpoint to send command, check if host or ssvm is down?"; } else { @@ -239,7 +238,7 @@ public void copyAsync(DataObject srcData, DataObject destData, Host destHost, } } _vmTemplateDetailsDao.persist(new VMTemplateDetailVO(template.getId(), StorPoolUtil.SP_STORAGE_POOL_ID, - String.valueOf(vInfo.getDataStore().getId()), false)); + String.valueOf(sInfo.getDataStore().getId()), false)); StorPoolUtil.spLog("StorPoolDataMotionStrategy.copyAsync Creating snapshot=%s for StorPool template=%s", volumeName, conn.getTemplateName()); final CopyCommandResult cmd = new CopyCommandResult(null, answer); diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolConfigurationManager.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolConfigurationManager.java index e4e930c8deea..00cef88c4cfc 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolConfigurationManager.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolConfigurationManager.java @@ -53,6 +53,10 @@ public class StorPoolConfigurationManager implements Configurable { "storpool.list.snapshots.delete.after.interval", "360", "The interval (in seconds) to fetch the StorPool snapshots with deleteAfter flag", false); + public static final ConfigKey StorPoolClusterLocation = new ConfigKey(String.class, "sp.cluster.location", "Advanced", null, + "StorPool cluster location", true, ConfigKey.Scope.StoragePool, null); + public static final ConfigKey StorPoolSubclusterEndpoint = new ConfigKey<>(String.class, "sp.cluster.endpoint", "Advanced", null, + "StorPool sub-cluster endpoint", true, ConfigKey.Scope.Cluster, null); @Override public String getConfigComponentName() { @@ -61,6 +65,6 @@ public String getConfigComponentName() { @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] { BypassSecondaryStorage, StorPoolClusterId, AlternativeEndPointEnabled, AlternativeEndpoint, VolumesStatsInterval, StorageStatsInterval, DeleteAfterInterval, ListSnapshotsWithDeleteAfterInterval }; + return new ConfigKey[] { BypassSecondaryStorage, StorPoolClusterId, AlternativeEndPointEnabled, AlternativeEndpoint, VolumesStatsInterval, StorageStatsInterval, DeleteAfterInterval, ListSnapshotsWithDeleteAfterInterval, StorPoolClusterLocation, StorPoolSubclusterEndpoint }; } } diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolSnapshotStrategy.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolSnapshotStrategy.java index 5cdb7b8cda1a..3fd5421a4f92 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolSnapshotStrategy.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolSnapshotStrategy.java @@ -16,11 +16,24 @@ // under the License. package org.apache.cloudstack.storage.snapshot; -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - +import com.cloud.api.query.dao.SnapshotJoinDao; +import com.cloud.api.query.vo.SnapshotJoinVO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.Snapshot; +import com.cloud.storage.SnapshotVO; +import com.cloud.storage.Storage; +import com.cloud.storage.dao.SnapshotDao; +import com.cloud.storage.dao.SnapshotDetailsDao; +import com.cloud.storage.dao.SnapshotDetailsVO; +import com.cloud.storage.dao.SnapshotZoneDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.NoTransitionException; +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.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event; @@ -30,33 +43,28 @@ import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy; import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority; +import org.apache.cloudstack.framework.async.AsyncCompletionCallback; +import org.apache.cloudstack.storage.command.CreateObjectAnswer; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO; import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.datastore.util.StorPoolHelper; import org.apache.cloudstack.storage.datastore.util.StorPoolUtil; import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse; import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc; +import org.apache.cloudstack.storage.to.SnapshotObjectTO; import org.apache.commons.collections.CollectionUtils; -import org.apache.logging.log4j.Logger; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.stereotype.Component; -import com.cloud.exception.InvalidParameterValueException; -import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor; -import com.cloud.storage.DataStoreRole; -import com.cloud.storage.Snapshot; -import com.cloud.storage.SnapshotVO; -import com.cloud.storage.VolumeVO; -import com.cloud.storage.dao.SnapshotDao; -import com.cloud.storage.dao.SnapshotDetailsDao; -import com.cloud.storage.dao.SnapshotDetailsVO; -import com.cloud.storage.dao.SnapshotZoneDao; -import com.cloud.storage.dao.VolumeDao; -import com.cloud.utils.exception.CloudRuntimeException; -import com.cloud.utils.fsm.NoTransitionException; +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.List; @Component @@ -83,6 +91,10 @@ public class StorPoolSnapshotStrategy implements SnapshotStrategy { DataStoreManager dataStoreMgr; @Inject SnapshotZoneDao snapshotZoneDao; + @Inject + SnapshotJoinDao snapshotJoinDao; + @Inject + private ClusterDao clusterDao; @Override public SnapshotInfo backupSnapshot(SnapshotInfo snapshotInfo) { @@ -105,40 +117,71 @@ public SnapshotInfo backupSnapshot(SnapshotInfo snapshotInfo) { public boolean deleteSnapshot(Long snapshotId, Long zoneId) { final SnapshotVO snapshotVO = _snapshotDao.findById(snapshotId); - VolumeVO volume = _volumeDao.findByIdIncludingRemoved(snapshotVO.getVolumeId()); String name = StorPoolHelper.getSnapshotName(snapshotId, snapshotVO.getUuid(), _snapshotStoreDao, _snapshotDetailsDao); boolean res = false; // clean-up snapshot from Storpool storage pools - StoragePoolVO storage = _primaryDataStoreDao.findById(volume.getPoolId()); - if (storage.getStorageProviderName().equals(StorPoolUtil.SP_PROVIDER_NAME)) { - try { - SpConnectionDesc conn = StorPoolUtil.getSpConnection(storage.getUuid(), storage.getId(), storagePoolDetailsDao, _primaryDataStoreDao); - SpApiResponse resp = StorPoolUtil.snapshotDelete(name, conn); - if (resp.getError() != null) { - final String err = String.format("Failed to clean-up Storpool snapshot %s. Error: %s", name, resp.getError()); - StorPoolUtil.spLog(err); - markSnapshotAsDestroyedIfAlreadyRemoved(snapshotId, resp); - throw new CloudRuntimeException(err); - } else { - res = deleteSnapshotFromDbIfNeeded(snapshotVO, zoneId); - StorPoolUtil.spLog("StorpoolSnapshotStrategy.deleteSnapshot: executed successfully=%s, snapshot uuid=%s, name=%s", res, snapshotVO.getUuid(), name); + List snapshotDataStoreVOS = _snapshotStoreDao.listBySnapshot(snapshotId, DataStoreRole.Primary); + List snapshotJoinVOList = snapshotJoinDao.listBySnapshotIdAndZoneId(zoneId, snapshotId); + try { + for (SnapshotJoinVO snapshot: snapshotJoinVOList) { + if (State.Destroyed.equals(snapshot.getStatus())) { + continue; + } + if (snapshot.getStoreRole().isImageStore()) { + continue; + } + StoragePoolVO storage = _primaryDataStoreDao.findById(snapshot.getStoreId()); + if (zoneId != null) { + if (!zoneId.equals(snapshot.getDataCenterId())) { + continue; + } + res = deleteSnapshot(snapshotId, zoneId, snapshotVO, name, storage); + break; } - } catch (Exception e) { - String errMsg = String.format("Cannot delete snapshot due to %s", e.getMessage()); - throw new CloudRuntimeException(errMsg); + res = deleteSnapshot(snapshotId, zoneId, snapshotVO, name, storage); } + } catch (Exception e) { + String errMsg = String.format("Cannot delete snapshot due to %s", e.getMessage()); + throw new CloudRuntimeException(errMsg); } + snapshotDataStoreVOS = _snapshotStoreDao.listBySnapshotId(snapshotId); + boolean areAllSnapshotsDestroyed = snapshotDataStoreVOS.stream().allMatch(v -> v.getState().equals(State.Destroyed) || v.getState().equals(State.Destroying)); + if (areAllSnapshotsDestroyed) { + updateSnapshotToDestroyed(snapshotVO); + return true; + } return res; } - private void markSnapshotAsDestroyedIfAlreadyRemoved(Long snapshotId, SpApiResponse resp) { - if (resp.getError().getName().equals("objectDoesNotExist")) { - SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findBySourceSnapshot(snapshotId, DataStoreRole.Primary); - if (snapshotOnPrimary != null) { - snapshotOnPrimary.setState(State.Destroyed); - _snapshotStoreDao.update(snapshotOnPrimary.getId(), snapshotOnPrimary); + private boolean deleteSnapshot(Long snapshotId, Long zoneId, SnapshotVO snapshotVO, String name, StoragePoolVO storage) { + + boolean res; + SpConnectionDesc conn = StorPoolUtil.getSpConnection(storage.getUuid(), storage.getId(), storagePoolDetailsDao, _primaryDataStoreDao); + SpApiResponse resp = StorPoolUtil.snapshotDelete(name, conn); + if (resp.getError() != null) { + if (resp.getError().getDescr().contains("still exported")) { + throw new CloudRuntimeException(String.format("The snapshot [%s] was exported to another cluster. [%s]", name, resp.getError())); + } + final String err = String.format("Failed to clean-up Storpool snapshot %s. Error: %s", name, resp.getError()); + StorPoolUtil.spLog(err); + if (resp.getError().getName().equals("objectDoesNotExist")) { + markSnapshotAsDestroyedIfAlreadyRemoved(snapshotId, storage.getId()); } + res = false; + } else { + markSnapshotAsDestroyedIfAlreadyRemoved(snapshotId, storage.getId()); + res = deleteSnapshotFromDbIfNeeded(snapshotVO, zoneId); + StorPoolUtil.spLog("StorpoolSnapshotStrategy.deleteSnapshot: executed successfully=%s, snapshot uuid=%s, name=%s", res, snapshotVO.getUuid(), name); + } + return res; + } + + private void markSnapshotAsDestroyedIfAlreadyRemoved(Long snapshotId, Long storeId) { + SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Primary, storeId, snapshotId); + if (snapshotOnPrimary != null) { + snapshotOnPrimary.setState(State.Destroyed); + _snapshotStoreDao.update(snapshotOnPrimary.getId(), snapshotOnPrimary); } } @@ -146,29 +189,20 @@ private void markSnapshotAsDestroyedIfAlreadyRemoved(Long snapshotId, SpApiRespo public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op) { logger.debug(String.format("StorpoolSnapshotStrategy.canHandle: snapshot=%s, uuid=%s, op=%s", snapshot.getName(), snapshot.getUuid(), op)); - if (op != SnapshotOperation.DELETE) { + if (op != SnapshotOperation.DELETE && op != SnapshotOperation.COPY) { return StrategyPriority.CANT_HANDLE; } - SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Primary); - if (snapshotOnPrimary == null) { + List pools = _primaryDataStoreDao.findPoolsByStorageType(Storage.StoragePoolType.StorPool); + if (CollectionUtils.isEmpty(pools)) { return StrategyPriority.CANT_HANDLE; } - if (zoneId != null) { // If zoneId is present, then it should be same as the zoneId of primary store - StoragePoolVO storagePoolVO = _primaryDataStoreDao.findById(snapshotOnPrimary.getDataStoreId()); - if (!zoneId.equals(storagePoolVO.getDataCenterId())) { - return StrategyPriority.CANT_HANDLE; + for (StoragePoolVO pool : pools) { + SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Primary, pool.getId(), snapshot.getId()); + if (snapshotOnPrimary != null && (snapshotOnPrimary.getState().equals(State.Ready) || snapshotOnPrimary.getState().equals(State.Created))) { + return StrategyPriority.HIGHEST; } } - String name = StorPoolHelper.getSnapshotName(snapshot.getId(), snapshot.getUuid(), _snapshotStoreDao, _snapshotDetailsDao); - if (name != null) { - StorPoolUtil.spLog("StorpoolSnapshotStrategy.canHandle: globalId=%s", name); - return StrategyPriority.HIGHEST; - } - SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(snapshot.getId(), snapshot.getUuid()); - if (snapshotDetails != null) { - _snapshotDetailsDao.remove(snapshotDetails.getId()); - } return StrategyPriority.CANT_HANDLE; } @@ -242,48 +276,23 @@ protected boolean areLastSnapshotRef(long snapshotId) { protected boolean deleteSnapshotOnImageAndPrimary(long snapshotId, DataStore store) { SnapshotInfo snapshotOnImage = snapshotDataFactory.getSnapshot(snapshotId, store); SnapshotObject obj = (SnapshotObject)snapshotOnImage; - boolean areLastSnapshotRef = areLastSnapshotRef(snapshotId); - try { - if (areLastSnapshotRef) { - obj.processEvent(Snapshot.Event.DestroyRequested); - } - } catch (NoTransitionException e) { - logger.debug("Failed to set the state to destroying: ", e); - return false; - } + boolean result = false; try { - boolean result = deleteSnapshotChain(snapshotOnImage); + result = deleteSnapshotChain(snapshotOnImage); _snapshotStoreDao.updateDisplayForSnapshotStoreRole(snapshotId, store.getId(), store.getRole(), false); - if (areLastSnapshotRef) { - obj.processEvent(Snapshot.Event.OperationSucceeded); - } - if (result) { - SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshotOnImage.getSnapshotId(), DataStoreRole.Primary); - if (snapshotOnPrimary != null) { - snapshotOnPrimary.setState(State.Destroyed); - _snapshotStoreDao.update(snapshotOnPrimary.getId(), snapshotOnPrimary); - } - } } catch (Exception e) { logger.debug("Failed to delete snapshot: ", e); - try { - if (areLastSnapshotRef) { - obj.processEvent(Snapshot.Event.OperationFailed); - } - } catch (NoTransitionException e1) { - logger.debug("Failed to change snapshot state: " + e.toString()); - } return false; } - return true; + return result; } private boolean deleteSnapshotFromDbIfNeeded(SnapshotVO snapshotVO, Long zoneId) { final long snapshotId = snapshotVO.getId(); SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(snapshotId, snapshotVO.getUuid()); if (snapshotDetails != null) { - _snapshotDetailsDao.removeDetails(snapshotId); + _snapshotDetailsDao.remove(snapshotId); } if (zoneId != null && List.of(Snapshot.State.Allocated, Snapshot.State.CreatedOnPrimary).contains(snapshotVO.getState())) { @@ -319,12 +328,6 @@ private boolean deleteSnapshotFromDbIfNeeded(SnapshotVO snapshotVO, Long zoneId) return true; } - if (snapshotVO.getState() == Snapshot.State.CreatedOnPrimary) { - snapshotVO.setState(Snapshot.State.Destroyed); - _snapshotDao.update(snapshotId, snapshotVO); - return true; - } - if (!Snapshot.State.BackedUp.equals(snapshotVO.getState()) && !Snapshot.State.Error.equals(snapshotVO.getState()) && !Snapshot.State.Destroying.equals(snapshotVO.getState())) { throw new InvalidParameterValueException("Can't delete snapshot " + snapshotId + " due to it is in " + snapshotVO.getState() + " Status"); @@ -346,7 +349,6 @@ private boolean deleteSnapshotFromDbIfNeeded(SnapshotVO snapshotVO, Long zoneId) if (CollectionUtils.isNotEmpty(retrieveSnapshotEntries(snapshotId, null))) { return true; } - updateSnapshotToDestroyed(snapshotVO); return true; } @@ -372,4 +374,88 @@ public boolean revertSnapshot(SnapshotInfo snapshot) { @Override public void postSnapshotCreation(SnapshotInfo snapshot) { } + + @Override + public void copySnapshot(DataObject snapshot, DataObject snapshotDest, AsyncCompletionCallback callback) { + + // export snapshot on remote + StoragePoolVO storagePoolVO = _primaryDataStoreDao.findById(snapshotDest.getDataStore().getId()); + String location = StorPoolConfigurationManager.StorPoolClusterLocation.valueIn(snapshotDest.getDataStore().getId()); + StorPoolUtil.spLog("StorpoolSnapshotStrategy.copySnapshot: snapshot %s to pool=%s", snapshot.getUuid(), storagePoolVO.getName()); + CreateCmdResult res = null; + SnapshotInfo srcSnapshot = (SnapshotInfo) snapshot; + SnapshotInfo destSnapshot = (SnapshotInfo) snapshotDest; + String err = null; + if (location != null) { + SpConnectionDesc connectionLocal = StorPoolUtil.getSpConnection(snapshot.getDataStore().getUuid(), + snapshot.getDataStore().getId(), storagePoolDetailsDao, _primaryDataStoreDao); + String snapshotName = StorPoolStorageAdaptor.getVolumeNameFromPath(srcSnapshot.getPath(), false); + Long clusterId = StorPoolHelper.findClusterIdByGlobalId(StorPoolUtil.getSnapshotClusterId("~" + snapshotName, connectionLocal), clusterDao); + connectionLocal = getSpConnectionDesc(connectionLocal, clusterId); + SpApiResponse resp = StorPoolUtil.snapshotExport("~" + snapshotName, location, connectionLocal); + if (resp.getError() != null) { + StorPoolUtil.spLog("Failed to export snapshot %s from %s due to %s", snapshotName, location, resp.getError()); + err = String.format("Failed to export snapshot %s from %s due to %s", snapshotName, location, resp.getError()); + res = new CreateCmdResult(destSnapshot.getPath(), null); + res.setResult(err); + callback.complete(res); + return; + } + SpConnectionDesc connectionRemote = StorPoolUtil.getSpConnection(storagePoolVO.getUuid(), + storagePoolVO.getId(), storagePoolDetailsDao, _primaryDataStoreDao); + String localLocation = StorPoolConfigurationManager.StorPoolClusterLocation + .valueIn(snapshot.getDataStore().getId()); + StoragePoolDetailVO template = storagePoolDetailsDao.findDetail(storagePoolVO.getId(), + StorPoolUtil.SP_TEMPLATE); + SpApiResponse respFromRemote = StorPoolUtil.snapshotFromRemote(snapshotName, localLocation, + template.getValue(), connectionRemote); + if (respFromRemote.getError() != null) { + StorPoolUtil.spLog("Failed to copy snapshot %s to %s due to %s", snapshotName, location, respFromRemote.getError()); + err = String.format("Failed to copy snapshot %s to %s due to %s", snapshotName, location, respFromRemote.getError()); + res = new CreateCmdResult(destSnapshot.getPath(), null); + res.setResult(err); + callback.complete(res); + return; + } + StorPoolUtil.spLog("The snapshot [%s] was copied from remote", snapshotName); + String detail = "~" + snapshotName + ";" + location; + SnapshotDetailsVO snapshotForRecovery = new SnapshotDetailsVO(snapshot.getId(), StorPoolUtil.SP_RECOVERED_SNAPSHOT, detail, true); + _snapshotDetailsDao.persist(snapshotForRecovery); + respFromRemote = StorPoolUtil.snapshotReconcile("~" + snapshotName, connectionRemote); + if (respFromRemote.getError() != null) { + StorPoolUtil.spLog("Failed to reconcile snapshot %s from %s due to %s", snapshotName, location, respFromRemote.getError()); + err = String.format("Failed to reconcile snapshot %s from %s due to %s", snapshotName, location, respFromRemote.getError()); + res = new CreateCmdResult(destSnapshot.getPath(), null); + res.setResult(err); + callback.complete(res); + return; + } + SnapshotDataStoreVO snapshotStore = _snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Primary, snapshotDest.getDataStore().getId(), destSnapshot.getSnapshotId()); + snapshotStore.setInstallPath(srcSnapshot.getPath()); + _snapshotStoreDao.update(snapshotStore.getId(), snapshotStore); + + } else { + res = new CreateCmdResult(destSnapshot.getPath(), null); + res.setResult("The snapshot is not in the right location"); + callback.complete(res); + return; + } + SnapshotObjectTO snap = (SnapshotObjectTO) snapshotDest.getTO(); + snap.setPath(srcSnapshot.getPath()); + CreateObjectAnswer answer = new CreateObjectAnswer(snap); + res = new CreateCmdResult(destSnapshot.getPath(), answer); + res.setResult(err); + callback.complete(res); + } + + public static SpConnectionDesc getSpConnectionDesc(SpConnectionDesc connectionLocal, Long clusterId) { + + String subClusterEndPoint = StorPoolConfigurationManager.StorPoolSubclusterEndpoint.valueIn(clusterId); + if (StringUtils.isNotEmpty(subClusterEndPoint)) { + String host = subClusterEndPoint.split(";")[0].split("=")[1]; + String token = subClusterEndPoint.split(";")[1].split("=")[1]; + connectionLocal = new SpConnectionDesc(host, token, connectionLocal.getTemplateName()); + } + return connectionLocal; + } } diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index a169ebc0f19f..5bc92184b18d 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -16,89 +16,6 @@ // under the License. package com.cloud.api; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - -import javax.annotation.PostConstruct; -import javax.inject.Inject; - -import com.cloud.cpu.CPU; -import org.apache.cloudstack.acl.Role; -import org.apache.cloudstack.acl.RoleService; -import org.apache.cloudstack.affinity.AffinityGroup; -import org.apache.cloudstack.affinity.AffinityGroupResponse; -import org.apache.cloudstack.affinity.dao.AffinityGroupDao; -import org.apache.cloudstack.api.ApiCommandResourceType; -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.ApiConstants.DomainDetails; -import org.apache.cloudstack.api.ApiConstants.HostDetails; -import org.apache.cloudstack.api.ApiConstants.VMDetails; -import org.apache.cloudstack.api.ResponseObject.ResponseView; -import org.apache.cloudstack.api.response.AccountResponse; -import org.apache.cloudstack.api.response.AsyncJobResponse; -import org.apache.cloudstack.api.response.BackupOfferingResponse; -import org.apache.cloudstack.api.response.BackupResponse; -import org.apache.cloudstack.api.response.BackupScheduleResponse; -import org.apache.cloudstack.api.response.DiskOfferingResponse; -import org.apache.cloudstack.api.response.DomainResponse; -import org.apache.cloudstack.api.response.DomainRouterResponse; -import org.apache.cloudstack.api.response.EventResponse; -import org.apache.cloudstack.api.response.SharedFSResponse; -import org.apache.cloudstack.api.response.HostForMigrationResponse; -import org.apache.cloudstack.api.response.HostResponse; -import org.apache.cloudstack.api.response.HostTagResponse; -import org.apache.cloudstack.api.response.ImageStoreResponse; -import org.apache.cloudstack.api.response.InstanceGroupResponse; -import org.apache.cloudstack.api.response.NetworkOfferingResponse; -import org.apache.cloudstack.api.response.ObjectStoreResponse; -import org.apache.cloudstack.api.response.ProjectAccountResponse; -import org.apache.cloudstack.api.response.ProjectInvitationResponse; -import org.apache.cloudstack.api.response.ProjectResponse; -import org.apache.cloudstack.api.response.ResourceIconResponse; -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.SnapshotResponse; -import org.apache.cloudstack.api.response.StoragePoolResponse; -import org.apache.cloudstack.api.response.StorageTagResponse; -import org.apache.cloudstack.api.response.TemplateResponse; -import org.apache.cloudstack.api.response.UserResponse; -import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.api.response.VolumeResponse; -import org.apache.cloudstack.api.response.VpcOfferingResponse; -import org.apache.cloudstack.api.response.ZoneResponse; -import org.apache.cloudstack.backup.Backup; -import org.apache.cloudstack.backup.BackupOffering; -import org.apache.cloudstack.backup.BackupSchedule; -import org.apache.cloudstack.backup.dao.BackupDao; -import org.apache.cloudstack.backup.dao.BackupOfferingDao; -import org.apache.cloudstack.backup.dao.BackupScheduleDao; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; -import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.cloudstack.framework.jobs.AsyncJob; -import org.apache.cloudstack.framework.jobs.AsyncJobManager; -import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao; -import org.apache.cloudstack.resourcedetail.SnapshotPolicyDetailVO; -import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; -import org.apache.cloudstack.resourcedetail.dao.SnapshotPolicyDetailsDao; -import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao; -import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO; -import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; -import org.apache.cloudstack.storage.sharedfs.SharedFS; -import org.apache.cloudstack.storage.sharedfs.query.dao.SharedFSJoinDao; -import org.apache.cloudstack.storage.sharedfs.query.vo.SharedFSJoinVO; - import com.cloud.agent.api.VgpuTypesInfo; import com.cloud.api.query.dao.AccountJoinDao; import com.cloud.api.query.dao.AffinityGroupJoinDao; @@ -158,6 +75,7 @@ import com.cloud.configuration.ConfigurationService; import com.cloud.configuration.Resource; import com.cloud.configuration.Resource.ResourceType; +import com.cloud.cpu.CPU; import com.cloud.dc.AccountVlanMapVO; import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterDetailsVO; @@ -361,6 +279,86 @@ import com.cloud.vm.dao.VMInstanceDao; import com.cloud.vm.snapshot.VMSnapshot; import com.cloud.vm.snapshot.dao.VMSnapshotDao; +import org.apache.cloudstack.acl.Role; +import org.apache.cloudstack.acl.RoleService; +import org.apache.cloudstack.affinity.AffinityGroup; +import org.apache.cloudstack.affinity.AffinityGroupResponse; +import org.apache.cloudstack.affinity.dao.AffinityGroupDao; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiConstants.DomainDetails; +import org.apache.cloudstack.api.ApiConstants.HostDetails; +import org.apache.cloudstack.api.ApiConstants.VMDetails; +import org.apache.cloudstack.api.ResponseObject.ResponseView; +import org.apache.cloudstack.api.response.AccountResponse; +import org.apache.cloudstack.api.response.AsyncJobResponse; +import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.BackupScheduleResponse; +import org.apache.cloudstack.api.response.DiskOfferingResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.DomainRouterResponse; +import org.apache.cloudstack.api.response.EventResponse; +import org.apache.cloudstack.api.response.HostForMigrationResponse; +import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.HostTagResponse; +import org.apache.cloudstack.api.response.ImageStoreResponse; +import org.apache.cloudstack.api.response.InstanceGroupResponse; +import org.apache.cloudstack.api.response.NetworkOfferingResponse; +import org.apache.cloudstack.api.response.ObjectStoreResponse; +import org.apache.cloudstack.api.response.ProjectAccountResponse; +import org.apache.cloudstack.api.response.ProjectInvitationResponse; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.ResourceIconResponse; +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.SharedFSResponse; +import org.apache.cloudstack.api.response.SnapshotResponse; +import org.apache.cloudstack.api.response.StoragePoolResponse; +import org.apache.cloudstack.api.response.StorageTagResponse; +import org.apache.cloudstack.api.response.TemplateResponse; +import org.apache.cloudstack.api.response.UserResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.VolumeResponse; +import org.apache.cloudstack.api.response.VpcOfferingResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupOffering; +import org.apache.cloudstack.backup.BackupSchedule; +import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.cloudstack.backup.dao.BackupOfferingDao; +import org.apache.cloudstack.backup.dao.BackupScheduleDao; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.jobs.AsyncJob; +import org.apache.cloudstack.framework.jobs.AsyncJobManager; +import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao; +import org.apache.cloudstack.resourcedetail.SnapshotPolicyDetailVO; +import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; +import org.apache.cloudstack.resourcedetail.dao.SnapshotPolicyDetailsDao; +import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao; +import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.sharedfs.SharedFS; +import org.apache.cloudstack.storage.sharedfs.query.dao.SharedFSJoinDao; +import org.apache.cloudstack.storage.sharedfs.query.vo.SharedFSJoinVO; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; public class ApiDBUtils { private static ManagementServer s_ms; @@ -1707,6 +1705,19 @@ public static List findSnapshotPolicyZones(SnapshotPolicy policy, return s_zoneDao.listByIds(zoneIds); } + public static List findSnapshotPolicyPools(SnapshotPolicy policy, Volume volume) { + List poolDetails = s_snapshotPolicyDetailsDao.findDetails(policy.getId(), ApiConstants.STORAGE_ID); + List poolIds = new ArrayList<>(); + for (SnapshotPolicyDetailVO detail : poolDetails) { + try { + poolIds.add(Long.valueOf(detail.getValue())); + } catch (NumberFormatException ignored) {} + } + if (volume != null && !poolIds.contains(volume.getPoolId())) { + poolIds.add(0, volume.getPoolId()); + } + return s_storagePoolDao.listByIds(poolIds); + } public static VpcOffering findVpcOfferingById(long offeringId) { return s_vpcOfferingDao.findById(offeringId); } diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 810f0abd7e00..653f54931239 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -16,39 +16,210 @@ // under the License. package com.cloud.api; -import static com.cloud.utils.NumbersUtil.toHumanReadableSize; - -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Date; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.TimeZone; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import javax.inject.Inject; - +import com.cloud.agent.api.VgpuTypesInfo; +import com.cloud.api.query.ViewResponseHelper; +import com.cloud.api.query.dao.UserVmJoinDao; +import com.cloud.api.query.vo.AccountJoinVO; +import com.cloud.api.query.vo.AsyncJobJoinVO; +import com.cloud.api.query.vo.ControlledViewEntity; +import com.cloud.api.query.vo.DataCenterJoinVO; +import com.cloud.api.query.vo.DiskOfferingJoinVO; +import com.cloud.api.query.vo.DomainRouterJoinVO; +import com.cloud.api.query.vo.EventJoinVO; +import com.cloud.api.query.vo.HostJoinVO; +import com.cloud.api.query.vo.ImageStoreJoinVO; +import com.cloud.api.query.vo.InstanceGroupJoinVO; +import com.cloud.api.query.vo.NetworkOfferingJoinVO; +import com.cloud.api.query.vo.ProjectAccountJoinVO; +import com.cloud.api.query.vo.ProjectInvitationJoinVO; +import com.cloud.api.query.vo.ProjectJoinVO; +import com.cloud.api.query.vo.ResourceTagJoinVO; +import com.cloud.api.query.vo.SecurityGroupJoinVO; +import com.cloud.api.query.vo.ServiceOfferingJoinVO; +import com.cloud.api.query.vo.StoragePoolJoinVO; +import com.cloud.api.query.vo.TemplateJoinVO; +import com.cloud.api.query.vo.UserAccountJoinVO; +import com.cloud.api.query.vo.UserVmJoinVO; +import com.cloud.api.query.vo.VolumeJoinVO; +import com.cloud.api.query.vo.VpcOfferingJoinVO; +import com.cloud.api.response.ApiResponseSerializer; import com.cloud.bgp.ASNumber; import com.cloud.bgp.ASNumberRange; +import com.cloud.capacity.Capacity; +import com.cloud.capacity.CapacityVO; +import com.cloud.capacity.dao.CapacityDaoImpl.SummedCapacity; +import com.cloud.configuration.ConfigurationManager; +import com.cloud.configuration.Resource.ResourceOwnerType; +import com.cloud.configuration.Resource.ResourceType; +import com.cloud.configuration.ResourceCount; +import com.cloud.configuration.ResourceLimit; import com.cloud.dc.ASNumberRangeVO; import com.cloud.dc.ASNumberVO; +import com.cloud.dc.ClusterDetailsDao; +import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenterGuestIpv6Prefix; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.HostPodVO; +import com.cloud.dc.Pod; +import com.cloud.dc.StorageNetworkIpRange; +import com.cloud.dc.Vlan; +import com.cloud.dc.Vlan.VlanType; import com.cloud.dc.VlanDetailsVO; +import com.cloud.dc.VlanVO; import com.cloud.dc.dao.ASNumberDao; import com.cloud.dc.dao.ASNumberRangeDao; import com.cloud.dc.dao.VlanDetailsDao; +import com.cloud.domain.Domain; +import com.cloud.domain.DomainVO; +import com.cloud.event.Event; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.gpu.GPU; +import com.cloud.host.ControlState; +import com.cloud.host.Host; +import com.cloud.host.HostVO; import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.HypervisorCapabilities; +import com.cloud.network.GuestVlan; +import com.cloud.network.GuestVlanRange; +import com.cloud.network.IpAddress; +import com.cloud.network.Ipv6Service; +import com.cloud.network.Network; +import com.cloud.network.Network.Capability; +import com.cloud.network.Network.Provider; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.NetworkPermission; +import com.cloud.network.NetworkProfile; +import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.Networks.IsolationType; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.OvsProvider; +import com.cloud.network.PhysicalNetwork; +import com.cloud.network.PhysicalNetworkServiceProvider; +import com.cloud.network.PhysicalNetworkTrafficType; +import com.cloud.network.PublicIpQuarantine; +import com.cloud.network.RemoteAccessVpn; +import com.cloud.network.RouterHealthCheckResult; +import com.cloud.network.Site2SiteCustomerGateway; +import com.cloud.network.Site2SiteVpnConnection; +import com.cloud.network.Site2SiteVpnGateway; +import com.cloud.network.VirtualRouterProvider; +import com.cloud.network.VpnUser; +import com.cloud.network.VpnUserVO; +import com.cloud.network.as.AutoScalePolicy; +import com.cloud.network.as.AutoScaleVmGroup; +import com.cloud.network.as.AutoScaleVmProfile; +import com.cloud.network.as.AutoScaleVmProfileVO; +import com.cloud.network.as.Condition; +import com.cloud.network.as.ConditionVO; +import com.cloud.network.as.Counter; +import com.cloud.network.dao.FirewallRulesDao; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.LoadBalancerVO; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkDetailVO; +import com.cloud.network.dao.NetworkDetailsDao; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.PhysicalNetworkVO; +import com.cloud.network.router.VirtualRouter; +import com.cloud.network.rules.FirewallRule; +import com.cloud.network.rules.HealthCheckPolicy; +import com.cloud.network.rules.LoadBalancer; +import com.cloud.network.rules.LoadBalancerContainer.Scheme; +import com.cloud.network.rules.PortForwardingRule; +import com.cloud.network.rules.PortForwardingRuleVO; +import com.cloud.network.rules.StaticNatRule; +import com.cloud.network.rules.StickinessPolicy; +import com.cloud.network.security.SecurityGroup; +import com.cloud.network.security.SecurityGroupVO; +import com.cloud.network.security.SecurityRule; +import com.cloud.network.security.SecurityRule.SecurityRuleType; +import com.cloud.network.vpc.NetworkACL; +import com.cloud.network.vpc.NetworkACLItem; +import com.cloud.network.vpc.PrivateGateway; +import com.cloud.network.vpc.StaticRoute; +import com.cloud.network.vpc.Vpc; +import com.cloud.network.vpc.VpcOffering; +import com.cloud.network.vpc.VpcVO; +import com.cloud.network.vpc.dao.VpcOfferingDao; +import com.cloud.offering.DiskOffering; +import com.cloud.offering.NetworkOffering; +import com.cloud.offering.NetworkOffering.Detail; +import com.cloud.offering.ServiceOffering; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.org.Cluster; +import com.cloud.projects.Project; +import com.cloud.projects.ProjectAccount; +import com.cloud.projects.ProjectInvitation; +import com.cloud.region.ha.GlobalLoadBalancerRule; +import com.cloud.resource.RollingMaintenanceManager; +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; +import com.cloud.server.ResourceTag.ResourceObjectType; +import com.cloud.service.ServiceOfferingVO; import com.cloud.storage.BucketVO; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.GuestOS; +import com.cloud.storage.GuestOSCategoryVO; +import com.cloud.storage.GuestOSHypervisor; +import com.cloud.storage.GuestOsCategory; +import com.cloud.storage.ImageStore; +import com.cloud.storage.Snapshot; +import com.cloud.storage.SnapshotVO; +import com.cloud.storage.StoragePool; +import com.cloud.storage.Upload; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.GuestOSCategoryDao; +import com.cloud.storage.dao.GuestOSDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.snapshot.SnapshotPolicy; +import com.cloud.storage.snapshot.SnapshotSchedule; +import com.cloud.tags.dao.ResourceTagDao; +import com.cloud.template.VirtualMachineTemplate; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.SSHKeyPair; +import com.cloud.user.User; +import com.cloud.user.UserAccount; +import com.cloud.user.UserData; +import com.cloud.user.UserStatisticsVO; +import com.cloud.user.dao.UserDataDao; +import com.cloud.user.dao.UserStatisticsDao; +import com.cloud.uservm.UserVm; +import com.cloud.utils.Pair; +import com.cloud.utils.crypt.DBEncryptionUtil; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.SearchCriteria.Op; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.Dhcp; +import com.cloud.utils.net.Ip; +import com.cloud.utils.net.NetUtils; +import com.cloud.utils.security.CertificateHelper; +import com.cloud.vm.ConsoleProxyVO; +import com.cloud.vm.InstanceGroup; +import com.cloud.vm.Nic; +import com.cloud.vm.NicExtraDhcpOptionVO; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicSecondaryIp; +import com.cloud.vm.NicVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.Type; +import com.cloud.vm.dao.NicExtraDhcpOptionDao; +import com.cloud.vm.dao.NicSecondaryIpVO; +import com.cloud.vm.snapshot.VMSnapshot; +import com.cloud.vm.snapshot.VMSnapshotVO; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.affinity.AffinityGroup; @@ -63,12 +234,12 @@ import org.apache.cloudstack.api.ResponseGenerator; import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd; +import org.apache.cloudstack.api.response.ASNRangeResponse; +import org.apache.cloudstack.api.response.ASNumberResponse; import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.ApplicationLoadBalancerInstanceResponse; import org.apache.cloudstack.api.response.ApplicationLoadBalancerResponse; import org.apache.cloudstack.api.response.ApplicationLoadBalancerRuleResponse; -import org.apache.cloudstack.api.response.ASNRangeResponse; -import org.apache.cloudstack.api.response.ASNumberResponse; import org.apache.cloudstack.api.response.AsyncJobResponse; import org.apache.cloudstack.api.response.AutoScalePolicyResponse; import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse; @@ -99,7 +270,6 @@ import org.apache.cloudstack.api.response.DomainRouterResponse; import org.apache.cloudstack.api.response.EventResponse; import org.apache.cloudstack.api.response.ExtractResponse; -import org.apache.cloudstack.api.response.SharedFSResponse; import org.apache.cloudstack.api.response.FirewallResponse; import org.apache.cloudstack.api.response.FirewallRuleResponse; import org.apache.cloudstack.api.response.GlobalLoadBalancerResponse; @@ -164,6 +334,7 @@ import org.apache.cloudstack.api.response.SecurityGroupRuleResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.cloudstack.api.response.ServiceResponse; +import org.apache.cloudstack.api.response.SharedFSResponse; import org.apache.cloudstack.api.response.Site2SiteCustomerGatewayResponse; import org.apache.cloudstack.api.response.Site2SiteVpnConnectionResponse; import org.apache.cloudstack.api.response.Site2SiteVpnGatewayResponse; @@ -230,10 +401,10 @@ import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; -import org.apache.cloudstack.storage.sharedfs.SharedFS; -import org.apache.cloudstack.storage.sharedfs.query.vo.SharedFSJoinVO; import org.apache.cloudstack.storage.object.Bucket; import org.apache.cloudstack.storage.object.ObjectStore; +import org.apache.cloudstack.storage.sharedfs.SharedFS; +import org.apache.cloudstack.storage.sharedfs.query.vo.SharedFSJoinVO; import org.apache.cloudstack.usage.Usage; import org.apache.cloudstack.usage.UsageService; import org.apache.cloudstack.usage.UsageTypes; @@ -241,205 +412,31 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import sun.security.x509.X509CertImpl; -import com.cloud.agent.api.VgpuTypesInfo; -import com.cloud.api.query.ViewResponseHelper; -import com.cloud.api.query.dao.UserVmJoinDao; -import com.cloud.api.query.vo.AccountJoinVO; -import com.cloud.api.query.vo.AsyncJobJoinVO; -import com.cloud.api.query.vo.ControlledViewEntity; -import com.cloud.api.query.vo.DataCenterJoinVO; -import com.cloud.api.query.vo.DiskOfferingJoinVO; -import com.cloud.api.query.vo.DomainRouterJoinVO; -import com.cloud.api.query.vo.EventJoinVO; -import com.cloud.api.query.vo.HostJoinVO; -import com.cloud.api.query.vo.ImageStoreJoinVO; -import com.cloud.api.query.vo.InstanceGroupJoinVO; -import com.cloud.api.query.vo.NetworkOfferingJoinVO; -import com.cloud.api.query.vo.ProjectAccountJoinVO; -import com.cloud.api.query.vo.ProjectInvitationJoinVO; -import com.cloud.api.query.vo.ProjectJoinVO; -import com.cloud.api.query.vo.ResourceTagJoinVO; -import com.cloud.api.query.vo.SecurityGroupJoinVO; -import com.cloud.api.query.vo.ServiceOfferingJoinVO; -import com.cloud.api.query.vo.StoragePoolJoinVO; -import com.cloud.api.query.vo.TemplateJoinVO; -import com.cloud.api.query.vo.UserAccountJoinVO; -import com.cloud.api.query.vo.UserVmJoinVO; -import com.cloud.api.query.vo.VolumeJoinVO; -import com.cloud.api.query.vo.VpcOfferingJoinVO; -import com.cloud.api.response.ApiResponseSerializer; -import com.cloud.capacity.Capacity; -import com.cloud.capacity.CapacityVO; -import com.cloud.capacity.dao.CapacityDaoImpl.SummedCapacity; -import com.cloud.configuration.ConfigurationManager; -import com.cloud.configuration.Resource.ResourceOwnerType; -import com.cloud.configuration.Resource.ResourceType; -import com.cloud.configuration.ResourceCount; -import com.cloud.configuration.ResourceLimit; -import com.cloud.dc.ClusterDetailsDao; -import com.cloud.dc.ClusterVO; -import com.cloud.dc.DataCenter; -import com.cloud.dc.DataCenterGuestIpv6Prefix; -import com.cloud.dc.DataCenterVO; -import com.cloud.dc.HostPodVO; -import com.cloud.dc.Pod; -import com.cloud.dc.StorageNetworkIpRange; -import com.cloud.dc.Vlan; -import com.cloud.dc.Vlan.VlanType; -import com.cloud.dc.VlanVO; -import com.cloud.domain.Domain; -import com.cloud.domain.DomainVO; -import com.cloud.event.Event; -import com.cloud.exception.InvalidParameterValueException; -import com.cloud.exception.PermissionDeniedException; -import com.cloud.gpu.GPU; -import com.cloud.host.ControlState; -import com.cloud.host.Host; -import com.cloud.host.HostVO; -import com.cloud.hypervisor.HypervisorCapabilities; -import com.cloud.network.GuestVlan; -import com.cloud.network.GuestVlanRange; -import com.cloud.network.IpAddress; -import com.cloud.network.Ipv6Service; -import com.cloud.network.Network; -import com.cloud.network.Network.Capability; -import com.cloud.network.Network.Provider; -import com.cloud.network.Network.Service; -import com.cloud.network.NetworkModel; -import com.cloud.network.NetworkPermission; -import com.cloud.network.NetworkProfile; -import com.cloud.network.Networks.BroadcastDomainType; -import com.cloud.network.Networks.IsolationType; -import com.cloud.network.Networks.TrafficType; -import com.cloud.network.OvsProvider; -import com.cloud.network.PhysicalNetwork; -import com.cloud.network.PhysicalNetworkServiceProvider; -import com.cloud.network.PhysicalNetworkTrafficType; -import com.cloud.network.PublicIpQuarantine; -import com.cloud.network.RemoteAccessVpn; -import com.cloud.network.RouterHealthCheckResult; -import com.cloud.network.Site2SiteCustomerGateway; -import com.cloud.network.Site2SiteVpnConnection; -import com.cloud.network.Site2SiteVpnGateway; -import com.cloud.network.VirtualRouterProvider; -import com.cloud.network.VpnUser; -import com.cloud.network.VpnUserVO; -import com.cloud.network.as.AutoScalePolicy; -import com.cloud.network.as.AutoScaleVmGroup; -import com.cloud.network.as.AutoScaleVmProfile; -import com.cloud.network.as.AutoScaleVmProfileVO; -import com.cloud.network.as.Condition; -import com.cloud.network.as.ConditionVO; -import com.cloud.network.as.Counter; -import com.cloud.network.dao.FirewallRulesDao; -import com.cloud.network.dao.IPAddressDao; -import com.cloud.network.dao.IPAddressVO; -import com.cloud.network.dao.LoadBalancerVO; -import com.cloud.network.dao.NetworkDao; -import com.cloud.network.dao.NetworkDetailVO; -import com.cloud.network.dao.NetworkDetailsDao; -import com.cloud.network.dao.NetworkServiceMapDao; -import com.cloud.network.dao.NetworkVO; -import com.cloud.network.dao.PhysicalNetworkVO; -import com.cloud.network.router.VirtualRouter; -import com.cloud.network.rules.FirewallRule; -import com.cloud.network.rules.HealthCheckPolicy; -import com.cloud.network.rules.LoadBalancer; -import com.cloud.network.rules.LoadBalancerContainer.Scheme; -import com.cloud.network.rules.PortForwardingRule; -import com.cloud.network.rules.PortForwardingRuleVO; -import com.cloud.network.rules.StaticNatRule; -import com.cloud.network.rules.StickinessPolicy; -import com.cloud.network.security.SecurityGroup; -import com.cloud.network.security.SecurityGroupVO; -import com.cloud.network.security.SecurityRule; -import com.cloud.network.security.SecurityRule.SecurityRuleType; -import com.cloud.network.vpc.NetworkACL; -import com.cloud.network.vpc.NetworkACLItem; -import com.cloud.network.vpc.PrivateGateway; -import com.cloud.network.vpc.StaticRoute; -import com.cloud.network.vpc.Vpc; -import com.cloud.network.vpc.VpcOffering; -import com.cloud.network.vpc.VpcVO; -import com.cloud.network.vpc.dao.VpcOfferingDao; -import com.cloud.offering.DiskOffering; -import com.cloud.offering.NetworkOffering; -import com.cloud.offering.NetworkOffering.Detail; -import com.cloud.offering.ServiceOffering; -import com.cloud.offerings.NetworkOfferingVO; -import com.cloud.offerings.dao.NetworkOfferingDao; -import com.cloud.org.Cluster; -import com.cloud.projects.Project; -import com.cloud.projects.ProjectAccount; -import com.cloud.projects.ProjectInvitation; -import com.cloud.region.ha.GlobalLoadBalancerRule; -import com.cloud.resource.RollingMaintenanceManager; -import com.cloud.server.ResourceIcon; -import com.cloud.server.ResourceTag; -import com.cloud.server.ResourceTag.ResourceObjectType; -import com.cloud.service.ServiceOfferingVO; -import com.cloud.storage.DataStoreRole; -import com.cloud.storage.DiskOfferingVO; -import com.cloud.storage.GuestOS; -import com.cloud.storage.GuestOSCategoryVO; -import com.cloud.storage.GuestOSHypervisor; -import com.cloud.storage.GuestOsCategory; -import com.cloud.storage.ImageStore; -import com.cloud.storage.Snapshot; -import com.cloud.storage.SnapshotVO; -import com.cloud.storage.StoragePool; -import com.cloud.storage.Upload; -import com.cloud.storage.VMTemplateVO; -import com.cloud.storage.Volume; -import com.cloud.storage.VolumeVO; -import com.cloud.storage.dao.GuestOSCategoryDao; -import com.cloud.storage.dao.GuestOSDao; -import com.cloud.storage.dao.VolumeDao; -import com.cloud.storage.snapshot.SnapshotPolicy; -import com.cloud.storage.snapshot.SnapshotSchedule; -import com.cloud.tags.dao.ResourceTagDao; -import com.cloud.template.VirtualMachineTemplate; -import com.cloud.user.Account; -import com.cloud.user.AccountManager; -import com.cloud.user.SSHKeyPair; -import com.cloud.user.User; -import com.cloud.user.UserAccount; -import com.cloud.user.UserData; -import com.cloud.user.UserStatisticsVO; -import com.cloud.user.dao.UserDataDao; -import com.cloud.user.dao.UserStatisticsDao; -import com.cloud.uservm.UserVm; -import com.cloud.utils.Pair; -import com.cloud.utils.crypt.DBEncryptionUtil; -import com.cloud.utils.db.EntityManager; -import com.cloud.utils.db.SearchBuilder; -import com.cloud.utils.db.SearchCriteria; -import com.cloud.utils.db.SearchCriteria.Op; -import com.cloud.utils.exception.CloudRuntimeException; -import com.cloud.utils.net.Dhcp; -import com.cloud.utils.net.Ip; -import com.cloud.utils.net.NetUtils; -import com.cloud.utils.security.CertificateHelper; -import com.cloud.vm.ConsoleProxyVO; -import com.cloud.vm.InstanceGroup; -import com.cloud.vm.Nic; -import com.cloud.vm.NicExtraDhcpOptionVO; -import com.cloud.vm.NicProfile; -import com.cloud.vm.NicSecondaryIp; -import com.cloud.vm.NicVO; -import com.cloud.vm.VMInstanceVO; -import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VirtualMachine.Type; -import com.cloud.vm.dao.NicExtraDhcpOptionDao; -import com.cloud.vm.dao.NicSecondaryIpVO; -import com.cloud.vm.snapshot.VMSnapshot; -import com.cloud.vm.snapshot.VMSnapshotVO; -import com.cloud.vm.snapshot.dao.VMSnapshotDao; +import javax.inject.Inject; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TimeZone; +import java.util.function.Consumer; +import java.util.stream.Collectors; -import sun.security.x509.X509CertImpl; +import static com.cloud.utils.NumbersUtil.toHumanReadableSize; public class ApiResponseHelper implements ResponseGenerator { @@ -878,6 +875,15 @@ public SnapshotPolicyResponse createSnapshotPolicyResponse(SnapshotPolicy policy zoneResponses.add(zoneResponse); } policyResponse.setZones(new HashSet<>(zoneResponses)); + List poolResponses = new ArrayList<>(); + List pools = ApiDBUtils.findSnapshotPolicyPools(policy, vol); + for (StoragePoolVO pool : pools) { + StoragePoolResponse storagePoolResponse = new StoragePoolResponse(); + storagePoolResponse.setId(pool.getUuid()); + storagePoolResponse.setName(pool.getName()); + poolResponses.add(storagePoolResponse); + } + policyResponse.setStoragePools(new HashSet<>(poolResponses)); return policyResponse; } diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 046c3c1e6bce..364d0d6137dc 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -16,157 +16,6 @@ // under the License. package com.cloud.api.query; -import static com.cloud.vm.VmDetailConstants.SSH_PUBLIC_KEY; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import javax.inject.Inject; - -import com.cloud.cpu.CPU; -import org.apache.cloudstack.acl.ControlledEntity; -import org.apache.cloudstack.acl.ControlledEntity.ACLType; -import org.apache.cloudstack.acl.SecurityChecker; -import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO; -import org.apache.cloudstack.affinity.AffinityGroupResponse; -import org.apache.cloudstack.affinity.AffinityGroupVMMapVO; -import org.apache.cloudstack.affinity.dao.AffinityGroupDomainMapDao; -import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; -import org.apache.cloudstack.api.ApiCommandResourceType; -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd; -import org.apache.cloudstack.api.InternalIdentity; -import org.apache.cloudstack.api.ResourceDetail; -import org.apache.cloudstack.api.ResponseGenerator; -import org.apache.cloudstack.api.ResponseObject.ResponseView; -import org.apache.cloudstack.api.command.admin.account.ListAccountsCmdByAdmin; -import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmd; -import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmdByAdmin; -import org.apache.cloudstack.api.command.admin.host.ListHostTagsCmd; -import org.apache.cloudstack.api.command.admin.host.ListHostsCmd; -import org.apache.cloudstack.api.command.admin.internallb.ListInternalLBVMsCmd; -import org.apache.cloudstack.api.command.admin.iso.ListIsosCmdByAdmin; -import org.apache.cloudstack.api.command.admin.management.ListMgmtsCmd; -import org.apache.cloudstack.api.command.admin.resource.icon.ListResourceIconCmd; -import org.apache.cloudstack.api.command.admin.router.GetRouterHealthCheckResultsCmd; -import org.apache.cloudstack.api.command.admin.router.ListRoutersCmd; -import org.apache.cloudstack.api.command.admin.snapshot.ListSnapshotsCmdByAdmin; -import org.apache.cloudstack.api.command.admin.storage.ListImageStoresCmd; -import org.apache.cloudstack.api.command.admin.storage.ListObjectStoragePoolsCmd; -import org.apache.cloudstack.api.command.admin.storage.ListSecondaryStagingStoresCmd; -import org.apache.cloudstack.api.command.admin.storage.ListStoragePoolsCmd; -import org.apache.cloudstack.api.command.admin.storage.ListStorageTagsCmd; -import org.apache.cloudstack.api.command.admin.storage.heuristics.ListSecondaryStorageSelectorsCmd; -import org.apache.cloudstack.api.command.admin.template.ListTemplatesCmdByAdmin; -import org.apache.cloudstack.api.command.admin.user.ListUsersCmd; -import org.apache.cloudstack.api.command.admin.vm.ListAffectedVmsForStorageScopeChangeCmd; -import org.apache.cloudstack.api.command.admin.zone.ListZonesCmdByAdmin; -import org.apache.cloudstack.api.command.user.account.ListAccountsCmd; -import org.apache.cloudstack.api.command.user.account.ListProjectAccountsCmd; -import org.apache.cloudstack.api.command.user.address.ListQuarantinedIpsCmd; -import org.apache.cloudstack.api.command.user.affinitygroup.ListAffinityGroupsCmd; -import org.apache.cloudstack.api.command.user.bucket.ListBucketsCmd; -import org.apache.cloudstack.api.command.user.event.ListEventsCmd; -import org.apache.cloudstack.api.command.user.iso.ListIsosCmd; -import org.apache.cloudstack.api.command.user.job.ListAsyncJobsCmd; -import org.apache.cloudstack.api.command.user.offering.ListDiskOfferingsCmd; -import org.apache.cloudstack.api.command.user.offering.ListServiceOfferingsCmd; -import org.apache.cloudstack.api.command.user.project.ListProjectInvitationsCmd; -import org.apache.cloudstack.api.command.user.project.ListProjectsCmd; -import org.apache.cloudstack.api.command.user.resource.ListDetailOptionsCmd; -import org.apache.cloudstack.api.command.user.securitygroup.ListSecurityGroupsCmd; -import org.apache.cloudstack.api.command.user.snapshot.CopySnapshotCmd; -import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd; -import org.apache.cloudstack.api.command.user.tag.ListTagsCmd; -import org.apache.cloudstack.api.command.user.template.ListTemplatesCmd; -import org.apache.cloudstack.api.command.user.template.ListVnfTemplatesCmd; -import org.apache.cloudstack.api.command.user.vm.ListVMsCmd; -import org.apache.cloudstack.api.command.user.vmgroup.ListVMGroupsCmd; -import org.apache.cloudstack.api.command.user.volume.ListResourceDetailsCmd; -import org.apache.cloudstack.api.command.user.volume.ListVolumesCmd; -import org.apache.cloudstack.api.command.user.zone.ListZonesCmd; -import org.apache.cloudstack.api.response.AccountResponse; -import org.apache.cloudstack.api.response.AsyncJobResponse; -import org.apache.cloudstack.api.response.BucketResponse; -import org.apache.cloudstack.api.response.DetailOptionsResponse; -import org.apache.cloudstack.api.response.DiskOfferingResponse; -import org.apache.cloudstack.api.response.DomainResponse; -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.HostTagResponse; -import org.apache.cloudstack.api.response.ImageStoreResponse; -import org.apache.cloudstack.api.response.InstanceGroupResponse; -import org.apache.cloudstack.api.response.IpQuarantineResponse; -import org.apache.cloudstack.api.response.ListResponse; -import org.apache.cloudstack.api.response.ManagementServerResponse; -import org.apache.cloudstack.api.response.ObjectStoreResponse; -import org.apache.cloudstack.api.response.ProjectAccountResponse; -import org.apache.cloudstack.api.response.ProjectInvitationResponse; -import org.apache.cloudstack.api.response.ProjectResponse; -import org.apache.cloudstack.api.response.ResourceDetailResponse; -import org.apache.cloudstack.api.response.ResourceIconResponse; -import org.apache.cloudstack.api.response.ResourceTagResponse; -import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse; -import org.apache.cloudstack.api.response.SecondaryStorageHeuristicsResponse; -import org.apache.cloudstack.api.response.SecurityGroupResponse; -import org.apache.cloudstack.api.response.ServiceOfferingResponse; -import org.apache.cloudstack.api.response.SnapshotResponse; -import org.apache.cloudstack.api.response.StoragePoolResponse; -import org.apache.cloudstack.api.response.StorageTagResponse; -import org.apache.cloudstack.api.response.TemplateResponse; -import org.apache.cloudstack.api.response.UserResponse; -import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.api.response.VirtualMachineResponse; -import org.apache.cloudstack.api.response.VolumeResponse; -import org.apache.cloudstack.api.response.ZoneResponse; -import org.apache.cloudstack.backup.BackupOfferingVO; -import org.apache.cloudstack.backup.dao.BackupOfferingDao; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; -import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities; -import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver; -import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; -import org.apache.cloudstack.engine.subsystem.api.storage.TemplateState; -import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.cloudstack.framework.config.Configurable; -import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; -import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementVO; -import org.apache.cloudstack.outofbandmanagement.dao.OutOfBandManagementDao; -import org.apache.cloudstack.query.QueryService; -import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO; -import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; -import org.apache.cloudstack.secstorage.HeuristicVO; -import org.apache.cloudstack.secstorage.dao.SecondaryStorageHeuristicDao; -import org.apache.cloudstack.secstorage.heuristics.Heuristic; -import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao; -import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO; -import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; -import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO; -import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; -import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; -import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.collections.MapUtils; -import org.apache.commons.lang3.EnumUtils; -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; - import com.cloud.api.query.dao.AccountJoinDao; import com.cloud.api.query.dao.AffinityGroupJoinDao; import com.cloud.api.query.dao.AsyncJobJoinDao; @@ -216,6 +65,7 @@ import com.cloud.api.query.vo.VolumeJoinVO; import com.cloud.cluster.ManagementServerHostVO; import com.cloud.cluster.dao.ManagementServerHostDao; +import com.cloud.cpu.CPU; import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenter; import com.cloud.dc.DedicatedResourceVO; @@ -344,6 +194,154 @@ import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.VMInstanceDao; +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.acl.ControlledEntity.ACLType; +import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO; +import org.apache.cloudstack.affinity.AffinityGroupResponse; +import org.apache.cloudstack.affinity.AffinityGroupVMMapVO; +import org.apache.cloudstack.affinity.dao.AffinityGroupDomainMapDao; +import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd; +import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.api.ResourceDetail; +import org.apache.cloudstack.api.ResponseGenerator; +import org.apache.cloudstack.api.ResponseObject.ResponseView; +import org.apache.cloudstack.api.command.admin.account.ListAccountsCmdByAdmin; +import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmd; +import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmdByAdmin; +import org.apache.cloudstack.api.command.admin.host.ListHostTagsCmd; +import org.apache.cloudstack.api.command.admin.host.ListHostsCmd; +import org.apache.cloudstack.api.command.admin.internallb.ListInternalLBVMsCmd; +import org.apache.cloudstack.api.command.admin.iso.ListIsosCmdByAdmin; +import org.apache.cloudstack.api.command.admin.management.ListMgmtsCmd; +import org.apache.cloudstack.api.command.admin.resource.icon.ListResourceIconCmd; +import org.apache.cloudstack.api.command.admin.router.GetRouterHealthCheckResultsCmd; +import org.apache.cloudstack.api.command.admin.router.ListRoutersCmd; +import org.apache.cloudstack.api.command.admin.snapshot.ListSnapshotsCmdByAdmin; +import org.apache.cloudstack.api.command.admin.storage.ListImageStoresCmd; +import org.apache.cloudstack.api.command.admin.storage.ListObjectStoragePoolsCmd; +import org.apache.cloudstack.api.command.admin.storage.ListSecondaryStagingStoresCmd; +import org.apache.cloudstack.api.command.admin.storage.ListStoragePoolsCmd; +import org.apache.cloudstack.api.command.admin.storage.ListStorageTagsCmd; +import org.apache.cloudstack.api.command.admin.storage.heuristics.ListSecondaryStorageSelectorsCmd; +import org.apache.cloudstack.api.command.admin.template.ListTemplatesCmdByAdmin; +import org.apache.cloudstack.api.command.admin.user.ListUsersCmd; +import org.apache.cloudstack.api.command.admin.vm.ListAffectedVmsForStorageScopeChangeCmd; +import org.apache.cloudstack.api.command.admin.zone.ListZonesCmdByAdmin; +import org.apache.cloudstack.api.command.user.account.ListAccountsCmd; +import org.apache.cloudstack.api.command.user.account.ListProjectAccountsCmd; +import org.apache.cloudstack.api.command.user.address.ListQuarantinedIpsCmd; +import org.apache.cloudstack.api.command.user.affinitygroup.ListAffinityGroupsCmd; +import org.apache.cloudstack.api.command.user.bucket.ListBucketsCmd; +import org.apache.cloudstack.api.command.user.event.ListEventsCmd; +import org.apache.cloudstack.api.command.user.iso.ListIsosCmd; +import org.apache.cloudstack.api.command.user.job.ListAsyncJobsCmd; +import org.apache.cloudstack.api.command.user.offering.ListDiskOfferingsCmd; +import org.apache.cloudstack.api.command.user.offering.ListServiceOfferingsCmd; +import org.apache.cloudstack.api.command.user.project.ListProjectInvitationsCmd; +import org.apache.cloudstack.api.command.user.project.ListProjectsCmd; +import org.apache.cloudstack.api.command.user.resource.ListDetailOptionsCmd; +import org.apache.cloudstack.api.command.user.securitygroup.ListSecurityGroupsCmd; +import org.apache.cloudstack.api.command.user.snapshot.CopySnapshotCmd; +import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd; +import org.apache.cloudstack.api.command.user.tag.ListTagsCmd; +import org.apache.cloudstack.api.command.user.template.ListTemplatesCmd; +import org.apache.cloudstack.api.command.user.template.ListVnfTemplatesCmd; +import org.apache.cloudstack.api.command.user.vm.ListVMsCmd; +import org.apache.cloudstack.api.command.user.vmgroup.ListVMGroupsCmd; +import org.apache.cloudstack.api.command.user.volume.ListResourceDetailsCmd; +import org.apache.cloudstack.api.command.user.volume.ListVolumesCmd; +import org.apache.cloudstack.api.command.user.zone.ListZonesCmd; +import org.apache.cloudstack.api.response.AccountResponse; +import org.apache.cloudstack.api.response.AsyncJobResponse; +import org.apache.cloudstack.api.response.BucketResponse; +import org.apache.cloudstack.api.response.DetailOptionsResponse; +import org.apache.cloudstack.api.response.DiskOfferingResponse; +import org.apache.cloudstack.api.response.DomainResponse; +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.HostTagResponse; +import org.apache.cloudstack.api.response.ImageStoreResponse; +import org.apache.cloudstack.api.response.InstanceGroupResponse; +import org.apache.cloudstack.api.response.IpQuarantineResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ManagementServerResponse; +import org.apache.cloudstack.api.response.ObjectStoreResponse; +import org.apache.cloudstack.api.response.ProjectAccountResponse; +import org.apache.cloudstack.api.response.ProjectInvitationResponse; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.ResourceDetailResponse; +import org.apache.cloudstack.api.response.ResourceIconResponse; +import org.apache.cloudstack.api.response.ResourceTagResponse; +import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse; +import org.apache.cloudstack.api.response.SecondaryStorageHeuristicsResponse; +import org.apache.cloudstack.api.response.SecurityGroupResponse; +import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.SnapshotResponse; +import org.apache.cloudstack.api.response.StoragePoolResponse; +import org.apache.cloudstack.api.response.StorageTagResponse; +import org.apache.cloudstack.api.response.TemplateResponse; +import org.apache.cloudstack.api.response.UserResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.VirtualMachineResponse; +import org.apache.cloudstack.api.response.VolumeResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.backup.BackupOfferingVO; +import org.apache.cloudstack.backup.dao.BackupOfferingDao; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateState; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; +import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementVO; +import org.apache.cloudstack.outofbandmanagement.dao.OutOfBandManagementDao; +import org.apache.cloudstack.query.QueryService; +import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO; +import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; +import org.apache.cloudstack.secstorage.HeuristicVO; +import org.apache.cloudstack.secstorage.dao.SecondaryStorageHeuristicDao; +import org.apache.cloudstack.secstorage.heuristics.Heuristic; +import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao; +import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.EnumUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.cloud.vm.VmDetailConstants.SSH_PUBLIC_KEY; @Component public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements QueryService, Configurable { @@ -5503,9 +5501,17 @@ public ListResponse listSnapshots(ListSnapshotsCmd cmd) { public SnapshotResponse listSnapshot(CopySnapshotCmd cmd) { Account caller = CallContext.current().getCallingAccount(); List zoneIds = cmd.getDestinationZoneIds(); + Long zoneId = null; + String location = null; + if (CollectionUtils.isNotEmpty(zoneIds)) { + zoneId = zoneIds.get(0); + location = Snapshot.LocationType.SECONDARY.name(); + } else { + location = cmd.getSnapshot().getLocationType() != null ? cmd.getSnapshot().getLocationType().name() : null; + } Pair, Integer> result = searchForSnapshotsWithParams(cmd.getId(), null, null, null, null, null, - null, null, zoneIds.get(0), Snapshot.LocationType.SECONDARY.name(), + null, null, zoneId, location, false, null, null, null, null, null, null, null, true, false, caller); ResponseView respView = ResponseView.Restricted; diff --git a/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDao.java b/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDao.java index 4e916e66ae78..7f8b1a53d6d7 100644 --- a/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDao.java +++ b/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDao.java @@ -17,16 +17,15 @@ package com.cloud.api.query.dao; -import java.util.List; - -import org.apache.cloudstack.api.ResponseObject; -import org.apache.cloudstack.api.response.SnapshotResponse; - import com.cloud.api.query.vo.SnapshotJoinVO; import com.cloud.utils.Pair; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDao; import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.response.SnapshotResponse; + +import java.util.List; public interface SnapshotJoinDao extends GenericDao { @@ -38,4 +37,6 @@ public interface SnapshotJoinDao extends GenericDao { List searchBySnapshotStorePair(String... pairs); List findByDistinctIds(Long zoneId, Long... ids); + + List listBySnapshotIdAndZoneId(Long zoneId, Long id); } diff --git a/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java index 42a35f4412c8..d3564699a11a 100644 --- a/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java @@ -17,23 +17,6 @@ package com.cloud.api.query.dao; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; - -import org.apache.cloudstack.annotation.AnnotationService; -import org.apache.cloudstack.annotation.dao.AnnotationDao; -import org.apache.cloudstack.api.ResponseObject; -import org.apache.cloudstack.api.response.SnapshotResponse; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.cloudstack.query.QueryService; - import com.cloud.api.ApiDBUtils; import com.cloud.api.ApiResponseHelper; import com.cloud.api.query.vo.SnapshotJoinVO; @@ -49,6 +32,21 @@ import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.vm.VMInstanceVO; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.response.SnapshotResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.query.QueryService; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class SnapshotJoinDaoImpl extends GenericDaoBaseWithTagInformation implements SnapshotJoinDao { @@ -65,6 +63,8 @@ public class SnapshotJoinDaoImpl extends GenericDaoBaseWithTagInformation snapshotIdsSearch; + private final SearchBuilder snapshotByZoneSearch; + SnapshotJoinDaoImpl() { snapshotStorePairSearch = createSearchBuilder(); snapshotStorePairSearch.and("snapshotStoreState", snapshotStorePairSearch.entity().getStoreState(), SearchCriteria.Op.IN); @@ -76,6 +76,11 @@ public class SnapshotJoinDaoImpl extends GenericDaoBaseWithTagInformation findByDistinctIds(Long zoneId, Long... ids) { sc.setParameters("idsIN", ids); return searchIncludingRemoved(sc, searchFilter, null, false); } + + public List listBySnapshotIdAndZoneId(Long zoneId, Long id) { + if (id == null) { + return new ArrayList<>(); + } + SearchCriteria sc = snapshotByZoneSearch.create(); + if (zoneId != null) { + sc.setParameters("zoneId", zoneId); + } + sc.setParameters("id", id); + return listBy(sc); + } } diff --git a/server/src/main/java/com/cloud/storage/CreateSnapshotPayload.java b/server/src/main/java/com/cloud/storage/CreateSnapshotPayload.java index 868f785bdc2b..b230b1bae103 100644 --- a/server/src/main/java/com/cloud/storage/CreateSnapshotPayload.java +++ b/server/src/main/java/com/cloud/storage/CreateSnapshotPayload.java @@ -28,6 +28,7 @@ public class CreateSnapshotPayload { private Snapshot.LocationType locationType; private boolean asyncBackup; private List zoneIds; + private List storagePoolIds; public Long getSnapshotPolicyId() { return snapshotPolicyId; @@ -78,4 +79,12 @@ public List getZoneIds() { public void setZoneIds(List zoneIds) { this.zoneIds = zoneIds; } + + public List getStoragePoolIds() { + return storagePoolIds; + } + + public void setStoragePoolIds(List storagePoolIds) { + this.storagePoolIds = storagePoolIds; + } } diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index cb859f2dde91..ba22b88e4f99 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -16,110 +16,6 @@ // under the License. package com.cloud.storage; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; - -import javax.inject.Inject; - -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.ApiErrorCode; -import org.apache.cloudstack.api.InternalIdentity; -import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.ChangeOfferingForVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.ExtractVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.GetUploadParamsForVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.UploadVolumeCmd; -import org.apache.cloudstack.api.response.GetUploadParamsResponse; -import org.apache.cloudstack.backup.Backup; -import org.apache.cloudstack.backup.BackupManager; -import org.apache.cloudstack.backup.dao.BackupDao; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.direct.download.DirectDownloadHelper; -import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; -import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; -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.DataStoreDriver; -import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; -import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; -import org.apache.cloudstack.engine.subsystem.api.storage.HostScope; -import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; -import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; -import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo; -import org.apache.cloudstack.engine.subsystem.api.storage.Scope; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; -import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; -import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; -import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService; -import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService.VolumeApiResult; -import org.apache.cloudstack.framework.async.AsyncCallFuture; -import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.cloudstack.framework.config.Configurable; -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.cloudstack.framework.jobs.AsyncJob; -import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext; -import org.apache.cloudstack.framework.jobs.AsyncJobManager; -import org.apache.cloudstack.framework.jobs.Outcome; -import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao; -import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; -import org.apache.cloudstack.framework.jobs.impl.OutcomeImpl; -import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; -import org.apache.cloudstack.jobs.JobInfo; -import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO; -import org.apache.cloudstack.resourcedetail.SnapshotPolicyDetailVO; -import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; -import org.apache.cloudstack.resourcedetail.dao.SnapshotPolicyDetailsDao; -import org.apache.cloudstack.snapshot.SnapshotHelper; -import org.apache.cloudstack.storage.command.AttachAnswer; -import org.apache.cloudstack.storage.command.AttachCommand; -import org.apache.cloudstack.storage.command.DettachCommand; -import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand; -import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; -import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; -import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; -import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; -import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO; -import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; -import org.apache.cloudstack.utils.identity.ManagementServerNode; -import org.apache.cloudstack.utils.imagestore.ImageStoreUtil; -import org.apache.cloudstack.utils.jsinterpreter.TagAsRuleHelper; -import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.collections.MapUtils; -import org.apache.commons.lang3.BooleanUtils; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; - import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; import com.cloud.agent.api.ModifyTargetsCommand; @@ -235,6 +131,109 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonParseException; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.ChangeOfferingForVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.ExtractVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.GetUploadParamsForVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.UploadVolumeCmd; +import org.apache.cloudstack.api.response.GetUploadParamsResponse; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.direct.download.DirectDownloadHelper; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; +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.DataStoreCapabilities; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; +import org.apache.cloudstack.engine.subsystem.api.storage.HostScope; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.Scope; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService.VolumeApiResult; +import org.apache.cloudstack.framework.async.AsyncCallFuture; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.jobs.AsyncJob; +import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext; +import org.apache.cloudstack.framework.jobs.AsyncJobManager; +import org.apache.cloudstack.framework.jobs.Outcome; +import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao; +import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; +import org.apache.cloudstack.framework.jobs.impl.OutcomeImpl; +import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; +import org.apache.cloudstack.jobs.JobInfo; +import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO; +import org.apache.cloudstack.resourcedetail.SnapshotPolicyDetailVO; +import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; +import org.apache.cloudstack.resourcedetail.dao.SnapshotPolicyDetailsDao; +import org.apache.cloudstack.snapshot.SnapshotHelper; +import org.apache.cloudstack.storage.command.AttachAnswer; +import org.apache.cloudstack.storage.command.AttachCommand; +import org.apache.cloudstack.storage.command.DettachCommand; +import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand; +import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO; +import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; +import org.apache.cloudstack.utils.identity.ManagementServerNode; +import org.apache.cloudstack.utils.imagestore.ImageStoreUtil; +import org.apache.cloudstack.utils.jsinterpreter.TagAsRuleHelper; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; +import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; + +import javax.inject.Inject; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiService, VmWorkJobHandler, Configurable { public static final String VM_WORK_JOB_HANDLER = VolumeApiServiceImpl.class.getSimpleName(); @@ -3648,9 +3647,9 @@ protected Volume liveMigrateVolume(Volume volume, StoragePool destPool) throws S @Override @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_CREATE, eventDescription = "taking snapshot", async = true) public Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, - Snapshot.LocationType locationType, boolean asyncBackup, Map tags, List zoneIds) + Snapshot.LocationType locationType, boolean asyncBackup, Map tags, List zoneIds, List poolIds) throws ResourceAllocationException { - final Snapshot snapshot = takeSnapshotInternal(volumeId, policyId, snapshotId, account, quiescevm, locationType, asyncBackup, zoneIds); + final Snapshot snapshot = takeSnapshotInternal(volumeId, policyId, snapshotId, account, quiescevm, locationType, asyncBackup, zoneIds, poolIds); if (snapshot != null && MapUtils.isNotEmpty(tags)) { taggedResourceService.createTags(Collections.singletonList(snapshot.getUuid()), ResourceTag.ResourceObjectType.Snapshot, tags, null); } @@ -3658,7 +3657,7 @@ public Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Acco } private Snapshot takeSnapshotInternal(Long volumeId, Long policyId, Long snapshotId, Account account, - boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup, List zoneIds) + boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup, List zoneIds, List poolIds) throws ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); VolumeInfo volume = volFactory.getVolume(volumeId); @@ -3671,6 +3670,11 @@ private Snapshot takeSnapshotInternal(Long volumeId, Long policyId, Long snapsho } List details = snapshotPolicyDetailsDao.findDetails(policyId, ApiConstants.ZONE_ID); zoneIds = details.stream().map(d -> Long.valueOf(d.getValue())).collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(poolIds)) { + throw new InvalidParameterValueException(String.format("%s can not be specified for snapshots linked with snapshot policy", ApiConstants.STORAGE_ID_LIST)); + } + List poolDetails = snapshotPolicyDetailsDao.findDetails(policyId, ApiConstants.STORAGE_ID); + poolIds = poolDetails.stream().map(d -> Long.valueOf(d.getValue())).collect(Collectors.toList()); } if (CollectionUtils.isNotEmpty(zoneIds)) { for (Long destZoneId : zoneIds) { @@ -3709,14 +3713,14 @@ private Snapshot takeSnapshotInternal(Long volumeId, Long policyId, Long snapsho placeHolder = createPlaceHolderWork(vm.getId()); try { return orchestrateTakeVolumeSnapshot(volumeId, policyId, snapshotId, account, quiescevm, - locationType, asyncBackup, zoneIds); + locationType, asyncBackup, zoneIds, poolIds); } finally { _workJobDao.expunge(placeHolder.getId()); } } else { Outcome outcome = takeVolumeSnapshotThroughJobQueue(vm.getId(), volumeId, policyId, - snapshotId, account.getId(), quiescevm, locationType, asyncBackup, zoneIds); + snapshotId, account.getId(), quiescevm, locationType, asyncBackup, zoneIds, poolIds); try { outcome.get(); @@ -3749,13 +3753,16 @@ private Snapshot takeSnapshotInternal(Long volumeId, Long policyId, Long snapsho if (CollectionUtils.isNotEmpty(zoneIds)) { payload.setZoneIds(zoneIds); } + if (CollectionUtils.isNotEmpty(poolIds)) { + payload.setStoragePoolIds(poolIds); + } volume.addPayload(payload); return volService.takeSnapshot(volume); } } private Snapshot orchestrateTakeVolumeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, - boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup, List zoneIds) + boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup, List zoneIds, List poolIds) throws ResourceAllocationException { VolumeInfo volume = volFactory.getVolume(volumeId); @@ -3768,7 +3775,7 @@ private Snapshot orchestrateTakeVolumeSnapshot(Long volumeId, Long policyId, Lon throw new InvalidParameterValueException("VolumeId: " + volumeId + " is not in " + Volume.State.Ready + " state but " + volume.getState() + ". Cannot take snapshot."); } - boolean isSnapshotOnStorPoolOnly = volume.getStoragePoolType() == StoragePoolType.StorPool && BooleanUtils.toBoolean(_configDao.getValue("sp.bypass.secondary.storage")); + boolean isSnapshotOnStorPoolOnly = volume.getStoragePoolType() == StoragePoolType.StorPool && SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value(); if (volume.getEncryptFormat() != null && volume.getAttachedVM() != null && volume.getAttachedVM().getState() != State.Stopped && !isSnapshotOnStorPoolOnly) { logger.debug(String.format("Refusing to take snapshot of encrypted volume (%s) on running VM (%s)", volume, volume.getAttachedVM())); throw new UnsupportedOperationException("Volume snapshots for encrypted volumes are not supported if VM is running"); @@ -3785,6 +3792,10 @@ private Snapshot orchestrateTakeVolumeSnapshot(Long volumeId, Long policyId, Lon if (CollectionUtils.isNotEmpty(zoneIds)) { payload.setZoneIds(zoneIds); } + if (CollectionUtils.isNotEmpty(poolIds)) { + payload.setStoragePoolIds(poolIds); + } + volume.addPayload(payload); return volService.takeSnapshot(volume); @@ -3800,7 +3811,7 @@ private boolean isOperationSupported(VMTemplateVO template, UserVmVO userVm) { @Override @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_CREATE, eventDescription = "allocating snapshot", create = true) - public Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, List zoneIds) throws ResourceAllocationException { + public Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, List zoneIds, List poolIds) throws ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); VolumeInfo volume = volFactory.getVolume(volumeId); @@ -3849,6 +3860,7 @@ public Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, if (storagePool == null) { throw new InvalidParameterValueException("VolumeId: " + volumeId + " please attach this volume to a VM before create snapshot for it"); } + boolean canCopyOnPrimary = canCopyOnPrimary(poolIds, volume, CollectionUtils.isEmpty(poolIds)); if (CollectionUtils.isNotEmpty(zoneIds)) { if (policyId != null && policyId > 0) { @@ -3857,7 +3869,7 @@ public Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, if (Snapshot.LocationType.PRIMARY.equals(locationType)) { throw new InvalidParameterValueException(String.format("%s cannot be specified with snapshot %s as %s", ApiConstants.ZONE_ID_LIST, ApiConstants.LOCATION_TYPE, Snapshot.LocationType.PRIMARY)); } - if (Boolean.FALSE.equals(SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value())) { + if (Boolean.FALSE.equals(SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value()) && !canCopyOnPrimary) { throw new InvalidParameterValueException("Backing up of snapshot has been disabled. Snapshot can not be taken for multiple zones"); } if (DataCenter.Type.Edge.equals(zone.getType())) { @@ -3881,6 +3893,24 @@ public Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, return snapshotMgr.allocSnapshot(volumeId, policyId, snapshotName, locationType, false, zoneIds); } + private boolean canCopyOnPrimary(List poolIds, VolumeInfo volume, boolean isPoolIdsEmpty) { + if (!isPoolIdsEmpty) { + for (Long poolId : poolIds){ + DataStore dataStore = dataStoreMgr.getDataStore(poolId, DataStoreRole.Primary); + StoragePoolVO sPool = _storagePoolDao.findById(poolId); + if (dataStore != null + && !dataStore.getDriver().getCapabilities().containsKey(DataStoreCapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES.toString()) + && sPool.getPoolType() != volume.getStoragePoolType() + && volume.getPoolId() == poolId) { + throw new InvalidParameterValueException("The specified pool doesn't support copying snapshots between zones" + poolId); + } + } + } else { + return false; + } + return true; + } + @Override public Snapshot allocSnapshotForVm(Long vmId, Long volumeId, String snapshotName) throws ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); @@ -4994,7 +5024,7 @@ private Outcome migrateVolumeThroughJobQueue(VMInstanceVO vm, VolumeVO v } public Outcome takeVolumeSnapshotThroughJobQueue(final Long vmId, final Long volumeId, final Long policyId, final Long snapshotId, final Long accountId, final boolean quiesceVm, - final Snapshot.LocationType locationType, final boolean asyncBackup, final List zoneIds) { + final Snapshot.LocationType locationType, final boolean asyncBackup, final List zoneIds, List poolIds) { final CallContext context = CallContext.current(); final User callingUser = context.getCallingUser(); @@ -5016,7 +5046,7 @@ public Outcome takeVolumeSnapshotThroughJobQueue(final Long vmId, fina // save work context info (there are some duplications) VmWorkTakeVolumeSnapshot workInfo = new VmWorkTakeVolumeSnapshot(callingUser.getId(), accountId != null ? accountId : callingAccount.getId(), vm.getId(), - VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId, policyId, snapshotId, quiesceVm, locationType, asyncBackup, zoneIds); + VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId, policyId, snapshotId, quiesceVm, locationType, asyncBackup, zoneIds, poolIds); workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo)); _jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId()); @@ -5067,7 +5097,7 @@ private Pair orchestrateMigrateVolume(VmWorkMigrateVolum private Pair orchestrateTakeVolumeSnapshot(VmWorkTakeVolumeSnapshot work) throws Exception { Account account = _accountDao.findById(work.getAccountId()); orchestrateTakeVolumeSnapshot(work.getVolumeId(), work.getPolicyId(), work.getSnapshotId(), account, - work.isQuiesceVm(), work.getLocationType(), work.isAsyncBackup(), work.getZoneIds()); + work.isQuiesceVm(), work.getLocationType(), work.isAsyncBackup(), work.getZoneIds(), work.getPoolIds()); return new Pair(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(work.getSnapshotId())); } diff --git a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java index 50c8ff8b83a7..ad9d3eaf97a9 100755 --- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java @@ -129,6 +129,7 @@ import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePool; +import com.cloud.storage.StoragePoolStatus; import com.cloud.storage.VMTemplateStorageResourceAssoc; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; @@ -174,6 +175,8 @@ import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; @Component public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implements SnapshotManager, SnapshotApiService, Configurable { @@ -784,7 +787,8 @@ public boolean deleteSnapshot(long snapshotId, Long zoneId) { boolean result = snapshotStrategy.deleteSnapshot(snapshotId, zoneId); if (result) { for (Long zId : zoneIds) { - if (snapshotCheck.getState() == Snapshot.State.BackedUp) { + List listActiveSnapshots = _snapshotStoreDao.listReadyBySnapshotId(snapshotId); + if (snapshotCheck.getState() == Snapshot.State.BackedUp && CollectionUtils.isEmpty(listActiveSnapshots)) { UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_DELETE, snapshotCheck.getAccountId(), zId, snapshotId, snapshotCheck.getName(), null, null, 0L, snapshotCheck.getClass().getName(), snapshotCheck.getUuid()); } @@ -1002,11 +1006,13 @@ public boolean deleteSnapshotDirsForAccount(long accountId) { return success; } - protected void validatePolicyZones(List zoneIds, VolumeVO volume, Account caller) { - if (CollectionUtils.isEmpty(zoneIds)) { + protected void validatePolicyZones(List zoneIds, List poolIds, VolumeVO volume, Account caller) { + boolean hasPools = CollectionUtils.isNotEmpty(poolIds); + boolean hasZones = CollectionUtils.isNotEmpty(zoneIds); + if (!hasZones && !hasPools) { return; } - if (Boolean.FALSE.equals(SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value())) { + if (Boolean.FALSE.equals(SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value()) && hasZones) { throw new InvalidParameterValueException("Backing up of snapshot has been disabled. Snapshot can not be taken for multiple zones"); } final DataCenterVO zone = dataCenterDao.findById(volume.getDataCenterId()); @@ -1014,8 +1020,16 @@ protected void validatePolicyZones(List zoneIds, VolumeVO volume, Account throw new InvalidParameterValueException("Backing up of snapshot is not supported by the zone of the volume. Snapshots can not be taken for multiple zones"); } boolean isRootAdminCaller = _accountMgr.isRootAdmin(caller.getId()); - for (Long zoneId : zoneIds) { - getCheckedDestinationZoneForSnapshotCopy(zoneId, isRootAdminCaller); + + if (hasZones) { + for (Long zoneId : zoneIds) { + getCheckedDestinationZoneForSnapshotCopy(zoneId, isRootAdminCaller); + } + } + if (hasPools) { + for (Long poolId : poolIds) { + getCheckedDestinationStorageForSnapshotCopy(poolId, isRootAdminCaller); + } } } @@ -1121,15 +1135,16 @@ public SnapshotPolicyVO createPolicy(CreateSnapshotPolicyCmd cmd, Account policy } final List zoneIds = cmd.getZoneIds(); - validatePolicyZones(zoneIds, volume, caller); + final List poolIds = cmd.getStoragePoolIds(); + validatePolicyZones(zoneIds, poolIds, volume, caller); Map tags = cmd.getTags(); boolean active = true; - return persistSnapshotPolicy(volume, schedule, timezoneId, intvType, maxSnaps, display, active, tags, zoneIds); + return persistSnapshotPolicy(volume, schedule, timezoneId, intvType, maxSnaps, display, active, tags, zoneIds, poolIds); } - protected SnapshotPolicyVO persistSnapshotPolicy(VolumeVO volume, String schedule, String timezone, IntervalType intervalType, int maxSnaps, boolean display, boolean active, Map tags, List zoneIds) { + protected SnapshotPolicyVO persistSnapshotPolicy(VolumeVO volume, String schedule, String timezone, IntervalType intervalType, int maxSnaps, boolean display, boolean active, Map tags, List zoneIds, List poolIds) { long volumeId = volume.getId(); String volumeDescription = volume.getVolumeDescription(); @@ -1142,13 +1157,14 @@ protected SnapshotPolicyVO persistSnapshotPolicy(VolumeVO volume, String schedul logger.debug(String.format("Acquired lock for creating snapshot policy [%s] for volume %s.", intervalType, volumeDescription)); + try { SnapshotPolicyVO policy = _snapshotPolicyDao.findOneByVolumeInterval(volumeId, intervalType); if (policy == null) { - policy = createSnapshotPolicy(volumeId, schedule, timezone, intervalType, maxSnaps, display, zoneIds); + policy = createSnapshotPolicy(volumeId, schedule, timezone, intervalType, maxSnaps, display, zoneIds, poolIds); } else { - updateSnapshotPolicy(policy, schedule, timezone, intervalType, maxSnaps, active, display, zoneIds); + updateSnapshotPolicy(policy, schedule, timezone, intervalType, maxSnaps, active, display, zoneIds, poolIds); } createTagsForSnapshotPolicy(tags, policy); @@ -1160,7 +1176,7 @@ protected SnapshotPolicyVO persistSnapshotPolicy(VolumeVO volume, String schedul } } - protected SnapshotPolicyVO createSnapshotPolicy(long volumeId, String schedule, String timezone, IntervalType intervalType, int maxSnaps, boolean display, List zoneIds) { + protected SnapshotPolicyVO createSnapshotPolicy(long volumeId, String schedule, String timezone, IntervalType intervalType, int maxSnaps, boolean display, List zoneIds, List poolIds) { SnapshotPolicyVO policy = new SnapshotPolicyVO(volumeId, schedule, timezone, intervalType, maxSnaps, display); policy = _snapshotPolicyDao.persist(policy); if (CollectionUtils.isNotEmpty(zoneIds)) { @@ -1170,12 +1186,19 @@ protected SnapshotPolicyVO createSnapshotPolicy(long volumeId, String schedule, } snapshotPolicyDetailsDao.saveDetails(details); } + if (CollectionUtils.isNotEmpty(poolIds)) { + List details = new ArrayList<>(); + for (Long poolId : poolIds) { + details.add(new SnapshotPolicyDetailVO(policy.getId(), ApiConstants.STORAGE_ID, String.valueOf(poolId))); + } + snapshotPolicyDetailsDao.saveDetails(details); + } _snapSchedMgr.scheduleNextSnapshotJob(policy); logger.debug(String.format("Created snapshot policy %s.", new ReflectionToStringBuilder(policy, ToStringStyle.JSON_STYLE).setExcludeFieldNames("id", "uuid", "active"))); return policy; } - protected void updateSnapshotPolicy(SnapshotPolicyVO policy, String schedule, String timezone, IntervalType intervalType, int maxSnaps, boolean active, boolean display, List zoneIds) { + protected void updateSnapshotPolicy(SnapshotPolicyVO policy, String schedule, String timezone, IntervalType intervalType, int maxSnaps, boolean active, boolean display, List zoneIds, List poolIds) { String previousPolicy = new ReflectionToStringBuilder(policy, ToStringStyle.JSON_STYLE).setExcludeFieldNames("id", "uuid").toString(); boolean previousDisplay = policy.isDisplay(); policy.setSchedule(schedule); @@ -1193,7 +1216,14 @@ protected void updateSnapshotPolicy(SnapshotPolicyVO policy, String schedule, St } snapshotPolicyDetailsDao.saveDetails(details); } - + if (CollectionUtils.isNotEmpty(poolIds)) { + List details = snapshotPolicyDetailsDao.listDetails(policy.getId()); + details = details.stream().filter(d -> !ApiConstants.STORAGE_ID.equals(d.getName())).collect(Collectors.toList()); + for (Long poolId : poolIds) { + details.add(new SnapshotPolicyDetailVO(policy.getId(), ApiConstants.STORAGE_ID, String.valueOf(poolId))); + } + snapshotPolicyDetailsDao.saveDetails(details); + } _snapSchedMgr.scheduleOrCancelNextSnapshotJobOnDisplayChange(policy, previousDisplay); taggedResourceService.deleteTags(Collections.singletonList(policy.getUuid()), ResourceObjectType.SnapshotPolicy, null); logger.debug(String.format("Updated snapshot policy %s to %s.", previousPolicy, new ReflectionToStringBuilder(policy, ToStringStyle.JSON_STYLE) @@ -1217,8 +1247,10 @@ public void copySnapshotPoliciesBetweenVolumes(VolumeVO srcVolume, VolumeVO dest for (SnapshotPolicyVO policy : policies) { List details = snapshotPolicyDetailsDao.findDetails(policy.getId(), ApiConstants.ZONE_ID); List zoneIds = details.stream().map(d -> Long.valueOf(d.getValue())).collect(Collectors.toList()); + List poolDetails = snapshotPolicyDetailsDao.findDetails(policy.getId(), ApiConstants.STORAGE_ID); + List poolIds = poolDetails.stream().map(d -> Long.valueOf(d.getValue())).collect(Collectors.toList()); persistSnapshotPolicy(destVolume, policy.getSchedule(), policy.getTimezone(), intervalTypes[policy.getInterval()], policy.getMaxSnaps(), - policy.isDisplay(), policy.isActive(), taggedResourceService.getTagsFromResource(ResourceObjectType.SnapshotPolicy, policy.getId()), zoneIds); + policy.isDisplay(), policy.isActive(), taggedResourceService.getTagsFromResource(ResourceObjectType.SnapshotPolicy, policy.getId()), zoneIds, poolIds); } } @@ -1455,9 +1487,15 @@ public SnapshotInfo takeSnapshot(VolumeInfo volume) throws ResourceAllocationExc boolean backupSnapToSecondary = isBackupSnapshotToSecondaryForZone(snapshot.getDataCenterId()); if (backupSnapToSecondary) { - backupSnapshotToSecondary(payload.getAsyncBackup(), snapshotStrategy, snapshotOnPrimary, payload.getZoneIds()); + backupSnapshotToSecondary(payload.getAsyncBackup(), snapshotStrategy, snapshotOnPrimary, payload.getZoneIds(), payload.getStoragePoolIds()); } else { logger.debug("skipping backup of snapshot [uuid=" + snapshot.getUuid() + "] to secondary due to configuration"); + if (CollectionUtils.isNotEmpty(payload.getStoragePoolIds()) && payload.getAsyncBackup()) { + snapshotStrategy = _storageStrategyFactory.getSnapshotStrategy(snapshot, SnapshotOperation.COPY); + if (snapshotStrategy != null) { + backupSnapshotExecutor.schedule(new BackupSnapshotTask(snapshotOnPrimary, snapshotBackupRetries - 1, snapshotStrategy, payload.getZoneIds(), payload.getStoragePoolIds()), 0, TimeUnit.SECONDS); + } + } snapshotOnPrimary.markBackedUp(); } @@ -1478,8 +1516,13 @@ public SnapshotInfo takeSnapshot(VolumeInfo volume) throws ResourceAllocationExc // Correct the resource count of snapshot in case of delta snapshots. _resourceLimitMgr.decrementResourceCount(snapshotOwner.getId(), ResourceType.secondary_storage, new Long(volume.getSize() - snapshotStoreRef.getPhysicalSize())); - if (!payload.getAsyncBackup() && backupSnapToSecondary) { - copyNewSnapshotToZones(snapshotId, snapshot.getDataCenterId(), payload.getZoneIds()); + if (!payload.getAsyncBackup()) { + if (backupSnapToSecondary) { + copyNewSnapshotToZones(snapshotId, snapshot.getDataCenterId(), payload.getZoneIds()); + } + if (CollectionUtils.isNotEmpty(payload.getStoragePoolIds())) { + copyNewSnapshotToZonesOnPrimary(payload, snapshot); + } } } catch (Exception e) { logger.debug("post process snapshot failed", e); @@ -1502,9 +1545,59 @@ public SnapshotInfo takeSnapshot(VolumeInfo volume) throws ResourceAllocationExc return snapshot; } - protected void backupSnapshotToSecondary(boolean asyncBackup, SnapshotStrategy snapshotStrategy, SnapshotInfo snapshotOnPrimary, List zoneIds) { + private void copyNewSnapshotToZonesOnPrimary(CreateSnapshotPayload payload, SnapshotInfo snapshot) { + SnapshotStrategy snapshotStrategy; + snapshotStrategy = _storageStrategyFactory.getSnapshotStrategy(snapshot, SnapshotOperation.COPY); + if (snapshotStrategy != null) { + for (Long storagePoolId : payload.getStoragePoolIds()) { + copySnapshotOnPool(snapshot, snapshotStrategy, storagePoolId); + } + } else { + logger.info("Unable to find snapshot strategy to handle the copy of a snapshot with id " + snapshot.getUuid()); + } + } + + private boolean copySnapshotOnPool(SnapshotInfo snapshot, SnapshotStrategy snapshotStrategy, Long storagePoolId) { + DataStore store = dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary); + SnapshotInfo snapshotOnStore = (SnapshotInfo) store.create(snapshot); +// if (snapshotOnStore.getStatus() == ObjectInDataStoreStateMachine.State.Allocated) { +// snapshotOnStore.processEvent(Event.CreateOnlyRequested); +// } else if (snapshotOnStore.getStatus() == ObjectInDataStoreStateMachine.State.Ready) { +// snapshotOnStore.processEvent(Event.CopyRequested); +// } else { +// logger.info(String.format("Cannot copy snapshot to another storage in different zone. It's not in the right state %s", snapshotOnStore.getStatus())); +// return false; +// } + + try { + AsyncCallFuture future = snapshotSrv.copySnapshot(snapshot, snapshotOnStore, snapshotStrategy); + SnapshotResult result = future.get(); + if (result.isFailed()) { + logger.debug(String.format("Copy snapshot ID: %d failed for primary storage %s: %s", snapshot.getSnapshotId(), storagePoolId, result.getResult())); + return false; + } + snapshotZoneDao.addSnapshotToZone(snapshot.getId(), snapshotOnStore.getDataCenterId()); + _resourceLimitMgr.incrementResourceCount(CallContext.current().getCallingUserId(), ResourceType.primary_storage, snapshot.getSize()); + if (CallContext.current().getCallingUserId() != Account.ACCOUNT_ID_SYSTEM) { + SnapshotVO snapshotVO = _snapshotDao.findByIdIncludingRemoved(snapshot.getSnapshotId()); + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_COPY, CallContext.current().getCallingUserId(), snapshotOnStore.getDataCenterId(), snapshotVO.getId(), null, null, null, snapshotVO.getSize(), + snapshotVO.getSize(), snapshotVO.getClass().getName(), snapshotVO.getUuid()); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } +// boolean copySuccessful = snapshotStrategy.copySnapshot(snapshot, snapshotOnStore, null); +// if (!copySuccessful) { +// snapshotOnStore.processEvent(Event.OperationFailed); +// } + return true; + } + + protected void backupSnapshotToSecondary(boolean asyncBackup, SnapshotStrategy snapshotStrategy, SnapshotInfo snapshotOnPrimary, List zoneIds, List poolIds) { if (asyncBackup) { - backupSnapshotExecutor.schedule(new BackupSnapshotTask(snapshotOnPrimary, snapshotBackupRetries - 1, snapshotStrategy, zoneIds), 0, TimeUnit.SECONDS); + backupSnapshotExecutor.schedule(new BackupSnapshotTask(snapshotOnPrimary, snapshotBackupRetries - 1, snapshotStrategy, zoneIds, poolIds), 0, TimeUnit.SECONDS); } else { SnapshotInfo backupedSnapshot = snapshotStrategy.backupSnapshot(snapshotOnPrimary); if (backupedSnapshot != null) { @@ -1519,33 +1612,46 @@ protected class BackupSnapshotTask extends ManagedContextRunnable { SnapshotStrategy snapshotStrategy; List zoneIds; + List poolIds; - public BackupSnapshotTask(SnapshotInfo snap, int maxRetries, SnapshotStrategy strategy, List zoneIds) { + public BackupSnapshotTask(SnapshotInfo snap, int maxRetries, SnapshotStrategy strategy, List zoneIds, List poolIds) { snapshot = snap; attempts = maxRetries; snapshotStrategy = strategy; this.zoneIds = zoneIds; + this.poolIds = poolIds; } @Override protected void runInContext() { try { logger.debug("Value of attempts is " + (snapshotBackupRetries - attempts)); + if (Boolean.TRUE.equals(SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value()) && CollectionUtils.isEmpty(zoneIds)) { + SnapshotInfo backupedSnapshot = snapshotStrategy.backupSnapshot(snapshot); - SnapshotInfo backupedSnapshot = snapshotStrategy.backupSnapshot(snapshot); + if (backupedSnapshot != null) { + snapshotStrategy.postSnapshotCreation(snapshot); + copyNewSnapshotToZones(snapshot.getId(), snapshot.getDataCenterId(), zoneIds); + } + } - if (backupedSnapshot != null) { - snapshotStrategy.postSnapshotCreation(snapshot); - copyNewSnapshotToZones(snapshot.getId(), snapshot.getDataCenterId(), zoneIds); + if (CollectionUtils.isNotEmpty(poolIds)) { + for (Long poolId: poolIds) { + copySnapshotOnPool(snapshot, snapshotStrategy, poolId); + } } } catch (final Exception e) { - if (attempts >= 0) { - logger.debug("Backing up of snapshot failed, for snapshot with ID " + snapshot.getSnapshotId() + ", left with " + attempts + " more attempts"); - backupSnapshotExecutor.schedule(new BackupSnapshotTask(snapshot, --attempts, snapshotStrategy, zoneIds), snapshotBackupRetryInterval, TimeUnit.SECONDS); - } else { - logger.debug("Done with " + snapshotBackupRetries + " attempts in backing up of snapshot with ID " + snapshot.getSnapshotId()); - snapshotSrv.cleanupOnSnapshotBackupFailure(snapshot); - } + decriseBackupSnapshotAttempts(); + } + } + + private void decriseBackupSnapshotAttempts() { + if (attempts >= 0) { + logger.debug("Backing up of snapshot failed, for snapshot with ID " + snapshot.getSnapshotId() + ", left with " + attempts + " more attempts"); + backupSnapshotExecutor.schedule(new BackupSnapshotTask(snapshot, --attempts, snapshotStrategy, zoneIds, poolIds), snapshotBackupRetryInterval, TimeUnit.SECONDS); + } else { + logger.debug("Done with " + snapshotBackupRetries + " attempts in backing up of snapshot with ID " + snapshot.getSnapshotId()); + snapshotSrv.cleanupOnSnapshotBackupFailure(snapshot); } } } @@ -1910,26 +2016,23 @@ private List copySnapshotToZones(SnapshotVO snapshotVO, DataStore srcSec return failedZones; } - protected Pair getCheckedSnapshotForCopy(final long snapshotId, final List destZoneIds, Long sourceZoneId) { - SnapshotVO snapshot = _snapshotDao.findById(snapshotId); - if (snapshot == null) { - throw new InvalidParameterValueException("Unable to find snapshot with id"); - } + protected Pair getCheckedSnapshotForCopy(final SnapshotVO snapshot, final List destZoneIds, Long sourceZoneId, boolean canCopyBeteenStoragePools) { // Verify snapshot is BackedUp and is on secondary store - if (!Snapshot.State.BackedUp.equals(snapshot.getState())) { + if (!Snapshot.State.BackedUp.equals(snapshot.getState()) && !canCopyBeteenStoragePools) { throw new InvalidParameterValueException("Snapshot is not backed up"); } - if (snapshot.getLocationType() != null && !Snapshot.LocationType.SECONDARY.equals(snapshot.getLocationType())) { + if (snapshot.getLocationType() != null && !Snapshot.LocationType.SECONDARY.equals(snapshot.getLocationType()) && !canCopyBeteenStoragePools) { throw new InvalidParameterValueException("Snapshot is not backed up"); } - if (CollectionUtils.isEmpty(destZoneIds)) { - throw new InvalidParameterValueException("Please specify valid destination zone(s)."); - } Volume volume = _volsDao.findById(snapshot.getVolumeId()); if (sourceZoneId == null) { sourceZoneId = volume.getDataCenterId(); } - if (destZoneIds.contains(sourceZoneId)) { + if (CollectionUtils.isEmpty(destZoneIds)) { + if (!canCopyBeteenStoragePools) { + throw new InvalidParameterValueException("Please specify valid destination zone(s)."); + } + } else if (destZoneIds.contains(sourceZoneId)) { throw new InvalidParameterValueException("Please specify different source and destination zones."); } DataCenterVO sourceZone = dataCenterDao.findById(sourceZoneId); @@ -1954,14 +2057,36 @@ protected DataCenterVO getCheckedDestinationZoneForSnapshotCopy(long zoneId, boo return dstZone; } + protected StoragePoolVO getCheckedDestinationStorageForSnapshotCopy(long poolId, boolean isRootAdmin) { + StoragePoolVO destPool = _storagePoolDao.findById(poolId); + if (destPool == null) { + throw new InvalidParameterValueException("Please specify a valid destination zone."); + } + if (!StoragePoolStatus.Up.equals(destPool.getStatus()) && !isRootAdmin) { + throw new PermissionDeniedException("Cannot perform this operation, the storage pool is not in Up state: " + destPool.getName()); + } + DataCenterVO destZone = dataCenterDao.findById(destPool.getDataCenterId()); + if (DataCenter.Type.Edge.equals(destZone.getType())) { + logger.error(String.format("Edge zone %s specified for snapshot copy", destZone)); + throw new InvalidParameterValueException(String.format("Snapshot copy is not supported by zone %s", destZone.getName())); + } + return destPool; + } + @Override @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_COPY, eventDescription = "copying snapshot", create = false) public Snapshot copySnapshot(CopySnapshotCmd cmd) throws StorageUnavailableException, ResourceAllocationException { final Long snapshotId = cmd.getId(); Long sourceZoneId = cmd.getSourceZoneId(); List destZoneIds = cmd.getDestinationZoneIds(); + List storagePoolIds = cmd.getStoragePoolIds(); Account caller = CallContext.current().getCallingAccount(); - Pair snapshotZonePair = getCheckedSnapshotForCopy(snapshotId, destZoneIds, sourceZoneId); + SnapshotVO snapshotVO = _snapshotDao.findById(snapshotId); + if (snapshotVO == null) { + throw new InvalidParameterValueException("Unable to find snapshot with id"); + } + boolean canCopyBetweenStoragePools = CollectionUtils.isNotEmpty(storagePoolIds) && canCopyOnPrimary(storagePoolIds, snapshotVO); + Pair snapshotZonePair = getCheckedSnapshotForCopy(snapshotVO, destZoneIds, sourceZoneId, canCopyBetweenStoragePools); SnapshotVO snapshot = snapshotZonePair.first(); sourceZoneId = snapshotZonePair.second(); Map dataCenterVOs = new HashMap<>(); @@ -1972,11 +2097,14 @@ public Snapshot copySnapshot(CopySnapshotCmd cmd) throws StorageUnavailableExcep } _accountMgr.checkAccess(caller, SecurityChecker.AccessType.OperateEntry, true, snapshot); DataStore srcSecStore = getSnapshotZoneImageStore(snapshotId, sourceZoneId); - if (srcSecStore == null) { + if (srcSecStore == null && !canCopyBetweenStoragePools) { throw new InvalidParameterValueException(String.format("There is no snapshot ID: %s ready on image store", snapshot.getUuid())); } + if (canCopyBetweenStoragePools) { + copySnapshotToPrimaryDifferentZone(storagePoolIds, snapshot); + } List failedZones = copySnapshotToZones(snapshot, srcSecStore, new ArrayList<>(dataCenterVOs.values())); - if (destZoneIds.size() > failedZones.size()){ + if (destZoneIds.size() > failedZones.size() || canCopyBetweenStoragePools){ if (!failedZones.isEmpty()) { logger.error(String.format("There were failures when copying snapshot to zones: %s", StringUtils.joinWith(", ", failedZones.toArray()))); @@ -1987,6 +2115,61 @@ public Snapshot copySnapshot(CopySnapshotCmd cmd) throws StorageUnavailableExcep } } + private boolean canCopyOnPrimary(List poolIds, Snapshot snapshot) { + List poolsToBeRemoved = new ArrayList<>(); + for (Long poolId : poolIds) { + PrimaryDataStore dataStore = (PrimaryDataStore) dataStoreMgr.getDataStore(poolId, DataStoreRole.Primary); + if (dataStore == null) { + poolsToBeRemoved.add(poolId); + continue; + } + SnapshotInfo snapshotInfo = snapshotFactory.getSnapshot(snapshot.getId(), poolId, DataStoreRole.Primary); + if (snapshotInfo != null) { + logger.debug(String.format("Snapshot [%s] already exist on pool [%s]", snapshot.getUuid(), dataStore.getName())); + continue; + } + + VolumeVO volume = _volsDao.findById(snapshot.getVolumeId()); + if (volume == null) { + poolsToBeRemoved.add(poolId); + continue; + } + if (!dataStore.getDriver().getCapabilities() + .containsKey(DataStoreCapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES.toString()) + && dataStore.getPoolType() != volume.getPoolType()) { + poolsToBeRemoved.add(poolId); + logger.debug(String.format("The %s does not support copy to %s between zones", dataStore.getPoolType(), volume.getPoolType())); + } + } + poolIds.removeAll(poolsToBeRemoved); + if (CollectionUtils.isEmpty(poolIds)) { + return false; + } + return true; + } + + private void copySnapshotToPrimaryDifferentZone(List poolIds, SnapshotVO snapshot) { + VolumeInfo volume = volFactory.getVolume(snapshot.getVolumeId()); + if (volume == null) { + throw new CloudRuntimeException("Failed to find volume with id: " + snapshot.getVolumeId()); + } + CreateSnapshotPayload payload = setPayload(poolIds, volume, snapshot); + SnapshotInfo snapshotInfo = snapshotFactory.getSnapshotOnPrimaryStore(snapshot.getId()); + copyNewSnapshotToZonesOnPrimary(payload, snapshotInfo); + } + + private CreateSnapshotPayload setPayload(List poolIds, VolumeInfo vol, SnapshotVO snapshotCreate) { + CreateSnapshotPayload payload = new CreateSnapshotPayload(); + payload.setSnapshotId(snapshotCreate.getId()); + payload.setSnapshotPolicyId(SnapshotVO.MANUAL_POLICY_ID); + payload.setLocationType(snapshotCreate.getLocationType()); + payload.setAccount(_accountMgr.getAccount(vol.getAccountId())); + payload.setAsyncBackup(false); + payload.setQuiescevm(false); + payload.setStoragePoolIds(poolIds); + return payload; + } + protected void copyNewSnapshotToZones(long snapshotId, long zoneId, List destZoneIds) { if (CollectionUtils.isEmpty(destZoneIds)) { return; diff --git a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java index 41e0b6f93fff..fbbf2b1fb319 100755 --- a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java @@ -16,102 +16,6 @@ // under the License. package com.cloud.template; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.stream.Collectors; - -import javax.inject.Inject; -import javax.naming.ConfigurationException; - -import com.cloud.cpu.CPU; -import org.apache.cloudstack.acl.SecurityChecker.AccessType; -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseCmd; -import org.apache.cloudstack.api.BaseListTemplateOrIsoPermissionsCmd; -import org.apache.cloudstack.api.BaseUpdateTemplateOrIsoCmd; -import org.apache.cloudstack.api.BaseUpdateTemplateOrIsoPermissionsCmd; -import org.apache.cloudstack.api.command.user.iso.DeleteIsoCmd; -import org.apache.cloudstack.api.command.user.iso.ExtractIsoCmd; -import org.apache.cloudstack.api.command.user.iso.GetUploadParamsForIsoCmd; -import org.apache.cloudstack.api.command.user.iso.ListIsoPermissionsCmd; -import org.apache.cloudstack.api.command.user.iso.RegisterIsoCmd; -import org.apache.cloudstack.api.command.user.iso.UpdateIsoCmd; -import org.apache.cloudstack.api.command.user.iso.UpdateIsoPermissionsCmd; -import org.apache.cloudstack.api.command.user.template.CopyTemplateCmd; -import org.apache.cloudstack.api.command.user.template.CreateTemplateCmd; -import org.apache.cloudstack.api.command.user.template.DeleteTemplateCmd; -import org.apache.cloudstack.api.command.user.template.ExtractTemplateCmd; -import org.apache.cloudstack.api.command.user.template.GetUploadParamsForTemplateCmd; -import org.apache.cloudstack.api.command.user.template.ListTemplatePermissionsCmd; -import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd; -import org.apache.cloudstack.api.command.user.template.RegisterVnfTemplateCmd; -import org.apache.cloudstack.api.command.user.template.UpdateTemplateCmd; -import org.apache.cloudstack.api.command.user.template.UpdateTemplatePermissionsCmd; -import org.apache.cloudstack.api.command.user.template.UpdateVnfTemplateCmd; -import org.apache.cloudstack.api.command.user.userdata.LinkUserDataToTemplateCmd; -import org.apache.cloudstack.api.response.GetUploadParamsResponse; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; -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.EndPoint; -import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; -import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; -import org.apache.cloudstack.engine.subsystem.api.storage.Scope; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; -import org.apache.cloudstack.engine.subsystem.api.storage.StorageCacheManager; -import org.apache.cloudstack.engine.subsystem.api.storage.StorageStrategyFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; -import org.apache.cloudstack.engine.subsystem.api.storage.TemplateService; -import org.apache.cloudstack.engine.subsystem.api.storage.TemplateService.TemplateApiResult; -import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; -import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; -import org.apache.cloudstack.framework.async.AsyncCallFuture; -import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.cloudstack.framework.config.Configurable; -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.cloudstack.framework.messagebus.MessageBus; -import org.apache.cloudstack.framework.messagebus.PublishScope; -import org.apache.cloudstack.managed.context.ManagedContextRunnable; -import org.apache.cloudstack.secstorage.dao.SecondaryStorageHeuristicDao; -import org.apache.cloudstack.secstorage.heuristics.HeuristicType; -import org.apache.cloudstack.snapshot.SnapshotHelper; -import org.apache.cloudstack.storage.command.AttachCommand; -import org.apache.cloudstack.storage.command.CommandResult; -import org.apache.cloudstack.storage.command.DettachCommand; -import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand; -import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; -import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; -import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; -import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; -import org.apache.cloudstack.storage.heuristics.HeuristicRuleHelper; -import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; -import org.apache.cloudstack.storage.template.VnfTemplateManager; -import org.apache.cloudstack.storage.template.VnfTemplateUtils; -import org.apache.cloudstack.storage.to.TemplateObjectTO; -import org.apache.cloudstack.utils.imagestore.ImageStoreUtil; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.collections.MapUtils; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; - import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; @@ -127,6 +31,7 @@ import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.configuration.Config; import com.cloud.configuration.Resource.ResourceType; +import com.cloud.cpu.CPU; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.DataCenterDao; @@ -220,6 +125,100 @@ import com.google.common.base.Joiner; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import org.apache.cloudstack.acl.SecurityChecker.AccessType; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.BaseListTemplateOrIsoPermissionsCmd; +import org.apache.cloudstack.api.BaseUpdateTemplateOrIsoCmd; +import org.apache.cloudstack.api.BaseUpdateTemplateOrIsoPermissionsCmd; +import org.apache.cloudstack.api.command.user.iso.DeleteIsoCmd; +import org.apache.cloudstack.api.command.user.iso.ExtractIsoCmd; +import org.apache.cloudstack.api.command.user.iso.GetUploadParamsForIsoCmd; +import org.apache.cloudstack.api.command.user.iso.ListIsoPermissionsCmd; +import org.apache.cloudstack.api.command.user.iso.RegisterIsoCmd; +import org.apache.cloudstack.api.command.user.iso.UpdateIsoCmd; +import org.apache.cloudstack.api.command.user.iso.UpdateIsoPermissionsCmd; +import org.apache.cloudstack.api.command.user.template.CopyTemplateCmd; +import org.apache.cloudstack.api.command.user.template.CreateTemplateCmd; +import org.apache.cloudstack.api.command.user.template.DeleteTemplateCmd; +import org.apache.cloudstack.api.command.user.template.ExtractTemplateCmd; +import org.apache.cloudstack.api.command.user.template.GetUploadParamsForTemplateCmd; +import org.apache.cloudstack.api.command.user.template.ListTemplatePermissionsCmd; +import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd; +import org.apache.cloudstack.api.command.user.template.RegisterVnfTemplateCmd; +import org.apache.cloudstack.api.command.user.template.UpdateTemplateCmd; +import org.apache.cloudstack.api.command.user.template.UpdateTemplatePermissionsCmd; +import org.apache.cloudstack.api.command.user.template.UpdateVnfTemplateCmd; +import org.apache.cloudstack.api.command.user.userdata.LinkUserDataToTemplateCmd; +import org.apache.cloudstack.api.response.GetUploadParamsResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; +import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.Scope; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.StorageCacheManager; +import org.apache.cloudstack.engine.subsystem.api.storage.StorageStrategyFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateService; +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateService.TemplateApiResult; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; +import org.apache.cloudstack.framework.async.AsyncCallFuture; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.messagebus.MessageBus; +import org.apache.cloudstack.framework.messagebus.PublishScope; +import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.secstorage.dao.SecondaryStorageHeuristicDao; +import org.apache.cloudstack.secstorage.heuristics.HeuristicType; +import org.apache.cloudstack.snapshot.SnapshotHelper; +import org.apache.cloudstack.storage.command.AttachCommand; +import org.apache.cloudstack.storage.command.CommandResult; +import org.apache.cloudstack.storage.command.DettachCommand; +import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand; +import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; +import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; +import org.apache.cloudstack.storage.heuristics.HeuristicRuleHelper; +import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; +import org.apache.cloudstack.storage.template.VnfTemplateManager; +import org.apache.cloudstack.storage.template.VnfTemplateUtils; +import org.apache.cloudstack.storage.to.TemplateObjectTO; +import org.apache.cloudstack.utils.imagestore.ImageStoreUtil; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; public class TemplateManagerImpl extends ManagerBase implements TemplateManager, TemplateApiService, Configurable { @@ -320,6 +319,8 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, @Inject private HeuristicRuleHelper heuristicRuleHelper; + protected boolean backupSnapshotAfterTakingSnapshot = SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value(); + private TemplateAdapter getAdapter(HypervisorType type) { TemplateAdapter adapter = null; if (type == HypervisorType.BareMetal) { @@ -1671,11 +1672,18 @@ public VirtualMachineTemplate createPrivateTemplate(CreateTemplateCmd command) t AsyncCallFuture future = null; if (snapshotId != null) { - DataStoreRole dataStoreRole = snapshotHelper.getDataStoreRole(snapshot); - kvmSnapshotOnlyInPrimaryStorage = snapshotHelper.isKvmSnapshotOnlyInPrimaryStorage(snapshot, dataStoreRole); + DataStoreRole dataStoreRole = snapshotHelper.getDataStoreRole(snapshot, zoneId); + kvmSnapshotOnlyInPrimaryStorage = snapshotHelper.isKvmSnapshotOnlyInPrimaryStorage(snapshot, dataStoreRole, zoneId); snapInfo = _snapshotFactory.getSnapshotWithRoleAndZone(snapshotId, dataStoreRole, zoneId); - if (dataStoreRole == DataStoreRole.Image || kvmSnapshotOnlyInPrimaryStorage) { + boolean skipCopyToSecondary = false; + + Map capabilities = snapInfo.getDataStore().getDriver().getCapabilities(); + boolean keepOnPrimary = MapUtils.isNotEmpty(capabilities) && capabilities.containsKey(DataStoreCapabilities.KEEP_SNAPSHOT_ON_PRIMARY_AND_BACKUP.toString()); + if (kvmSnapshotOnlyInPrimaryStorage && keepOnPrimary) { + skipCopyToSecondary = true; + } + if (dataStoreRole == DataStoreRole.Image || !skipCopyToSecondary) { snapInfo = snapshotHelper.backupSnapshotToSecondaryStorageIfNotExists(snapInfo, dataStoreRole, snapshot, kvmSnapshotOnlyInPrimaryStorage); _accountMgr.checkAccess(caller, null, true, snapInfo); DataStore snapStore = snapInfo.getDataStore(); @@ -1683,6 +1691,16 @@ public VirtualMachineTemplate createPrivateTemplate(CreateTemplateCmd command) t if (snapStore != null) { store = snapStore; // pick snapshot image store to create template } + } else if (skipCopyToSecondary) { + ImageStoreVO imageStore = _imgStoreDao.findOneByZoneAndProtocol(zoneId, "nfs"); + if (imageStore == null) { + throw new CloudRuntimeException(String.format("Could not find an NFS secondary storage pool on zone %s to use as a temporary location " + + "for instance conversion", zoneId)); + } + DataStore dataStore = _dataStoreMgr.getDataStore(imageStore.getId(), DataStoreRole.Image); + if (dataStore != null) { + store = dataStore; + } } future = _tmpltSvr.createTemplateFromSnapshotAsync(snapInfo, tmplInfo, store); diff --git a/server/src/main/java/org/apache/cloudstack/snapshot/SnapshotHelper.java b/server/src/main/java/org/apache/cloudstack/snapshot/SnapshotHelper.java index 64d9b3467e3f..c24fa9820883 100644 --- a/server/src/main/java/org/apache/cloudstack/snapshot/SnapshotHelper.java +++ b/server/src/main/java/org/apache/cloudstack/snapshot/SnapshotHelper.java @@ -19,15 +19,17 @@ package org.apache.cloudstack.snapshot; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import javax.inject.Inject; - +import com.cloud.api.query.dao.SnapshotJoinDao; +import com.cloud.api.query.vo.SnapshotJoinVO; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.Snapshot; +import com.cloud.storage.SnapshotVO; +import com.cloud.storage.Storage.StoragePoolType; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.SnapshotDao; +import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; @@ -45,18 +47,16 @@ import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; -import com.cloud.hypervisor.Hypervisor; -import com.cloud.hypervisor.Hypervisor.HypervisorType; -import com.cloud.storage.DataStoreRole; -import com.cloud.storage.Snapshot; -import com.cloud.storage.SnapshotVO; -import com.cloud.storage.Storage.StoragePoolType; -import com.cloud.storage.VolumeVO; -import com.cloud.storage.dao.SnapshotDao; -import com.cloud.utils.exception.CloudRuntimeException; +import javax.inject.Inject; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; public class SnapshotHelper { protected Logger logger = LogManager.getLogger(getClass()); @@ -82,6 +82,9 @@ public class SnapshotHelper { @Inject protected PrimaryDataStoreDao primaryDataStoreDao; + @Inject + protected SnapshotJoinDao snapshotJoinDao; + protected boolean backupSnapshotAfterTakingSnapshot = SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value(); protected final Set storagePoolTypesToValidateWithBackupSnapshotAfterTakingSnapshot = new HashSet<>(Arrays.asList(StoragePoolType.RBD, @@ -92,7 +95,17 @@ public class SnapshotHelper { * @param snapInfo the snapshot info to delete. */ public void expungeTemporarySnapshot(boolean kvmSnapshotOnlyInPrimaryStorage, SnapshotInfo snapInfo) { - if (!kvmSnapshotOnlyInPrimaryStorage) { + long storeId = snapInfo.getDataStore().getId(); + long zoneId = dataStorageManager.getStoreZoneId(storeId, snapInfo.getDataStore().getRole()); + + Map capabilities = snapInfo.getDataStore().getDriver().getCapabilities(); + boolean keepOnPrimary = MapUtils.isNotEmpty(capabilities) && capabilities.containsKey(DataStoreCapabilities.KEEP_SNAPSHOT_ON_PRIMARY_AND_BACKUP.toString()); + if (keepOnPrimary) { + logger.debug("The primary storage does not delete the snapshots even if there is a backup on secondary"); + return; + } + List snapshots = snapshotJoinDao.listBySnapshotIdAndZoneId(zoneId, snapInfo.getSnapshotId()); + if (kvmSnapshotOnlyInPrimaryStorage || snapshots.size() <= 1) { if (snapInfo != null) { logger.trace(String.format("Snapshot [%s] is not a temporary backup to create a volume from snapshot. Not expunging it.", snapInfo.getId())); } @@ -119,9 +132,7 @@ public void expungeTemporarySnapshot(boolean kvmSnapshotOnlyInPrimaryStorage, Sn } } - long storeId = snapInfo.getDataStore().getId(); if (!DataStoreRole.Image.equals(snapInfo.getDataStore().getRole())) { - long zoneId = dataStorageManager.getStoreZoneId(storeId, snapInfo.getDataStore().getRole()); SnapshotInfo imageStoreSnapInfo = snapshotFactory.getSnapshotWithRoleAndZone(snapInfo.getId(), DataStoreRole.Image, zoneId); storeId = imageStoreSnapInfo.getDataStore().getId(); } @@ -182,8 +193,11 @@ protected boolean isSnapshotBackupable(SnapshotInfo snapInfo, DataStoreRole data * @return true if hypervisor is {@link HypervisorType#KVM} and data store role is {@link DataStoreRole#Primary} and global setting "snapshot.backup.to.secondary" is false, * else false. */ - public boolean isKvmSnapshotOnlyInPrimaryStorage(Snapshot snapshot, DataStoreRole dataStoreRole){ - return snapshot.getHypervisorType() == Hypervisor.HypervisorType.KVM && dataStoreRole == DataStoreRole.Primary && !backupSnapshotAfterTakingSnapshot; + public boolean isKvmSnapshotOnlyInPrimaryStorage(Snapshot snapshot, DataStoreRole dataStoreRole, Long zoneId){ + List snapshots = snapshotJoinDao.listBySnapshotIdAndZoneId(zoneId, snapshot.getSnapshotId()); + boolean isKvmSnapshotOnlyInPrimaryStorage = snapshots.stream().filter(s -> s.getStoreRole().equals(DataStoreRole.Image)).count() == 0; + + return snapshot.getHypervisorType() == Hypervisor.HypervisorType.KVM && dataStoreRole == DataStoreRole.Primary && isKvmSnapshotOnlyInPrimaryStorage; } public DataStoreRole getDataStoreRole(Snapshot snapshot) { @@ -216,10 +230,21 @@ public DataStoreRole getDataStoreRole(Snapshot snapshot) { return DataStoreRole.Image; } - /** - * Verifies if it is a KVM volume that has snapshots only in primary storage. - * @throws CloudRuntimeException If it is a KVM volume and has at least one snapshot only in primary storage. - */ + public DataStoreRole getDataStoreRole(Snapshot snapshot, Long zoneId) { + if (zoneId == null) { + getDataStoreRole(snapshot); + } + List snapshots = snapshotJoinDao.listBySnapshotIdAndZoneId(zoneId, snapshot.getId()); + boolean snapshotOnPrimary = snapshots.stream().anyMatch(s -> s.getStoreRole().equals(DataStoreRole.Primary)); + if (snapshotOnPrimary) { + return DataStoreRole.Primary; + } + return DataStoreRole.Image; + } + /** + * Verifies if it is a KVM volume that has snapshots only in primary storage. + * @throws CloudRuntimeException If it is a KVM volume and has at least one snapshot only in primary storage. + */ public void checkKvmVolumeSnapshotsOnlyInPrimaryStorage(VolumeVO volumeVo, HypervisorType hypervisorType) throws CloudRuntimeException { if (HypervisorType.KVM != hypervisorType) { logger.trace(String.format("The %s hypervisor [%s] is not KVM, therefore we will not check if the snapshots are only in primary storage.", volumeVo, hypervisorType)); diff --git a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java index 80835891327f..617baf010b8d 100644 --- a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java +++ b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java @@ -541,7 +541,7 @@ public void testTakeSnapshotF1() throws ResourceAllocationException { when(volumeDataFactoryMock.getVolume(anyLong())).thenReturn(volumeInfoMock); when(volumeInfoMock.getState()).thenReturn(Volume.State.Allocated); lenient().when(volumeInfoMock.getPoolId()).thenReturn(1L); - volumeApiServiceImpl.takeSnapshot(5L, Snapshot.MANUAL_POLICY_ID, 3L, null, false, null, false, null, null); + volumeApiServiceImpl.takeSnapshot(5L, Snapshot.MANUAL_POLICY_ID, 3L, null, false, null, false, null, null, null); } @Test @@ -554,7 +554,7 @@ public void testTakeSnapshotF2() throws ResourceAllocationException { final TaggedResourceService taggedResourceService = Mockito.mock(TaggedResourceService.class); Mockito.lenient().when(taggedResourceService.createTags(any(), any(), any(), any())).thenReturn(null); ReflectionTestUtils.setField(volumeApiServiceImpl, "taggedResourceService", taggedResourceService); - volumeApiServiceImpl.takeSnapshot(5L, Snapshot.MANUAL_POLICY_ID, 3L, null, false, null, false, null, null); + volumeApiServiceImpl.takeSnapshot(5L, Snapshot.MANUAL_POLICY_ID, 3L, null, false, null, false, null, null, null); } @Test @@ -602,7 +602,7 @@ public void testUpdateMissingRootDiskControllerWithValidChainInfo() { @Test public void testAllocSnapshotNonManagedStorageArchive() { try { - volumeApiServiceImpl.allocSnapshot(6L, 1L, "test", Snapshot.LocationType.SECONDARY, null); + volumeApiServiceImpl.allocSnapshot(6L, 1L, "test", Snapshot.LocationType.SECONDARY, null, null); } catch (InvalidParameterValueException e) { Assert.assertEquals(e.getMessage(), "VolumeId: 6 LocationType is supported only for managed storage"); return; diff --git a/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerImplTest.java b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerImplTest.java index e6c2a0d0f3cf..a74c41b4257f 100644 --- a/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerImplTest.java +++ b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerImplTest.java @@ -16,30 +16,6 @@ // under the License. package com.cloud.storage.snapshot; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutionException; - -import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; -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.SnapshotDataFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotResult; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService; -import org.apache.cloudstack.framework.async.AsyncCallFuture; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; -import org.mockito.stubbing.Answer; - import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.DataCenterDao; @@ -62,6 +38,29 @@ import com.cloud.user.ResourceLimitService; import com.cloud.user.dao.AccountDao; import com.cloud.utils.Pair; +import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; +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.SnapshotDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotResult; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService; +import org.apache.cloudstack.framework.async.AsyncCallFuture; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; @RunWith(MockitoJUnitRunner.class) public class SnapshotManagerImplTest { @@ -176,7 +175,7 @@ public void testGetStoreRefsAndZonesForSnapshotDeleteSingle() { } @Test public void testValidatePolicyZonesNoZones() { - snapshotManager.validatePolicyZones(null, Mockito.mock(VolumeVO.class), Mockito.mock(Account.class)); + snapshotManager.validatePolicyZones(null, null, Mockito.mock(VolumeVO.class), Mockito.mock(Account.class)); } @Test(expected = InvalidParameterValueException.class) @@ -186,7 +185,7 @@ public void testValidatePolicyZonesVolumeEdgeZone() { DataCenterVO zone = Mockito.mock(DataCenterVO.class); Mockito.when(zone.getType()).thenReturn(DataCenter.Type.Edge); Mockito.when(dataCenterDao.findById(1L)).thenReturn(zone); - snapshotManager.validatePolicyZones(List.of(1L), volumeVO, Mockito.mock(Account.class)); + snapshotManager.validatePolicyZones(List.of(1L), null, volumeVO, Mockito.mock(Account.class)); } @Test(expected = InvalidParameterValueException.class) @@ -197,7 +196,7 @@ public void testValidatePolicyZonesNullZone() { Mockito.when(zone.getType()).thenReturn(DataCenter.Type.Core); Mockito.when(dataCenterDao.findById(1L)).thenReturn(zone); Mockito.when(dataCenterDao.findById(2L)).thenReturn(null); - snapshotManager.validatePolicyZones(List.of(2L), volumeVO, Mockito.mock(Account.class)); + snapshotManager.validatePolicyZones(List.of(2L), null, volumeVO, Mockito.mock(Account.class)); } @Test(expected = PermissionDeniedException.class) @@ -211,7 +210,7 @@ public void testValidatePolicyZonesDisabledZone() { Mockito.when(zone1.getAllocationState()).thenReturn(Grouping.AllocationState.Disabled); Mockito.when(dataCenterDao.findById(2L)).thenReturn(zone1); Mockito.when(accountManager.isRootAdmin(Mockito.any())).thenReturn(false); - snapshotManager.validatePolicyZones(List.of(2L), volumeVO, Mockito.mock(Account.class)); + snapshotManager.validatePolicyZones(List.of(2L), null, volumeVO, Mockito.mock(Account.class)); } @Test(expected = InvalidParameterValueException.class) @@ -225,7 +224,7 @@ public void testValidatePolicyZonesEdgeZone() { Mockito.when(zone1.getType()).thenReturn(DataCenter.Type.Edge); Mockito.when(zone1.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled); Mockito.when(dataCenterDao.findById(2L)).thenReturn(zone1); - snapshotManager.validatePolicyZones(List.of(2L), volumeVO, Mockito.mock(Account.class)); + snapshotManager.validatePolicyZones(List.of(2L), null, volumeVO, Mockito.mock(Account.class)); } @Test @@ -239,7 +238,7 @@ public void testValidatePolicyZonesValidZone() { Mockito.when(zone1.getType()).thenReturn(DataCenter.Type.Core); Mockito.when(zone1.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled); Mockito.when(dataCenterDao.findById(2L)).thenReturn(zone1); - snapshotManager.validatePolicyZones(List.of(2L), volumeVO, Mockito.mock(Account.class)); + snapshotManager.validatePolicyZones(List.of(2L), null, volumeVO, Mockito.mock(Account.class)); } @Test @@ -308,15 +307,14 @@ public void testCopyNewSnapshotToZones() { @Test(expected = InvalidParameterValueException.class) public void testGetCheckedSnapshotForCopyNoSnapshot() { - snapshotManager.getCheckedSnapshotForCopy(1L, List.of(100L), null); + SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class); + snapshotManager.getCheckedSnapshotForCopy(snapshotVO, List.of(100L), null, false); } @Test(expected = InvalidParameterValueException.class) public void testGetCheckedSnapshotForCopyNoSnapshotBackup() { - final long snapshotId = 1L; SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class); - Mockito.when(snapshotDao.findById(snapshotId)).thenReturn(snapshotVO); - snapshotManager.getCheckedSnapshotForCopy(snapshotId, List.of(100L), null); + snapshotManager.getCheckedSnapshotForCopy(snapshotVO, List.of(100L), null, false); } @Test(expected = InvalidParameterValueException.class) @@ -325,73 +323,62 @@ public void testGetCheckedSnapshotForCopyNotOnSecondary() { SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class); Mockito.when(snapshotVO.getState()).thenReturn(Snapshot.State.BackedUp); Mockito.when(snapshotVO.getLocationType()).thenReturn(Snapshot.LocationType.PRIMARY); - Mockito.when(snapshotDao.findById(snapshotId)).thenReturn(snapshotVO); - snapshotManager.getCheckedSnapshotForCopy(snapshotId, List.of(100L), null); + snapshotManager.getCheckedSnapshotForCopy(snapshotVO, List.of(100L), null, false); } @Test(expected = InvalidParameterValueException.class) public void testGetCheckedSnapshotForCopyDestNotSpecified() { - final long snapshotId = 1L; SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class); Mockito.when(snapshotVO.getState()).thenReturn(Snapshot.State.BackedUp); - Mockito.when(snapshotDao.findById(snapshotId)).thenReturn(snapshotVO); - snapshotManager.getCheckedSnapshotForCopy(snapshotId, new ArrayList<>(), null); + snapshotManager.getCheckedSnapshotForCopy(snapshotVO, new ArrayList<>(), 1L, false); } @Test(expected = InvalidParameterValueException.class) public void testGetCheckedSnapshotForCopyDestContainsSource() { - final long snapshotId = 1L; SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class); Mockito.when(snapshotVO.getState()).thenReturn(Snapshot.State.BackedUp); Mockito.when(snapshotVO.getVolumeId()).thenReturn(1L); - Mockito.when(snapshotDao.findById(snapshotId)).thenReturn(snapshotVO); Mockito.when(volumeDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(VolumeVO.class)); - snapshotManager.getCheckedSnapshotForCopy(snapshotId, List.of(100L, 1L), 1L); + snapshotManager.getCheckedSnapshotForCopy(snapshotVO, List.of(100L, 1L), 1L, false); } @Test(expected = InvalidParameterValueException.class) public void testGetCheckedSnapshotForCopyNullSourceZone() { - final long snapshotId = 1L; SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class); Mockito.when(snapshotVO.getState()).thenReturn(Snapshot.State.BackedUp); Mockito.when(snapshotVO.getVolumeId()).thenReturn(1L); - Mockito.when(snapshotDao.findById(snapshotId)).thenReturn(snapshotVO); VolumeVO volumeVO = Mockito.mock(VolumeVO.class); Mockito.when(volumeVO.getDataCenterId()).thenReturn(1L); Mockito.when(volumeDao.findById(Mockito.anyLong())).thenReturn(volumeVO); - snapshotManager.getCheckedSnapshotForCopy(snapshotId, List.of(100L, 101L), null); + snapshotManager.getCheckedSnapshotForCopy(snapshotVO, List.of(100L, 101L), null, false); } @Test public void testGetCheckedSnapshotForCopyValid() { - final long snapshotId = 1L; final Long zoneId = 1L; SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class); Mockito.when(snapshotVO.getState()).thenReturn(Snapshot.State.BackedUp); Mockito.when(snapshotVO.getVolumeId()).thenReturn(1L); - Mockito.when(snapshotDao.findById(snapshotId)).thenReturn(snapshotVO); VolumeVO volumeVO = Mockito.mock(VolumeVO.class); Mockito.when(volumeVO.getDataCenterId()).thenReturn(zoneId); Mockito.when(volumeDao.findById(Mockito.anyLong())).thenReturn(volumeVO); Mockito.when(dataCenterDao.findById(zoneId)).thenReturn(Mockito.mock(DataCenterVO.class)); - Pair result = snapshotManager.getCheckedSnapshotForCopy(snapshotId, List.of(100L, 101L), null); + Pair result = snapshotManager.getCheckedSnapshotForCopy(snapshotVO, List.of(100L, 101L), null, false); Assert.assertNotNull(result.first()); Assert.assertEquals(zoneId, result.second()); } @Test public void testGetCheckedSnapshotForCopyNullDest() { - final long snapshotId = 1L; final Long zoneId = 1L; SnapshotVO snapshotVO = Mockito.mock(SnapshotVO.class); Mockito.when(snapshotVO.getState()).thenReturn(Snapshot.State.BackedUp); Mockito.when(snapshotVO.getVolumeId()).thenReturn(1L); - Mockito.when(snapshotDao.findById(snapshotId)).thenReturn(snapshotVO); VolumeVO volumeVO = Mockito.mock(VolumeVO.class); Mockito.when(volumeVO.getDataCenterId()).thenReturn(zoneId); Mockito.when(volumeDao.findById(Mockito.anyLong())).thenReturn(volumeVO); Mockito.when(dataCenterDao.findById(zoneId)).thenReturn(Mockito.mock(DataCenterVO.class)); - Pair result = snapshotManager.getCheckedSnapshotForCopy(snapshotId, List.of(100L, 101L), null); + Pair result = snapshotManager.getCheckedSnapshotForCopy(snapshotVO, List.of(100L, 101L), null, false); Assert.assertNotNull(result.first()); Assert.assertEquals(zoneId, result.second()); } diff --git a/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java index 28903c72cc3c..d71039c6be65 100755 --- a/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java +++ b/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java @@ -16,65 +16,13 @@ // under the License. package com.cloud.storage.snapshot; -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - import com.cloud.api.ApiDBUtils; -import com.cloud.exception.PermissionDeniedException; -import com.cloud.storage.Storage; -import org.apache.cloudstack.api.command.user.snapshot.ExtractSnapshotCmd; -import org.apache.cloudstack.context.CallContext; -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.ObjectInDataStoreStateMachine; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation; -import org.apache.cloudstack.engine.subsystem.api.storage.StorageStrategyFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; -import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.cloudstack.snapshot.SnapshotHelper; -import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; -import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; -import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.BDDMockito; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; -import org.mockito.stubbing.Answer; - import com.cloud.configuration.Resource.ResourceType; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.DataCenterDao; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceAllocationException; import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.Hypervisor.HypervisorType; @@ -86,6 +34,7 @@ import com.cloud.storage.Snapshot; import com.cloud.storage.SnapshotPolicyVO; import com.cloud.storage.SnapshotVO; +import com.cloud.storage.Storage; import com.cloud.storage.Volume; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.SnapshotDao; @@ -107,6 +56,56 @@ import com.cloud.vm.snapshot.VMSnapshot; import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; +import org.apache.cloudstack.api.command.user.snapshot.ExtractSnapshotCmd; +import org.apache.cloudstack.context.CallContext; +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.ObjectInDataStoreStateMachine; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation; +import org.apache.cloudstack.engine.subsystem.api.storage.StorageStrategyFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.snapshot.SnapshotHelper; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.BDDMockito; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class SnapshotManagerTest { @@ -430,7 +429,7 @@ public void validateCreateSnapshotPolicy(){ Mockito.doReturn(null).when(snapshotSchedulerMock).scheduleNextSnapshotJob(any()); SnapshotPolicyVO result = _snapshotMgr.createSnapshotPolicy(TEST_VOLUME_ID, TEST_SNAPSHOT_POLICY_SCHEDULE, TEST_SNAPSHOT_POLICY_TIMEZONE, TEST_SNAPSHOT_POLICY_INTERVAL, - TEST_SNAPSHOT_POLICY_MAX_SNAPS, TEST_SNAPSHOT_POLICY_DISPLAY, null); + TEST_SNAPSHOT_POLICY_MAX_SNAPS, TEST_SNAPSHOT_POLICY_DISPLAY, null, null); assertSnapshotPolicyResultAgainstPreBuiltInstance(result, null); } @@ -445,7 +444,7 @@ public void validateUpdateSnapshotPolicy(){ TEST_SNAPSHOT_POLICY_MAX_SNAPS, TEST_SNAPSHOT_POLICY_DISPLAY); _snapshotMgr.updateSnapshotPolicy(snapshotPolicyVo, TEST_SNAPSHOT_POLICY_SCHEDULE, TEST_SNAPSHOT_POLICY_TIMEZONE, - TEST_SNAPSHOT_POLICY_INTERVAL, TEST_SNAPSHOT_POLICY_MAX_SNAPS, TEST_SNAPSHOT_POLICY_DISPLAY, TEST_SNAPSHOT_POLICY_ACTIVE, null); + TEST_SNAPSHOT_POLICY_INTERVAL, TEST_SNAPSHOT_POLICY_MAX_SNAPS, TEST_SNAPSHOT_POLICY_DISPLAY, TEST_SNAPSHOT_POLICY_ACTIVE, null, null); assertSnapshotPolicyResultAgainstPreBuiltInstance(snapshotPolicyVo, null); } @@ -480,7 +479,7 @@ public void validatePersistSnapshotPolicyLockIsNotAquiredMustThrowException() { Mockito.doReturn(false).when(globalLockMock).lock(Mockito.anyInt()); _snapshotMgr.persistSnapshotPolicy(volumeMock, TEST_SNAPSHOT_POLICY_SCHEDULE, TEST_SNAPSHOT_POLICY_TIMEZONE, TEST_SNAPSHOT_POLICY_INTERVAL, TEST_SNAPSHOT_POLICY_MAX_SNAPS, - TEST_SNAPSHOT_POLICY_DISPLAY, TEST_SNAPSHOT_POLICY_ACTIVE, mapStringStringMock, null); + TEST_SNAPSHOT_POLICY_DISPLAY, TEST_SNAPSHOT_POLICY_ACTIVE, mapStringStringMock, null, null); } } @@ -505,7 +504,7 @@ private void testPersistSnapshotPolicyLockAcquired(boolean forUpdate) { for (IntervalType intervalType : listIntervalTypes) { Mockito.doReturn(forUpdate ? snapshotPolicyVoInstance : null).when(snapshotPolicyDaoMock).findOneByVolumeInterval(Mockito.anyLong(), Mockito.eq(intervalType)); SnapshotPolicyVO result = _snapshotMgr.persistSnapshotPolicy(volumeMock, TEST_SNAPSHOT_POLICY_SCHEDULE, TEST_SNAPSHOT_POLICY_TIMEZONE, intervalType, - TEST_SNAPSHOT_POLICY_MAX_SNAPS, TEST_SNAPSHOT_POLICY_DISPLAY, TEST_SNAPSHOT_POLICY_ACTIVE, null, null); + TEST_SNAPSHOT_POLICY_MAX_SNAPS, TEST_SNAPSHOT_POLICY_DISPLAY, TEST_SNAPSHOT_POLICY_ACTIVE, null, null, null); assertSnapshotPolicyResultAgainstPreBuiltInstance(result, (short)intervalType.ordinal()); } diff --git a/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java b/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java index 98b1c05dba83..c6215014ee7f 100755 --- a/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java +++ b/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java @@ -20,6 +20,7 @@ import com.cloud.agent.AgentManager; +import com.cloud.api.query.dao.SnapshotJoinDao; import com.cloud.api.query.dao.UserVmJoinDao; import com.cloud.configuration.Resource; import com.cloud.dc.dao.DataCenterDao; @@ -204,6 +205,8 @@ public class TemplateManagerImplTest { AccountManager _accountMgr; @Inject VnfTemplateManager vnfTemplateManager; + @Inject + SnapshotJoinDao snapshotJoinDao; @Inject HeuristicRuleHelper heuristicRuleHelperMock; @@ -975,6 +978,11 @@ public SecondaryStorageHeuristicDao secondaryStorageHeuristicDao() { public HeuristicRuleHelper heuristicRuleHelper() { return Mockito.mock(HeuristicRuleHelper.class); } + @Bean + public SnapshotJoinDao snapshotJoinDao() { + return Mockito.mock(SnapshotJoinDao.class); + } + public static class Library implements TypeFilter { @Override diff --git a/server/src/test/java/org/apache/cloudstack/snapshot/SnapshotHelperTest.java b/server/src/test/java/org/apache/cloudstack/snapshot/SnapshotHelperTest.java index 84e7144e5f62..b2d3996a499d 100644 --- a/server/src/test/java/org/apache/cloudstack/snapshot/SnapshotHelperTest.java +++ b/server/src/test/java/org/apache/cloudstack/snapshot/SnapshotHelperTest.java @@ -19,13 +19,15 @@ package org.apache.cloudstack.snapshot; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - +import com.cloud.api.query.dao.SnapshotJoinDao; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.SnapshotDao; +import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; @@ -42,12 +44,12 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import com.cloud.hypervisor.Hypervisor; -import com.cloud.hypervisor.Hypervisor.HypervisorType; -import com.cloud.storage.DataStoreRole; -import com.cloud.storage.VolumeVO; -import com.cloud.storage.dao.SnapshotDao; -import com.cloud.utils.exception.CloudRuntimeException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; @RunWith(MockitoJUnitRunner.class) public class SnapshotHelperTest { @@ -83,6 +85,8 @@ public class SnapshotHelperTest { @Mock VolumeVO volumeVoMock; + @Mock + SnapshotJoinDao snapshotJoinDao; List dataStoreRoles = Arrays.asList(DataStoreRole.values()); @@ -94,10 +98,18 @@ public void init() { snapshotHelperSpy.storageStrategyFactory = storageStrategyFactoryMock; snapshotHelperSpy.snapshotDao = snapshotDaoMock; snapshotHelperSpy.dataStorageManager = dataStoreManager; + snapshotHelperSpy.snapshotJoinDao = snapshotJoinDao; } @Test public void validateExpungeTemporarySnapshotNotAKvmSnapshotOnPrimaryStorageDoNothing() { + DataStore store = Mockito.mock(DataStore.class); + DataStoreDriver storeDriver = Mockito.mock(DataStoreDriver.class); + Mockito.when(snapshotInfoMock.getDataStore()).thenReturn(store); + Mockito.when(snapshotInfoMock.getDataStore().getId()).thenReturn(1L); + Mockito.when(snapshotInfoMock.getSnapshotId()).thenReturn(1L); + Mockito.when(snapshotInfoMock.getDataStore().getDriver()).thenReturn(storeDriver); + Mockito.when(snapshotInfoMock.getDataStore().getDriver().getCapabilities()).thenReturn(new HashMap<>()); snapshotHelperSpy.expungeTemporarySnapshot(false, snapshotInfoMock); Mockito.verifyNoInteractions(snapshotServiceMock, snapshotDataStoreDaoMock); } @@ -105,27 +117,28 @@ public void validateExpungeTemporarySnapshotNotAKvmSnapshotOnPrimaryStorageDoNot @Test public void validateExpungeTemporarySnapshotKvmSnapshotOnPrimaryStorageExpungesSnapshot() { DataStore store = Mockito.mock(DataStore.class); + DataStoreDriver storeDriver = Mockito.mock(DataStoreDriver.class); + Mockito.when(store.getRole()).thenReturn(DataStoreRole.Image); Mockito.when(store.getId()).thenReturn(1L); Mockito.when(snapshotInfoMock.getDataStore()).thenReturn(store); - Mockito.doReturn(true).when(snapshotServiceMock).deleteSnapshot(Mockito.any()); - Mockito.doReturn(true).when(snapshotDataStoreDaoMock).expungeReferenceBySnapshotIdAndDataStoreRole(Mockito.anyLong(), Mockito.anyLong(), Mockito.any()); - + Mockito.when(snapshotInfoMock.getDataStore().getDriver()).thenReturn(storeDriver); + Mockito.when(snapshotInfoMock.getDataStore().getDriver().getCapabilities()).thenReturn(new HashMap<>()); snapshotHelperSpy.expungeTemporarySnapshot(true, snapshotInfoMock); - - Mockito.verify(snapshotServiceMock).deleteSnapshot(Mockito.any()); - Mockito.verify(snapshotDataStoreDaoMock).expungeReferenceBySnapshotIdAndDataStoreRole(Mockito.anyLong(), Mockito.anyLong(), Mockito.any()); } @Test public void validateIsKvmSnapshotOnlyInPrimaryStorageBackupToSecondaryTrue() { List hypervisorTypes = Arrays.asList(Hypervisor.HypervisorType.values()); - snapshotHelperSpy.backupSnapshotAfterTakingSnapshot = true; - hypervisorTypes.forEach(type -> { Mockito.doReturn(type).when(snapshotInfoMock).getHypervisorType(); dataStoreRoles.forEach(role -> { - Assert.assertFalse(snapshotHelperSpy.isKvmSnapshotOnlyInPrimaryStorage(snapshotInfoMock, role)); + if (!role.equals(DataStoreRole.Primary)) { + Assert.assertFalse(snapshotHelperSpy.isKvmSnapshotOnlyInPrimaryStorage(snapshotInfoMock, role, 1l)); + } else { + if (type.equals(HypervisorType.KVM)) + Assert.assertTrue(snapshotHelperSpy.isKvmSnapshotOnlyInPrimaryStorage(snapshotInfoMock, role, 1l)); + } }); }); } @@ -139,9 +152,9 @@ public void validateIsKvmSnapshotOnlyInPrimaryStorageBackupToSecondaryFalse() { Mockito.doReturn(type).when(snapshotInfoMock).getHypervisorType(); dataStoreRoles.forEach(role -> { if (type == Hypervisor.HypervisorType.KVM && role == DataStoreRole.Primary) { - Assert.assertTrue(snapshotHelperSpy.isKvmSnapshotOnlyInPrimaryStorage(snapshotInfoMock, role)); + Assert.assertTrue(snapshotHelperSpy.isKvmSnapshotOnlyInPrimaryStorage(snapshotInfoMock, role, null)); } else { - Assert.assertFalse(snapshotHelperSpy.isKvmSnapshotOnlyInPrimaryStorage(snapshotInfoMock, role)); + Assert.assertFalse(snapshotHelperSpy.isKvmSnapshotOnlyInPrimaryStorage(snapshotInfoMock, role, null)); } }); }); diff --git a/test/integration/plugins/storpool/TestSnapshotCopyOnPrimary.py b/test/integration/plugins/storpool/TestSnapshotCopyOnPrimary.py new file mode 100644 index 000000000000..b01ea2661efa --- /dev/null +++ b/test/integration/plugins/storpool/TestSnapshotCopyOnPrimary.py @@ -0,0 +1,255 @@ +# 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. + +import time + +# Import Local Modules +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.cloudstackAPI import (createSnapshot, + deleteSnapshot, + copySnapshot, + createVolume, + createTemplate, + listOsTypes) +from marvin.lib.utils import (cleanup_resources, + random_gen) +from marvin.lib.base import (Account, + Zone, + ServiceOffering, + DiskOffering, + VirtualMachine, + Volume, + Snapshot, + Template, + StoragePool) +from marvin.lib.common import (get_domain, + get_zone, + get_template) +from marvin.lib.decoratorGenerators import skipTestIf +from marvin.codes import FAILED, PASS +from nose.plugins.attrib import attr +import logging +from sp_util import (TestData, StorPoolHelper) +import math + +class TestSnapshotCopy(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestSnapshotCopy, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + + cls._cleanup = [] + cls.logger = logging.getLogger('TestSnapshotCopy') + cls.testsNotSupported = False + cls.zones = Zone.list(cls.apiclient) + cls.pools = StoragePool.list(cls.apiclient, status="Up") + enabled_core_zones = [] + if not isinstance(cls.zones, list): + cls.testsNotSupported = True + elif len(cls.zones) < 2: + cls.testsNotSupported = True + else: + for z in cls.zones: + if z.type == 'Core' and z.allocationstate == 'Enabled': + enabled_core_zones.append(z) + if len(enabled_core_zones) < 2: + cls.testsNotSupported = True + + if cls.testsNotSupported == True: + cls.logger.info("Unsupported") + return + + cls.additional_zone = None + for z in enabled_core_zones: + if z.id != cls.zone.id: + cls.additional_zone = z + + cls.storpool_pool = None + for pool in cls.pools: + if pool.provider == "StorPool" and pool.zoneid != cls.zone.id: + cls.storpool_pool = pool + break + + template = get_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"]) + if template == FAILED: + assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] + + # Set Zones and disk offerings + cls.services["small"]["zoneid"] = cls.zone.id + cls.services["small"]["template"] = template.id + cls.services["iso"]["zoneid"] = cls.zone.id + + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + domainid=cls.domain.id) + cls._cleanup.append(cls.account) + + cls.helper = StorPoolHelper() + + compute_offering_service = cls.services["service_offerings"]["tiny"].copy() + cls.service_offering = ServiceOffering.create( + cls.apiclient, + compute_offering_service) + cls._cleanup.append(cls.service_offering) + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["virtual_machine"]["template"] = template.id + cls.virtual_machine = VirtualMachine.create( + cls.apiclient, + cls.services["virtual_machine"], + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id, + mode=cls.services["mode"] + ) + cls._cleanup.append(cls.virtual_machine) + cls.volume = Volume.list( + cls.apiclient, + virtualmachineid=cls.virtual_machine.id, + type='ROOT', + listall=True + )[0] + + @classmethod + def tearDownClass(cls): + super(TestSnapshotCopy, cls).tearDownClass() + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.userapiclient = self.testClient.getUserApiClient( + UserName=self.account.name, + DomainName=self.account.domain + ) + self.dbclient = self.testClient.getDbConnection() + self.snapshot_id = None + self.cleanup = [] + + def tearDown(self): + super(TestSnapshotCopy, self).tearDown() + + + @skipTestIf("testsNotSupported") + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_01_take_snapshot_multi_zone(self): + """Test to take volume snapshot in multiple StorPool primary storage pools + """ + + snapshot = Snapshot.create(self.userapiclient, volume_id=self.volume.id, pool_ids =[str(self.storpool_pool.id)]) + self.snapshot_id = snapshot.id + self.helper.verify_snapshot_copies(self.userapiclient, self.snapshot_id, [self.zone.id, self.additional_zone.id]) + time.sleep(420) + Snapshot.delete(snapshot, self.userapiclient) + return + + @skipTestIf("testsNotSupported") + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_02_copy_snapshot_multi_pools(self): + """Test to take volume snapshot on StorPool primary storage and then copy on StorPool primary storage in another pool + """ + + snapshot = Snapshot.create(self.userapiclient, volume_id=self.volume.id) + self.snapshot_id = snapshot.id + Snapshot.copy(self.userapiclient, self.snapshot_id, pool_ids=[str(self.storpool_pool.id)], source_zone_id=self.zone.id) + self.helper.verify_snapshot_copies(self.userapiclient, self.snapshot_id, [self.zone.id, self.additional_zone.id]) + time.sleep(420) + Snapshot.delete(snapshot, self.userapiclient) + return + + @skipTestIf("testsNotSupported") + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_03_take_snapshot_multi_pools_delete_single_zone(self): + """Test to take volume snapshot in multiple StorPool storages in diff zones and delete from one zone + """ + + snapshot = Snapshot.create(self.userapiclient, volume_id=self.volume.id, pool_ids=[str(self.storpool_pool.id)]) + self.snapshot_id = snapshot.id + self.helper.verify_snapshot_copies(self.userapiclient, self.snapshot_id, [self.zone.id, self.additional_zone.id]) + time.sleep(420) + Snapshot.delete(snapshot, self.userapiclient, self.zone.id) + self.helper.verify_snapshot_copies(self.userapiclient, self.snapshot_id, [self.additional_zone.id]) + self.cleanup.append(snapshot) + return + + @skipTestIf("testsNotSupported") + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_04_copy_snapshot_multi_zone_delete_all(self): + """Test to take volume snapshot on StorPool, copy in another StorPool primary storage in another zone and delete for all + """ + + snapshot = Snapshot.create(self.userapiclient, volume_id=self.volume.id) + self.snapshot_id = snapshot.id + Snapshot.copy(self.userapiclient, self.snapshot_id, pool_ids=[str(self.storpool_pool.id)], source_zone_id=self.zone.id) + self.helper.verify_snapshot_copies(self.userapiclient, self.snapshot_id, [self.zone.id, self.additional_zone.id]) + time.sleep(420) + Snapshot.delete(snapshot, self.userapiclient) + snapshot_entries = Snapshot.list(self.userapiclient, id=snapshot.id) + if snapshot_entries and isinstance(snapshot_entries, list) and len(snapshot_entries) > 0: + self.fail("Snapshot delete for all zones failed") + return + + @skipTestIf("testsNotSupported") + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_05_take_snapshot_multi_zone_create_volume_additional_zone(self): + """Test to take volume snapshot on StorPool in multiple zones and create a volume in one of the additional zones + """ + + snapshot = Snapshot.create(self.userapiclient,volume_id=self.volume.id, pool_ids=[str(self.storpool_pool.id)]) + self.snapshot_id = snapshot.id + self.helper.verify_snapshot_copies(self.userapiclient, self.snapshot_id, [self.zone.id, self.additional_zone.id]) + disk_offering_id = None + if snapshot.volumetype == 'ROOT': + service = self.services["disk_offering"] + service["disksize"] = math.ceil(snapshot.virtualsize/(1024*1024*1024)) + self.disk_offering = DiskOffering.create( + self.apiclient, + service + ) + self.cleanup.append(self.disk_offering) + disk_offering_id = self.disk_offering.id + + self.volume = Volume.create(self.userapiclient, {"diskname":"StorPoolDisk-1" }, snapshotid=self.snapshot_id, zoneid=self.zone.id, diskofferingid=disk_offering_id) + self.cleanup.append(self.volume) + time.sleep(420) + Snapshot.delete(snapshot, self.userapiclient) + if self.zone.id != self.volume.zoneid: + self.fail("Volume from snapshot not created in the additional zone") + return + + @skipTestIf("testsNotSupported") + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_06_take_snapshot_multi_zone_create_template_additional_zone(self): + """Test to take volume snapshot in multiple StorPool primary storages in diff zones and create a volume in one of the additional zones + """ + snapshot = Snapshot.create(self.userapiclient, volume_id=self.volume.id, pool_ids=[str(self.storpool_pool.id)]) + self.snapshot_id = snapshot.id + self.helper.verify_snapshot_copies(self.userapiclient, self.snapshot_id, [self.zone.id, self.additional_zone.id]) + self.template = self.helper.create_snapshot_template(self.userapiclient, self.services, self.snapshot_id, self.additional_zone.id) + if self.additional_zone.id != self.template.zoneid: + self.fail("Template from snapshot not created in the additional zone") + time.sleep(420) + Snapshot.delete(snapshot, self.userapiclient) + self.cleanup.append(self.template) + return diff --git a/test/integration/plugins/storpool/sp_util.py b/test/integration/plugins/storpool/sp_util.py index 70f36609af5c..19e6015f8ce9 100644 --- a/test/integration/plugins/storpool/sp_util.py +++ b/test/integration/plugins/storpool/sp_util.py @@ -847,3 +847,59 @@ def getHostToDeployOrMigrate(cls, apiclient, hostsToavoid, clustersToDeploy): break return destinationHost + + @classmethod + def create_snapshot_template(cls, apiclient, services, snapshot_id, zone_id): + cmd = createTemplate.createTemplateCmd() + cmd.displaytext = "TemplateFromSnap" + name = "-".join([cmd.displaytext, random_gen()]) + cmd.name = name + if "ostypeid" in services: + cmd.ostypeid = services["ostypeid"] + elif "ostype" in services: + sub_cmd = listOsTypes.listOsTypesCmd() + sub_cmd.description = services["ostype"] + ostypes = apiclient.listOsTypes(sub_cmd) + + if not isinstance(ostypes, list): + cls.fail("Unable to find Ostype id with desc: %s" % + services["ostype"]) + cmd.ostypeid = ostypes[0].id + else: + cls.fail("Unable to find Ostype is required for creating template") + + cmd.isfeatured = True + cmd.ispublic = True + cmd.isextractable = False + + cmd.snapshotid = snapshot_id + cmd.zoneid = zone_id + apiclient.createTemplate(cmd) + templates = Template.list(apiclient, name=name, templatefilter="self") + if not isinstance(templates, list) and len(templates) < 0: + cls.fail("Unable to find created template with name %s" % name) + template = Template(templates[0].__dict__) + return template + + @classmethod + def verify_snapshot_copies(cls, userapiclient, snapshot_id, zone_ids): + snapshot_entries = Snapshot.list(userapiclient, id=snapshot_id, showunique=False) + if not isinstance(snapshot_entries, list): + cls.fail("Unable to list snapshot for multiple zones") + snapshots = set() + new_list = [] + for obj in snapshot_entries: + if obj.zoneid not in snapshots: + new_list.append(obj) + snapshots.add(obj.zoneid) + + if len(new_list) != len(zone_ids): + cls.fail("Undesired list snapshot size for multiple zones") + for zone_id in zone_ids: + zone_found = False + for entry in new_list: + if entry.zoneid == zone_id: + zone_found = True + break + if zone_found == False: + cls.fail("Unable to find snapshot entry for the zone ID: %s" % zone_id) \ No newline at end of file diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 557434ea2ee3..1449b0e6c383 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -1127,7 +1127,7 @@ def __init__(self, items): @classmethod def create(cls, apiclient, services, zoneid=None, account=None, - domainid=None, diskofferingid=None, projectid=None, size=None): + domainid=None, diskofferingid=None, projectid=None, size=None, snapshotid=None): """Create Volume""" cmd = createVolume.createVolumeCmd() cmd.name = "-".join([services["diskname"], random_gen()]) @@ -1158,6 +1158,9 @@ def create(cls, apiclient, services, zoneid=None, account=None, if size: cmd.size = size + if snapshotid: + cmd.snapshotid = snapshotid + return Volume(apiclient.createVolume(cmd).__dict__) def update(self, apiclient, **kwargs): @@ -1364,7 +1367,7 @@ def __init__(self, items): @classmethod def create(cls, apiclient, volume_id, account=None, - domainid=None, projectid=None, locationtype=None, asyncbackup=None): + domainid=None, projectid=None, locationtype=None, asyncbackup=None, zoneids=None, pool_ids=None): """Create Snapshot""" cmd = createSnapshot.createSnapshotCmd() cmd.volumeid = volume_id @@ -1378,12 +1381,18 @@ def create(cls, apiclient, volume_id, account=None, cmd.locationtype = locationtype if asyncbackup: cmd.asyncbackup = asyncbackup + if zoneids: + cmd.zoneids = zoneids + if pool_ids: + cmd.storageids = pool_ids return Snapshot(apiclient.createSnapshot(cmd).__dict__) - def delete(self, apiclient): + def delete(self, apiclient, zone_id=None): """Delete Snapshot""" cmd = deleteSnapshot.deleteSnapshotCmd() cmd.id = self.id + if zone_id: + cmd.zoneid = zone_id apiclient.deleteSnapshot(cmd) @classmethod @@ -1396,6 +1405,20 @@ def list(cls, apiclient, **kwargs): cmd.listall = True return (apiclient.listSnapshots(cmd)) + @classmethod + def copy(cls, apiclient, snapshotid, zone_ids=None, source_zone_id=None, pool_ids=None): + """ Copy snapshot to another zone or a primary storage in another zone""" + cmd = copySnapshot.copySnapshotCmd() + cmd.id = snapshotid + if source_zone_id: + cmd.sourcezoneid = source_zone_id + if zone_ids: + cmd.zoneids = zone_ids + if pool_ids: + cmd.storageids = pool_ids + return Snapshot(apiclient.copySnapshot(cmd).__dict__) + + def validateState(self, apiclient, snapshotstate, timeout=600): """Check if snapshot is in required state returnValue: List[Result, Reason] @@ -1431,7 +1454,7 @@ def __init__(self, items): @classmethod def create(cls, apiclient, services, volumeid=None, - account=None, domainid=None, projectid=None, randomise=True): + account=None, domainid=None, projectid=None, randomise=True, snapshotid=None, zoneid=None): """Create template from Volume""" # Create template from Virtual machine and Volume ID cmd = createTemplate.createTemplateCmd() @@ -1477,6 +1500,12 @@ def create(cls, apiclient, services, volumeid=None, if projectid: cmd.projectid = projectid + + if snapshotid: + cmd.snapshotid = snapshotid + + if zoneid: + cmd.zoneid = zoneid return Template(apiclient.createTemplate(cmd).__dict__) @classmethod diff --git a/ui/src/views/storage/SnapshotZones.vue b/ui/src/views/storage/SnapshotZones.vue index 66bd0f3a9d08..a989cdfc4e20 100644 --- a/ui/src/views/storage/SnapshotZones.vue +++ b/ui/src/views/storage/SnapshotZones.vue @@ -309,7 +309,6 @@ export default { const params = {} params.id = this.resource.id params.showunique = false - params.locationtype = 'Secondary' params.listall = true params.page = this.page params.pagesize = this.pageSize @@ -320,6 +319,9 @@ export default { api('listSnapshots', params).then(json => { this.dataSource = json.listsnapshotsresponse.snapshot || [] this.itemCount = json.listsnapshotsresponse.count || 0 + if (this.itemCount > 0) { + this.dataSource = this.dataSource.filter((obj, index) => this.dataSource.findIndex((item) => item.zoneid === obj.zoneid) === index) + } }).catch(error => { this.$notifyError(error) }).finally(() => { From 9f643fc514016a11cdf22727fc3bbbd63a9e7e9a Mon Sep 17 00:00:00 2001 From: Slavka Peleva Date: Wed, 14 Aug 2024 11:09:15 +0300 Subject: [PATCH 2/5] added option in the UI for the storage pools Added drop down to choose the primary storage pools to copy a snapshot Small fixes --- .../StorPoolAbandonObjectsCollector.java | 6 +- .../datastore/util/StorPoolHelper.java | 11 ++++ .../storage/datastore/util/StorPoolUtil.java | 3 +- .../snapshot/StorPoolSnapshotStrategy.java | 27 ++++---- .../cloud/storage/VolumeApiServiceImpl.java | 1 + .../storage/snapshot/SnapshotManagerImpl.java | 2 + .../cloud/template/TemplateManagerImpl.java | 4 +- .../cloudstack/snapshot/SnapshotHelper.java | 16 +++++ ui/public/locales/en.json | 2 + ui/src/views/storage/FormSchedule.vue | 47 +++++++++++++- ui/src/views/storage/SnapshotZones.vue | 61 +++++++++++++++++-- ui/src/views/storage/TakeSnapshot.vue | 44 +++++++++++++ 12 files changed, 197 insertions(+), 27 deletions(-) diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/collector/StorPoolAbandonObjectsCollector.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/collector/StorPoolAbandonObjectsCollector.java index 1310cde72222..4a7f56a8d251 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/collector/StorPoolAbandonObjectsCollector.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/collector/StorPoolAbandonObjectsCollector.java @@ -41,7 +41,6 @@ import org.apache.cloudstack.storage.datastore.util.StorPoolUtil; import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpApiResponse; import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc; -import org.apache.cloudstack.storage.snapshot.StorPoolSnapshotStrategy; import org.apache.commons.collections.CollectionUtils; import javax.inject.Inject; @@ -379,12 +378,13 @@ protected void runInContext() { } if (snapshots.contains(name)) { Long clusterId = StorPoolHelper.findClusterIdByGlobalId(StorPoolUtil.getSnapshotClusterId(name, conn), clusterDao); - conn = StorPoolSnapshotStrategy.getSpConnectionDesc(conn, clusterId); + conn = StorPoolHelper.getSpConnectionDesc(conn, clusterId); SpApiResponse resp = StorPoolUtil.snapshotUnexport(name, location, conn); if (resp.getError() == null) { + StorPoolUtil.spLog("Unexport of snapshot %s was successful", name); recoveredSnapshots.add(snapshot.getId()); } else { - logger.debug(String.format("Could not recover StorPool snapshot %s", resp.getError())); + StorPoolUtil.spLog("Could not recover StorPool snapshot %s", resp.getError()); } } } diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolHelper.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolHelper.java index d78549337611..b9f71dce63bb 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolHelper.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolHelper.java @@ -306,4 +306,15 @@ public static boolean isPoolSupportsAllFunctionalityFromPreviousVersion(StorageP } return true; } + + public static StorPoolUtil.SpConnectionDesc getSpConnectionDesc(StorPoolUtil.SpConnectionDesc connectionLocal, Long clusterId) { + + String subClusterEndPoint = StorPoolConfigurationManager.StorPoolSubclusterEndpoint.valueIn(clusterId); + if (StringUtils.isNotEmpty(subClusterEndPoint)) { + String host = subClusterEndPoint.split(";")[0].split("=")[1]; + String token = subClusterEndPoint.split(";")[1].split("=")[1]; + connectionLocal = new StorPoolUtil.SpConnectionDesc(host, token, connectionLocal.getTemplateName()); + } + return connectionLocal; + } } diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java index 5f9618b8c03c..e3ecf0ec06c4 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java @@ -683,7 +683,8 @@ public static SpApiResponse snapshotExport(String name, String location, SpConne public static SpApiResponse snapshotUnexport(String name, String location, SpConnectionDesc conn) { Map json = new HashMap<>(); json.put("snapshot", name); - json.put("location", location); + json.put("force", true); + json.put("all", true); return POST("SnapshotUnexport", json, conn); } diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolSnapshotStrategy.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolSnapshotStrategy.java index 3fd5421a4f92..0907d17a9b2f 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolSnapshotStrategy.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolSnapshotStrategy.java @@ -57,7 +57,6 @@ import org.apache.cloudstack.storage.datastore.util.StorPoolUtil.SpConnectionDesc; import org.apache.cloudstack.storage.to.SnapshotObjectTO; import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.stereotype.Component; @@ -196,6 +195,12 @@ public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperat if (CollectionUtils.isEmpty(pools)) { return StrategyPriority.CANT_HANDLE; } + List snapshots = snapshotJoinDao.listBySnapshotIdAndZoneId(zoneId, snapshot.getId()); + boolean snapshotNotOnStorPool = snapshots.stream().filter(s -> s.getStoreRole().equals(DataStoreRole.Primary)).count() == 0; + + if (snapshotNotOnStorPool) { + return StrategyPriority.CANT_HANDLE; + } for (StoragePoolVO pool : pools) { SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Primary, pool.getId(), snapshot.getId()); if (snapshotOnPrimary != null && (snapshotOnPrimary.getState().equals(State.Ready) || snapshotOnPrimary.getState().equals(State.Created))) { @@ -391,7 +396,7 @@ public void copySnapshot(DataObject snapshot, DataObject snapshotDest, AsyncComp snapshot.getDataStore().getId(), storagePoolDetailsDao, _primaryDataStoreDao); String snapshotName = StorPoolStorageAdaptor.getVolumeNameFromPath(srcSnapshot.getPath(), false); Long clusterId = StorPoolHelper.findClusterIdByGlobalId(StorPoolUtil.getSnapshotClusterId("~" + snapshotName, connectionLocal), clusterDao); - connectionLocal = getSpConnectionDesc(connectionLocal, clusterId); + connectionLocal = StorPoolHelper.getSpConnectionDesc(connectionLocal, clusterId); SpApiResponse resp = StorPoolUtil.snapshotExport("~" + snapshotName, location, connectionLocal); if (resp.getError() != null) { StorPoolUtil.spLog("Failed to export snapshot %s from %s due to %s", snapshotName, location, resp.getError()); @@ -401,6 +406,9 @@ public void copySnapshot(DataObject snapshot, DataObject snapshotDest, AsyncComp callback.complete(res); return; } + String detail = "~" + snapshotName + ";" + location; + SnapshotDetailsVO snapshotForRecovery = new SnapshotDetailsVO(snapshot.getId(), StorPoolUtil.SP_RECOVERED_SNAPSHOT, detail, true); + _snapshotDetailsDao.persist(snapshotForRecovery); SpConnectionDesc connectionRemote = StorPoolUtil.getSpConnection(storagePoolVO.getUuid(), storagePoolVO.getId(), storagePoolDetailsDao, _primaryDataStoreDao); String localLocation = StorPoolConfigurationManager.StorPoolClusterLocation @@ -418,9 +426,7 @@ public void copySnapshot(DataObject snapshot, DataObject snapshotDest, AsyncComp return; } StorPoolUtil.spLog("The snapshot [%s] was copied from remote", snapshotName); - String detail = "~" + snapshotName + ";" + location; - SnapshotDetailsVO snapshotForRecovery = new SnapshotDetailsVO(snapshot.getId(), StorPoolUtil.SP_RECOVERED_SNAPSHOT, detail, true); - _snapshotDetailsDao.persist(snapshotForRecovery); + respFromRemote = StorPoolUtil.snapshotReconcile("~" + snapshotName, connectionRemote); if (respFromRemote.getError() != null) { StorPoolUtil.spLog("Failed to reconcile snapshot %s from %s due to %s", snapshotName, location, respFromRemote.getError()); @@ -447,15 +453,4 @@ public void copySnapshot(DataObject snapshot, DataObject snapshotDest, AsyncComp res.setResult(err); callback.complete(res); } - - public static SpConnectionDesc getSpConnectionDesc(SpConnectionDesc connectionLocal, Long clusterId) { - - String subClusterEndPoint = StorPoolConfigurationManager.StorPoolSubclusterEndpoint.valueIn(clusterId); - if (StringUtils.isNotEmpty(subClusterEndPoint)) { - String host = subClusterEndPoint.split(";")[0].split("=")[1]; - String token = subClusterEndPoint.split(";")[1].split("=")[1]; - connectionLocal = new SpConnectionDesc(host, token, connectionLocal.getTemplateName()); - } - return connectionLocal; - } } diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index ba22b88e4f99..d120e73f587a 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -3908,6 +3908,7 @@ private boolean canCopyOnPrimary(List poolIds, VolumeInfo volume, boolean } else { return false; } + snapshotHelper.checkIfThereAreMoreThanOnePoolInTheZone(poolIds); return true; } diff --git a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java index ad9d3eaf97a9..e474d37289db 100755 --- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java @@ -1027,6 +1027,7 @@ protected void validatePolicyZones(List zoneIds, List poolIds, Volum } } if (hasPools) { + snapshotHelper.checkIfThereAreMoreThanOnePoolInTheZone(poolIds); for (Long poolId : poolIds) { getCheckedDestinationStorageForSnapshotCopy(poolId, isRootAdminCaller); } @@ -2101,6 +2102,7 @@ public Snapshot copySnapshot(CopySnapshotCmd cmd) throws StorageUnavailableExcep throw new InvalidParameterValueException(String.format("There is no snapshot ID: %s ready on image store", snapshot.getUuid())); } if (canCopyBetweenStoragePools) { + snapshotHelper.checkIfThereAreMoreThanOnePoolInTheZone(storagePoolIds); copySnapshotToPrimaryDifferentZone(storagePoolIds, snapshot); } List failedZones = copySnapshotToZones(snapshot, srcSecStore, new ArrayList<>(dataCenterVOs.values())); diff --git a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java index fbbf2b1fb319..7cf7c7fbdad6 100755 --- a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java @@ -1683,7 +1683,7 @@ public VirtualMachineTemplate createPrivateTemplate(CreateTemplateCmd command) t if (kvmSnapshotOnlyInPrimaryStorage && keepOnPrimary) { skipCopyToSecondary = true; } - if (dataStoreRole == DataStoreRole.Image || !skipCopyToSecondary) { + if (dataStoreRole == DataStoreRole.Image) { snapInfo = snapshotHelper.backupSnapshotToSecondaryStorageIfNotExists(snapInfo, dataStoreRole, snapshot, kvmSnapshotOnlyInPrimaryStorage); _accountMgr.checkAccess(caller, null, true, snapInfo); DataStore snapStore = snapInfo.getDataStore(); @@ -1691,7 +1691,7 @@ public VirtualMachineTemplate createPrivateTemplate(CreateTemplateCmd command) t if (snapStore != null) { store = snapStore; // pick snapshot image store to create template } - } else if (skipCopyToSecondary) { + } else if (keepOnPrimary) { ImageStoreVO imageStore = _imgStoreDao.findOneByZoneAndProtocol(zoneId, "nfs"); if (imageStore == null) { throw new CloudRuntimeException(String.format("Could not find an NFS secondary storage pool on zone %s to use as a temporary location " + diff --git a/server/src/main/java/org/apache/cloudstack/snapshot/SnapshotHelper.java b/server/src/main/java/org/apache/cloudstack/snapshot/SnapshotHelper.java index c24fa9820883..aae54637e552 100644 --- a/server/src/main/java/org/apache/cloudstack/snapshot/SnapshotHelper.java +++ b/server/src/main/java/org/apache/cloudstack/snapshot/SnapshotHelper.java @@ -51,7 +51,9 @@ import org.apache.logging.log4j.Logger; import javax.inject.Inject; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -295,4 +297,18 @@ protected Set getSnapshotIdsOnlyInPrimaryStorage(long volumeId) { return snapshotIdsOnlyInPrimaryStorage; } + + public void checkIfThereAreMoreThanOnePoolInTheZone(List poolIds) { + List poolsInOneZone = new ArrayList<>(); + for (Long poolId : poolIds) { + StoragePoolVO pool = primaryDataStoreDao.findById(poolId); + if (pool != null) { + poolsInOneZone.add(pool.getDataCenterId()); + } + } + boolean moreThanOnePoolForZone = poolsInOneZone.stream().filter(itr -> Collections.frequency(poolsInOneZone, itr) > 1).count() > 1; + if (moreThanOnePoolForZone) { + throw new CloudRuntimeException("Cannot copy the snapshot on multiple storage pools in one zone"); + } + } } diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index eb1be420b2ed..46209fc3e904 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -2022,6 +2022,7 @@ "label.select.source.vcenter.datacenter": "Select the source VMware vCenter Datacenter", "label.select.tier": "Select Network Tier", "label.select.zones": "Select zones", +"label.select.storagepools": "Select storage pools", "label.select.2fa.provider": "Select the provider", "label.selected.storage": "Selected storage", "label.self": "Mine", @@ -2184,6 +2185,7 @@ "label.storagemotionenabled": "Storage motion enabled", "label.storagepolicy": "Storage policy", "label.storagepool": "Storage pool", +"label.storagepools": "Storage pools", "label.storagepool.tooltip": "Destination Storage Pool. Volume should be located in this Storage Pool", "label.storagetags": "Storage tags", "label.storagetype": "Storage type", diff --git a/ui/src/views/storage/FormSchedule.vue b/ui/src/views/storage/FormSchedule.vue index ffd89088c4b0..45f340d914bd 100644 --- a/ui/src/views/storage/FormSchedule.vue +++ b/ui/src/views/storage/FormSchedule.vue @@ -169,6 +169,32 @@ + + + + + + + + + {{ opt.name || opt.description }} + + + + +
{{ $t('label.tags') }}
@@ -272,7 +298,8 @@ export default { timeZoneMap: [], fetching: false, listDayOfWeek: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'], - zones: [] + zones: [], + storagePools: [] } }, created () { @@ -307,6 +334,7 @@ export default { }) if (this.resourceType === 'Volume') { this.fetchZoneData() + this.fetchStoragePoolData() } }, fetchZoneData () { @@ -323,6 +351,20 @@ export default { this.zoneLoading = false }) }, + fetchStoragePoolData () { + const params = {} + params.showicon = true + this.storagePoolsLoading = true + api('listStoragePools', params).then(json => { + const listStoragePools = json.liststoragepoolsresponse.storagepool + if (listStoragePools) { + this.storagePools = listStoragePools + this.storagePools = this.storagePools.filter(pool => pool.storagecapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES && pool.zoneid !== this.resource.zoneid) + } + }).finally(() => { + this.storagePoolsLoading = false + }) + }, fetchTimeZone (value) { this.timeZoneMap = [] this.fetching = true @@ -422,6 +464,9 @@ export default { if (values.zoneids && values.zoneids.length > 0) { params.zoneids = values.zoneids.join() } + if (values.storageids && values.storageids.length > 0) { + params.storageids = values.storageids.join() + } switch (values.intervaltype) { case 'hourly': params.schedule = values.time diff --git a/ui/src/views/storage/SnapshotZones.vue b/ui/src/views/storage/SnapshotZones.vue index a989cdfc4e20..8c4da14429c2 100644 --- a/ui/src/views/storage/SnapshotZones.vue +++ b/ui/src/views/storage/SnapshotZones.vue @@ -137,10 +137,39 @@ - + + + +
+ + + + + {{ opt.name || opt.description }} +
+
+
+
{{ $t('label.cancel') }} - {{ $t('label.ok') }} + + {{ $t('label.ok') }} +
@@ -238,6 +267,8 @@ export default { currentRecord: {}, zones: [], zoneLoading: false, + storagePools: [], + storagePoolLoading: false, copyLoading: false, deleteLoading: false, showDeleteSnapshot: false, @@ -297,12 +328,17 @@ export default { } } }, + computed: { + isCopySnapshotSubmitDisabled () { + return this.form.storageid.length === 0 && this.form.zoneid.length === 0 + } + }, methods: { initForm () { this.formRef = ref() this.form = reactive({}) this.rules = reactive({ - zoneid: [{ type: 'array', required: true, message: this.$t('message.error.select') }] + zoneid: [{ type: 'array', required: false }] }) }, fetchData () { @@ -488,10 +524,26 @@ export default { this.zoneLoading = false }) }, + fetchStoragePoolData () { + const params = {} + params.showicon = true + this.storagePoolsLoading = true + api('listStoragePools', params).then(json => { + const listStoragePools = json.liststoragepoolsresponse.storagepool + if (listStoragePools) { + this.storagePools = listStoragePools + this.storagePools = this.storagePools.filter(pool => pool.storagecapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES && pool.zoneid !== this.resource.zoneid) + } + }).finally(() => { + this.storagePoolsLoading = false + }) + }, showCopySnapshot (record) { this.currentRecord = record this.form.zoneid = [] + this.form.storageid = [] this.fetchZoneData() + this.fetchStoragePoolData() this.showCopyActionForm = true }, onShowDeleteModal (record) { @@ -520,7 +572,8 @@ export default { const params = { id: this.currentRecord.id, sourcezoneid: this.currentRecord.zoneid, - destzoneids: values.zoneid.join() + destzoneids: values.zoneid.join(), + storageids: values.storageid.join() } this.copyLoading = true api(this.copyApi, params).then(json => { diff --git a/ui/src/views/storage/TakeSnapshot.vue b/ui/src/views/storage/TakeSnapshot.vue index ee0eafbb56a2..f28c3b9556d8 100644 --- a/ui/src/views/storage/TakeSnapshot.vue +++ b/ui/src/views/storage/TakeSnapshot.vue @@ -66,6 +66,30 @@ + + + + + + + + {{ opt.name || opt.description }} + + + + @@ -158,6 +182,8 @@ export default { inputVisible: '', zones: [], zoneLoading: false, + storagePools: [], + storagePoolLoading: false, tags: [], dataSource: [] } @@ -170,6 +196,7 @@ export default { this.quiescevm = this.resource.quiescevm this.supportsStorageSnapshot = this.resource.supportsstoragesnapshot this.fetchZoneData() + this.fetchStoragePoolData() }, computed: { formattedAdditionalZoneMessage () { @@ -200,6 +227,20 @@ export default { this.zoneLoading = false }) }, + fetchStoragePoolData () { + const params = {} + params.showicon = true + this.storagePoolsLoading = true + api('listStoragePools', params).then(json => { + const listStoragePools = json.liststoragepoolsresponse.storagepool + if (listStoragePools) { + this.storagePools = listStoragePools + this.storagePools = this.storagePools.filter(pool => pool.storagecapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES && pool.zoneid !== this.resource.zoneid) + } + }).finally(() => { + this.storagePoolsLoading = false + }) + }, handleSubmit (e) { e.preventDefault() if (this.actionLoading) return @@ -223,6 +264,9 @@ export default { if (values.zoneids && values.zoneids.length > 0) { params.zoneids = values.zoneids.join() } + if (values.storageids && values.storageids.length > 0) { + params.storageids = values.storageids.join() + } for (let i = 0; i < this.tags.length; i++) { const formattedTagData = {} const tag = this.tags[i] From e19923c875f00a1a32239e69d58c3224c35da278 Mon Sep 17 00:00:00 2001 From: Slavka Peleva Date: Fri, 30 Aug 2024 17:10:44 +0300 Subject: [PATCH 3/5] Addressed comments --- .../cloudstack/api/command/user/snapshot/CopySnapshotCmd.java | 2 +- .../engine/subsystem/api/storage/DataStoreCapabilities.java | 2 +- .../datastore/driver/StorPoolPrimaryDataStoreDriver.java | 2 +- .../src/main/java/com/cloud/storage/VolumeApiServiceImpl.java | 2 +- .../java/com/cloud/storage/snapshot/SnapshotManagerImpl.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java index b66714a09f1e..b84fe9067203 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java @@ -91,7 +91,7 @@ public class CopySnapshotCmd extends BaseAsyncCmd implements UserCmd { entityType = StoragePoolResponse.class, required = false, description = "A comma-separated list of IDs of the storage pools in other zones in which the snapshot will be made available. " + - "The snapshot will always be made available in the zone in which the volume is present.") + "The snapshot will always be made available in the zone in which the volume is present. Currently supported for StorPool only") protected List storagePoolIds; ///////////////////////////////////////////////////// diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java index 8016d4cdc42c..0b97190d1261 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java @@ -44,7 +44,7 @@ public enum DataStoreCapabilities { /** * indicates that the driver supports copying snapshot between zones on pools of the same type */ - CAN_COPY_SNAPSHOT_BETWEEN_ZONES, + CAN_COPY_SNAPSHOT_BETWEEN_ZONES_AND_SAME_POOL_TYPE, /** * indicates that the storage does not need to delete the snapshot when creating a volume/template from it * and the setting `snapshot.backup.to.secondary` is enabled diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java index 365b5ffd4beb..485d7ea796a3 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java @@ -185,7 +185,7 @@ private SnapshotDataStoreVO getSnapshotImageStoreRef(long snapshotId, long zoneI @Override public Map getCapabilities() { Map mapCapabilities = new HashMap<>(); - mapCapabilities.put(DataStoreCapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES.toString(), Boolean.TRUE.toString()); + mapCapabilities.put(DataStoreCapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES_AND_SAME_POOL_TYPE.toString(), Boolean.TRUE.toString()); mapCapabilities.put(DataStoreCapabilities.KEEP_SNAPSHOT_ON_PRIMARY_AND_BACKUP.toString(), Boolean.TRUE.toString()); return mapCapabilities; } diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index d120e73f587a..8f9d22c7bd80 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -3899,7 +3899,7 @@ private boolean canCopyOnPrimary(List poolIds, VolumeInfo volume, boolean DataStore dataStore = dataStoreMgr.getDataStore(poolId, DataStoreRole.Primary); StoragePoolVO sPool = _storagePoolDao.findById(poolId); if (dataStore != null - && !dataStore.getDriver().getCapabilities().containsKey(DataStoreCapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES.toString()) + && !dataStore.getDriver().getCapabilities().containsKey(DataStoreCapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES_AND_SAME_POOL_TYPE.toString()) && sPool.getPoolType() != volume.getStoragePoolType() && volume.getPoolId() == poolId) { throw new InvalidParameterValueException("The specified pool doesn't support copying snapshots between zones" + poolId); diff --git a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java index e474d37289db..eef957eaabff 100755 --- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java @@ -2137,7 +2137,7 @@ private boolean canCopyOnPrimary(List poolIds, Snapshot snapshot) { continue; } if (!dataStore.getDriver().getCapabilities() - .containsKey(DataStoreCapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES.toString()) + .containsKey(DataStoreCapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES_AND_SAME_POOL_TYPE.toString()) && dataStore.getPoolType() != volume.getPoolType()) { poolsToBeRemoved.add(poolId); logger.debug(String.format("The %s does not support copy to %s between zones", dataStore.getPoolType(), volume.getPoolType())); From 156654e1f3670ca4e29d3736f12795e882575a79 Mon Sep 17 00:00:00 2001 From: Slavka Peleva Date: Mon, 9 Sep 2024 13:41:13 +0300 Subject: [PATCH 4/5] Address reviews --- .../api/storage/DataStoreCapabilities.java | 7 +- .../orchestration/VolumeOrchestrator.java | 5 +- .../StorPoolAbandonObjectsCollector.java | 8 +- .../StorPoolPrimaryDataStoreDriver.java | 1 - .../storage/snapshot/SnapshotManagerImpl.java | 146 ++++++++---------- .../cloud/template/TemplateManagerImpl.java | 7 +- .../cloudstack/snapshot/SnapshotHelper.java | 12 +- .../snapshot/SnapshotHelperTest.java | 5 - ui/src/views/storage/FormSchedule.vue | 2 +- ui/src/views/storage/SnapshotZones.vue | 2 +- ui/src/views/storage/TakeSnapshot.vue | 2 +- 11 files changed, 89 insertions(+), 108 deletions(-) diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java index 0b97190d1261..b221a90c0b32 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java @@ -44,10 +44,5 @@ public enum DataStoreCapabilities { /** * indicates that the driver supports copying snapshot between zones on pools of the same type */ - CAN_COPY_SNAPSHOT_BETWEEN_ZONES_AND_SAME_POOL_TYPE, - /** - * indicates that the storage does not need to delete the snapshot when creating a volume/template from it - * and the setting `snapshot.backup.to.secondary` is enabled - */ - KEEP_SNAPSHOT_ON_PRIMARY_AND_BACKUP + CAN_COPY_SNAPSHOT_BETWEEN_ZONES_AND_SAME_POOL_TYPE } diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java index 07c7e4dc1e07..ae1a4d461dd8 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@ -117,7 +117,6 @@ import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; 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.DataStoreCapabilities; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; @@ -567,8 +566,8 @@ public VolumeInfo createVolumeFromSnapshot(Volume volume, Snapshot snapshot, Use boolean kvmSnapshotOnlyInPrimaryStorage = snapshotHelper.isKvmSnapshotOnlyInPrimaryStorage(snapshot, dataStoreRole, volume.getDataCenterId()); boolean skipCopyToSecondary = false; - Map capabilities = snapInfo.getDataStore().getDriver().getCapabilities(); - boolean keepOnPrimary = MapUtils.isNotEmpty(capabilities) && capabilities.containsKey(DataStoreCapabilities.KEEP_SNAPSHOT_ON_PRIMARY_AND_BACKUP.toString()); + boolean keepOnPrimary = snapshotHelper.isStorPoolStorage(snapInfo); + if (kvmSnapshotOnlyInPrimaryStorage && keepOnPrimary) { skipCopyToSecondary = true; } diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/collector/StorPoolAbandonObjectsCollector.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/collector/StorPoolAbandonObjectsCollector.java index 4a7f56a8d251..ca9e3c01a368 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/collector/StorPoolAbandonObjectsCollector.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/collector/StorPoolAbandonObjectsCollector.java @@ -365,11 +365,15 @@ protected void runInContext() { SpConnectionDesc conn = StorPoolUtil.getSpConnection(storagePool.getUuid(), storagePool.getId(), storagePoolDetailsDao, storagePoolDao); JsonArray arr = StorPoolUtil.snapshotsList(conn); - List snapshots = snapshotsForRcovery(arr); + List snapshots = snapshotsForRecovery(arr); if (snapshots.isEmpty()) { continue; } for (SnapshotDetailsVO snapshot : snapshotDetails) { + String[] snapshotOnRemote = snapshot.getValue().split(";"); + if (snapshotOnRemote.length != 2) { + continue; + } String name = snapshot.getValue().split(";")[0]; String location = snapshot.getValue().split(";")[1]; if (name == null || location == null) { @@ -398,7 +402,7 @@ protected void runInContext() { } } - private static List snapshotsForRcovery(JsonArray arr) { + private static List snapshotsForRecovery(JsonArray arr) { List snapshots = new ArrayList<>(); for (int i = 0; i < arr.size(); i++) { boolean recoveringFromRemote = arr.get(i).getAsJsonObject().get("recoveringFromRemote").getAsBoolean(); diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java index 485d7ea796a3..e46caf60c509 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java @@ -186,7 +186,6 @@ private SnapshotDataStoreVO getSnapshotImageStoreRef(long snapshotId, long zoneI public Map getCapabilities() { Map mapCapabilities = new HashMap<>(); mapCapabilities.put(DataStoreCapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES_AND_SAME_POOL_TYPE.toString(), Boolean.TRUE.toString()); - mapCapabilities.put(DataStoreCapabilities.KEEP_SNAPSHOT_ON_PRIMARY_AND_BACKUP.toString(), Boolean.TRUE.toString()); return mapCapabilities; } diff --git a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java index eef957eaabff..cd315efe670a 100755 --- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java @@ -16,78 +16,11 @@ // under the License. package com.cloud.storage.snapshot; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.TimeZone; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import javax.inject.Inject; -import javax.naming.ConfigurationException; - -import org.apache.cloudstack.acl.SecurityChecker; -import com.cloud.api.ApiDBUtils; -import org.apache.cloudstack.annotation.AnnotationService; -import org.apache.cloudstack.annotation.dao.AnnotationDao; -import org.apache.cloudstack.api.ApiCommandResourceType; -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.command.user.snapshot.CopySnapshotCmd; -import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd; -import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotPoliciesCmd; -import org.apache.cloudstack.api.command.user.snapshot.ExtractSnapshotCmd; -import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotPoliciesCmd; -import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd; -import org.apache.cloudstack.api.command.user.snapshot.UpdateSnapshotPolicyCmd; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; -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.EndPoint; -import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; -import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotResult; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy; -import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation; -import org.apache.cloudstack.engine.subsystem.api.storage.StorageStrategyFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; -import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; -import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; -import org.apache.cloudstack.framework.async.AsyncCallFuture; -import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.cloudstack.framework.config.Configurable; -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.cloudstack.managed.context.ManagedContextRunnable; -import org.apache.cloudstack.resourcedetail.SnapshotPolicyDetailVO; -import org.apache.cloudstack.resourcedetail.dao.SnapshotPolicyDetailsDao; -import org.apache.cloudstack.snapshot.SnapshotHelper; -import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; -import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; -import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; -import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.collections.MapUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.builder.ReflectionToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; -import org.springframework.stereotype.Component; - import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; import com.cloud.agent.api.DeleteSnapshotsDirCommand; import com.cloud.alert.AlertManager; +import com.cloud.api.ApiDBUtils; import com.cloud.api.commands.ListRecurringSnapshotScheduleCmd; import com.cloud.api.query.MutualExclusiveIdsManagerBase; import com.cloud.configuration.Config; @@ -175,8 +108,73 @@ import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao; +import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.command.user.snapshot.CopySnapshotCmd; +import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd; +import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotPoliciesCmd; +import org.apache.cloudstack.api.command.user.snapshot.ExtractSnapshotCmd; +import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotPoliciesCmd; +import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd; +import org.apache.cloudstack.api.command.user.snapshot.UpdateSnapshotPolicyCmd; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; +import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotResult; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation; +import org.apache.cloudstack.engine.subsystem.api.storage.StorageStrategyFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; +import org.apache.cloudstack.framework.async.AsyncCallFuture; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.resourcedetail.SnapshotPolicyDetailVO; +import org.apache.cloudstack.resourcedetail.dao.SnapshotPolicyDetailsDao; +import org.apache.cloudstack.snapshot.SnapshotHelper; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.ReflectionToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; @Component public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implements SnapshotManager, SnapshotApiService, Configurable { @@ -1561,14 +1559,6 @@ private void copyNewSnapshotToZonesOnPrimary(CreateSnapshotPayload payload, Snap private boolean copySnapshotOnPool(SnapshotInfo snapshot, SnapshotStrategy snapshotStrategy, Long storagePoolId) { DataStore store = dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary); SnapshotInfo snapshotOnStore = (SnapshotInfo) store.create(snapshot); -// if (snapshotOnStore.getStatus() == ObjectInDataStoreStateMachine.State.Allocated) { -// snapshotOnStore.processEvent(Event.CreateOnlyRequested); -// } else if (snapshotOnStore.getStatus() == ObjectInDataStoreStateMachine.State.Ready) { -// snapshotOnStore.processEvent(Event.CopyRequested); -// } else { -// logger.info(String.format("Cannot copy snapshot to another storage in different zone. It's not in the right state %s", snapshotOnStore.getStatus())); -// return false; -// } try { AsyncCallFuture future = snapshotSrv.copySnapshot(snapshot, snapshotOnStore, snapshotStrategy); @@ -1589,10 +1579,6 @@ private boolean copySnapshotOnPool(SnapshotInfo snapshot, SnapshotStrategy snaps } catch (ExecutionException e) { throw new RuntimeException(e); } -// boolean copySuccessful = snapshotStrategy.copySnapshot(snapshot, snapshotOnStore, null); -// if (!copySuccessful) { -// snapshotOnStore.processEvent(Event.OperationFailed); -// } return true; } diff --git a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java index 7cf7c7fbdad6..c11b39a44fd5 100755 --- a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java @@ -154,7 +154,6 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; -import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; @@ -1677,13 +1676,11 @@ public VirtualMachineTemplate createPrivateTemplate(CreateTemplateCmd command) t snapInfo = _snapshotFactory.getSnapshotWithRoleAndZone(snapshotId, dataStoreRole, zoneId); boolean skipCopyToSecondary = false; - - Map capabilities = snapInfo.getDataStore().getDriver().getCapabilities(); - boolean keepOnPrimary = MapUtils.isNotEmpty(capabilities) && capabilities.containsKey(DataStoreCapabilities.KEEP_SNAPSHOT_ON_PRIMARY_AND_BACKUP.toString()); + boolean keepOnPrimary = snapshotHelper.isStorPoolStorage(snapInfo); if (kvmSnapshotOnlyInPrimaryStorage && keepOnPrimary) { skipCopyToSecondary = true; } - if (dataStoreRole == DataStoreRole.Image) { + if (dataStoreRole == DataStoreRole.Image || !skipCopyToSecondary) { snapInfo = snapshotHelper.backupSnapshotToSecondaryStorageIfNotExists(snapInfo, dataStoreRole, snapshot, kvmSnapshotOnlyInPrimaryStorage); _accountMgr.checkAccess(caller, null, true, snapInfo); DataStore snapStore = snapInfo.getDataStore(); diff --git a/server/src/main/java/org/apache/cloudstack/snapshot/SnapshotHelper.java b/server/src/main/java/org/apache/cloudstack/snapshot/SnapshotHelper.java index aae54637e552..f5478a377c31 100644 --- a/server/src/main/java/org/apache/cloudstack/snapshot/SnapshotHelper.java +++ b/server/src/main/java/org/apache/cloudstack/snapshot/SnapshotHelper.java @@ -100,12 +100,11 @@ public void expungeTemporarySnapshot(boolean kvmSnapshotOnlyInPrimaryStorage, Sn long storeId = snapInfo.getDataStore().getId(); long zoneId = dataStorageManager.getStoreZoneId(storeId, snapInfo.getDataStore().getRole()); - Map capabilities = snapInfo.getDataStore().getDriver().getCapabilities(); - boolean keepOnPrimary = MapUtils.isNotEmpty(capabilities) && capabilities.containsKey(DataStoreCapabilities.KEEP_SNAPSHOT_ON_PRIMARY_AND_BACKUP.toString()); - if (keepOnPrimary) { + if (isStorPoolStorage(snapInfo)) { logger.debug("The primary storage does not delete the snapshots even if there is a backup on secondary"); return; } + List snapshots = snapshotJoinDao.listBySnapshotIdAndZoneId(zoneId, snapInfo.getSnapshotId()); if (kvmSnapshotOnlyInPrimaryStorage || snapshots.size() <= 1) { if (snapInfo != null) { @@ -141,6 +140,13 @@ public void expungeTemporarySnapshot(boolean kvmSnapshotOnlyInPrimaryStorage, Sn snapshotDataStoreDao.expungeReferenceBySnapshotIdAndDataStoreRole(snapInfo.getId(), storeId, DataStoreRole.Image); } + public boolean isStorPoolStorage(SnapshotInfo snapInfo) { + if (DataStoreRole.Primary.equals(snapInfo.getDataStore().getRole())) { + StoragePoolVO storagePoolVO = primaryDataStoreDao.findById(snapInfo.getDataStore().getId()); + return storagePoolVO != null && StoragePoolType.StorPool.equals(storagePoolVO.getPoolType()); + } + return false; + } /** * Backup the snapshot to secondary storage if it should be backed up and was not yet or it is a temporary backup to create a volume. * @return The parameter snapInfo if the snapshot is not backupable, else backs up the snapshot to secondary storage and returns its info. diff --git a/server/src/test/java/org/apache/cloudstack/snapshot/SnapshotHelperTest.java b/server/src/test/java/org/apache/cloudstack/snapshot/SnapshotHelperTest.java index b2d3996a499d..3666932e63e5 100644 --- a/server/src/test/java/org/apache/cloudstack/snapshot/SnapshotHelperTest.java +++ b/server/src/test/java/org/apache/cloudstack/snapshot/SnapshotHelperTest.java @@ -46,7 +46,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -108,8 +107,6 @@ public void validateExpungeTemporarySnapshotNotAKvmSnapshotOnPrimaryStorageDoNot Mockito.when(snapshotInfoMock.getDataStore()).thenReturn(store); Mockito.when(snapshotInfoMock.getDataStore().getId()).thenReturn(1L); Mockito.when(snapshotInfoMock.getSnapshotId()).thenReturn(1L); - Mockito.when(snapshotInfoMock.getDataStore().getDriver()).thenReturn(storeDriver); - Mockito.when(snapshotInfoMock.getDataStore().getDriver().getCapabilities()).thenReturn(new HashMap<>()); snapshotHelperSpy.expungeTemporarySnapshot(false, snapshotInfoMock); Mockito.verifyNoInteractions(snapshotServiceMock, snapshotDataStoreDaoMock); } @@ -122,8 +119,6 @@ public void validateExpungeTemporarySnapshotKvmSnapshotOnPrimaryStorageExpungesS Mockito.when(store.getRole()).thenReturn(DataStoreRole.Image); Mockito.when(store.getId()).thenReturn(1L); Mockito.when(snapshotInfoMock.getDataStore()).thenReturn(store); - Mockito.when(snapshotInfoMock.getDataStore().getDriver()).thenReturn(storeDriver); - Mockito.when(snapshotInfoMock.getDataStore().getDriver().getCapabilities()).thenReturn(new HashMap<>()); snapshotHelperSpy.expungeTemporarySnapshot(true, snapshotInfoMock); } diff --git a/ui/src/views/storage/FormSchedule.vue b/ui/src/views/storage/FormSchedule.vue index 45f340d914bd..5d412d059671 100644 --- a/ui/src/views/storage/FormSchedule.vue +++ b/ui/src/views/storage/FormSchedule.vue @@ -359,7 +359,7 @@ export default { const listStoragePools = json.liststoragepoolsresponse.storagepool if (listStoragePools) { this.storagePools = listStoragePools - this.storagePools = this.storagePools.filter(pool => pool.storagecapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES && pool.zoneid !== this.resource.zoneid) + this.storagePools = this.storagePools.filter(pool => pool.storagecapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES_AND_SAME_POOL_TYPE && pool.zoneid !== this.resource.zoneid) } }).finally(() => { this.storagePoolsLoading = false diff --git a/ui/src/views/storage/SnapshotZones.vue b/ui/src/views/storage/SnapshotZones.vue index 8c4da14429c2..6c500eb43431 100644 --- a/ui/src/views/storage/SnapshotZones.vue +++ b/ui/src/views/storage/SnapshotZones.vue @@ -532,7 +532,7 @@ export default { const listStoragePools = json.liststoragepoolsresponse.storagepool if (listStoragePools) { this.storagePools = listStoragePools - this.storagePools = this.storagePools.filter(pool => pool.storagecapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES && pool.zoneid !== this.resource.zoneid) + this.storagePools = this.storagePools.filter(pool => pool.storagecapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES_AND_SAME_POOL_TYPE && pool.zoneid !== this.resource.zoneid) } }).finally(() => { this.storagePoolsLoading = false diff --git a/ui/src/views/storage/TakeSnapshot.vue b/ui/src/views/storage/TakeSnapshot.vue index f28c3b9556d8..61c811d9519c 100644 --- a/ui/src/views/storage/TakeSnapshot.vue +++ b/ui/src/views/storage/TakeSnapshot.vue @@ -235,7 +235,7 @@ export default { const listStoragePools = json.liststoragepoolsresponse.storagepool if (listStoragePools) { this.storagePools = listStoragePools - this.storagePools = this.storagePools.filter(pool => pool.storagecapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES && pool.zoneid !== this.resource.zoneid) + this.storagePools = this.storagePools.filter(pool => pool.storagecapabilities.CAN_COPY_SNAPSHOT_BETWEEN_ZONES_AND_SAME_POOL_TYPE && pool.zoneid !== this.resource.zoneid) } }).finally(() => { this.storagePoolsLoading = false From 88d13d2ae5c5a4297d3f5417251c573f909a15f3 Mon Sep 17 00:00:00 2001 From: Slavka Peleva Date: Mon, 16 Sep 2024 17:23:52 +0300 Subject: [PATCH 5/5] fix snapshot deletion --- .../snapshot/StorPoolSnapshotStrategy.java | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolSnapshotStrategy.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolSnapshotStrategy.java index 0907d17a9b2f..65d7b1ac0796 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolSnapshotStrategy.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/snapshot/StorPoolSnapshotStrategy.java @@ -36,6 +36,7 @@ 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.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.State; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; @@ -155,35 +156,49 @@ public boolean deleteSnapshot(Long snapshotId, Long zoneId) { private boolean deleteSnapshot(Long snapshotId, Long zoneId, SnapshotVO snapshotVO, String name, StoragePoolVO storage) { - boolean res; + boolean res = false; SpConnectionDesc conn = StorPoolUtil.getSpConnection(storage.getUuid(), storage.getId(), storagePoolDetailsDao, _primaryDataStoreDao); SpApiResponse resp = StorPoolUtil.snapshotDelete(name, conn); + List snapshotInfos = snapshotDataFactory.getSnapshots(snapshotId, zoneId); + processResult(snapshotInfos, ObjectInDataStoreStateMachine.Event.DestroyRequested); if (resp.getError() != null) { if (resp.getError().getDescr().contains("still exported")) { + processResult(snapshotInfos, Event.OperationFailed); throw new CloudRuntimeException(String.format("The snapshot [%s] was exported to another cluster. [%s]", name, resp.getError())); } final String err = String.format("Failed to clean-up Storpool snapshot %s. Error: %s", name, resp.getError()); StorPoolUtil.spLog(err); if (resp.getError().getName().equals("objectDoesNotExist")) { - markSnapshotAsDestroyedIfAlreadyRemoved(snapshotId, storage.getId()); + return true; } - res = false; } else { - markSnapshotAsDestroyedIfAlreadyRemoved(snapshotId, storage.getId()); res = deleteSnapshotFromDbIfNeeded(snapshotVO, zoneId); StorPoolUtil.spLog("StorpoolSnapshotStrategy.deleteSnapshot: executed successfully=%s, snapshot uuid=%s, name=%s", res, snapshotVO.getUuid(), name); } + if (res) { + processResult(snapshotInfos, Event.OperationSuccessed); + cleanUpDestroyedRecords(snapshotId); + } else { + processResult(snapshotInfos, Event.OperationFailed); + } return res; } - private void markSnapshotAsDestroyedIfAlreadyRemoved(Long snapshotId, Long storeId) { - SnapshotDataStoreVO snapshotOnPrimary = _snapshotStoreDao.findByStoreSnapshot(DataStoreRole.Primary, storeId, snapshotId); - if (snapshotOnPrimary != null) { - snapshotOnPrimary.setState(State.Destroyed); - _snapshotStoreDao.update(snapshotOnPrimary.getId(), snapshotOnPrimary); + private void cleanUpDestroyedRecords(Long snapshotId) { + List snapshots = _snapshotStoreDao.listBySnapshotId(snapshotId); + for (SnapshotDataStoreVO snapshot : snapshots) { + if (snapshot.getInstallPath().contains("/dev/storpool-byid") && State.Destroyed.equals(snapshot.getState())) { + _snapshotStoreDao.remove(snapshot.getId()); + } } } + private void processResult(List snapshotInfos, ObjectInDataStoreStateMachine.Event event) { + for (SnapshotInfo snapshot : snapshotInfos) { + SnapshotObject snapshotObject = (SnapshotObject) snapshot; + snapshotObject.processEvent(event); + } + } @Override public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op) { logger.debug(String.format("StorpoolSnapshotStrategy.canHandle: snapshot=%s, uuid=%s, op=%s", snapshot.getName(), snapshot.getUuid(), op)); @@ -196,9 +211,15 @@ public StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperat return StrategyPriority.CANT_HANDLE; } List snapshots = snapshotJoinDao.listBySnapshotIdAndZoneId(zoneId, snapshot.getId()); - boolean snapshotNotOnStorPool = snapshots.stream().filter(s -> s.getStoreRole().equals(DataStoreRole.Primary)).count() == 0; + boolean snapshotNotOnStorPool = snapshots.stream().filter(s -> DataStoreRole.Primary.equals(s.getStoreRole())).count() == 0; if (snapshotNotOnStorPool) { + for (SnapshotJoinVO snapshotOnStore : snapshots) { + SnapshotDataStoreVO snap = _snapshotStoreDao.findOneBySnapshotAndDatastoreRole(snapshot.getId(), DataStoreRole.Image); + if (snap != null && snap.getInstallPath() != null && snap.getInstallPath().startsWith(StorPoolUtil.SP_DEV_PATH)) { + return StrategyPriority.HIGHEST; + } + } return StrategyPriority.CANT_HANDLE; } for (StoragePoolVO pool : pools) {