Skip to content

Commit 8cc7b49

Browse files
committed
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.
1 parent 5baac44 commit 8cc7b49

File tree

51 files changed

+2384
-1280
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2384
-1280
lines changed

api/src/main/java/com/cloud/storage/VolumeApiService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@ public interface VolumeApiService {
108108

109109
Volume detachVolumeFromVM(DetachVolumeCmd cmd);
110110

111-
Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup, Map<String, String> tags, List<Long> zoneIds)
111+
Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup, Map<String, String> tags, List<Long> zoneIds, List<Long> poolIds)
112112
throws ResourceAllocationException;
113113

114-
Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, List<Long> zoneIds) throws ResourceAllocationException;
114+
Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, List<Long> zoneIds, List<Long> poolIds) throws ResourceAllocationException;
115115

116116
Volume updateVolume(long volumeId, String path, String state, Long storageId, Boolean displayVolume, String customId, long owner, String chainInfo, String name);
117117

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,7 @@ public class ApiConstants {
10131013

10141014
public static final String ZONE_ID_LIST = "zoneids";
10151015
public static final String DESTINATION_ZONE_ID_LIST = "destzoneids";
1016+
public static final String STORAGE_ID_LIST = "storageids";
10161017
public static final String ADMIN = "admin";
10171018
public static final String CHECKSUM_PARAMETER_PREFIX_DESCRIPTION = "The parameter containing the checksum will be considered a MD5sum if it is not prefixed\n"
10181019
+ " and just a plain ascii/utf8 representation of a hexadecimal string. If it is required to\n"

api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@
1717

1818
package org.apache.cloudstack.api.command.user.snapshot;
1919

20-
import java.util.ArrayList;
21-
import java.util.List;
22-
20+
import com.cloud.dc.DataCenter;
21+
import com.cloud.event.EventTypes;
22+
import com.cloud.exception.ResourceAllocationException;
23+
import com.cloud.exception.ResourceUnavailableException;
24+
import com.cloud.exception.StorageUnavailableException;
25+
import com.cloud.storage.Snapshot;
26+
import com.cloud.user.Account;
2327
import org.apache.cloudstack.acl.RoleType;
2428
import org.apache.cloudstack.api.APICommand;
2529
import org.apache.cloudstack.api.ApiCommandResourceType;
@@ -31,26 +35,23 @@
3135
import org.apache.cloudstack.api.ServerApiException;
3236
import org.apache.cloudstack.api.command.user.UserCmd;
3337
import org.apache.cloudstack.api.response.SnapshotResponse;
38+
import org.apache.cloudstack.api.response.StoragePoolResponse;
3439
import org.apache.cloudstack.api.response.ZoneResponse;
3540
import org.apache.cloudstack.context.CallContext;
3641
import org.apache.commons.collections.CollectionUtils;
37-
38-
import com.cloud.dc.DataCenter;
39-
import com.cloud.event.EventTypes;
40-
import com.cloud.exception.ResourceAllocationException;
41-
import com.cloud.exception.ResourceUnavailableException;
42-
import com.cloud.exception.StorageUnavailableException;
43-
import com.cloud.storage.Snapshot;
44-
import com.cloud.user.Account;
4542
import org.apache.logging.log4j.LogManager;
4643
import org.apache.logging.log4j.Logger;
4744

45+
import java.util.ArrayList;
46+
import java.util.List;
47+
4848
@APICommand(name = "copySnapshot", description = "Copies a snapshot from one zone to another.",
4949
responseObject = SnapshotResponse.class, responseView = ResponseObject.ResponseView.Restricted,
5050
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
5151
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
5252
public class CopySnapshotCmd extends BaseAsyncCmd implements UserCmd {
5353
public static final Logger logger = LogManager.getLogger(CopySnapshotCmd.class.getName());
54+
private Snapshot snapshot;
5455

5556
/////////////////////////////////////////////////////
5657
//////////////// API parameters /////////////////////
@@ -84,6 +85,15 @@ public class CopySnapshotCmd extends BaseAsyncCmd implements UserCmd {
8485
"Do not specify destzoneid and destzoneids together, however one of them is required.")
8586
protected List<Long> destZoneIds;
8687

88+
@Parameter(name = ApiConstants.STORAGE_ID_LIST,
89+
type=CommandType.LIST,
90+
collectionType = CommandType.UUID,
91+
entityType = StoragePoolResponse.class,
92+
required = false,
93+
description = "A comma-separated list of IDs of the storage pools in other zones in which the snapshot will be made available. " +
94+
"The snapshot will always be made available in the zone in which the volume is present.")
95+
protected List<Long> storagePoolIds;
96+
8797
/////////////////////////////////////////////////////
8898
/////////////////// Accessors ///////////////////////
8999
/////////////////////////////////////////////////////
@@ -106,7 +116,11 @@ public List<Long> getDestinationZoneIds() {
106116
destIds.add(destZoneId);
107117
return destIds;
108118
}
109-
return null;
119+
return new ArrayList<>();
120+
}
121+
122+
public List<Long> getStoragePoolIds() {
123+
return storagePoolIds;
110124
}
111125

112126
@Override
@@ -152,7 +166,7 @@ public long getEntityOwnerId() {
152166
@Override
153167
public void execute() throws ResourceUnavailableException {
154168
try {
155-
if (destZoneId == null && CollectionUtils.isEmpty(destZoneIds))
169+
if (destZoneId == null && CollectionUtils.isEmpty(destZoneIds) && CollectionUtils.isEmpty(storagePoolIds))
156170
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
157171
"Either destzoneid or destzoneids parameters have to be specified.");
158172

@@ -161,7 +175,7 @@ public void execute() throws ResourceUnavailableException {
161175
"Both destzoneid and destzoneids cannot be specified at the same time.");
162176

163177
CallContext.current().setEventDetails(getEventDescription());
164-
Snapshot snapshot = _snapshotService.copySnapshot(this);
178+
snapshot = _snapshotService.copySnapshot(this);
165179

166180
if (snapshot != null) {
167181
SnapshotResponse response = _queryService.listSnapshot(this);
@@ -177,6 +191,13 @@ public void execute() throws ResourceUnavailableException {
177191
logger.warn("Exception: ", ex);
178192
throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, ex.getMessage());
179193
}
194+
}
195+
196+
public Snapshot getSnapshot() {
197+
return snapshot;
198+
}
180199

200+
public void setSnapshot(Snapshot snapshot) {
201+
this.snapshot = snapshot;
181202
}
182203
}

api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.apache.cloudstack.api.response.DomainResponse;
3333
import org.apache.cloudstack.api.response.SnapshotPolicyResponse;
3434
import org.apache.cloudstack.api.response.SnapshotResponse;
35+
import org.apache.cloudstack.api.response.StoragePoolResponse;
3536
import org.apache.cloudstack.api.response.VolumeResponse;
3637
import org.apache.cloudstack.api.response.ZoneResponse;
3738
import org.apache.commons.collections.MapUtils;
@@ -99,6 +100,15 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd {
99100
since = "4.19.0")
100101
protected List<Long> zoneIds;
101102

103+
@Parameter(name = ApiConstants.STORAGE_ID_LIST,
104+
type=CommandType.LIST,
105+
collectionType = CommandType.UUID,
106+
entityType = StoragePoolResponse.class,
107+
description = "A comma-separated list of IDs of the storage pools in other zones in which the snapshot will be made available. " +
108+
"The snapshot will always be made available in the zone in which the volume is present.",
109+
since = "4.20.0")
110+
protected List<Long> storagePoolIds;
111+
102112
private String syncObjectType = BaseAsyncCmd.snapshotHostSyncObject;
103113

104114
// ///////////////////////////////////////////////////
@@ -161,6 +171,10 @@ public List<Long> getZoneIds() {
161171
return zoneIds;
162172
}
163173

174+
public List<Long> getStoragePoolIds() {
175+
return storagePoolIds;
176+
}
177+
164178
// ///////////////////////////////////////////////////
165179
// ///////////// API Implementation///////////////////
166180
// ///////////////////////////////////////////////////
@@ -209,7 +223,7 @@ public ApiCommandResourceType getApiResourceType() {
209223

210224
@Override
211225
public void create() throws ResourceAllocationException {
212-
Snapshot snapshot = _volumeService.allocSnapshot(getVolumeId(), getPolicyId(), getSnapshotName(), getLocationType(), getZoneIds());
226+
Snapshot snapshot = _volumeService.allocSnapshot(getVolumeId(), getPolicyId(), getSnapshotName(), getLocationType(), getZoneIds(), getStoragePoolIds());
213227
if (snapshot != null) {
214228
setEntityId(snapshot.getId());
215229
setEntityUuid(snapshot.getUuid());
@@ -223,7 +237,7 @@ public void execute() {
223237
Snapshot snapshot;
224238
try {
225239
snapshot =
226-
_volumeService.takeSnapshot(getVolumeId(), getPolicyId(), getEntityId(), _accountService.getAccount(getEntityOwnerId()), getQuiescevm(), getLocationType(), getAsyncBackup(), getTags(), getZoneIds());
240+
_volumeService.takeSnapshot(getVolumeId(), getPolicyId(), getEntityId(), _accountService.getAccount(getEntityOwnerId()), getQuiescevm(), getLocationType(), getAsyncBackup(), getTags(), getZoneIds(), getStoragePoolIds());
227241

228242
if (snapshot != null) {
229243
SnapshotResponse response = _responseGenerator.createSnapshotResponse(snapshot);

api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmd.java

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@
1616
// under the License.
1717
package org.apache.cloudstack.api.command.user.snapshot;
1818

19-
import java.util.Collection;
20-
import java.util.HashMap;
21-
import java.util.List;
22-
import java.util.Map;
23-
19+
import com.cloud.exception.InvalidParameterValueException;
20+
import com.cloud.exception.PermissionDeniedException;
21+
import com.cloud.projects.Project;
22+
import com.cloud.storage.Volume;
23+
import com.cloud.storage.snapshot.SnapshotPolicy;
24+
import com.cloud.user.Account;
2425
import org.apache.cloudstack.acl.RoleType;
2526
import org.apache.cloudstack.api.APICommand;
2627
import org.apache.cloudstack.api.ApiCommandResourceType;
@@ -30,16 +31,15 @@
3031
import org.apache.cloudstack.api.Parameter;
3132
import org.apache.cloudstack.api.ServerApiException;
3233
import org.apache.cloudstack.api.response.SnapshotPolicyResponse;
34+
import org.apache.cloudstack.api.response.StoragePoolResponse;
3335
import org.apache.cloudstack.api.response.VolumeResponse;
3436
import org.apache.cloudstack.api.response.ZoneResponse;
3537
import org.apache.commons.collections.MapUtils;
3638

37-
import com.cloud.exception.InvalidParameterValueException;
38-
import com.cloud.exception.PermissionDeniedException;
39-
import com.cloud.projects.Project;
40-
import com.cloud.storage.Volume;
41-
import com.cloud.storage.snapshot.SnapshotPolicy;
42-
import com.cloud.user.Account;
39+
import java.util.Collection;
40+
import java.util.HashMap;
41+
import java.util.List;
42+
import java.util.Map;
4343

4444
@APICommand(name = "createSnapshotPolicy", description = "Creates a snapshot policy for the account.", responseObject = SnapshotPolicyResponse.class,
4545
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@@ -83,6 +83,14 @@ public class CreateSnapshotPolicyCmd extends BaseCmd {
8383
"The snapshots will always be made available in the zone in which the volume is present.")
8484
protected List<Long> zoneIds;
8585

86+
@Parameter(name = ApiConstants.STORAGE_ID_LIST,
87+
type=CommandType.LIST,
88+
collectionType = CommandType.UUID,
89+
entityType = StoragePoolResponse.class,
90+
description = "A comma-separated list of IDs of the storage pools in other zones in which the snapshot will be made available. " +
91+
"The snapshot will always be made available in the zone in which the volume is present.",
92+
since = "4.20.0")
93+
protected List<Long> storagePoolIds;
8694
/////////////////////////////////////////////////////
8795
/////////////////// Accessors ///////////////////////
8896
/////////////////////////////////////////////////////
@@ -119,6 +127,8 @@ public List<Long> getZoneIds() {
119127
return zoneIds;
120128
}
121129

130+
public List<Long> getStoragePoolIds() { return storagePoolIds; }
131+
122132
/////////////////////////////////////////////////////
123133
/////////////// API Implementation///////////////////
124134
/////////////////////////////////////////////////////

api/src/main/java/org/apache/cloudstack/api/response/SnapshotPolicyResponse.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,16 @@
1616
// under the License.
1717
package org.apache.cloudstack.api.response;
1818

19-
import java.util.LinkedHashSet;
20-
import java.util.Set;
21-
19+
import com.cloud.serializer.Param;
20+
import com.cloud.storage.snapshot.SnapshotPolicy;
21+
import com.google.gson.annotations.SerializedName;
2222
import org.apache.cloudstack.acl.RoleType;
2323
import org.apache.cloudstack.api.ApiConstants;
2424
import org.apache.cloudstack.api.BaseResponseWithTagInformation;
2525
import org.apache.cloudstack.api.EntityReference;
2626

27-
import com.cloud.serializer.Param;
28-
import com.cloud.storage.snapshot.SnapshotPolicy;
29-
import com.google.gson.annotations.SerializedName;
27+
import java.util.LinkedHashSet;
28+
import java.util.Set;
3029

3130
@EntityReference(value = SnapshotPolicy.class)
3231
public class SnapshotPolicyResponse extends BaseResponseWithTagInformation {
@@ -62,9 +61,14 @@ public class SnapshotPolicyResponse extends BaseResponseWithTagInformation {
6261
@Param(description = "The list of zones in which snapshot backup is scheduled", responseObject = ZoneResponse.class, since = "4.19.0")
6362
protected Set<ZoneResponse> zones;
6463

64+
@SerializedName(ApiConstants.STORAGE)
65+
@Param(description = "The list of pools in which snapshot backup is scheduled", responseObject = StoragePoolResponse.class, since = "4.20.0")
66+
protected Set<StoragePoolResponse> storagePools;
67+
6568
public SnapshotPolicyResponse() {
6669
tags = new LinkedHashSet<ResourceTagResponse>();
6770
zones = new LinkedHashSet<>();
71+
storagePools = new LinkedHashSet<>();
6872
}
6973

7074
public String getId() {
@@ -130,4 +134,6 @@ public void setTags(Set<ResourceTagResponse> tags) {
130134
public void setZones(Set<ZoneResponse> zones) {
131135
this.zones = zones;
132136
}
137+
138+
public void setStoragePools(Set<StoragePoolResponse> pools) { this.storagePools = pools; }
133139
}

api/src/test/java/org/apache/cloudstack/api/command/test/CreateSnapshotCmdTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public void testCreateSuccess() {
9393
Snapshot snapshot = Mockito.mock(Snapshot.class);
9494
try {
9595
Mockito.when(volumeApiService.takeSnapshot(nullable(Long.class), nullable(Long.class), isNull(),
96-
nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), nullable(Map.class), nullable(List.class))).thenReturn(snapshot);
96+
nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), nullable(Map.class), nullable(List.class), nullable(List.class))).thenReturn(snapshot);
9797

9898
} catch (Exception e) {
9999
Assert.fail("Received exception when success expected " + e.getMessage());
@@ -126,7 +126,7 @@ public void testCreateFailure() {
126126

127127
try {
128128
Mockito.when(volumeApiService.takeSnapshot(nullable(Long.class), nullable(Long.class), nullable(Long.class),
129-
nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), any(), Mockito.anyList())).thenReturn(null);
129+
nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), any(), Mockito.anyList(), Mockito.anyList())).thenReturn(null);
130130
} catch (Exception e) {
131131
Assert.fail("Received exception when success expected " + e.getMessage());
132132
}

engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,14 @@ public enum DataStoreCapabilities {
4040
/**
4141
* indicates that this driver supports reverting a volume to a snapshot state
4242
*/
43-
CAN_REVERT_VOLUME_TO_SNAPSHOT
43+
CAN_REVERT_VOLUME_TO_SNAPSHOT,
44+
/**
45+
* indicates that the driver supports copying snapshot between zones on pools of the same type
46+
*/
47+
CAN_COPY_SNAPSHOT_BETWEEN_ZONES,
48+
/**
49+
* indicates that the storage does not need to delete the snapshot when creating a volume/template from it
50+
* and the setting `snapshot.backup.to.secondary` is enabled
51+
*/
52+
KEEP_SNAPSHOT_ON_PRIMARY_AND_BACKUP
4453
}

engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotService.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,6 @@ public interface SnapshotService {
4242
AsyncCallFuture<SnapshotResult> copySnapshot(SnapshotInfo snapshot, String copyUrl, DataStore dataStore) throws ResourceUnavailableException;
4343

4444
AsyncCallFuture<CreateCmdResult> queryCopySnapshot(SnapshotInfo snapshot) throws ResourceUnavailableException;
45+
46+
AsyncCallFuture<SnapshotResult> copySnapshot(SnapshotInfo sourceSnapshot, SnapshotInfo destSnapshot, SnapshotStrategy strategy);
4547
}

engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotStrategy.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616
// under the License.
1717
package org.apache.cloudstack.engine.subsystem.api.storage;
1818

19+
1920
import com.cloud.storage.Snapshot;
21+
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
2022

2123
public interface SnapshotStrategy {
2224

2325
enum SnapshotOperation {
24-
TAKE, BACKUP, DELETE, REVERT
26+
TAKE, BACKUP, DELETE, REVERT, COPY
2527
}
2628

2729
SnapshotInfo takeSnapshot(SnapshotInfo snapshot);
@@ -35,4 +37,7 @@ enum SnapshotOperation {
3537
StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op);
3638

3739
void postSnapshotCreation(SnapshotInfo snapshot);
40+
41+
default void copySnapshot(DataObject snapshotSource, DataObject snapshotDest, AsyncCompletionCallback<CreateCmdResult> caller) {
42+
}
3843
}

0 commit comments

Comments
 (0)