/*
* Copyright (c) 2012-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.DbObjectMapper.toNamedRelatedResource;
import static com.emc.storageos.api.mapper.TaskMapper.toTask;
import static com.emc.storageos.db.client.constraint.ContainmentConstraint.Factory.getBlockSnapshotByConsistencyGroup;
import static com.emc.storageos.db.client.constraint.ContainmentConstraint.Factory.getVolumesByConsistencyGroup;
import static com.emc.storageos.db.client.util.CommonTransformerFunctions.fctnDataObjectToID;
import static com.emc.storageos.model.block.Copy.SyncDirection.SOURCE_TO_TARGET;
import static com.emc.storageos.svcs.errorhandling.resources.ServiceCode.CONTROLLER_ERROR;
import static com.google.common.collect.Collections2.transform;
import static com.google.common.collect.Lists.newArrayList;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.PUT;
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.BlockMapper;
import com.emc.storageos.api.mapper.TaskMapper;
import com.emc.storageos.api.mapper.functions.MapBlockConsistencyGroup;
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.BlockService.ProtectionOp;
import com.emc.storageos.api.service.impl.resource.fullcopy.BlockFullCopyManager;
import com.emc.storageos.api.service.impl.resource.fullcopy.BlockFullCopyUtils;
import com.emc.storageos.api.service.impl.resource.snapshot.BlockSnapshotSessionManager;
import com.emc.storageos.api.service.impl.resource.snapshot.BlockSnapshotSessionUtils;
import com.emc.storageos.api.service.impl.resource.utils.BlockServiceUtils;
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.ProjOwnedResRepFilter;
import com.emc.storageos.api.service.impl.response.ResRepFilter;
import com.emc.storageos.api.service.impl.response.SearchedResRepList;
import com.emc.storageos.db.client.DbClient;
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.BlockConsistencyGroup;
import com.emc.storageos.db.client.model.BlockConsistencyGroup.Types;
import com.emc.storageos.db.client.model.BlockMirror;
import com.emc.storageos.db.client.model.BlockObject;
import com.emc.storageos.db.client.model.BlockSnapshot;
import com.emc.storageos.db.client.model.BlockSnapshot.TechnologyType;
import com.emc.storageos.db.client.model.BlockSnapshotSession;
import com.emc.storageos.db.client.model.DataObject;
import com.emc.storageos.db.client.model.DiscoveredDataObject;
import com.emc.storageos.db.client.model.DiscoveredDataObject.Type;
import com.emc.storageos.db.client.model.NamedURI;
import com.emc.storageos.db.client.model.Operation;
import com.emc.storageos.db.client.model.Project;
import com.emc.storageos.db.client.model.ProtectionSystem;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.model.Task;
import com.emc.storageos.db.client.model.VirtualArray;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.model.Volume.PersonalityTypes;
import com.emc.storageos.db.client.model.VolumeGroup;
import com.emc.storageos.db.client.model.util.BlockConsistencyGroupUtils;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.db.exceptions.DatabaseException;
import com.emc.storageos.model.BulkIdParam;
import com.emc.storageos.model.BulkRestRep;
import com.emc.storageos.model.NamedRelatedResourceRep;
import com.emc.storageos.model.RelatedResourceRep;
import com.emc.storageos.model.ResourceOperationTypeEnum;
import com.emc.storageos.model.ResourceTypeEnum;
import com.emc.storageos.model.SnapshotList;
import com.emc.storageos.model.TaskList;
import com.emc.storageos.model.TaskResourceRep;
import com.emc.storageos.model.block.BlockConsistencyGroupBulkRep;
import com.emc.storageos.model.block.BlockConsistencyGroupCreate;
import com.emc.storageos.model.block.BlockConsistencyGroupRestRep;
import com.emc.storageos.model.block.BlockConsistencyGroupSnapshotCreate;
import com.emc.storageos.model.block.BlockConsistencyGroupUpdate;
import com.emc.storageos.model.block.BlockSnapshotRestRep;
import com.emc.storageos.model.block.BlockSnapshotSessionList;
import com.emc.storageos.model.block.BulkDeleteParam;
import com.emc.storageos.model.block.CopiesParam;
import com.emc.storageos.model.block.Copy;
import com.emc.storageos.model.block.NamedVolumesList;
import com.emc.storageos.model.block.SnapshotSessionCreateParam;
import com.emc.storageos.model.block.SnapshotSessionLinkTargetsParam;
import com.emc.storageos.model.block.SnapshotSessionRelinkTargetsParam;
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.VolumeRestRep;
import com.emc.storageos.protectioncontroller.RPController;
import com.emc.storageos.protectionorchestrationcontroller.ProtectionOrchestrationController;
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.services.util.StorageDriverManager;
import com.emc.storageos.services.util.TimeUtils;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import com.emc.storageos.svcs.errorhandling.resources.BadRequestException;
import com.emc.storageos.svcs.errorhandling.resources.InternalException;
import com.emc.storageos.svcs.errorhandling.resources.ServiceCodeException;
import com.emc.storageos.util.VPlexUtil;
import com.emc.storageos.volumecontroller.BlockController;
import com.emc.storageos.volumecontroller.ControllerException;
import com.emc.storageos.volumecontroller.impl.ControllerUtils;
import com.emc.storageos.volumecontroller.impl.smis.SRDFOperations.Mode;
import com.google.common.collect.Table;
import com.google.common.collect.Table.Cell;
@Path("/block/consistency-groups")
@DefaultPermissions(readRoles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, readAcls = { ACL.OWN,
ACL.ALL }, writeRoles = { Role.TENANT_ADMIN }, writeAcls = { ACL.OWN, ACL.ALL })
public class BlockConsistencyGroupService extends TaskResourceService {
private static final String BLOCKSERVICEAPIIMPL_GROUP = "group";
private static final Logger _log = LoggerFactory.getLogger(BlockConsistencyGroupService.class);
private static final int CG_MAX_LIMIT = 64;
private static final String FULL_COPY = "Full copy";
// A reference to the placement manager.
private PlacementManager _placementManager;
// A reference to the block service.
private BlockService _blockService;
// Block service implementations
private Map<String, BlockServiceApi> _blockServiceApis;
/**
* Setter for the placement manager.
*
* @param placementManager A reference to the placement manager.
*/
public void setPlacementManager(PlacementManager placementManager) {
_placementManager = placementManager;
}
/**
* Setter for the block service.
*
* @param blockService A reference to the block service.
*/
public void setBlockService(BlockService blockService) {
_blockService = blockService;
}
public void setBlockServiceApis(final Map<String, BlockServiceApi> serviceInterfaces) {
_blockServiceApis = serviceInterfaces;
}
private BlockServiceApi getBlockServiceImpl(final String type) {
return _blockServiceApis.get(type);
}
private BlockServiceApi getBlockServiceImpl(final BlockConsistencyGroup cg) {
BlockServiceApi blockServiceApiImpl = null;
if (cg.checkForType(Types.RP)) {
blockServiceApiImpl = getBlockServiceImpl(BlockConsistencyGroup.Types.RP.toString().toLowerCase());
} else if (cg.checkForType(Types.VPLEX)) {
blockServiceApiImpl = getBlockServiceImpl(BlockConsistencyGroup.Types.VPLEX.toString().toLowerCase());
} else {
blockServiceApiImpl = getBlockServiceImpl(BLOCKSERVICEAPIIMPL_GROUP);
}
return blockServiceApiImpl;
}
/**
* Get the specific BlockServiceApiImpl based on the storage system type.
*
* @param system The storage system instance
* @return BloackServiceApiImpl for the storage system type.
*/
private BlockServiceApi getBlockServiceImpl(final StorageSystem system) {
BlockServiceApi blockServiceApiImpl = null;
String systemType = system.getSystemType();
if (systemType.equals(DiscoveredDataObject.Type.rp.name()) ||
systemType.equals(DiscoveredDataObject.Type.vplex.name())) {
blockServiceApiImpl = getBlockServiceImpl(systemType);
} else {
blockServiceApiImpl = getBlockServiceImpl(BLOCKSERVICEAPIIMPL_GROUP);
}
return blockServiceApiImpl;
}
@Override
protected DataObject queryResource(final URI id) {
ArgValidator.checkUri(id);
final Class<? extends DataObject> clazz;
if (URIUtil.isType(id, BlockSnapshotSession.class)) {
clazz = BlockSnapshotSession.class;
} else if (URIUtil.isType(id, BlockSnapshot.class)) {
clazz = BlockSnapshot.class;
} else {
clazz = BlockConsistencyGroup.class;
}
final DataObject resource = _permissionsHelper.getObjectById(id, clazz);
ArgValidator.checkEntityNotNull(resource, id, isIdEmbeddedInURL(id));
return resource;
}
@Override
protected URI getTenantOwner(final URI id) {
return null;
}
/**
* Create a new consistency group
*
* You can create a consistency group, but adding volumes into it will be done using in the
* volume create operations:
*
* 1. Create CG object in Bourne 2. Operation will be synchronous
*
*
* @prereq none
*
* @param param
*
* @brief Create consistency group
* @return Consistency Group created
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public BlockConsistencyGroupRestRep createConsistencyGroup(
final BlockConsistencyGroupCreate param) {
checkForDuplicateName(param.getName(), BlockConsistencyGroup.class);
ArgValidator.checkIsAlphaNumeric(param.getName());
// Validate name
ArgValidator.checkFieldNotEmpty(param.getName(), "name");
// Validate name not greater than 64 characters
ArgValidator.checkFieldLengthMaximum(param.getName(), CG_MAX_LIMIT, "name");
// Validate project
ArgValidator.checkFieldUriType(param.getProject(), Project.class, "project");
final Project project = _dbClient.queryObject(Project.class, param.getProject());
ArgValidator
.checkEntity(project, param.getProject(), isIdEmbeddedInURL(param.getProject()));
// Verify the user is authorized.
verifyUserIsAuthorizedForRequest(project);
// Create Consistency Group in db
final BlockConsistencyGroup consistencyGroup = new BlockConsistencyGroup();
consistencyGroup.setId(URIUtil.createId(BlockConsistencyGroup.class));
consistencyGroup.setLabel(param.getName());
consistencyGroup.setProject(new NamedURI(project.getId(), project.getLabel()));
consistencyGroup.setTenant(project.getTenantOrg());
// disable array consistency if user has selected not to create backend replication group
consistencyGroup.setArrayConsistency(param.getArrayConsistency());
_dbClient.createObject(consistencyGroup);
return map(consistencyGroup, null, _dbClient);
}
/**
* Show details for a specific consistency group
*
*
* @prereq none
*
* @param id the URN of a ViPR Consistency group
*
* @brief Show consistency group
* @return Consistency group
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public BlockConsistencyGroupRestRep getConsistencyGroup(@PathParam("id") final URI id) {
ArgValidator.checkFieldUriType(id, BlockConsistencyGroup.class, "id");
// Query for the consistency group
final BlockConsistencyGroup consistencyGroup = (BlockConsistencyGroup) queryResource(id);
// Get the implementation for the CG.
BlockServiceApi blockServiceApiImpl = getBlockServiceImpl(consistencyGroup);
// Get the CG volumes
List<Volume> volumes = BlockConsistencyGroupUtils.getActiveVolumesInCG(consistencyGroup,
_dbClient, null);
// If no volumes, just return the consistency group
if (volumes.isEmpty()) {
return map(consistencyGroup, null, _dbClient);
}
Set<URI> volumeURIs = new HashSet<URI>();
for (Volume volume : volumes) {
volumeURIs.add(volume.getId());
}
return map(consistencyGroup, volumeURIs, _dbClient);
}
/**
* Deletes a consistency group
*
* Do not delete if snapshots exist for consistency group
*
*
* @prereq Dependent snapshot resources must be deleted
*
* @param id the URN of a ViPR Consistency group
*
* @brief Delete consistency group
* @return TaskResourceRep
*
* @throws InternalException
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/deactivate")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskResourceRep deleteConsistencyGroup(@PathParam("id") final URI id,
@DefaultValue("FULL") @QueryParam("type") String type) throws InternalException {
// Query for the given consistency group and verify it is valid.
final BlockConsistencyGroup consistencyGroup = (BlockConsistencyGroup) queryResource(id);
ArgValidator.checkReference(BlockConsistencyGroup.class, id, checkForDelete(consistencyGroup));
// Create a unique task identifier.
String task = UUID.randomUUID().toString();
// If the consistency group is inactive, has yet to be created on
// a storage system, or this is a ViPR Only delete, then the deletion
// is not controller specific. We essentially just mark the CG for
// deletion. Note that the CG may be uncreated, but in the process of
// being created, which means that volumes would reference the CG.
// So, we do need to verify that no volumes reference the CG.
if (deletingUncreatedConsistencyGroup(consistencyGroup) ||
VolumeDeleteTypeEnum.VIPR_ONLY.name().equals(type)) {
markCGForDeletion(consistencyGroup);
return finishDeactivateTask(consistencyGroup, task);
}
// Otherwise, we need to clean up the array consistency groups.
TaskResourceRep taskRep = null;
try {
List<StorageSystem> vplexSystems = BlockConsistencyGroupUtils.getVPlexStorageSystems(consistencyGroup, _dbClient);
if (!vplexSystems.isEmpty()) {
// If there is a VPLEX system, then we simply call the VPLEX controller which
// will delete all VPLEX CGS on all VPLEX systems, and also all local CGs on
// all local systems.
BlockServiceApi blockServiceApi = getBlockServiceImpl(DiscoveredDataObject.Type.vplex.name());
taskRep = blockServiceApi.deleteConsistencyGroup(vplexSystems.get(0), consistencyGroup, task);
} else {
// Otherwise, we call the block controller to delete the local CGs on all local systems.
List<URI> localSystemURIs = BlockConsistencyGroupUtils.getLocalSystems(consistencyGroup, _dbClient);
if (!localSystemURIs.isEmpty()) {
boolean foundSystem = false;
for (URI localSystemURI : localSystemURIs) {
StorageSystem localSystem = _dbClient.queryObject(StorageSystem.class, localSystemURI);
if (localSystem != null) {
foundSystem = true;
BlockServiceApi blockServiceApi = getBlockServiceImpl(BLOCKSERVICEAPIIMPL_GROUP);
taskRep = blockServiceApi.deleteConsistencyGroup(localSystem, consistencyGroup, task);
if (Task.Status.error.name().equals(taskRep.getState())) {
break;
}
} else {
_log.warn("Local system {} for consistency group {} does not exist",
localSystemURI, consistencyGroup.getLabel());
}
}
// Check to make sure we found at least one of these local systems.
if (!foundSystem) {
// For some reason we have a CG with local systems, but none of them
// are in the database. In this case, we will log a warning and mark
// it for deletion.
_log.warn("Deleting created consistency group {} where none of the local systems for the group exist",
consistencyGroup.getLabel());
markCGForDeletion(consistencyGroup);
return finishDeactivateTask(consistencyGroup, task);
}
} else {
// For some reason the CG has no VPLEX or local systems but is
// marked as being active and created. In this case, we will log
// a warning and mark it for deletion.
_log.info("Deleting created consistency group {} with no local or VPLEX systems", consistencyGroup.getLabel());
markCGForDeletion(consistencyGroup);
return finishDeactivateTask(consistencyGroup, task);
}
}
} catch (APIException | InternalException e) {
String errorMsg = String.format("Exception attempting to delete consistency group %s: %s", consistencyGroup.getLabel(),
e.getMessage());
_log.error(errorMsg);
taskRep.setState(Operation.Status.error.name());
taskRep.setMessage(errorMsg);
_dbClient.error(BlockConsistencyGroup.class, taskRep.getResource().getId(), task, e);
} catch (Exception e) {
String errorMsg = String.format("Exception attempting to delete consistency group %s: %s", consistencyGroup.getLabel(),
e.getMessage());
_log.error(errorMsg);
APIException apie = APIException.internalServerErrors.genericApisvcError(errorMsg, e);
taskRep.setState(Operation.Status.error.name());
taskRep.setMessage(apie.getMessage());
_dbClient.error(BlockConsistencyGroup.class, taskRep.getResource().getId(), task, apie);
}
// Make sure that the CG is marked for deletion if
// the request was successful.
if (Task.Status.ready.name().equals(taskRep.getState())) {
markCGForDeletion(consistencyGroup);
}
return taskRep;
}
/**
* Update the CG so it is deleted.
*
* @param consistencyGroup A reference to the consistency group.
*/
private void markCGForDeletion(BlockConsistencyGroup consistencyGroup) {
if (!consistencyGroup.getInactive()) {
consistencyGroup.setStorageController(null);
consistencyGroup.setInactive(true);
_dbClient.updateObject(consistencyGroup);
}
}
/**
* Check to see if the consistency group is active and not created. In
* this case we can delete the consistency group. Otherwise we should
* not delete the consistency group. Note if VPLEX CG has been created,
* we should call the VPlexConsistencyGroupManager to remove it.
*
* @param consistencyGroup
* A reference to the CG.
*
* @return True if the CG is active and not created.
*/
private boolean deletingUncreatedConsistencyGroup(
final BlockConsistencyGroup consistencyGroup) {
// If the consistency group is active and not created we can delete it,
// otherwise we cannot.
return (!consistencyGroup.getInactive() && !consistencyGroup.created()
&& !consistencyGroup.getTypes().contains(BlockConsistencyGroup.Types.VPLEX.name()));
}
/**
* Retrieve resource representations based on input ids.
*
*
* @prereq none
*
* @param param
* POST data containing the id list.
*
* @brief List data of consistency group resources
* @return list of representations.
*
* @throws DatabaseException
* When an error occurs querying the database.
*/
@POST
@Path("/bulk")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Override
public BlockConsistencyGroupBulkRep getBulkResources(final BulkIdParam param) {
return (BlockConsistencyGroupBulkRep) super.getBulkResources(param);
}
/**
* Creates a consistency group snapshot
*
*
* @prereq none
*
* @param consistencyGroupId
* - Consistency group URI
* @param param
*
* @brief Create consistency group snapshot
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshots")
@CheckPermission(roles = { Role.SYSTEM_ADMIN }, acls = { ACL.ANY })
public TaskList createConsistencyGroupSnapshot(@PathParam("id") final URI consistencyGroupId,
final BlockConsistencyGroupSnapshotCreate param) {
ArgValidator.checkFieldUriType(consistencyGroupId, BlockConsistencyGroup.class, "id");
// Query Consistency Group
final BlockConsistencyGroup consistencyGroup = (BlockConsistencyGroup) queryResource(consistencyGroupId);
// Ensure that the Consistency Group has been created on all of its defined
// system types.
if (!consistencyGroup.created()) {
throw APIException.badRequests.consistencyGroupNotCreated();
}
// RP CG's must use applications to create snapshots
if (isIdEmbeddedInURL(consistencyGroupId) && consistencyGroup.checkForType(Types.RP)) {
throw APIException.badRequests.snapshotsNotSupportedForRPCGs();
}
// Validate CG information in the request
validateVolumesInReplicationGroups(consistencyGroup, param.getVolumes(), _dbClient);
// Get the block service implementation
BlockServiceApi blockServiceApiImpl = getBlockServiceImpl(consistencyGroup);
Table<URI, String, List<Volume>> storageRgToVolumes = null;
if (!param.getVolumes().isEmpty()) {
// Volume group snapshot
// group volumes by backend storage system and replication group
storageRgToVolumes = BlockServiceUtils.getReplicationGroupVolumes(param.getVolumes(), consistencyGroupId, _dbClient, uriInfo);
} else {
// CG snapshot
storageRgToVolumes = BlockServiceUtils.getReplicationGroupVolumes(
blockServiceApiImpl.getActiveCGVolumes(consistencyGroup), _dbClient);
}
// Validation replication group volumes to ensure there aren't mixed meta and non-meta devices
validateReplicationGroupDevices(storageRgToVolumes);
TaskList taskList = new TaskList();
for (Cell<URI, String, List<Volume>> cell : storageRgToVolumes.cellSet()) {
List<Volume> volumeList = cell.getValue();
if (volumeList == null || volumeList.isEmpty()) {
_log.warn(String.format("No volume in replication group %s", cell.getColumnKey()));
continue;
}
// Generate task id
String taskId = UUID.randomUUID().toString();
// Set snapshot type.
String snapshotType = BlockSnapshot.TechnologyType.NATIVE.toString();
// Validate the snapshot request.
String snapshotName = TimeUtils.formatDateForCurrent(param.getName());
// Set the read only flag.
final Boolean readOnly = param.getReadOnly() == null ? Boolean.FALSE : param.getReadOnly();
// Set the create inactive flag.
final Boolean createInactive = param.getCreateInactive() == null ? Boolean.FALSE
: param.getCreateInactive();
blockServiceApiImpl.validateCreateSnapshot(volumeList.get(0), volumeList, snapshotType, snapshotName, readOnly, getFullCopyManager());
// Prepare and create the snapshots for the group.
List<URI> snapIdList = new ArrayList<URI>();
List<BlockSnapshot> snapshotList = new ArrayList<BlockSnapshot>();
snapshotList.addAll(blockServiceApiImpl.prepareSnapshots(
volumeList, snapshotType, snapshotName, snapIdList, taskId));
for (BlockSnapshot snapshot : snapshotList) {
taskList.getTaskList().add(toTask(snapshot, taskId));
}
addConsistencyGroupTask(consistencyGroup, taskList, taskId,
ResourceOperationTypeEnum.CREATE_CONSISTENCY_GROUP_SNAPSHOT);
try {
blockServiceApiImpl.createSnapshot(volumeList.get(0), snapIdList, snapshotType, createInactive, readOnly, taskId);
auditBlockConsistencyGroup(OperationTypeEnum.CREATE_CONSISTENCY_GROUP_SNAPSHOT,
AuditLogManager.AUDITLOG_SUCCESS, AuditLogManager.AUDITOP_BEGIN, param.getName(),
consistencyGroup.getId().toString());
} catch (Exception ex) {
_log.error("Unexpected Exception occurred when creating snapshot for replication group {}",
cell.getColumnKey(), ex);
}
}
return taskList;
}
/**
* Validates the volumes in each replication group to ensure there aren't mixed meta and
* non-meta volumes.
*
* @param storageRgToVolumes the list of volumes by storage system and replication group.
*/
private void validateReplicationGroupDevices(Table<URI, String, List<Volume>> storageRgToVolumes) {
for (Cell<URI, String, List<Volume>> cell : storageRgToVolumes.cellSet()) {
List<Volume> volumeList = cell.getValue();
boolean metaDevices = false;
boolean nonMetaDevices = false;
for (Volume vol : volumeList) {
if (vol.getMetaMemberCount() != null && vol.getMetaMemberCount() > 0) {
metaDevices = true;
} else {
nonMetaDevices = true;
}
}
if (metaDevices && nonMetaDevices) {
throw APIException.badRequests.cgSnapshotNotAllowedMixedDevices(cell.getColumnKey());
}
}
}
/**
* Validate the volumes we are requested to snap all contain the proper replication group instance information.
*
* @param consistencyGroup consistency group object
* @param volumes incoming request parameters
* @param dbClient dbclient
*/
private void validateVolumesInReplicationGroups(BlockConsistencyGroup consistencyGroup, List<URI> volumes,
DbClient dbClient) {
// Get all of the volumes from the consistency group
Iterator<Volume> volumeIterator = null;
if (volumes == null || volumes.isEmpty()) {
URIQueryResultList uriQueryResultList = new URIQueryResultList();
dbClient.queryByConstraint(getVolumesByConsistencyGroup(consistencyGroup.getId()), uriQueryResultList);
volumeIterator = dbClient.queryIterativeObjects(Volume.class, uriQueryResultList);
} else {
volumeIterator = dbClient.queryIterativeObjects(Volume.class, volumes);
}
if (volumeIterator == null || !volumeIterator.hasNext()) {
throw APIException.badRequests.cgReplicationNotAllowedMissingReplicationGroupNoVols(consistencyGroup.getLabel());
}
while (volumeIterator.hasNext()) {
Volume volume = volumeIterator.next();
if (volume.getInactive()) {
continue;
}
// Ignore RP journal volume in this validation
if (NullColumnValueGetter.isNullValue(volume.getPersonality())
|| !Volume.PersonalityTypes.METADATA.name().equalsIgnoreCase(volume.getPersonality())) {
// If it's a VPLEX volume, check both backing volumes to make sure they have replication group instance set
if (volume.isVPlexVolume(dbClient)) {
Volume backendVolume = VPlexUtil.getVPLEXBackendVolume(volume, false, dbClient);
if (backendVolume != null && NullColumnValueGetter.isNullValue(backendVolume.getReplicationGroupInstance())) {
// Ignore HA volumes not in a consistency group if a CG is specified; no snap sessions on HA side
if (consistencyGroup != null && !NullColumnValueGetter.isNullURI(backendVolume.getConsistencyGroup())) {
throw APIException.badRequests.cgReplicationNotAllowedMissingReplicationGroup(backendVolume.getLabel());
}
}
backendVolume = VPlexUtil.getVPLEXBackendVolume(volume, true, dbClient);
if (backendVolume != null && NullColumnValueGetter.isNullValue(backendVolume.getReplicationGroupInstance())) {
throw APIException.badRequests.cgReplicationNotAllowedMissingReplicationGroup(backendVolume.getLabel());
}
} else {
// Non-VPLEX, just check for replication group instance
if (NullColumnValueGetter.isNullValue(volume.getReplicationGroupInstance())) {
throw APIException.badRequests.cgReplicationNotAllowedMissingReplicationGroup(volume.getLabel());
}
}
}
}
}
/**
* List snapshots in the consistency group
*
*
* @prereq none
*
* @param consistencyGroupId
* - Consistency group URI
*
* @brief List snapshots in the consistency group
* @return The list of snapshots in the consistency group
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshots")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public SnapshotList getConsistencyGroupSnapshots(@PathParam("id") final URI consistencyGroupId) {
ArgValidator.checkUri(consistencyGroupId);
final Class<? extends DataObject> clazz = URIUtil.isType(consistencyGroupId,
BlockSnapshot.class) ? BlockSnapshot.class : BlockConsistencyGroup.class;
final DataObject consistencyGroup = _permissionsHelper.getObjectById(consistencyGroupId,
clazz);
ArgValidator.checkEntityNotNull(consistencyGroup, consistencyGroupId,
isIdEmbeddedInURL(consistencyGroupId));
List<Volume> volumes = ControllerUtils.getVolumesPartOfCG(consistencyGroupId, _dbClient);
// if any of the source volumes are in an application, replica management must be done via the application
for (Volume srcVol : volumes) {
if (srcVol.getApplication(_dbClient) != null) {
return new SnapshotList();
}
}
SnapshotList list = new SnapshotList();
List<URI> snapshotsURIs = new ArrayList<URI>();
// Find all volumes assigned to the group
final URIQueryResultList cgSnapshotsResults = new URIQueryResultList();
_dbClient.queryByConstraint(getBlockSnapshotByConsistencyGroup(consistencyGroupId),
cgSnapshotsResults);
if (!cgSnapshotsResults.iterator().hasNext()) {
return list;
}
while (cgSnapshotsResults.iterator().hasNext()) {
URI snapshot = cgSnapshotsResults.iterator().next();
snapshotsURIs.add(snapshot);
}
List<BlockSnapshot> snapshots = _dbClient.queryObject(BlockSnapshot.class, snapshotsURIs);
List<NamedRelatedResourceRep> activeSnapshots = new ArrayList<NamedRelatedResourceRep>();
List<NamedRelatedResourceRep> inactiveSnapshots = new ArrayList<NamedRelatedResourceRep>();
for (BlockSnapshot snapshot : snapshots) {
if (snapshot.getInactive()) {
inactiveSnapshots.add(toNamedRelatedResource(snapshot));
} else {
activeSnapshots.add(toNamedRelatedResource(snapshot));
}
}
list.getSnapList().addAll(inactiveSnapshots);
list.getSnapList().addAll(activeSnapshots);
return list;
}
/**
* List snapshot sessions in the consistency group
*
*
* @prereq none
*
* @param consistencyGroupId
* - Consistency group URI
*
* @brief List snapshot sessions in the consistency group
* @return The list of snapshot sessions in the consistency group
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshot-sessions")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public BlockSnapshotSessionList getConsistencyGroupSnapshotSessions(@PathParam("id") final URI consistencyGroupId) {
ArgValidator.checkUri(consistencyGroupId);
BlockConsistencyGroup consistencyGroup = _permissionsHelper.getObjectById(consistencyGroupId,
BlockConsistencyGroup.class);
ArgValidator.checkEntityNotNull(consistencyGroup, consistencyGroupId,
isIdEmbeddedInURL(consistencyGroupId));
return getSnapshotSessionManager().getSnapshotSessionsForConsistencyGroup(consistencyGroup);
}
/**
* Show the specified Consistency Group Snapshot
*
*
* @prereq none
*
* @param consistencyGroupId
* - Consistency group URI
* @param snapshotId
* - Consistency group snapshot URI
*
* @brief Show consistency group snapshot
* @return BlockSnapshotRestRep
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshots/{sid}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public BlockSnapshotRestRep getConsistencyGroupSnapshot(
@PathParam("id") final URI consistencyGroupId, @PathParam("sid") final URI snapshotId) {
final BlockConsistencyGroup consistencyGroup = (BlockConsistencyGroup) queryResource(consistencyGroupId);
final BlockSnapshot snapshot = (BlockSnapshot) queryResource(snapshotId);
verifySnapshotIsForConsistencyGroup(snapshot, consistencyGroup);
return BlockMapper.map(_dbClient, snapshot);
}
/**
* Activate the specified Consistency Group Snapshot
*
*
* @prereq Create consistency group snapshot as inactive
*
* @param consistencyGroupId
* - Consistency group URI
* @param snapshotId
* - Consistency group snapshot URI
*
* @brief Activate consistency group snapshot
* @return TaskResourceRep
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshots/{sid}/activate")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskResourceRep activateConsistencyGroupSnapshot(
@PathParam("id") final URI consistencyGroupId, @PathParam("sid") final URI snapshotId) {
Operation op = new Operation();
op.setResourceType(ResourceOperationTypeEnum.ACTIVATE_CONSISTENCY_GROUP_SNAPSHOT);
final BlockConsistencyGroup consistencyGroup = (BlockConsistencyGroup) queryResource(consistencyGroupId);
final BlockSnapshot snapshot = (BlockSnapshot) queryResource(snapshotId);
verifySnapshotIsForConsistencyGroup(snapshot, consistencyGroup);
// check for backend CG
if (BlockConsistencyGroupUtils.getLocalSystemsInCG(consistencyGroup, _dbClient).isEmpty()) {
_log.error("{} Group Snapshot operations not supported when there is no backend CG", consistencyGroup.getId());
throw APIException.badRequests.cannotCreateSnapshotOfCG();
}
final StorageSystem device = _dbClient.queryObject(StorageSystem.class,
snapshot.getStorageController());
final BlockController controller = getController(BlockController.class,
device.getSystemType());
final 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();
op.setMessage("The consistency group 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);
try {
final List<URI> snapshotList = new ArrayList<URI>();
// Query all the snapshots by snapshot label
final List<BlockSnapshot> snaps = ControllerUtils.getSnapshotsPartOfReplicationGroup(snapshot, _dbClient);
// Build a URI list with all the snapshots ids
for (BlockSnapshot snap : snaps) {
snapshotList.add(snap.getId());
}
// Activate snapshots
controller.activateSnapshot(device.getId(), snapshotList, task);
} catch (final ControllerException e) {
throw new ServiceCodeException(
CONTROLLER_ERROR,
e,
"An exception occurred when activating consistency group snapshot {0}. Caused by: {1}",
new Object[] { snapshotId, e.getMessage() });
}
auditBlockConsistencyGroup(OperationTypeEnum.ACTIVATE_CONSISTENCY_GROUP_SNAPSHOT,
AuditLogManager.AUDITLOG_SUCCESS, AuditLogManager.AUDITOP_BEGIN, snapshot.getId()
.toString(),
snapshot.getLabel());
return toTask(snapshot, task, op);
}
/**
* Deactivate the specified Consistency Group Snapshot
*
*
* @prereq none
*
* @param consistencyGroupId
* - Consistency group URI
* @param snapshotId
* - Consistency group snapshot URI
*
* @brief Deactivate consistency group snapshot
* @return TaskResourceRep
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshots/{sid}/deactivate")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList deactivateConsistencyGroupSnapshot(
@PathParam("id") final URI consistencyGroupId, @PathParam("sid") final URI snapshotId) {
final BlockConsistencyGroup consistencyGroup = (BlockConsistencyGroup) queryResource(consistencyGroupId);
// Snapshots of RecoverPoint consistency groups is not supported.
if (isIdEmbeddedInURL(consistencyGroupId) && consistencyGroup.checkForType(Types.RP)) {
throw APIException.badRequests.snapshotsNotSupportedForRPCGs();
}
// check for backend CG
if (BlockConsistencyGroupUtils.getLocalSystemsInCG(consistencyGroup, _dbClient).isEmpty()) {
_log.error("{} Group Snapshot operations not supported when there is no backend CG", consistencyGroup.getId());
throw APIException.badRequests.cannotCreateSnapshotOfCG();
}
final BlockSnapshot snapshot = (BlockSnapshot) queryResource(snapshotId);
verifySnapshotIsForConsistencyGroup(snapshot, consistencyGroup);
// 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);
ArgValidator.checkReference(BlockSnapshot.class, snapshotId, checkForDelete(snapshot, excludeTypes));
// Snapshot session linked targets must be unlinked instead.
BlockSnapshotSession session = BlockSnapshotSessionUtils.getLinkedTargetSnapshotSession(snapshot, _dbClient);
if (session != null) {
return deactivateAndUnlinkTargetVolumesForSession(session, snapshot);
}
// Generate task id
final String task = UUID.randomUUID().toString();
TaskList response = new TaskList();
// Not an error if the snapshot we try to delete is already deleted
if (snapshot.getInactive()) {
Operation op = new Operation();
op.ready("The consistency group snapshot has already been deactivated");
op.setResourceType(ResourceOperationTypeEnum.DELETE_CONSISTENCY_GROUP_SNAPSHOT);
_dbClient.createTaskOpStatus(BlockSnapshot.class, snapshot.getId(), task, op);
response.getTaskList().add(toTask(snapshot, task, op));
return response;
}
List<BlockSnapshot> snapshots = new ArrayList<BlockSnapshot>();
snapshots = ControllerUtils.getSnapshotsPartOfReplicationGroup(snapshot, _dbClient);
// Get the snapshot parent volume.
Volume parentVolume = _permissionsHelper.getObjectById(snapshot.getParent(), Volume.class);
// Check that there are no pending tasks for these snapshots.
checkForPendingTasks(Arrays.asList(parentVolume.getTenant().getURI()), snapshots);
for (BlockSnapshot snap : snapshots) {
Operation snapOp = _dbClient.createTaskOpStatus(BlockSnapshot.class, snap.getId(), task,
ResourceOperationTypeEnum.DEACTIVATE_VOLUME_SNAPSHOT);
response.getTaskList().add(toTask(snap, task, snapOp));
}
addConsistencyGroupTask(consistencyGroup, response, task,
ResourceOperationTypeEnum.DEACTIVATE_CONSISTENCY_GROUP_SNAPSHOT);
try {
BlockServiceApi blockServiceApiImpl = BlockService.getBlockServiceImpl(parentVolume, _dbClient);
blockServiceApiImpl.deleteSnapshot(snapshot, snapshots, task, VolumeDeleteTypeEnum.FULL.name());
} catch (APIException | InternalException e) {
String errorMsg = String.format("Exception attempting to delete snapshot %s: %s", snapshot.getId(), e.getMessage());
_log.error(errorMsg);
for (TaskResourceRep taskResourceRep : response.getTaskList()) {
taskResourceRep.setState(Operation.Status.error.name());
taskResourceRep.setMessage(errorMsg);
@SuppressWarnings({ "unchecked" })
Class<? extends DataObject> clazz = URIUtil
.getModelClass(taskResourceRep.getResource().getId());
_dbClient.error(clazz, taskResourceRep.getResource().getId(), task, e);
}
throw e;
} catch (Exception e) {
String errorMsg = String.format("Exception attempting to delete snapshot %s: %s", snapshot.getId(), e.getMessage());
_log.error(errorMsg);
APIException apie = APIException.internalServerErrors.genericApisvcError(errorMsg, e);
for (TaskResourceRep taskResourceRep : response.getTaskList()) {
taskResourceRep.setState(Operation.Status.error.name());
taskResourceRep.setMessage(apie.getMessage());
@SuppressWarnings("unchecked")
Class<? extends DataObject> clazz = URIUtil
.getModelClass(taskResourceRep.getResource().getId());
_dbClient.error(clazz, taskResourceRep.getResource().getId(), task, apie);
}
throw apie;
}
auditBlockConsistencyGroup(OperationTypeEnum.DELETE_CONSISTENCY_GROUP_SNAPSHOT,
AuditLogManager.AUDITLOG_SUCCESS, AuditLogManager.AUDITOP_BEGIN, snapshot.getId()
.toString(),
snapshot.getLabel());
return response;
}
/**
* Deactivate the specified Consistency Group Snapshot
*
*
* @prereq none
*
* @param consistencyGroupId
* - Consistency group URI
* @param snapshotSessionId
* - Consistency group snapshot URI
*
* @brief Deactivate consistency group snapshot
* @return TaskResourceRep
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshot-sessions/{sid}/deactivate")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList deactivateConsistencyGroupSnapshotSession(
@PathParam("id") final URI consistencyGroupId, @PathParam("sid") final URI snapshotSessionId) {
return getSnapshotSessionManager().deleteSnapshotSession(snapshotSessionId, VolumeDeleteTypeEnum.FULL.name());
}
/**
* Restore the specified consistency group snapshot
*
*
* @prereq Activate consistency group snapshot
*
* @param consistencyGroupId
* - Consistency group URI
* @param snapshotId
* - Consistency group snapshot URI
*
* @brief Restore consistency group snapshot
* @return TaskResourceRep
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshots/{sid}/restore")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskResourceRep restoreConsistencyGroupSnapshot(
@PathParam("id") final URI consistencyGroupId, @PathParam("sid") final URI snapshotId) {
// Get the consistency group and snapshot and verify the snapshot
// is actually associated with the consistency group.
final BlockConsistencyGroup consistencyGroup = (BlockConsistencyGroup) queryResource(consistencyGroupId);
final BlockSnapshot snapshot = (BlockSnapshot) queryResource(snapshotId);
verifySnapshotIsForConsistencyGroup(snapshot, consistencyGroup);
// check for backend CG
if (BlockConsistencyGroupUtils.getLocalSystemsInCG(consistencyGroup, _dbClient).isEmpty()) {
_log.error("{} Group Snapshot operations not supported when there is no backend CG", consistencyGroup.getId());
throw APIException.badRequests.cannotCreateSnapshotOfCG();
}
// Get the parent volume.
final Volume snapshotParentVolume = _permissionsHelper.getObjectById(snapshot.getParent(), Volume.class);
// Get the block implementation
BlockServiceApi blockServiceApiImpl = getBlockServiceImpl(consistencyGroup);
// Validate the snapshot restore.
blockServiceApiImpl.validateRestoreSnapshot(snapshot, snapshotParentVolume);
// Create the restore operation task for the snapshot.
final String taskId = UUID.randomUUID().toString();
final Operation op = _dbClient.createTaskOpStatus(BlockSnapshot.class,
snapshot.getId(), taskId, ResourceOperationTypeEnum.RESTORE_CONSISTENCY_GROUP_SNAPSHOT);
// Restore the snapshot.
blockServiceApiImpl.restoreSnapshot(snapshot, snapshotParentVolume, null, taskId);
auditBlockConsistencyGroup(OperationTypeEnum.RESTORE_CONSISTENCY_GROUP_SNAPSHOT,
AuditLogManager.AUDITLOG_SUCCESS, AuditLogManager.AUDITOP_BEGIN,
snapshotId.toString(), consistencyGroupId.toString(), snapshot.getStorageController().toString());
return toTask(snapshot, taskId, op);
}
/**
* Restores the data on the array snapshot point-in-time copy represented by the
* BlockSnapshotSession instance with the passed id, to the snapshot session source
* object.
*
* @brief Restore snapshot session to source
*
* @prereq None
*
* @param consistencyGroupId The URI of the BlockConsistencyGroup.
* @param snapSessionId The URI of the BlockSnapshotSession instance to be restored.
*
* @return TaskResourceRep representing the snapshot session task.
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshot-sessions/{sid}/restore")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskResourceRep restoreConsistencyGroupSnapshotSession(
@PathParam("id") final URI consistencyGroupId, @PathParam("sid") final URI snapSessionId) {
// Get the consistency group and snapshot and verify the snapshot session
// is actually associated with the consistency group.
final BlockConsistencyGroup consistencyGroup = (BlockConsistencyGroup) queryResource(consistencyGroupId);
final BlockSnapshotSession snapSession = (BlockSnapshotSession) queryResource(snapSessionId);
verifySnapshotSessionIsForConsistencyGroup(snapSession, consistencyGroup);
return getSnapshotSessionManager().restoreSnapshotSession(snapSessionId);
}
/**
* Resynchronize the specified consistency group snapshot
*
*
* @prereq Activate consistency group snapshot
*
* @param consistencyGroupId
* - Consistency group URI
* @param snapshotId
* - Consistency group snapshot URI
*
* @brief Resynchronize consistency group snapshot
* @return TaskResourceRep
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshots/{sid}/resynchronize")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskResourceRep resynchronizeConsistencyGroupSnapshot(
@PathParam("id") final URI consistencyGroupId, @PathParam("sid") final URI snapshotId) {
// Get the consistency group and snapshot and verify the snapshot
// is actually associated with the consistency group.
final BlockConsistencyGroup consistencyGroup = (BlockConsistencyGroup) queryResource(consistencyGroupId);
final BlockSnapshot snapshot = (BlockSnapshot) queryResource(snapshotId);
verifySnapshotIsForConsistencyGroup(snapshot, consistencyGroup);
// check for backend CG
if (BlockConsistencyGroupUtils.getLocalSystemsInCG(consistencyGroup, _dbClient).isEmpty()) {
_log.error("{} Group Snapshot operations not supported when there is no backend CG", consistencyGroup.getId());
throw APIException.badRequests.cannotCreateSnapshotOfCG();
}
// Get the storage system for the consistency group.
StorageSystem storage = _permissionsHelper.getObjectById(snapshot.getStorageController(), StorageSystem.class);
// 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 IBM XIV storage system type is not supported
if (Type.ibmxiv.name().equalsIgnoreCase(storage.getSystemType())) {
throw APIException.methodNotAllowed.notSupportedWithReason(
"Snapshot resynchronization is not supported on IBM XIV 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");
}
if (storage.checkIfVmax3()) {
throw APIException.methodNotAllowed.notSupportedWithReason(
"Snapshot resynchronization is not supported on VMAX3 storage systems");
}
// Get the parent volume.
final Volume snapshotParentVolume = _permissionsHelper.getObjectById(snapshot.getParent(), Volume.class);
// Get the block implementation
BlockServiceApi blockServiceApiImpl = getBlockServiceImpl(consistencyGroup);
// Validate the snapshot restore.
blockServiceApiImpl.validateResynchronizeSnapshot(snapshot, snapshotParentVolume);
// Create the restore operation task for the snapshot.
final String taskId = UUID.randomUUID().toString();
final Operation op = _dbClient.createTaskOpStatus(BlockSnapshot.class,
snapshot.getId(), taskId, ResourceOperationTypeEnum.RESYNCHRONIZE_CONSISTENCY_GROUP_SNAPSHOT);
// Resync the snapshot.
blockServiceApiImpl.resynchronizeSnapshot(snapshot, snapshotParentVolume, taskId);
auditBlockConsistencyGroup(OperationTypeEnum.RESTORE_CONSISTENCY_GROUP_SNAPSHOT,
AuditLogManager.AUDITLOG_SUCCESS, AuditLogManager.AUDITOP_BEGIN,
snapshotId.toString(), consistencyGroupId.toString(), snapshot.getStorageController().toString());
return toTask(snapshot, taskId, op);
}
/**
* Record audit log for Block 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 auditBlockConsistencyGroup(final OperationTypeEnum auditType,
final String operationalStatus, final String operationStage, final Object... descparams) {
_auditMgr.recordAuditLog(URI.create(getUserFromContext().getTenantId()),
URI.create(getUserFromContext().getName()), "block", auditType,
System.currentTimeMillis(), operationalStatus, operationStage, descparams);
}
private void verifyUserIsAuthorizedForRequest(final Project project) {
StorageOSUser user = getUserFromContext();
if (!(_permissionsHelper.userHasGivenRole(user, project.getTenantOrg().getURI(),
Role.TENANT_ADMIN) || _permissionsHelper.userHasGivenACL(user, project.getId(),
ACL.OWN, ACL.ALL))) {
throw APIException.forbidden.insufficientPermissionsForUser(user.getName());
}
}
/**
* Block consistency group is not a zone level resource
*
* @return false
*/
@Override
protected boolean isZoneLevelResource() {
return false;
}
@Override
protected ResourceTypeEnum getResourceType() {
return ResourceTypeEnum.BLOCK_CONSISTENCY_GROUP;
}
@SuppressWarnings("unchecked")
@Override
public Class<BlockConsistencyGroup> getResourceClass() {
return BlockConsistencyGroup.class;
}
/**
* Retrieve volume representations based on input ids.
*
* @return list of volume representations.
*
* @throws DatabaseException
* When an error occurs querying the database.
*/
@Override
public BlockConsistencyGroupBulkRep queryBulkResourceReps(final List<URI> ids) {
Iterator<BlockConsistencyGroup> _dbIterator = _dbClient.queryIterativeObjects(
getResourceClass(), ids);
return new BlockConsistencyGroupBulkRep(BulkList.wrapping(_dbIterator,
MapBlockConsistencyGroup.getInstance(_dbClient)));
}
@Override
protected BulkRestRep queryFilteredBulkResourceReps(final List<URI> ids) {
Iterator<BlockConsistencyGroup> _dbIterator = _dbClient.queryIterativeObjects(
getResourceClass(), ids);
BulkList.ResourceFilter<BlockConsistencyGroup> filter = new BulkList.ProjectResourceFilter<BlockConsistencyGroup>(
getUserFromContext(), _permissionsHelper);
return new BlockConsistencyGroupBulkRep(BulkList.wrapping(_dbIterator,
MapBlockConsistencyGroup.getInstance(_dbClient), filter));
}
/**
* Get search results by name in zone or project.
*
* @return SearchedResRepList
*/
@Override
protected SearchedResRepList getNamedSearchResults(final String name, final URI projectId) {
SearchedResRepList resRepList = new SearchedResRepList(getResourceType());
if (projectId == null) {
_dbClient.queryByConstraint(
PrefixConstraint.Factory.getLabelPrefixConstraint(getResourceClass(), name),
resRepList);
} else {
_dbClient.queryByConstraint(ContainmentPrefixConstraint.Factory
.getConsistencyGroupUnderProjectConstraint(projectId, name), resRepList);
}
return resRepList;
}
/**
* Get search results by project alone.
*
* @return SearchedResRepList
*/
@Override
protected SearchedResRepList getProjectSearchResults(final URI projectId) {
SearchedResRepList resRepList = new SearchedResRepList(getResourceType());
_dbClient.queryByConstraint(
ContainmentConstraint.Factory.getProjectBlockConsistencyGroupConstraint(projectId),
resRepList);
return resRepList;
}
/**
* Get object specific permissions filter
*
*/
@Override
protected ResRepFilter<? extends RelatedResourceRep> getPermissionFilter(
final StorageOSUser user, final PermissionsHelper permissionsHelper) {
return new ProjOwnedResRepFilter(user, permissionsHelper, BlockConsistencyGroup.class);
}
/**
* Update the specified consistency group
*
*
* @prereq none
*
* @param id the URN of a ViPR Consistency group
*
* @brief Update consistency group
* @return TaskResourceRep
*/
@PUT
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskResourceRep updateConsistencyGroup(@PathParam("id") final URI id,
final BlockConsistencyGroupUpdate param) {
// Get the consistency group.
BlockConsistencyGroup consistencyGroup = (BlockConsistencyGroup) queryResource(id);
StorageDriverManager storageDriverManager = (StorageDriverManager) StorageDriverManager.getApplicationContext().getBean(
StorageDriverManager.STORAGE_DRIVER_MANAGER);
// Verify a volume was specified to be added or removed.
if (!param.hasEitherAddOrRemoveVolumes()) {
throw APIException.badRequests.noVolumesToBeAddedRemovedFromCG();
}
// TODO require a check if requested list contains all volumes/replicas?
// For replicas, check replica count with volume count in CG
StorageSystem cgStorageSystem = null;
// Throw exception if the operation is attempted on volumes that are in RP CG.
if (consistencyGroup.isRPProtectedCG()) {
throw APIException.badRequests.operationNotAllowedOnRPVolumes();
}
// if consistency group is not created yet, then get the storage system from the block object to be added
// This method also supports adding volumes or replicas to CG (VMAX - SMIS 8.0.x)
if ((!consistencyGroup.created() || NullColumnValueGetter.isNullURI(consistencyGroup.getStorageController()))
&& param.hasVolumesToAdd()) { // we just need to check the case of add volumes in this case
BlockObject bo = BlockObject.fetch(_dbClient, param.getAddVolumesList().getVolumes().get(0));
cgStorageSystem = _permissionsHelper.getObjectById(
bo.getStorageController(), StorageSystem.class);
} else {
cgStorageSystem = _permissionsHelper.getObjectById(
consistencyGroup.getStorageController(), StorageSystem.class);
}
// IBMXIV, XtremIO, VPlex, VNX, ScaleIO, and VMax volumes only
String systemType = cgStorageSystem.getSystemType();
if (!storageDriverManager.isDriverManaged(cgStorageSystem.getSystemType())) {
if (!systemType.equals(DiscoveredDataObject.Type.vplex.name())
&& !systemType.equals(DiscoveredDataObject.Type.vnxblock.name())
&& !systemType.equals(DiscoveredDataObject.Type.vmax.name())
&& !systemType.equals(DiscoveredDataObject.Type.vnxe.name())
&& !systemType.equals(DiscoveredDataObject.Type.unity.name())
&& !systemType.equals(DiscoveredDataObject.Type.ibmxiv.name())
&& !systemType.equals(DiscoveredDataObject.Type.scaleio.name())
&& !systemType.equals(DiscoveredDataObject.Type.xtremio.name())) {
throw APIException.methodNotAllowed.notSupported();
}
}
// Get the specific BlockServiceApiImpl based on the storage system type.
BlockServiceApi blockServiceApiImpl = getBlockServiceImpl(cgStorageSystem);
List<URI> volIds = null;
Set<URI> addSet = new HashSet<URI>();
boolean isReplica = true;
if (param.hasVolumesToAdd()) {
volIds = param.getAddVolumesList().getVolumes();
addSet.addAll(volIds);
URI volId = volIds.get(0);
if (URIUtil.isType(volId, Volume.class)) {
Volume volume = _permissionsHelper.getObjectById(volId, Volume.class);
ArgValidator.checkEntity(volume, volId, false);
if (!BlockFullCopyUtils.isVolumeFullCopy(volume, _dbClient)) {
isReplica = false;
}
}
}
List<Volume> cgVolumes = blockServiceApiImpl.getActiveCGVolumes(consistencyGroup);
// check if add volume list is same as existing volumes in CG
boolean volsAlreadyInCG = false;
if (!isReplica && cgVolumes != null && !cgVolumes.isEmpty()) {
Collection<URI> cgVolIds = transform(cgVolumes, fctnDataObjectToID());
if (addSet.size() == cgVolIds.size()) {
volsAlreadyInCG = addSet.containsAll(cgVolIds);
}
}
// Verify that the add and remove lists do not contain the same volume.
if (param.hasBothAddAndRemoveVolumes()) {
/*
* Make sure the add and remove lists are unique by getting the intersection and
* verifying the size is 0.
*/
Set<URI> removeSet = new HashSet<URI>(param.getRemoveVolumesList().getVolumes());
addSet.retainAll(removeSet);
if (!addSet.isEmpty()) {
throw APIException.badRequests.sameVolumesInAddRemoveList();
}
}
if (cgStorageSystem.getUsingSmis80() && cgStorageSystem.deviceIsType(Type.vmax)) {
// CG can have replicas
if (_log.isDebugEnabled()) {
_log.debug("CG can have replicas for VMAX with SMI-S 8.x");
}
} else if (param.hasVolumesToRemove() || (!isReplica && !volsAlreadyInCG)) {
// CG cannot have replicas when adding/removing volumes to/from CG
// Check snapshots
// Adding/removing volumes to/from a consistency group
// is not supported when the consistency group has active
// snapshots.
URIQueryResultList cgSnapshotsResults = new URIQueryResultList();
_dbClient.queryByConstraint(getBlockSnapshotByConsistencyGroup(id), cgSnapshotsResults);
Iterator<URI> cgSnapshotsIter = cgSnapshotsResults.iterator();
while (cgSnapshotsIter.hasNext()) {
BlockSnapshot cgSnapshot = _dbClient.queryObject(BlockSnapshot.class, cgSnapshotsIter.next());
if ((cgSnapshot != null) && (!cgSnapshot.getInactive())) {
throw APIException.badRequests.notAllowedWhenCGHasSnapshots();
}
}
// VNX group clones and mirrors are just list of replicas, no corresponding group on array side
if (!cgStorageSystem.deviceIsType(Type.vnxblock)) {
// Check mirrors
// Adding/removing volumes to/from a consistency group
// is not supported when existing volumes in CG have mirrors.
if (cgVolumes != null && !cgVolumes.isEmpty()) {
Volume firstVolume = cgVolumes.get(0);
StringSet mirrors = firstVolume.getMirrors();
if (mirrors != null && !mirrors.isEmpty()) {
throw APIException.badRequests.notAllowedWhenCGHasMirrors();
}
}
// Check clones
// Adding/removing volumes to/from a consistency group
// is not supported when the consistency group has
// volumes with full copies to which they are still
// attached or has volumes that are full copies that
// are still attached to their source volumes.
getFullCopyManager().verifyConsistencyGroupCanBeUpdated(consistencyGroup,
cgVolumes);
}
}
// Verify the volumes to be removed.
List<URI> removeVolumesList = new ArrayList<URI>();
if (param.hasVolumesToRemove()) {
for (URI volumeURI : param.getRemoveVolumesList().getVolumes()) {
// Validate the volume to be removed exists.
if (URIUtil.isType(volumeURI, Volume.class)) {
Volume volume = _permissionsHelper.getObjectById(volumeURI, Volume.class);
ArgValidator.checkEntity(volume, volumeURI, false);
/**
* Remove SRDF volume from CG is not supported.
*/
if (volume.checkForSRDF()) {
throw APIException.badRequests.notAllowedOnSRDFConsistencyGroups();
}
if (!BlockFullCopyUtils.isVolumeFullCopy(volume, _dbClient)) {
blockServiceApiImpl.verifyRemoveVolumeFromCG(volume, cgVolumes);
}
}
removeVolumesList.add(volumeURI);
}
}
URI xivPoolURI = null;
if (systemType.equals(DiscoveredDataObject.Type.ibmxiv.name()) && !cgVolumes.isEmpty()) {
Volume firstVolume = cgVolumes.get(0);
xivPoolURI = firstVolume.getPool();
}
// Verify the volumes to be added.
List<URI> addVolumesList = new ArrayList<URI>();
List<Volume> volumes = new ArrayList<Volume>();
if (param.hasVolumesToAdd()) {
for (URI volumeURI : param.getAddVolumesList().getVolumes()) {
// Validate the volume to be added exists.
Volume volume = null;
if (!isReplica) {
volume = _permissionsHelper.getObjectById(volumeURI, Volume.class);
ArgValidator.checkEntity(volume, volumeURI, false);
blockServiceApiImpl.verifyAddVolumeToCG(volume, consistencyGroup,
cgVolumes, cgStorageSystem);
volumes.add(volume);
} else {
verifyAddReplicaToCG(volumeURI, consistencyGroup, cgStorageSystem);
}
// IBM XIV specific checking
if (systemType.equals(DiscoveredDataObject.Type.ibmxiv.name())) {
// all volumes should be on the same storage pool
if (xivPoolURI == null) {
xivPoolURI = volume.getPool();
} else {
if (!xivPoolURI.equals(volume.getPool())) {
throw APIException.badRequests
.invalidParameterIBMXIVConsistencyGroupVolumeNotInPool(volumeURI, xivPoolURI);
}
}
}
// Add the volume to list.
addVolumesList.add(volumeURI);
}
if (!volumes.isEmpty()) {
blockServiceApiImpl.verifyReplicaCount(volumes, cgVolumes, volsAlreadyInCG);
}
}
// Create the task id;
String taskId = UUID.randomUUID().toString();
// Call the block service API to update the consistency group.
return blockServiceApiImpl.updateConsistencyGroup(cgStorageSystem, cgVolumes,
consistencyGroup, addVolumesList, removeVolumesList, taskId);
}
/**
* Validates the replicas to be added to Consistency group.
* - verifies that the replicas are not internal objects,
* - checks if the given CG is its source volume's CG,
* - validates that the replica is not in any other CG,
* - verifies the project for the replicas to be added is same
* as the project for the consistency group.
*/
private void verifyAddReplicaToCG(URI blockURI, BlockConsistencyGroup cg,
StorageSystem cgStorageSystem) {
BlockObject blockObject = BlockObject.fetch(_dbClient, blockURI);
// Don't allow partially ingested object to be added to CG.
BlockServiceUtils.validateNotAnInternalBlockObject(blockObject, false);
URI sourceVolumeURI = null;
URI blockProjectURI = null;
if (blockObject instanceof BlockSnapshot) {
BlockSnapshot snapshot = (BlockSnapshot) blockObject;
blockProjectURI = snapshot.getProject().getURI();
sourceVolumeURI = snapshot.getParent().getURI();
} else if (blockObject instanceof BlockMirror) {
BlockMirror mirror = (BlockMirror) blockObject;
blockProjectURI = mirror.getProject().getURI();
sourceVolumeURI = mirror.getSource().getURI();
} else if (blockObject instanceof Volume) {
Volume volume = (Volume) blockObject;
blockProjectURI = volume.getProject().getURI();
sourceVolumeURI = volume.getAssociatedSourceVolume();
}
// check if the given CG is its source volume's CG
Volume sourceVolume = null;
if (!NullColumnValueGetter.isNullURI(sourceVolumeURI)) {
sourceVolume = _dbClient.queryObject(Volume.class, sourceVolumeURI);
}
if (sourceVolume == null
|| !cg.getId().equals(sourceVolume.getConsistencyGroup())) {
throw APIException.badRequests
.invalidParameterSourceVolumeNotInGivenConsistencyGroup(
sourceVolumeURI, cg.getId());
}
// Validate that the replica is not in any other CG.
if (!NullColumnValueGetter.isNullURI(blockObject.getConsistencyGroup())
&& !cg.getId().equals(blockObject.getConsistencyGroup())) {
throw APIException.badRequests
.invalidParameterVolumeAlreadyInAConsistencyGroup(
cg.getId(), blockObject.getConsistencyGroup());
}
// Verify the project for the replicas to be added is same
// as the project for the consistency group.
URI cgProjectURI = cg.getProject().getURI();
if (!blockProjectURI.equals(cgProjectURI)) {
List<Project> projects = _dbClient.queryObjectField(Project.class,
"label", Arrays.asList(cgProjectURI, blockProjectURI));
throw APIException.badRequests
.consistencyGroupAddVolumeThatIsInDifferentProject(
blockObject.getLabel(), projects.get(0).getLabel(),
projects.get(1).getLabel());
}
}
/**
* For APIs that act on a snapshot for a consistency group, ensures that
* the passed snapshot is associated with the passed consistency group, else
* throws a bad request exception.
*
* @param snapshot A reference to a snapshot
* @param consistencyGroup A reference to a consistency group
*/
private void verifySnapshotIsForConsistencyGroup(BlockSnapshot snapshot,
BlockConsistencyGroup consistencyGroup) {
URI snapshotCGURI = snapshot.getConsistencyGroup();
if ((NullColumnValueGetter.isNullURI(snapshotCGURI)) || (!snapshotCGURI.equals(consistencyGroup.getId()))) {
throw APIException.badRequests.snapshotIsNotForConsistencyGroup(
snapshot.getLabel(), consistencyGroup.getLabel());
}
}
/**
* For APIs that act on a snapshot session for a consistency group, ensures that
* the passed snapshot session is associated with the passed consistency group, else
* throws a bad request exception.
*
* @param snapSession A reference to a snapshot session.
* @param consistencyGroup A reference to a consistency group
*/
private void verifySnapshotSessionIsForConsistencyGroup(BlockSnapshotSession snapSession,
BlockConsistencyGroup consistencyGroup) {
URI snapSessionCGURI = snapSession.getConsistencyGroup();
if ((NullColumnValueGetter.isNullURI(snapSessionCGURI)) || (!snapSessionCGURI.equals(consistencyGroup.getId()))) {
throw APIException.badRequests.snapshotSessionIsNotForConsistencyGroup(
snapSession.getLabel(), consistencyGroup.getLabel());
}
}
/**
* Simply return a task that indicates that the operation completed.
*
* @param consistencyGroup [in] BlockConsistencyGroup object
* @param task [in] - Operation task ID
* @return
*/
private TaskResourceRep finishDeactivateTask(BlockConsistencyGroup consistencyGroup, String task) {
URI id = consistencyGroup.getId();
Operation op = new Operation();
op.ready();
op.setProgress(100);
op.setResourceType(ResourceOperationTypeEnum.DELETE_CONSISTENCY_GROUP);
Operation status = _dbClient.createTaskOpStatus(BlockConsistencyGroup.class, id, task, op);
return toTask(consistencyGroup, task, status);
}
/**
* Creates a consistency group full copy.
*
* @prereq none
*
* @param cgURI The URI of the consistency group.
* @param param The request data specifying the parameters for the request.
*
* @brief Create consistency group full copy.
*
* @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.SYSTEM_ADMIN }, acls = { ACL.ANY })
public TaskList createConsistencyGroupFullCopy(@PathParam("id") URI cgURI,
VolumeFullCopyCreateParam param) {
// Verify the consistency group in the requests and get the
// volumes in the consistency group.
List<Volume> cgVolumes = verifyCGForFullCopyRequest(cgURI);
// Get the storage system for the consistency group.
StorageSystem storage = _permissionsHelper.getObjectById(cgVolumes.get(0).getStorageController(), StorageSystem.class);
// Group clone for IBM XIV storage system type is not supported
if (Type.ibmxiv.name().equalsIgnoreCase(storage.getSystemType())) {
throw APIException.methodNotAllowed.notSupportedWithReason(
"Consistency Group Full Copy is not supported on IBM XIV storage systems");
}
// block CG operation if any of its volumes is in COPY type VolumeGroup (Application)
validateVolumeNotPartOfApplication(cgVolumes, FULL_COPY);
// Grab the first volume and call the block full copy
// manager to create the full copies for the volumes
// in the CG. Note that it will take into account the
// fact that the volume is in a CG.
return getFullCopyManager().createFullCopy(cgVolumes.get(0).getId(), param);
}
/**
* Creates a consistency group snapshot session
*
* @prereq none
* @param consistencyGroupId Consistency group URI
* @param param
* @brief Create consistency group snapshot session
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshot-sessions")
@CheckPermission(roles = { Role.SYSTEM_ADMIN }, acls = { ACL.ANY })
public TaskList createConsistencyGroupSnapshotSession(@PathParam("id") URI consistencyGroupId,
SnapshotSessionCreateParam param) {
// Grab the first volume and call the block snapshot session
// manager to create the snapshot sessions for the volumes
// in the CG. Note that it will take into account the
// fact that the volume is in a CG.
BlockConsistencyGroup cg = queryObject(BlockConsistencyGroup.class, consistencyGroupId, true);
// RP CG's must use applications to create snapshots
if (isIdEmbeddedInURL(consistencyGroupId) && cg.checkForType(Types.RP)) {
throw APIException.badRequests.snapshotsNotSupportedForRPCGs();
}
// Validate CG information in the request
validateVolumesInReplicationGroups(cg, param.getVolumes(), _dbClient);
return getSnapshotSessionManager().createSnapshotSession(cg, param, getFullCopyManager());
}
/**
* The method implements the API to create and link new target
* volumes to an existing BlockSnapshotSession instance.
*
* @brief Link target volumes to a snapshot session.
*
* @prereq The block snapshot session has been created and the maximum
* number of targets has not already been linked to the snapshot sessions
* for the source object.
*
* @param id The URI of the BlockSnapshotSession instance to which the
* new targets will be linked.
* @param param The linked target information.
*
* @return A TaskList representing the snapshot session task.
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshot-sessions/{sid}/link-targets")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList linkTargetVolumes(@PathParam("id") URI id,
@PathParam("sid") URI sessionId,
SnapshotSessionLinkTargetsParam param) {
validateSessionPartOfConsistencyGroup(id, sessionId);
return getSnapshotSessionManager().linkTargetVolumesToSnapshotSession(sessionId, param);
}
/**
* This method implements the API to re-link a target to either its current
* snapshot session or to a different snapshot session of the same source.
*
* @brief Relink target volumes to snapshot sessions.
*
* @prereq The target volumes are linked to a snapshot session of the same source object.
*
* @param id The URI of the BlockConsistencyGroup instance to which the
* the session is created for.
* @param sessionId The URI of the BlockSnapshotSession instance to which the
* the targets will be re-linked.
* @param param The linked target information.
*
* @return A TaskList representing the snapshot session tasks.
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshot-sessions/{sid}/relink-targets")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList relinkTargetVolumes(@PathParam("id") URI id,
@PathParam("sid") URI sessionId,
SnapshotSessionRelinkTargetsParam param) {
validateSessionPartOfConsistencyGroup(id, sessionId);
return getSnapshotSessionManager().relinkTargetVolumesToSnapshotSession(sessionId, param);
}
/**
* The method implements the API to unlink target volumes from an existing
* BlockSnapshotSession instance and optionally delete those target volumes.
*
* @brief Unlink target volumes from a snapshot session.
*
* @prereq A snapshot session is created and target volumes have previously
* been linked to that snapshot session.
*
* @param id The URI of the BlockSnapshotSession instance to which the targets are linked.
* @param param The linked target information.
*
* @return A TaskResourceRep representing the snapshot session task.
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshot-sessions/{sid}/unlink-targets")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskResourceRep unlinkTargetVolumesForSession(@PathParam("id") URI id,
@PathParam("sid") URI sessionId,
SnapshotSessionUnlinkTargetsParam param) {
validateSessionPartOfConsistencyGroup(id, sessionId);
return unlinkTargetVolumesFromSnapshotSession(sessionId, param, OperationTypeEnum.UNLINK_SNAPSHOT_SESSION_TARGET);
}
/**
* Unlink target volumes from an existing BlockSnapshotSession instance and optionally delete
* those target volumes.
*
* @param sessionURI The URI of the BlockSnapshotSession instance to which the targets are linked.
* @param param he linked target information.
* @param opType The operation type for the audit log.
*
* @return A TaskResourceRep representing the snapshot session task.
*/
private TaskResourceRep unlinkTargetVolumesFromSnapshotSession(URI sessionURI, SnapshotSessionUnlinkTargetsParam param,
OperationTypeEnum opType) {
return getSnapshotSessionManager().unlinkTargetVolumesFromSnapshotSession(sessionURI, param, opType);
}
/**
* This method is called when a linked BlockSnapshot for a BlockSnapshotSession is passed to
* {@link #deactivateConsistencyGroupSnapshot(URI, URI)} and we must instead unlink&delete it.
*
* @param session The BlockSnapshotSession.
* @param snapshot The BlockSnapshot.
* @return TaskList wrapping the single TaskResourceRep.
*/
private TaskList deactivateAndUnlinkTargetVolumesForSession(BlockSnapshotSession session, BlockSnapshot snapshot) {
SnapshotSessionUnlinkTargetParam unlink = new SnapshotSessionUnlinkTargetParam(snapshot.getId(), true);
SnapshotSessionUnlinkTargetsParam param = new SnapshotSessionUnlinkTargetsParam(newArrayList(unlink));
TaskResourceRep task = unlinkTargetVolumesFromSnapshotSession(session.getId(), param,
OperationTypeEnum.DELETE_CONSISTENCY_GROUP_SNAPSHOT);
return new TaskList(newArrayList(task));
}
/**
* Activate the specified consistency group full copy.
*
* @prereq Create consistency group full copy as inactive.
*
* @param cgURI The URI of the consistency group.
* @param fullCopyURI The URI of the full copy.
*
* @brief Activate consistency group full copy.
*
* @return TaskList
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies/{fcid}/activate")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList activateConsistencyGroupFullCopy(
@PathParam("id") URI cgURI, @PathParam("fcid") URI fullCopyURI) {
// Verify the consistency group in the request and get the
// volumes in the consistency group.
List<Volume> cgVolumes = verifyCGForFullCopyRequest(cgURI);
// block CG operation if any of its volumes is in COPY type VolumeGroup (Application)
if (isIdEmbeddedInURL(cgURI)) {
validateVolumeNotPartOfApplication(cgVolumes, FULL_COPY);
}
// Verify the full copy.
URI fcSourceURI = verifyFullCopyForCopyRequest(fullCopyURI, cgVolumes);
// Activate the full copy. Note that it will take into account the
// fact that the volume is in a CG.
return getFullCopyManager().activateFullCopy(fcSourceURI, fullCopyURI);
}
/**
* Detach the specified consistency group full copy.
*
* @prereq Create consistency group full copy as active.
*
* @param cgURI The URI of the consistency group.
* @param fullCopyURI The URI of the full copy.
*
* @brief Detach consistency group full copy.
*
* @return TaskList
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies/{fcid}/detach")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList detachConsistencyGroupFullCopy(
@PathParam("id") URI cgURI, @PathParam("fcid") URI fullCopyURI) {
// Verify the consistency group in the request and get the
// volumes in the consistency group.
List<Volume> cgVolumes = verifyCGForFullCopyRequest(cgURI);
// block CG operation if any of its volumes is in COPY type VolumeGroup (Application)
if (isIdEmbeddedInURL(cgURI)) {
validateVolumeNotPartOfApplication(cgVolumes, FULL_COPY);
}
// Get the full copy source.
Volume fullCopyVolume = (Volume) BlockFullCopyUtils.queryFullCopyResource(
fullCopyURI, uriInfo, false, _dbClient);
URI fcSourceURI = fullCopyVolume.getAssociatedSourceVolume();
if (!NullColumnValueGetter.isNullURI(fcSourceURI)) {
verifyFullCopyForCopyRequest(fullCopyURI, cgVolumes);
}
// Detach the full copy. Note that it will take into account the
// fact that the volume is in a CG.
return getFullCopyManager().detachFullCopy(fcSourceURI, fullCopyURI);
}
/**
* Restore the specified consistency group full copy.
*
* @prereq Create consistency group full copy as active.
*
* @param cgURI The URI of the consistency group.
* @param fullCopyURI The URI of the full copy.
*
* @brief Restore consistency group full copy.
*
* @return TaskList
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies/{fcid}/restore")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList restoreConsistencyGroupFullCopy(
@PathParam("id") URI cgURI, @PathParam("fcid") URI fullCopyURI) {
// Verify the consistency group in the request and get the
// volumes in the consistency group.
List<Volume> cgVolumes = verifyCGForFullCopyRequest(cgURI);
// block CG operation if any of its volumes is in COPY type VolumeGroup (Application)
if (isIdEmbeddedInURL(cgURI)) {
validateVolumeNotPartOfApplication(cgVolumes, FULL_COPY);
}
// Verify the full copy.
URI fcSourceURI = verifyFullCopyForCopyRequest(fullCopyURI, cgVolumes);
// Restore the full copy. Note that it will take into account the
// fact that the volume is in a CG.
return getFullCopyManager().restoreFullCopy(fcSourceURI, fullCopyURI);
}
/**
* Resynchronize the specified consistency group full copy.
*
* @prereq Create consistency group full copy as active.
*
* @param cgURI The URI of the consistency group.
* @param fullCopyURI The URI of the full copy.
*
* @brief Resynchronize consistency group full copy.
*
* @return TaskList
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies/{fcid}/resynchronize")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList resynchronizeConsistencyGroupFullCopy(
@PathParam("id") URI cgURI, @PathParam("fcid") URI fullCopyURI) {
// Verify the consistency group in the request and get the
// volumes in the consistency group.
List<Volume> cgVolumes = verifyCGForFullCopyRequest(cgURI);
// block CG operation if any of its volumes is in COPY type VolumeGroup (Application)
if (isIdEmbeddedInURL(cgURI)) {
validateVolumeNotPartOfApplication(cgVolumes, FULL_COPY);
}
// Verify the full copy.
URI fcSourceURI = verifyFullCopyForCopyRequest(fullCopyURI, cgVolumes);
// Resynchronize the full copy. Note that it will take into account the
// fact that the volume is in a CG.
return getFullCopyManager().resynchronizeFullCopy(fcSourceURI, fullCopyURI);
}
/**
* Deactivate the specified consistency group full copy.
*
* @prereq none
*
* @param cgURI The URI of the consistency group.
* @param fullCopyURI The URI of the full copy.
*
* @brief Deactivate consistency group full copy.
*
* @return TaskList
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies/{fcid}/deactivate")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList deactivateConsistencyGroupFullCopy(@PathParam("id") URI cgURI,
@PathParam("fcid") URI fullCopyURI) {
// Verify the consistency group in the request and get the
// volumes in the consistency group.
List<Volume> cgVolumes = verifyCGForFullCopyRequest(cgURI);
// block CG operation if any of its volumes is in COPY type VolumeGroup (Application)
if (isIdEmbeddedInURL(cgURI)) {
validateVolumeNotPartOfApplication(cgVolumes, FULL_COPY);
}
// Verify the full copy.
verifyFullCopyForCopyRequest(fullCopyURI, cgVolumes);
// Full copies are volumes, and volumes are deleted by the
// block service. The block service deactivate method will
// ensure that full copies in CGs results in all associated
// being deleted under appropriate conditions.
BulkDeleteParam param = new BulkDeleteParam();
param.setIds(Arrays.asList(fullCopyURI));
return _blockService.deleteVolumes(param, false, VolumeDeleteTypeEnum.FULL.name());
}
/**
* List full copies for a consistency group
*
* @prereq none
*
* @param cgURI The URI of the consistency group.
*
* @brief List full copies for a consistency group
*
* @return The list of full copies for the consistency group
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public NamedVolumesList getConsistencyGroupFullCopies(@PathParam("id") URI cgURI) {
// Verify the consistency group in the request and get the
// volumes in the consistency group.
List<Volume> cgVolumes = verifyCGForFullCopyRequest(cgURI);
// if any of the source volumes are in an application, replica management must be done via the application
for (Volume srcVol : cgVolumes) {
if (srcVol.getApplication(_dbClient) != null) {
return new NamedVolumesList();
}
}
// Cycle over the volumes in the consistency group and
// get the full copies for each volume in the group.
NamedVolumesList cgFullCopyList = new NamedVolumesList();
for (Volume cgVolume : cgVolumes) {
NamedVolumesList cgVolumeFullCopies = getFullCopyManager().getFullCopiesForSource(cgVolume.getId());
cgFullCopyList.getVolumes().addAll(cgVolumeFullCopies.getVolumes());
}
return cgFullCopyList;
}
/**
* Get the specified consistency group full copy.
*
* @prereq none
*
* @param cgURI The URI of the consistency group.
* @param fullCopyURI The URI of the full copy.
*
* @brief Get the specified consistency group full copy.
*
* @return The full copy volume.
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies/{fcid}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public VolumeRestRep getConsistencyGroupFullCopy(@PathParam("id") URI cgURI,
@PathParam("fcid") URI fullCopyURI) {
// Verify the consistency group in the request and get the
// volumes in the consistency group.
List<Volume> cgVolumes = verifyCGForFullCopyRequest(cgURI);
// Verify the full copy.
verifyFullCopyForCopyRequest(fullCopyURI, cgVolumes);
// Get and return the full copy.
return getFullCopyManager().getFullCopy(fullCopyURI);
}
/**
* Request to reverse the replication direction, i.e. R1 and R2 are interchanged.
*
* @prereq none
*
* @param id the URI of a BlockConsistencyGroup
* @param param Copy to swap
*
* @brief reversing roles of source and target
* @return TaskList
*
* @throws ControllerException
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/continuous-copies/swap")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskList swap(@PathParam("id") URI id, CopiesParam param) throws ControllerException {
TaskResourceRep taskResp = null;
TaskList taskList = new TaskList();
// Validate the source volume URI
ArgValidator.checkFieldUriType(id, BlockConsistencyGroup.class, "id");
// Validate the list of copies
ArgValidator.checkFieldNotEmpty(param.getCopies(), "copies");
// Query Consistency Group
final BlockConsistencyGroup consistencyGroup = (BlockConsistencyGroup) queryResource(id);
// Ensure that the Consistency Group has been created on all of its defined
// system types.
if (!consistencyGroup.created()) {
throw APIException.badRequests.consistencyGroupNotCreated();
}
List<Copy> copies = param.getCopies();
if (copies.size() > 1) {
throw APIException.badRequests.swapCopiesParamCanOnlyBeOne();
}
Copy copy = copies.get(0);
ArgValidator.checkFieldUriType(copy.getCopyID(), VirtualArray.class, "copyId");
ArgValidator.checkFieldNotEmpty(copy.getType(), "type");
if (TechnologyType.RP.name().equalsIgnoreCase(copy.getType())) {
taskResp = performProtectionAction(id, copy, ProtectionOp.SWAP.getRestOp());
taskList.getTaskList().add(taskResp);
} else if (TechnologyType.SRDF.name().equalsIgnoreCase(copy.getType())) {
taskResp = performSRDFProtectionAction(id, copy, ProtectionOp.SWAP.getRestOp());
taskList.getTaskList().add(taskResp);
} else {
throw APIException.badRequests.invalidCopyType(copy.getType());
}
return taskList;
}
/**
*
* Request to failover the protection link associated with the copy. The target
* copy is specified by identifying the virtual array in param.copyId.
*
* NOTE: This is an asynchronous operation.
*
* If volume is srdf protected, then invoking failover internally triggers
* SRDF SWAP on volume pairs.
*
* @prereq none
*
* @param id the URI of a BlockConsistencyGroup
* @param param Copy to failover to
*
* @brief Failover the protection link
* @return TaskList
*
* @throws ControllerException
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/continuous-copies/failover")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskList failoverProtection(@PathParam("id") URI id, CopiesParam param) throws ControllerException {
TaskResourceRep taskResp = null;
TaskList taskList = new TaskList();
// Validate the consistency group URI
ArgValidator.checkFieldUriType(id, BlockConsistencyGroup.class, "id");
// Validate the list of copies
ArgValidator.checkFieldNotEmpty(param.getCopies(), "copies");
// Query Consistency Group
final BlockConsistencyGroup consistencyGroup = (BlockConsistencyGroup) queryResource(id);
// Ensure that the Consistency Group has been created on all of its defined
// system types.
if (!consistencyGroup.created()) {
throw APIException.badRequests.consistencyGroupNotCreated();
}
List<Copy> copies = param.getCopies();
if (copies.size() > 1) {
throw APIException.badRequests.failoverCopiesParamCanOnlyBeOne();
}
Copy copy = copies.get(0);
ArgValidator.checkFieldUriType(copy.getCopyID(), VirtualArray.class, "copyId");
ArgValidator.checkFieldNotEmpty(copy.getType(), "type");
if (TechnologyType.RP.name().equalsIgnoreCase(copy.getType())) {
taskResp = performProtectionAction(id, copy, ProtectionOp.FAILOVER.getRestOp());
taskList.getTaskList().add(taskResp);
} else if (TechnologyType.SRDF.name().equalsIgnoreCase(copy.getType())) {
taskResp = performSRDFProtectionAction(id, copy, ProtectionOp.FAILOVER.getRestOp());
taskList.getTaskList().add(taskResp);
} else {
throw APIException.badRequests.invalidCopyType(copy.getType());
}
return taskList;
}
/**
* Request to change the access mode on the provided copy.
*
* NOTE: This is an asynchronous operation.
*
* Currently only supported for RecoverPoint protected volumes. If volume is SRDF protected,
* then we do nothing and return the task.
*
* @prereq none
*
* @param id the URN of a ViPR Source volume
* @param param Copy to change access mode on
*
* @brief Changes the access mode for a copy.
* @return TaskList
*
* @throws ControllerException
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/continuous-copies/accessmode")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskList changeAccessMode(@PathParam("id") URI id, CopiesParam param) throws ControllerException {
TaskResourceRep taskResp = null;
TaskList taskList = new TaskList();
// Validate the consistency group URI
ArgValidator.checkFieldUriType(id, BlockConsistencyGroup.class, "id");
// Validate the list of copies
ArgValidator.checkFieldNotEmpty(param.getCopies(), "copies");
List<Copy> copies = param.getCopies();
if (copies.size() != 1) {
// Change access mode operations can only be performed on a single copy
throw APIException.badRequests.changeAccessCopiesParamCanOnlyBeOne();
}
Copy copy = copies.get(0);
ArgValidator.checkFieldNotEmpty(copy.getType(), "type");
ArgValidator.checkFieldNotEmpty(copy.getAccessMode(), "accessMode");
if (copy.getType().equalsIgnoreCase(TechnologyType.RP.toString())) {
taskResp = performProtectionAction(id, copy, ProtectionOp.CHANGE_ACCESS_MODE.getRestOp());
taskList.getTaskList().add(taskResp);
} else if (copy.getType().equalsIgnoreCase(TechnologyType.SRDF.toString())) {
_log.warn("Changing access mode is currently not supported for SRDF. Returning empty task list (no-op).");
return taskList;
} else {
throw APIException.badRequests.invalidCopyType(copy.getType());
}
return taskList;
}
/**
* Request to cancel fail over on already failed over consistency group.
*
* @prereq none
*
* @param id the URI of the BlockConsistencyGroup.
* @param param Copy to fail back
*
* @brief fail back to source again
* @return TaskList
*
* @throws ControllerException
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/continuous-copies/failover-cancel")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskList failoverCancel(@PathParam("id") URI id, CopiesParam param) throws ControllerException {
TaskResourceRep taskResp = null;
TaskList taskList = new TaskList();
// Validate the source volume URI
ArgValidator.checkFieldUriType(id, BlockConsistencyGroup.class, "id");
// Validate the list of copies
ArgValidator.checkFieldNotEmpty(param.getCopies(), "copies");
// Query Consistency Group
final BlockConsistencyGroup consistencyGroup = (BlockConsistencyGroup) queryResource(id);
// Ensure that the Consistency Group has been created on all of its defined
// system types.
if (!consistencyGroup.created()) {
throw APIException.badRequests.consistencyGroupNotCreated();
}
List<Copy> copies = param.getCopies();
if (copies.size() > 1) {
throw APIException.badRequests.failOverCancelCopiesParamCanOnlyBeOne();
}
Copy copy = copies.get(0);
ArgValidator.checkFieldUriType(copy.getCopyID(), VirtualArray.class, "copyId");
ArgValidator.checkFieldNotEmpty(copy.getType(), "type");
if (TechnologyType.RP.name().equalsIgnoreCase(copy.getType())) {
taskResp = performProtectionAction(id, copy, ProtectionOp.FAILOVER_CANCEL.getRestOp());
taskList.getTaskList().add(taskResp);
} else if (TechnologyType.SRDF.name().equalsIgnoreCase(copy.getType())) {
taskResp = performSRDFProtectionAction(id, copy, ProtectionOp.FAILOVER_CANCEL.getRestOp());
taskList.getTaskList().add(taskResp);
} else {
throw APIException.badRequests.invalidCopyType(copy.getType());
}
return taskList;
}
/**
* Since all of the protection operations are very similar, this method does all of the work.
* We keep the actual REST methods separate mostly for the purpose of documentation generators.
*
* @param consistencyGroupId the URI of the BlockConsistencyGroup to perform the protection action against.
* @param targetVarrayId the target virtual array.
* @param pointInTime any point in time, specified in UTC.
* Allowed values: "yyyy-MM-dd_HH:mm:ss" formatted date or datetime in milliseconds.
* @param op operation to perform (pause, stop, failover, etc)
* @return task resource rep
* @throws InternalException
*/
private TaskResourceRep
performProtectionAction(URI consistencyGroupId, Copy copy, String op)
throws InternalException {
ArgValidator.checkFieldUriType(consistencyGroupId, BlockConsistencyGroup.class, "id");
ArgValidator.checkFieldUriType(copy.getCopyID(), VirtualArray.class, "copyId");
// Get the BlockConsistencyGroup and target VirtualArray associated with the request.
final BlockConsistencyGroup consistencyGroup = (BlockConsistencyGroup) queryResource(consistencyGroupId);
final VirtualArray targetVirtualArray = _permissionsHelper.getObjectById(copy.getCopyID(), VirtualArray.class);
ArgValidator.checkEntity(consistencyGroup, consistencyGroupId, true);
ArgValidator.checkEntity(targetVirtualArray, copy.getCopyID(), true);
// The consistency group needs to be associated with RecoverPoint in order to perform the operation.
if (!consistencyGroup.checkForType(Types.RP)) {
// Attempt to do protection link management on unprotected CG
throw APIException.badRequests.consistencyGroupMustBeRPProtected(consistencyGroupId);
}
if (op.equalsIgnoreCase(ProtectionOp.SWAP.getRestOp()) && !NullColumnValueGetter.isNullURI(consistencyGroupId)) {
ExportUtils.validateConsistencyGroupBookmarksExported(_dbClient, consistencyGroupId);
}
// Catch any attempts to use an invalid access mode
if (op.equalsIgnoreCase(ProtectionOp.CHANGE_ACCESS_MODE.getRestOp()) &&
!Copy.ImageAccessMode.DIRECT_ACCESS.name().equalsIgnoreCase(copy.getAccessMode())) {
throw APIException.badRequests.unsupportedAccessMode(copy.getAccessMode());
}
// Verify that the supplied target Virtual Array is being referenced by at least one target volume in the CG.
List<Volume> targetVolumes = getTargetVolumes(consistencyGroup, copy.getCopyID());
if (targetVolumes == null || targetVolumes.isEmpty()) {
// The supplied target varray is not referenced by any target volumes in the CG.
throw APIException.badRequests.targetVirtualArrayDoesNotMatch(consistencyGroupId, copy.getCopyID());
}
// Get the first target volume
Volume targetVolume = targetVolumes.get(0);
String task = UUID.randomUUID().toString();
Operation status = new Operation();
status.setResourceType(ProtectionOp.getResourceOperationTypeEnum(op));
_dbClient.createTaskOpStatus(BlockConsistencyGroup.class, consistencyGroupId, task, status);
ProtectionSystem system = _dbClient.queryObject(ProtectionSystem.class,
targetVolume.getProtectionController());
String deviceType = system.getSystemType();
if (!deviceType.equals(DiscoveredDataObject.Type.rp.name())) {
throw APIException.badRequests.protectionForRpClusters();
}
RPController controller = getController(RPController.class, system.getSystemType());
controller.performProtectionOperation(system.getId(), consistencyGroupId, targetVolume.getId(), copy.getPointInTime(),
copy.getAccessMode(), op, task);
/*
* auditOp(OperationTypeEnum.PERFORM_PROTECTION_ACTION, true, AuditLogManager.AUDITOP_BEGIN,
* op, copyID.toString(), id.toString(), system.getId().toString());
*/
return toTask(consistencyGroup, task, status);
}
/**
* Performs the SRDF Protection operation.
*
* @param consistencyGroupId the URI of the BlockConsistencyGroup to perform the protection action against.
* @param copy the copy to operate on
* @param op operation to perform (pause, stop, failover, etc)
* @return task resource rep
* @throws InternalException
*/
private TaskResourceRep performSRDFProtectionAction(URI consistencyGroupId, Copy copy, String op)
throws InternalException {
URI targetVarrayId = copy.getCopyID();
ArgValidator.checkFieldUriType(targetVarrayId, VirtualArray.class, "copyID");
ArgValidator.checkFieldUriType(consistencyGroupId, BlockConsistencyGroup.class, "id");
// Get the BlockConsistencyGroup and target VirtualArray associated with the request.
final BlockConsistencyGroup consistencyGroup = (BlockConsistencyGroup) queryResource(consistencyGroupId);
final VirtualArray targetVirtualArray = _permissionsHelper.getObjectById(targetVarrayId, VirtualArray.class);
ArgValidator.checkEntity(consistencyGroup, consistencyGroupId, true);
ArgValidator.checkEntity(targetVirtualArray, targetVarrayId, true);
// The consistency group needs to be associated with SRDF in order to perform the operation.
if (!consistencyGroup.checkForType(Types.SRDF)) {
// Attempting to perform an SRDF operation on a non-SRDF consistency group
throw APIException.badRequests.consistencyGroupMustBeSRDFProtected(consistencyGroupId);
}
// Get the block service implementation
BlockServiceApi blockServiceApiImpl = getBlockServiceImpl(consistencyGroup);
// Get a list of CG volumes.
List<Volume> volumeList = BlockConsistencyGroupUtils.getActiveVolumesInCG(consistencyGroup, _dbClient, null);
if (volumeList == null || volumeList.isEmpty()) {
throw APIException.badRequests.consistencyGroupContainsNoVolumes(consistencyGroup.getId());
}
Volume srcVolume = null;
// Find a source volume in the SRDF local CG
for (Volume volume : volumeList) {
if (volume.getPersonality() != null &&
volume.getPersonality().equals(PersonalityTypes.SOURCE.name())) {
srcVolume = volume;
break;
}
}
if (srcVolume == null) {
// CG contains no source volumes.
throw APIException.badRequests.srdfCgContainsNoSourceVolumes(consistencyGroup.getId());
}
// Find the target volume that corresponds to the source whose Virtual Array matches
// the specified target Virtual Array. From that, obtain the remote SRDF CG.
BlockConsistencyGroup targetCg = null;
if (srcVolume.getSrdfTargets() != null && !srcVolume.getSrdfTargets().isEmpty()) {
for (String uri : srcVolume.getSrdfTargets()) {
Volume target = _dbClient.queryObject(Volume.class, URI.create(uri));
if (target.getVirtualArray().equals(targetVarrayId)) {
targetCg = _dbClient.queryObject(BlockConsistencyGroup.class, target.getConsistencyGroup());
break;
}
}
}
// Get all target CG target volumes for validation
List<Volume> targetVolumes = getTargetVolumes(targetCg, targetVarrayId);
for (Volume tgtVolume : targetVolumes) {
if (!Volume.isSRDFProtectedVolume(tgtVolume)) {
// All target volumes matching specified target virtual array must be SRDF
// protected.
throw APIException.badRequests.volumeMustBeSRDFProtected(tgtVolume.getId());
}
}
if (targetVolumes == null || targetVolumes.isEmpty()) {
// The supplied target varray is not referenced by any target volumes in the CG.
throw APIException.badRequests.targetVirtualArrayDoesNotMatch(targetCg.getId(), targetVarrayId);
}
// Get the first volume
Volume targetVolume = targetVolumes.get(0);
// COP-25377. We need to block failover and swap operations for SRDF ACTIVE COPY MODE
if (Mode.ACTIVE.toString().equalsIgnoreCase(targetVolume.getSrdfCopyMode())
&& (op.equalsIgnoreCase(ProtectionOp.FAILOVER_CANCEL.getRestOp()) ||
op.equalsIgnoreCase(ProtectionOp.FAILOVER.getRestOp()) || op.equalsIgnoreCase(ProtectionOp.SWAP.getRestOp()))) {
throw BadRequestException.badRequests.operationNotPermittedOnSRDFActiveCopyMode(op);
}
String task = UUID.randomUUID().toString();
Operation status = new Operation();
status.setResourceType(ProtectionOp.getResourceOperationTypeEnum(op));
_dbClient.createTaskOpStatus(BlockConsistencyGroup.class, targetCg.getId(), task, status);
if (op.equalsIgnoreCase(ProtectionOp.FAILOVER_TEST_CANCEL.getRestOp()) ||
op.equalsIgnoreCase(ProtectionOp.FAILOVER_TEST.getRestOp())) {
_dbClient.ready(BlockConsistencyGroup.class, targetCg.getId(), task);
return toTask(targetCg, task, status);
}
/*
* CTRL-6972: In the absence of a /restore API, we re-use /sync with a syncDirection parameter for
* specifying either SMI-S Resume or Restore:
* SOURCE_TO_TARGET -> ViPR Resume -> SMI-S Resume -> SRDF Incremental Establish (R1 overwrites R2)
* TARGET_TO_SOURCE -> ViPR Sync -> SMI-S Restore -> SRDF Full Restore (R2 overwrites R1)
*/
if (op.equalsIgnoreCase(ProtectionOp.SYNC.getRestOp()) &&
SOURCE_TO_TARGET.toString().equalsIgnoreCase(copy.getSyncDirection())) {
op = ProtectionOp.RESUME.getRestOp();
} else if (BlockService.isSuspendCopyRequest(op, copy)) {
op = ProtectionOp.SUSPEND.getRestOp();
}
StorageSystem system = _dbClient.queryObject(StorageSystem.class,
targetVolume.getStorageController());
ProtectionOrchestrationController controller =
getController(ProtectionOrchestrationController.class,
ProtectionOrchestrationController.PROTECTION_ORCHESTRATION_DEVICE);
// Create a new duplicate copy of the original copy. Update the copyId field to be the
// ID of the target volume. Existing SRDF controller logic needs the target volume
// to operate off of, not the virtual array.
Copy updatedCopy = new Copy(copy.getType(), copy.getSync(), targetVolume.getId(),
copy.getName(), copy.getCount());
controller.performSRDFProtectionOperation(system.getId(), updatedCopy, op, task);
return toTask(targetCg, task, status);
}
/**
* Gets all target volumes from the given consistency group that references the specified
* virtual array.
*
* @param consistencyGroup the consistency group.
* @param targetVarrayId the target virtual array.
* @return a list of matching target volumes from the consistency group.
*/
private List<Volume> getTargetVolumes(BlockConsistencyGroup consistencyGroup, URI targetVarrayId) {
List<Volume> targetVolumes = new ArrayList<Volume>();
if (consistencyGroup != null && targetVarrayId != null) {
// Get the block service implementation
BlockServiceApi blockServiceApiImpl = getBlockServiceImpl(consistencyGroup);
// Get a list of CG volumes.
List<Volume> volumeList = null;
if (consistencyGroup.checkForType(Types.RP)) {
volumeList = blockServiceApiImpl.getActiveCGVolumes(consistencyGroup);
} else {
volumeList = BlockConsistencyGroupUtils.getActiveNonVplexVolumesInCG(consistencyGroup, _dbClient, null);
}
if (volumeList == null || volumeList.isEmpty()) {
throw APIException.badRequests.consistencyGroupContainsNoVolumes(consistencyGroup.getId());
}
// Find all target volumes in the CG that match the specified target virtual array.
for (Volume volume : volumeList) {
if (volume.getPersonality() != null &&
volume.getPersonality().equals(PersonalityTypes.TARGET.name()) &&
volume.getVirtualArray() != null && volume.getVirtualArray().equals(targetVarrayId)) {
targetVolumes.add(volume);
}
}
if (targetVolumes.isEmpty()) {
_log.info(String
.format("Unable to find any target volumes in consistency group %s. There are no target volumes matching target virtual array %s.",
consistencyGroup.getId(), targetVarrayId));
}
}
return targetVolumes;
}
/**
* 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;
}
/**
* Verifies the CG passe din the request is valid and contains volumes.
*
* @param cgURI The URI of a consistency group.
*
* @return The volumes in the consistency group.
*/
private List<Volume> verifyCGForFullCopyRequest(URI cgURI) {
// Query Consistency Group.
BlockConsistencyGroup consistencyGroup = (BlockConsistencyGroup) queryResource(cgURI);
// Ensure that the Consistency Group has been created on all of its
// defined system types.
if (!consistencyGroup.created()) {
throw APIException.badRequests.consistencyGroupNotCreated();
}
// Get the block service implementation.
BlockServiceApi blockServiceApiImpl = getBlockServiceImpl(consistencyGroup);
// Get the volumes in the consistency group.
List<Volume> cgVolumes = blockServiceApiImpl.getActiveCGVolumes(consistencyGroup);
if (cgVolumes.isEmpty()) {
throw APIException.badRequests
.fullCopyOperationNotAllowedOnEmptyCG(consistencyGroup.getLabel());
}
return cgVolumes;
}
/**
* Verifies that the passed full copy URI and ensure that it
* represents a full copy for a volume in the passed list of
* volumes, which are the volumes for a specific consistency
* group.
*
* @param fullCopyURI The URI of a full copy volume.
* @param cgVolumes The list of volumes in a consistency group.
*
* @return The URI of the full copy source.
*/
private URI verifyFullCopyForCopyRequest(URI fullCopyURI, List<Volume> cgVolumes) {
// Get the full copy source.
Volume fullCopyVolume = (Volume) BlockFullCopyUtils.queryFullCopyResource(
fullCopyURI, uriInfo, true, _dbClient);
URI fcSourceURI = fullCopyVolume.getAssociatedSourceVolume();
if (NullColumnValueGetter.isNullURI(fcSourceURI)) {
throw APIException.badRequests
.fullCopyOperationNotAllowedNotAFullCopy(fullCopyVolume.getLabel());
}
// Verify the source is in the consistency group.
boolean sourceInCG = false;
for (Volume cgVolume : cgVolumes) {
if (cgVolume.getId().equals(fcSourceURI)) {
sourceInCG = true;
break;
}
}
if (!sourceInCG) {
throw APIException.badRequests
.fullCopyOperationNotAllowedSourceNotInCG(fullCopyVolume.getLabel());
}
return fcSourceURI;
}
/**
* Check if any of the given CG volumes is part of an application.
* If so, throw an error indicating replica operation is not supported on CG
* and it should be performed at application level.
*
* @param volume the CG volume
*/
private void validateVolumeNotPartOfApplication(List<Volume> volumes, String replicaType) {
for (Volume volume : volumes) {
VolumeGroup volumeGroup = volume.getApplication(_dbClient);
if (volumeGroup != null) {
throw APIException.badRequests.replicaOperationNotAllowedOnCGVolumePartOfCopyTypeVolumeGroup(volumeGroup.getLabel(),
replicaType);
}
}
}
private void validateSessionPartOfConsistencyGroup(URI cgId, URI sessionId) {
ArgValidator.checkUri(sessionId);
ArgValidator.checkUri(cgId);
BlockSnapshotSession session = _dbClient.queryObject(BlockSnapshotSession.class, sessionId);
BlockConsistencyGroup cg = _dbClient.queryObject(BlockConsistencyGroup.class, cgId);
if (session == null) {
throw APIException.notFound.unableToFindEntityInURL(sessionId);
}
if (cg == null) {
throw APIException.notFound.unableToFindEntityInURL(cgId);
}
if (!cg.getId().equals(session.getConsistencyGroup())) {
throw APIException.badRequests.snapshotSessionIsNotForConsistencyGroup(session.getLabel(), cg.getLabel());
}
}
/**
* Creates tasks against consistency groups associated with a request and adds them to the given task list.
*
* @param group
* @param taskList
* @param taskId
* @param operationTypeEnum
*/
protected void addConsistencyGroupTask(BlockConsistencyGroup group, TaskList taskList,
String taskId,
ResourceOperationTypeEnum operationTypeEnum) {
Operation op = _dbClient.createTaskOpStatus(BlockConsistencyGroup.class, group.getId(), taskId,
operationTypeEnum);
taskList.getTaskList().add(TaskMapper.toTask(group, taskId, op));
}
/**
* 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;
}
}