/*
* Copyright (c) 2008-2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.api.service.impl.resource;
import static com.emc.storageos.api.mapper.BlockMapper.map;
import static com.emc.storageos.api.mapper.TaskMapper.toTask;
import static com.emc.storageos.svcs.errorhandling.resources.ServiceCode.API_BAD_REQUEST;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.api.mapper.functions.MapBlockSnapshot;
import com.emc.storageos.api.service.authorization.PermissionsHelper;
import com.emc.storageos.api.service.impl.placement.PlacementManager;
import com.emc.storageos.api.service.impl.resource.fullcopy.BlockFullCopyManager;
import com.emc.storageos.api.service.impl.resource.snapshot.BlockSnapshotSessionManager;
import com.emc.storageos.api.service.impl.resource.utils.ExportUtils;
import com.emc.storageos.api.service.impl.response.BulkList;
import com.emc.storageos.api.service.impl.response.BulkList.PermissionsEnforcingResourceFilter;
import com.emc.storageos.api.service.impl.response.BulkList.ResourceFilter;
import com.emc.storageos.api.service.impl.response.ProjOwnedSnapResRepFilter;
import com.emc.storageos.api.service.impl.response.ResRepFilter;
import com.emc.storageos.api.service.impl.response.SearchedResRepList;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.constraint.ContainmentConstraint;
import com.emc.storageos.db.client.constraint.ContainmentPrefixConstraint;
import com.emc.storageos.db.client.constraint.PrefixConstraint;
import com.emc.storageos.db.client.constraint.URIQueryResultList;
import com.emc.storageos.db.client.model.BlockObject;
import com.emc.storageos.db.client.model.BlockSnapshot;
import com.emc.storageos.db.client.model.BlockSnapshotSession;
import com.emc.storageos.db.client.model.DataObject;
import com.emc.storageos.db.client.model.DiscoveredDataObject.Type;
import com.emc.storageos.db.client.model.ExportGroup;
import com.emc.storageos.db.client.model.ExportMask;
import com.emc.storageos.db.client.model.NamedURI;
import com.emc.storageos.db.client.model.OpStatusMap;
import com.emc.storageos.db.client.model.Operation;
import com.emc.storageos.db.client.model.Project;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.model.VirtualPool;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.model.BlockSnapshot.TechnologyType;
import com.emc.storageos.db.client.util.CustomQueryUtility;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.model.BulkIdParam;
import com.emc.storageos.model.BulkRestRep;
import com.emc.storageos.model.RelatedResourceRep;
import com.emc.storageos.model.ResourceOperationTypeEnum;
import com.emc.storageos.model.ResourceTypeEnum;
import com.emc.storageos.model.TaskList;
import com.emc.storageos.model.TaskResourceRep;
import com.emc.storageos.model.block.BlockSnapshotBulkRep;
import com.emc.storageos.model.block.BlockSnapshotRestRep;
import com.emc.storageos.model.block.SnapshotSessionCreateParam;
import com.emc.storageos.model.block.SnapshotSessionUnlinkTargetParam;
import com.emc.storageos.model.block.SnapshotSessionUnlinkTargetsParam;
import com.emc.storageos.model.block.VolumeDeleteTypeEnum;
import com.emc.storageos.model.block.VolumeFullCopyCreateParam;
import com.emc.storageos.model.block.export.ITLBulkRep;
import com.emc.storageos.model.block.export.ITLRestRepList;
import com.emc.storageos.security.audit.AuditLogManager;
import com.emc.storageos.security.authentication.StorageOSUser;
import com.emc.storageos.security.authorization.ACL;
import com.emc.storageos.security.authorization.CheckPermission;
import com.emc.storageos.security.authorization.DefaultPermissions;
import com.emc.storageos.security.authorization.Role;
import com.emc.storageos.services.OperationTypeEnum;
import com.emc.storageos.svcs.errorhandling.model.ServiceCoded;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import com.emc.storageos.svcs.errorhandling.resources.InternalException;
import com.emc.storageos.svcs.errorhandling.resources.ServiceCodeException;
import com.emc.storageos.volumecontroller.BlockController;
import com.emc.storageos.volumecontroller.impl.ControllerUtils;
import com.emc.storageos.volumecontroller.impl.smis.srdf.SRDFUtils;
/**
* @author burckb
*
*/
@Path("/block/snapshots")
@DefaultPermissions(readRoles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, readAcls = { ACL.ANY }, writeRoles = {
Role.TENANT_ADMIN }, writeAcls = { ACL.ANY })
public class BlockSnapshotService extends TaskResourceService {
private static final String EVENT_SERVICE_TYPE = "BlockSnapshot";
@Override
public String getServiceType() {
return EVENT_SERVICE_TYPE;
}
private static final Logger _log = LoggerFactory.getLogger(BlockSnapshotService.class);
// Block service implementations
private Map<String, BlockServiceApi> _blockServiceApis;
// A reference to the placement manager.
private PlacementManager _placementManager;
public void setBlockServiceApis(final Map<String, BlockServiceApi> serviceInterfaces) {
_blockServiceApis = serviceInterfaces;
}
private BlockServiceApi getBlockServiceImpl(final String type) {
return _blockServiceApis.get(type);
}
/**
* Setter for the placement manager.
*
* @param placementManager A reference to the placement manager.
*/
public void setPlacementManager(PlacementManager placementManager) {
_placementManager = placementManager;
}
/**
*
*
* @brief
*
* @prereq
*
* @param id
* @param param
*
* @return
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshot-sessions")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList createSnapshotSession(@PathParam("id") URI id, SnapshotSessionCreateParam param) {
return getSnapshotSessionManager().createSnapshotSession(id, param, getFullCopyManager());
}
/**
* Creates and returns an instance of the block snapshot session manager to handle
* a snapshot session creation request.
*
* @return BlockSnapshotSessionManager
*/
private BlockSnapshotSessionManager getSnapshotSessionManager() {
BlockSnapshotSessionManager snapshotSessionManager = new BlockSnapshotSessionManager(_dbClient,
_permissionsHelper, _auditMgr, _coordinator, sc, uriInfo, _request);
return snapshotSessionManager;
}
/**
* Get snapshot details
*
* @prereq none
* @param id the URN of a ViPR snapshot
* @brief Show snapshot
* @return Block snapshot
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public BlockSnapshotRestRep getSnapshot(@PathParam("id") URI id) {
ArgValidator.checkFieldUriType(id, BlockSnapshot.class, "id");
BlockSnapshot snap = (BlockSnapshot) queryResource(id);
return map(_dbClient, snap);
}
@Override
protected BlockObject queryResource(URI id) {
ArgValidator.checkUri(id);
BlockObject blockObj = BlockObject.fetch(_dbClient, id);
ArgValidator.checkEntityNotNull(blockObj, id, isIdEmbeddedInURL(id));
return blockObj;
}
@Override
protected URI getTenantOwner(URI id) {
BlockSnapshot snapshot = (BlockSnapshot) queryResource(id);
URI projectUri = snapshot.getProject().getURI();
ArgValidator.checkUri(projectUri);
Project project = _permissionsHelper.getObjectById(projectUri, Project.class);
ArgValidator.checkEntityNotNull(project, projectUri, isIdEmbeddedInURL(projectUri));
return project.getTenantOrg().getURI();
}
/**
* Deactivate volume snapshot, will result in permanent deletion of the requested snapshot from the storage system it was created on
* and will move the snapshot to a "marked-for-delete" state after the deletion happens on the array side.
* It will be deleted by the garbage collector on a subsequent iteration
* If this snapshot was created from a volume that is part of a consistency group,
* then all the related snapshots will be deactivated, as well.
*
* If "?type=VIPR_ONLY" is added to the path, it will delete snapshot only from ViPR data base and leaves the snapshot on storage array
* as it is.
* Possible value for attribute type : FULL, VIPR_ONLY
* FULL : Deletes the snapshot permanently on array and ViPR data base.
* VIPR_ONLY : Deletes the snapshot only from ViPR data base and leaves the snapshot on array as it is.
*
*
* @prereq none
* @param id the URN of a ViPR snapshot
* @param type the type of deletion {@link DefaultValue} FULL
* @brief Delete snapshot
* @return Snapshot information
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/deactivate")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList deactivateSnapshot(@PathParam("id") URI id, @DefaultValue("FULL") @QueryParam("type") String type) {
_log.info("Executing {} snapshot delete for snapshot {}", type, id);
String opStage = null;
boolean successStatus = true;
String taskId = UUID.randomUUID().toString();
TaskList response = new TaskList();
// Get the snapshot.
BlockSnapshot snap = (BlockSnapshot) queryResource(id);
// We can ignore dependencies on BlockSnapshotSession. In this case
// the BlockSnapshot instance is a linked target for a BlockSnapshotSession
// and we will unlink the snapshot from the session and delete it.
List<Class<? extends DataObject>> excludeTypes = new ArrayList<Class<? extends DataObject>>();
excludeTypes.add(BlockSnapshotSession.class);
if (VolumeDeleteTypeEnum.VIPR_ONLY.name().equals(type)) {
excludeTypes.add(ExportGroup.class);
excludeTypes.add(ExportMask.class);
}
ArgValidator.checkReference(BlockSnapshot.class, id, checkForDelete(snap, excludeTypes));
if (!VolumeDeleteTypeEnum.VIPR_ONLY.name().equals(type)) {
// The audit log message operation stage.
opStage = AuditLogManager.AUDITOP_BEGIN;
// If the BlockSnapshot instance represents a linked target, then
// we need to unlink the target form the snapshot session and then
// delete the target.
URIQueryResultList snapSessionURIs = new URIQueryResultList();
_dbClient.queryByConstraint(ContainmentConstraint.Factory.getLinkedTargetSnapshotSessionConstraint(id), snapSessionURIs);
Iterator<URI> snapSessionURIsIter = snapSessionURIs.iterator();
if (snapSessionURIsIter.hasNext()) {
_log.info("Snapshot is linked target for a snapshot session");
SnapshotSessionUnlinkTargetsParam param = new SnapshotSessionUnlinkTargetsParam();
List<SnapshotSessionUnlinkTargetParam> targetInfoList = new ArrayList<SnapshotSessionUnlinkTargetParam>();
SnapshotSessionUnlinkTargetParam targetInfo = new SnapshotSessionUnlinkTargetParam(id, Boolean.TRUE);
targetInfoList.add(targetInfo);
param.setLinkedTargets(targetInfoList);
response.getTaskList().add(getSnapshotSessionManager().unlinkTargetVolumesFromSnapshotSession(
snapSessionURIsIter.next(), param, OperationTypeEnum.DELETE_VOLUME_SNAPSHOT));
return response;
}
// Not an error if the snapshot we try to delete is already deleted
if (snap.getInactive()) {
_log.info("Snapshot is already inactive");
Operation op = new Operation();
op.ready("The snapshot has already been deleted");
op.setResourceType(ResourceOperationTypeEnum.DELETE_VOLUME_SNAPSHOT);
_dbClient.createTaskOpStatus(BlockSnapshot.class, snap.getId(), taskId, op);
response.getTaskList().add(toTask(snap, taskId, op));
return response;
}
}
// Get the storage system.
StorageSystem device = _dbClient.queryObject(StorageSystem.class, snap.getStorageController());
// Determine all snapshots to delete.
List<BlockSnapshot> snapshots = new ArrayList<BlockSnapshot>();
final URI cgId = snap.getConsistencyGroup();
if (!NullColumnValueGetter.isNullURI(cgId) && !NullColumnValueGetter.isNullValue(snap.getReplicationGroupInstance())) {
// Collect all the BlockSnapshots if part of a CG.
snapshots = ControllerUtils.getSnapshotsPartOfReplicationGroup(snap, _dbClient);
} else {
// Snap is not part of a CG so only delete the snap
snapshots.add(snap);
}
// Get the snapshot parent volume.
Volume parentVolume = _permissionsHelper.getObjectById(snap.getParent(), Volume.class);
// Check that there are no pending tasks for these snapshots.
checkForPendingTasks(Arrays.asList(parentVolume.getTenant().getURI()), snapshots);
// Create tasks on the volume.
for (BlockSnapshot snapshot : snapshots) {
Operation snapOp = _dbClient.createTaskOpStatus(BlockSnapshot.class, snapshot.getId(), taskId,
ResourceOperationTypeEnum.DELETE_VOLUME_SNAPSHOT);
response.getTaskList().add(toTask(snapshot, taskId, snapOp));
}
// Note that for snapshots of VPLEX volumes, the parent volume for the
// snapshot is the source side backend volume, which will have the same
// vpool as the VPLEX volume and therefore, the correct implementation
// should be returned.
try {
BlockServiceApi blockServiceApiImpl = BlockService.getBlockServiceImpl(parentVolume, _dbClient);
blockServiceApiImpl.deleteSnapshot(snap, snapshots, taskId, type);
} catch (APIException | InternalException e) {
successStatus = false;
String errorMsg = String.format("Exception attempting to delete snapshot %s: %s", snap.getId(), e.getMessage());
_log.error(errorMsg);
for (TaskResourceRep taskResourceRep : response.getTaskList()) {
taskResourceRep.setState(Operation.Status.error.name());
taskResourceRep.setMessage(errorMsg);
_dbClient.error(BlockSnapshot.class, taskResourceRep.getResource().getId(), taskId, e);
}
} catch (Exception e) {
successStatus = false;
String errorMsg = String.format("Exception attempting to delete snapshot %s: %s", snap.getId(), e.getMessage());
_log.error(errorMsg);
ServiceCoded sc = APIException.internalServerErrors.genericApisvcError(errorMsg, e);
for (TaskResourceRep taskResourceRep : response.getTaskList()) {
taskResourceRep.setState(Operation.Status.error.name());
taskResourceRep.setMessage(sc.getMessage());
_dbClient.error(BlockSnapshot.class, taskResourceRep.getResource().getId(), taskId, sc);
}
}
auditOp(OperationTypeEnum.DELETE_VOLUME_SNAPSHOT, successStatus, opStage,
id.toString(), snap.getLabel(), snap.getParent().getName(), device.getId().toString());
return response;
}
/**
* Get list of snapshot exports
* Returns the initiator-target pairings for this snapshot. The tenant user specifies the initiators
* using the export snapshot call. The system selects the target ports.
* Format of initiator is "21:11:22:33:44:55:66:77:10:00:00:00:c9:5c:90:43" for Fiber Channel
* or "iqn.emc.com:myhost" for iSCSI.
*
* @prereq none
* @param id the URN of a ViPR Snapshot
* @brief List snapshot exports
* @return List of exports
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/exports")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public ITLRestRepList getSnapshotExports(@PathParam("id") URI id) {
ArgValidator.checkFieldUriType(id, BlockSnapshot.class, "id");
queryResource(id);
return ExportUtils.getBlockObjectInitiatorTargets(id, _dbClient, isIdEmbeddedInURL(id));
}
/**
* Call will restore this snapshot to the volume that it is associated with.
* If this snapshot was created from a volume in a consistency group, then all
* related snapshots will be restored to their respective volumes.
*
* @prereq none
* @param id [required] - the URN of a ViPR block snapshot to restore from
* @brief Restore snapshot to volume
* @return TaskResourceRep - Task resource object for tracking this operation
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
@Path("/{id}/restore")
public TaskResourceRep restore(@PathParam("id") URI id, @QueryParam("syncDirection") String syncDirection) {
// Validate an get the snapshot to be restored.
ArgValidator.checkFieldUriType(id, BlockSnapshot.class, "id");
BlockSnapshot snapshot = (BlockSnapshot) queryResource(id);
// Validate syncDirection
if (syncDirection != null) {
validateSyncDirection(syncDirection);
}
// Get the block service API implementation for the snapshot parent volume.
Volume parentVolume = _permissionsHelper.getObjectById(snapshot.getParent(), Volume.class);
// Make sure that we don't have some pending
// operation against the parent volume
checkForPendingTasks(Arrays.asList(parentVolume.getTenant().getURI()), Arrays.asList(parentVolume));
// Get the storage system for the volume
StorageSystem storage = _permissionsHelper.getObjectById(parentVolume.getStorageController(), StorageSystem.class);
if (storage.checkIfVmax3()) {
if (snapshot.getSettingsInstance() == null && !TechnologyType.RP.name().equals(snapshot.getTechnologyType())) {
throw APIException.badRequests.snapshotNullSettingsInstance(snapshot.getLabel());
}
}
// restore for OpenStack storage system type is not supported
if (Type.openstack.name().equalsIgnoreCase(storage.getSystemType())) {
throw APIException.methodNotAllowed.notSupportedWithReason(
String.format("Snapshot restore is not possible on third-party storage systems"));
}
BlockServiceApi blockServiceApiImpl = BlockService.getBlockServiceImpl(parentVolume, _dbClient);
// Validate the restore snapshot request.
blockServiceApiImpl.validateRestoreSnapshot(snapshot, parentVolume);
// Create the task identifier.
String taskId = UUID.randomUUID().toString();
// Create the operation status entry in the status map for the snapshot.
Operation op = new Operation();
op.setResourceType(ResourceOperationTypeEnum.RESTORE_VOLUME_SNAPSHOT);
_dbClient.createTaskOpStatus(BlockSnapshot.class, snapshot.getId(), taskId, op);
snapshot.getOpStatus().put(taskId, op);
blockServiceApiImpl.restoreSnapshot(snapshot, parentVolume, syncDirection, taskId);
// Create the audit log entry.
auditOp(OperationTypeEnum.RESTORE_VOLUME_SNAPSHOT, true, AuditLogManager.AUDITOP_BEGIN,
id.toString(), parentVolume.getId().toString(), snapshot.getStorageController().toString());
return toTask(snapshot, taskId, op);
}
public void validateSyncDirection(String syncDirection) {
if (syncDirection.equals(SRDFUtils.SyncDirection.SOURCE_TO_TARGET.name()) ||
syncDirection.equals(SRDFUtils.SyncDirection.TARGET_TO_SOURCE.name()) ||
syncDirection.equals(SRDFUtils.SyncDirection.NONE.name())) {
return;
} else {
throw APIException.badRequests.syncDirectionIsNotValid(syncDirection);
}
}
/**
* Call will resynchronize this snapshot from the volume that it is associated with.
* If this snapshot was created from a volume in a consistency group, then all
* related snapshots will be resynchronized.
*
* @prereq none
* @param id [required] - the URN of a ViPR block snapshot to restore from
* @brief Resynchronize snapshot
* @return TaskResourceRep - Task resource object for tracking this operation
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
@Path("/{id}/resynchronize")
public TaskResourceRep resynchronize(@PathParam("id") URI id) {
// Validate an get the snapshot to be restored.
ArgValidator.checkFieldUriType(id, BlockSnapshot.class, "id");
BlockSnapshot snapshot = (BlockSnapshot) queryResource(id);
// Get the block service API implementation for the snapshot parent volume.
Volume volume = _permissionsHelper.getObjectById(snapshot.getParent(), Volume.class);
// Get the storage system for the volume
StorageSystem storage = _permissionsHelper.getObjectById(volume.getStorageController(), StorageSystem.class);
if (storage.checkIfVmax3()) {
if (snapshot.getSettingsInstance() == null) {
throw APIException.badRequests.snapshotNullSettingsInstance(snapshot.getLabel());
}
}
// resync for OpenStack storage system type is not supported
if (Type.openstack.name().equalsIgnoreCase(storage.getSystemType())) {
throw APIException.methodNotAllowed.notSupportedWithReason(
String.format("Snapshot resynchronization is not possible on third-party storage systems"));
}
// resync for VNX storage system type is not supported
if (Type.vnxblock.name().equalsIgnoreCase(storage.getSystemType())) {
throw APIException.methodNotAllowed.notSupportedWithReason(
"Snapshot resynchronization is not supported on VNX storage systems");
}
BlockServiceApi blockServiceApiImpl = BlockService.getBlockServiceImpl(volume, _dbClient);
// Validate the resync snapshot request.
blockServiceApiImpl.validateResynchronizeSnapshot(snapshot, volume);
// Create the task identifier.
String taskId = UUID.randomUUID().toString();
// Create the operation status entry in the status map for the snapshot.
Operation op = new Operation();
op.setResourceType(ResourceOperationTypeEnum.RESYNCHRONIZE_VOLUME_SNAPSHOT);
_dbClient.createTaskOpStatus(BlockSnapshot.class, snapshot.getId(), taskId, op);
snapshot.getOpStatus().put(taskId, op);
// Resync the snapshot.
blockServiceApiImpl.resynchronizeSnapshot(snapshot, volume, taskId);
// Create the audit log entry.
auditOp(OperationTypeEnum.RESYNCHRONIZE_VOLUME_SNAPSHOT, true, AuditLogManager.AUDITOP_BEGIN,
id.toString(), volume.getId().toString(), snapshot.getStorageController().toString());
return toTask(snapshot, taskId, op);
}
/**
* Call will activate this snapshot, essentially establishing the synchronization
* between the source and target. The "heavy lifting" of getting the snapshot
* to the point where it can be activated should have been done by the create.
*
* @prereq Create snapshot as inactive
* @param id [required] - the URN of a ViPR block snapshot to restore from
* @brief Activate snapshot
* @return TaskResourceRep - Task resource object for tracking this operation
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
@Path("/{id}/activate")
public TaskResourceRep activate(@PathParam("id") URI id) {
Operation op = new Operation();
op.setResourceType(ResourceOperationTypeEnum.ACTIVATE_VOLUME_SNAPSHOT);
ArgValidator.checkFieldUriType(id, BlockSnapshot.class, "id");
BlockSnapshot snapshot = (BlockSnapshot) queryResource(id);
// Get the block service API implementation for the snapshot parent volume.
Volume parentVolume = _permissionsHelper.getObjectById(snapshot.getParent(), Volume.class);
// Make sure that we don't have some pending
// operation against the parent volume
checkForPendingTasks(Arrays.asList(parentVolume.getTenant().getURI()), Arrays.asList(parentVolume));
StorageSystem device = _dbClient.queryObject(StorageSystem.class, snapshot.getStorageController());
BlockController controller = getController(BlockController.class, device.getSystemType());
String task = UUID.randomUUID().toString();
// If the snapshot is already active, there would be no need to queue
// another request to activate it again.
if (snapshot.getIsSyncActive()) {
op.ready("Snapshot is already active");
_dbClient.createTaskOpStatus(BlockSnapshot.class, snapshot.getId(), task, op);
return toTask(snapshot, task, op);
}
_dbClient.createTaskOpStatus(BlockSnapshot.class, snapshot.getId(), task, op);
List<URI> snapshotList = new ArrayList<URI>();
if (!NullColumnValueGetter.isNullURI(snapshot.getConsistencyGroup())) {
List<BlockSnapshot> snapshots = ControllerUtils.getSnapshotsPartOfReplicationGroup(snapshot, _dbClient);
for (BlockSnapshot snap : snapshots) {
snapshotList.add(snap.getId());
}
} else {
snapshotList.add(id);
}
// If the volume is under protection
if (snapshot.getEmName() != null) {
// RP snapshots cannot be activated so throw exception
throw new ServiceCodeException(API_BAD_REQUEST, "RecoverPoint snapshots cannot be activated.",
null);
} else {
controller.activateSnapshot(device.getId(), snapshotList, task);
}
auditOp(OperationTypeEnum.ACTIVATE_VOLUME_SNAPSHOT, true, AuditLogManager.AUDITOP_BEGIN,
snapshot.getId().toString(), snapshot.getLabel());
return toTask(snapshot, task, op);
}
/**
* Generates a group synchronized between volume Replication group
* and snapshot Replication group.
*
* @prereq There should be existing Storage synchronized relations
* between volumes and snapshots.
*
* @param id [required] - the URN of a ViPR block snapshot
*
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
@Path("/{id}/start")
public TaskResourceRep startSnapshot(@PathParam("id") URI id)
throws InternalException {
// Validate and get the snapshot.
ArgValidator.checkFieldUriType(id, BlockSnapshot.class, "id");
BlockSnapshot snapshot = (BlockSnapshot) queryResource(id);
// Get the block service API implementation for the snapshot parent volume.
Volume volume = _permissionsHelper.getObjectById(snapshot.getParent(), Volume.class);
// Get the storage system for the volume
StorageSystem storage = _permissionsHelper.getObjectById(volume.getStorageController(), StorageSystem.class);
if (!snapshot.getIsSyncActive()) {
throw APIException.badRequests.cannotEstablishGroupRelationForInactiveSnapshot(snapshot.getLabel());
}
if (!volume.hasConsistencyGroup() ||
!snapshot.hasConsistencyGroup()) {
throw APIException.badRequests.blockObjectHasNoConsistencyGroup();
}
// Create the task identifier.
String taskId = UUID.randomUUID().toString();
BlockServiceApi blockServiceApiImpl = getBlockServiceImpl("default");
// Create the audit log entry.
auditOp(OperationTypeEnum.ESTABLISH_VOLUME_SNAPSHOT, true, AuditLogManager.AUDITOP_BEGIN,
id.toString(), volume.getId().toString(), snapshot.getStorageController().toString());
return blockServiceApiImpl.establishVolumeAndSnapshotGroupRelation(storage, volume, snapshot, taskId);
}
/**
* Create a full copy as a volume of the specified snapshot.
*
* @brief Create full copies as volumes
*
* @prereq none
*
* @param id Source snapshot URI
* @param param POST data containing full copy creation information
*
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskList createFullCopy(@PathParam("id") URI id,
VolumeFullCopyCreateParam param) throws InternalException {
BlockSnapshot snapshot = (BlockSnapshot) queryResource(id);
// Get the block service API implementation for the snapshot parent volume.
Volume parentVolume = _permissionsHelper.getObjectById(snapshot.getParent(), Volume.class);
// Make sure that we don't have some pending
// operation against the parent volume
checkForPendingTasks(Arrays.asList(parentVolume.getTenant().getURI()), Arrays.asList(parentVolume));
return getFullCopyManager().createFullCopy(id, param);
}
/**
* Creates and returns an instance of the block full copy manager to handle
* a full copy request.
*
* @return BlockFullCopyManager
*/
private BlockFullCopyManager getFullCopyManager() {
BlockFullCopyManager fcManager = new BlockFullCopyManager(_dbClient,
_permissionsHelper, _auditMgr, _coordinator, _placementManager, sc, uriInfo,
_request, null);
return fcManager;
}
/**
* Record audit log for BlockSnapshot service
*
* @param auditType Type of AuditLog
* @param operationalStatus Status of operation
* @param operationStage Stage of operation.
* For sync operation, it should be null;
* For async operation, it should be "BEGIN" or "END";
* @param descparams Description paramters
*/
public void auditBlockSnapshot(OperationTypeEnum auditType,
String operationalStatus,
String operationStage,
Object... descparams) {
_auditMgr.recordAuditLog(URI.create(getUserFromContext().getTenantId()),
URI.create(getUserFromContext().getName()),
EVENT_SERVICE_TYPE,
auditType,
System.currentTimeMillis(),
operationalStatus,
operationStage,
descparams);
}
/**
* Retrieve resource representations based on input ids.
*
* @prereq none
* @param param POST data containing the id list.
* @brief List data of block snapshot resources
* @return list of representations.
*/
@POST
@Path("/bulk")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Override
public BlockSnapshotBulkRep getBulkResources(BulkIdParam param) {
return (BlockSnapshotBulkRep) super.getBulkResources(param);
}
/**
* Return all the export information related to snaphot ids passed.
* This will be in the form of a list of initiator / target pairs
* for all the initiators that have been paired with a target
* storage port.
*
*
* @prereq none
*
* @param param POST data containing the id list.
*
* @brief Show export information for snapshots
* @return List of exports
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/exports/bulk")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public ITLBulkRep getSnapshotsExports(BulkIdParam param) {
List<URI> snapshotIdList = param.getIds();
ITLBulkRep list = new ITLBulkRep();
for (URI snapshotId : snapshotIdList) {
ArgValidator.checkFieldUriType(snapshotId, BlockSnapshot.class, "id");
queryResource(snapshotId);
list.getExportList().addAll(
ExportUtils.getBlockObjectInitiatorTargets(snapshotId, _dbClient, isIdEmbeddedInURL(snapshotId)).getExportList());
}
return list;
}
@SuppressWarnings("unchecked")
@Override
public Class<BlockSnapshot> getResourceClass() {
return BlockSnapshot.class;
}
/**
* Retrieve BlockSnapshot representations based on input ids.
*
* @return list of BlockSnapshot representations.
*/
@Override
public BlockSnapshotBulkRep queryBulkResourceReps(List<URI> ids) {
Iterator<BlockSnapshot> _dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids);
return new BlockSnapshotBulkRep(BulkList.wrapping(_dbIterator, MapBlockSnapshot.getInstance(_dbClient)));
}
@Override
protected BulkRestRep queryFilteredBulkResourceReps(
List<URI> ids) {
Iterator<BlockSnapshot> _dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids);
ResourceFilter<BlockSnapshot> filter = new BlockSnapshotFilter(getUserFromContext(), _permissionsHelper);
return new BlockSnapshotBulkRep(BulkList.wrapping(_dbIterator, MapBlockSnapshot.getInstance(_dbClient), filter));
}
private class BlockSnapshotFilter extends PermissionsEnforcingResourceFilter<BlockSnapshot> {
protected BlockSnapshotFilter(StorageOSUser user,
PermissionsHelper permissionsHelper) {
super(user, permissionsHelper);
}
@Override
public boolean isAccessible(BlockSnapshot resource) {
boolean ret = false;
ret = isTenantAccessible(getTenantOwner(resource.getId()));
if (!ret) {
NamedURI proj = resource.getProject();
if (proj != null) {
ret = isProjectAccessible(proj.getURI());
}
}
return ret;
}
}
/**
* Block snapshot is not a zone level resource
*/
@Override
protected boolean isZoneLevelResource() {
return false;
}
@Override
protected ResourceTypeEnum getResourceType() {
return ResourceTypeEnum.BLOCK_SNAPSHOT;
}
/**
* Get search results by name in zone or project.
*
* @return SearchedResRepList
*/
@Override
protected SearchedResRepList getNamedSearchResults(String name, URI projectId) {
SearchedResRepList resRepList = new SearchedResRepList(getResourceType());
if (projectId == null) {
_dbClient.queryByConstraint(
PrefixConstraint.Factory.getLabelPrefixConstraint(getResourceClass(), name),
resRepList);
} else {
_dbClient.queryByConstraint(
ContainmentPrefixConstraint.Factory.getBlockSnapshotUnderProjectConstraint(
projectId, name),
resRepList);
}
return resRepList;
}
/**
* Get search results by project alone.
*
* @return SearchedResRepList
*/
@Override
protected SearchedResRepList getProjectSearchResults(URI projectId) {
SearchedResRepList resRepList = new SearchedResRepList(getResourceType());
_dbClient.queryByConstraint(
ContainmentConstraint.Factory.getProjectBlockSnapshotConstraint(projectId),
resRepList);
return resRepList;
}
/**
* Get object specific permissions filter
*
*/
@Override
public ResRepFilter<? extends RelatedResourceRep> getPermissionFilter(StorageOSUser user,
PermissionsHelper permissionsHelper) {
return new ProjOwnedSnapResRepFilter(user, permissionsHelper, BlockSnapshot.class);
}
/**
* Exposes the target volume associated with BlockSnapshot instance with the passed id
* as a ViPR Volume. Currently, the BlockSnapshot instance must represent a snapshot
* whose parent volume is the backend volume for a VPLEX volume. That is, it must be a
* VPLEX snapshot. The purpose is to expose the backend snapshot as a VPLEX volume so
* that access to the snapshot is via the VPLEX rather than directly via the backend
* storage system.
*
* @brief Expose snapshot as a volume.
*
* @prereq None
*
* @param id The URI of the BlockSnapshot instance.
*
* @return A TaskResourceRep.
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/expose")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskResourceRep exposeSnapshotAsVolume(@PathParam("id") URI id) {
// Validate and get the block snapshot.
ArgValidator.checkFieldUriType(id, BlockSnapshot.class, "id");
BlockSnapshot snapshot = (BlockSnapshot) queryResource(id);
// Verify it is a block snapshot for a VPLEX volume.
URI sourceVolumeURI = snapshot.getParent().getURI();
Volume sourceVolume = _dbClient.queryObject(Volume.class, sourceVolumeURI);
if (!Volume.checkForVplexBackEndVolume(_dbClient, sourceVolume)) {
throw APIException.badRequests.cantExposeNonVPLEXSnapshot(snapshot.getLabel());
}
// Verify it is not marked for deletion.
if (snapshot.getInactive()) {
throw APIException.badRequests.cantExposeInactiveSnapshot(snapshot.getLabel());
}
// Verify that it has been activated such that the target volume
// reflects the source volume data. Otherwise, when the snapshot
// is activated, the read cache for the VPLEX volume would be invalid.
if (!snapshot.getIsSyncActive()) {
throw APIException.badRequests.cantExposeUnsynchronizedSnapshot(snapshot.getLabel());
}
// Verify it has yet to be used to create a VPLEX volume.
// A snapshot can only be used to create a single VPLEX volume.
// This is because multiple VPLEX volumes cannot use the same
// backend volume. We know it has already been imported to
// VPLEX if there is an active volume in the database with the
// same native GUID as the snapshot.
String snapshotNativeGuid = snapshot.getNativeGuid();
if (!CustomQueryUtility.getActiveVolumeByNativeGuid(_dbClient, snapshotNativeGuid).isEmpty()) {
throw APIException.badRequests.cantExposeSnapshotAlreadyExposed(snapshot.getLabel());
}
// If the backend VPLEX snapshot snapshot is not currently exposed as
// a VPLEX volume and it is exported, then it must be exported to a
// host/cluster. If this is the case, we don't allow the user to expose
// the snapshot as a VPLEX volume. If the user subsequently writes to the
// snapshot, the VPLEX volume built on top of it, would not be valid as
// the VPLEX would not be aware of these writes. Further, we know this will
// fail for VMAX3 backend snapshots with the error "A device cannot belong
// to more than one storage group in use by FAST". We thought it best to
// disable for all platforms for the reason discussed.
if (snapshot.isSnapshotExported(_dbClient)) {
throw APIException.badRequests.cantExposeExportedSnapshot(snapshot.getLabel());
}
// Get the virtual pool of the snapshot source volume. We need to set
// a virtual pool for the VPLEX volume that will be created. Currently,
// we use the virtual pool for the source volume, even though it may not
// apply for the VPLEX volume we create. It really should not matter much
// as we will limit the operations on this VPLEX volume to Export/Unexport,
// so really, the only parameters we need are the PATH parameters, which
// implies this VPLEX volume would have the same path constraints. The
// alternative is to create a generic internal virtual pool.
VirtualPool sourceVolumeVpool = _dbClient.queryObject(VirtualPool.class, sourceVolume.getVirtualPool());
// Create a new INTERNAL Volume instance using the volume info
// in the block snapshot. VPLEX volumes are created from Volume
// instances rather then BlockObject instances, so we cannot use
// the BlockSnapshot directly. The other alternative is to modify
// all code involved with VPLEX volume creation and all code that
// accesses or modifies the backend volumes for VPLEX volumes. This
// would be a large, invasive undertaking.
Volume backendVolume = prepareVPLEXBackendVolumeFromSnapshot(snapshot, sourceVolume);
// Create a unique task identifier.
String taskId = UUID.randomUUID().toString();
// Get the VPLEX block service implementation and call the routine to
// import a non-VPLEX volume into VPLEX.
try {
VPlexBlockServiceApiImpl vplexBlocSvcApi = (VPlexBlockServiceApiImpl) getBlockServiceImpl("vplex");
vplexBlocSvcApi.importVirtualVolume(snapshot.getStorageController(), backendVolume, sourceVolumeVpool, taskId);
} catch (Exception e) {
_log.error("Exception importing snapshot to VPLEX", e);
backendVolume.setInactive(true);
_dbClient.persistObject(backendVolume);
throw e;
}
// Create and return the task for this request. NOte that we
// create the operation on the snapshot, but the import routine
// will expect the operation to be on the volume. We updated the
// completer to update the status on the snapshot as well if we
// find a snapshot with the same native guid as the volume.
Operation op = new Operation();
op.setResourceType(ResourceOperationTypeEnum.CREATE_VPLEX_VOLUME_FROM_SNAPSHOT);
_dbClient.createTaskOpStatus(BlockSnapshot.class, id, taskId, op);
snapshot.getOpStatus().put(taskId, op);
// Return the task.
return toTask(snapshot, taskId, op);
}
/**
* Creates a Volume instance to represent the snapshot target volume represented
* by the passed BlockSnapshot instance.
*
* @param snapshot A reference to a BlockSnapshot instance.
* @param sourceVolume A reference to the snapshot source Volume.
*
* @return A new INTERNAL Volume instance representing the snapshot target volume.
*/
private Volume prepareVPLEXBackendVolumeFromSnapshot(BlockSnapshot snapshot, Volume sourceVolume) {
// Create a new Volume instance and populate its fields with the
// target volume information in the passed snapshot. Note that
// not all fields need be populated. This is an INTERNAL volume
// and the operations on the VPLEX volume created on top of it
// will be limited. We set the fields that accurately reflect
// the volume and are necessary to allow it to be used as a
// backend volume for a VPLEX volume and to allow that VPLEX
// volume to subsequently be exported to hosts/clusters.
Volume volume = new Volume();
volume.setId(URIUtil.createId(Volume.class));
volume.setLabel(snapshot.getLabel());
volume.setWWN(snapshot.getWWN());
volume.setNativeId(snapshot.getNativeId());
volume.setNativeGuid(snapshot.getNativeGuid());
volume.setAlternateName(snapshot.getAlternateName());
volume.setDeviceLabel(snapshot.getDeviceLabel());
volume.setProvisionedCapacity(snapshot.getProvisionedCapacity());
volume.setAllocatedCapacity(snapshot.getAllocatedCapacity());
volume.setVirtualArray(snapshot.getVirtualArray());
volume.setProject(snapshot.getProject());
volume.setStorageController(snapshot.getStorageController());
volume.setSystemType(snapshot.getSystemType());
StringSet protocols = new StringSet();
protocols.addAll(snapshot.getProtocol());
volume.setProtocol(protocols);
volume.addInternalFlags(DataObject.Flag.INTERNAL_OBJECT);
volume.setOpStatus(new OpStatusMap());
// Some of the volume parameters must come from the source volume
// of the passed block snapshot including the Tenant.
volume.setTenant(sourceVolume.getTenant());
// The snapshot does not contain the user requested capacity.
volume.setCapacity(sourceVolume.getCapacity());
// The snapshot target should be thin if the source volume was
// thinly provisioned. This will control be used to control whether
// or not thin rebuilds occur when the volume we create is
// distributed.
volume.setThinlyProvisioned(sourceVolume.getThinlyProvisioned());
// The source side backend volume for a VPLEX volume always
// has the same virtual pool as the VPLEX volume, and we will
// use the source volume virtual pool for the VPLEX volume.
volume.setVirtualPool(sourceVolume.getVirtualPool());
// Set auto tier policy from the source volume. This comes
// into play in vmax3 export operations.
volume.setAutoTieringPolicyUri(sourceVolume.getAutoTieringPolicyUri());
// The pool is not currently set as the BlockSnapshot does not
// keep the storage pool for the target volume. This did not
// cause issues for the allowed operations (export/unexport/delete)
// for the VPLEX volume built on the snapshot. We could alternatively
// try and set the storage pool in the block snapshot instance so
// we have the actual storage pool.
// volume.setPool(TBD);
// Create the instance in the database.
_dbClient.createObject(volume);
return volume;
}
}