Skip to content

Commit

Permalink
Support of snapshot copy to different StorPool primary storage betwee…
Browse files Browse the repository at this point in the history
…n zones

Added support to copy a snapshot to another StorPool primary storage in
different zones.
  • Loading branch information
slavkap committed Aug 30, 2024
1 parent a5f5560 commit 9cccac3
Show file tree
Hide file tree
Showing 51 changed files with 2,332 additions and 1,225 deletions.
4 changes: 2 additions & 2 deletions api/src/main/java/com/cloud/storage/VolumeApiService.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,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<String, String> tags, List<Long> zoneIds)
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)
throws ResourceAllocationException;

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 /////////////////////
Expand Down Expand Up @@ -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<Long> 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<Long> storagePoolIds;

/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
Expand All @@ -106,7 +116,11 @@ public List<Long> getDestinationZoneIds() {
destIds.add(destZoneId);
return destIds;
}
return null;
return new ArrayList<>();
}

public List<Long> getStoragePoolIds() {
return storagePoolIds;
}

@Override
Expand Down Expand Up @@ -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.");

Expand All @@ -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);
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -99,6 +100,15 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd {
since = "4.19.0")
protected List<Long> 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<Long> storagePoolIds;

private String syncObjectType = BaseAsyncCmd.snapshotHostSyncObject;

// ///////////////////////////////////////////////////
Expand Down Expand Up @@ -161,6 +171,10 @@ public List<Long> getZoneIds() {
return zoneIds;
}

public List<Long> getStoragePoolIds() {
return storagePoolIds;
}

// ///////////////////////////////////////////////////
// ///////////// API Implementation///////////////////
// ///////////////////////////////////////////////////
Expand Down Expand Up @@ -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());
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -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<Long> 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<Long> storagePoolIds;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
Expand Down Expand Up @@ -119,6 +127,8 @@ public List<Long> getZoneIds() {
return zoneIds;
}

public List<Long> getStoragePoolIds() { return storagePoolIds; }

/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<ZoneResponse> 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<StoragePoolResponse> storagePools;

public SnapshotPolicyResponse() {
tags = new LinkedHashSet<ResourceTagResponse>();
zones = new LinkedHashSet<>();
storagePools = new LinkedHashSet<>();
}

public String getId() {
Expand Down Expand Up @@ -130,4 +134,6 @@ public void setTags(Set<ResourceTagResponse> tags) {
public void setZones(Set<ZoneResponse> zones) {
this.zones = zones;
}

public void setStoragePools(Set<StoragePoolResponse> pools) { this.storagePools = pools; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@ public interface SnapshotService {
AsyncCallFuture<SnapshotResult> copySnapshot(SnapshotInfo snapshot, String copyUrl, DataStore dataStore) throws ResourceUnavailableException;

AsyncCallFuture<CreateCmdResult> queryCopySnapshot(SnapshotInfo snapshot) throws ResourceUnavailableException;

AsyncCallFuture<SnapshotResult> copySnapshot(SnapshotInfo sourceSnapshot, SnapshotInfo destSnapshot, SnapshotStrategy strategy);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<CreateCmdResult> caller) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ public class VmWorkTakeVolumeSnapshot extends VmWork {
private boolean quiesceVm;
private Snapshot.LocationType locationType;
private boolean asyncBackup;

private List<Long> poolIds;
private List<Long> 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<Long> zoneIds) {
boolean asyncBackup, List<Long> zoneIds, List<Long> poolIds) {
super(userId, accountId, vmId, handlerName);
this.volumeId = volumeId;
this.policyId = policyId;
Expand All @@ -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() {
Expand Down Expand Up @@ -71,4 +72,8 @@ public boolean isAsyncBackup() {
public List<Long> getZoneIds() {
return zoneIds;
}

public List<Long> getPoolIds() {
return poolIds;
}
}
Loading

0 comments on commit 9cccac3

Please sign in to comment.