/*
* Copyright (c) 2008-2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.api.service.impl.resource;
import static com.emc.storageos.api.mapper.BlockMapper.map;
import static com.emc.storageos.api.mapper.DbObjectMapper.toNamedRelatedResource;
import static com.emc.storageos.api.mapper.ProtectionMapper.map;
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.db.client.util.NullColumnValueGetter.isNullURI;
import static com.emc.storageos.model.block.Copy.SyncDirection.SOURCE_TO_TARGET;
import static com.google.common.collect.Collections2.transform;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.parseBoolean;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
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.functions.MapVolume;
import com.emc.storageos.api.service.authorization.PermissionsHelper;
import com.emc.storageos.api.service.impl.placement.PlacementManager;
import com.emc.storageos.api.service.impl.placement.StorageScheduler;
import com.emc.storageos.api.service.impl.placement.VirtualPoolUtil;
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.AsyncTaskExecutorIntf;
import com.emc.storageos.api.service.impl.resource.utils.BlockServiceUtils;
import com.emc.storageos.api.service.impl.resource.utils.CapacityUtils;
import com.emc.storageos.api.service.impl.resource.utils.ExportUtils;
import com.emc.storageos.api.service.impl.resource.utils.VirtualPoolChangeAnalyzer;
import com.emc.storageos.api.service.impl.resource.utils.VolumeIngestionUtil;
import com.emc.storageos.api.service.impl.response.BulkList;
import com.emc.storageos.api.service.impl.response.FilterIterator;
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.RestLinkFactory;
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.AlternateIdConstraint;
import com.emc.storageos.db.client.constraint.Constraint;
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.ExportGroup;
import com.emc.storageos.db.client.model.ExportMask;
import com.emc.storageos.db.client.model.ExportPathParams;
import com.emc.storageos.db.client.model.Host;
import com.emc.storageos.db.client.model.Migration;
import com.emc.storageos.db.client.model.Operation;
import com.emc.storageos.db.client.model.Project;
import com.emc.storageos.db.client.model.ProtectionSet;
import com.emc.storageos.db.client.model.ProtectionSystem;
import com.emc.storageos.db.client.model.RemoteDirectorGroup;
import com.emc.storageos.db.client.model.RemoteDirectorGroup.SupportedCopyModes;
import com.emc.storageos.db.client.model.StoragePool;
import com.emc.storageos.db.client.model.StoragePort;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringMap;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.model.TenantOrg;
import com.emc.storageos.db.client.model.VirtualArray;
import com.emc.storageos.db.client.model.VirtualPool;
import com.emc.storageos.db.client.model.VirtualPool.RPCopyMode;
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.VplexMirror;
import com.emc.storageos.db.client.model.VpoolRemoteCopyProtectionSettings;
import com.emc.storageos.db.client.model.util.BlockConsistencyGroupUtils;
import com.emc.storageos.db.client.util.CustomQueryUtility;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.db.client.util.SizeUtil;
import com.emc.storageos.db.client.util.StringSetUtil;
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.RestLinkRep;
import com.emc.storageos.model.SnapshotList;
import com.emc.storageos.model.TaskList;
import com.emc.storageos.model.TaskResourceRep;
import com.emc.storageos.model.block.BlockMirrorRestRep;
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.MigrationList;
import com.emc.storageos.model.block.MirrorList;
import com.emc.storageos.model.block.NamedVolumesList;
import com.emc.storageos.model.block.NativeContinuousCopyCreate;
import com.emc.storageos.model.block.RelatedStoragePool;
import com.emc.storageos.model.block.SnapshotSessionCreateParam;
import com.emc.storageos.model.block.VirtualArrayChangeParam;
import com.emc.storageos.model.block.VirtualPoolChangeParam;
import com.emc.storageos.model.block.VolumeBulkRep;
import com.emc.storageos.model.block.VolumeCreate;
import com.emc.storageos.model.block.VolumeDeleteTypeEnum;
import com.emc.storageos.model.block.VolumeExpandParam;
import com.emc.storageos.model.block.VolumeFullCopyCreateParam;
import com.emc.storageos.model.block.VolumeRestRep;
import com.emc.storageos.model.block.VolumeSnapshotParam;
import com.emc.storageos.model.block.VolumeVirtualArrayChangeParam;
import com.emc.storageos.model.block.VolumeVirtualPoolChangeParam;
import com.emc.storageos.model.block.export.ITLBulkRep;
import com.emc.storageos.model.block.export.ITLRestRepList;
import com.emc.storageos.model.protection.ProtectionSetRestRep;
import com.emc.storageos.model.search.SearchResultResourceRep;
import com.emc.storageos.model.search.SearchResults;
import com.emc.storageos.model.vpool.VirtualPoolChangeList;
import com.emc.storageos.model.vpool.VirtualPoolChangeOperationEnum;
import com.emc.storageos.protectioncontroller.ProtectionController;
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.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.InternalServerErrorException;
import com.emc.storageos.svcs.errorhandling.resources.ServiceCode;
import com.emc.storageos.svcs.errorhandling.resources.ServiceCodeException;
import com.emc.storageos.util.VPlexSrdfUtil;
import com.emc.storageos.util.VPlexUtil;
import com.emc.storageos.volumecontroller.AsyncTask;
import com.emc.storageos.volumecontroller.ControllerException;
import com.emc.storageos.volumecontroller.impl.ControllerUtils;
import com.emc.storageos.volumecontroller.impl.smis.SRDFOperations.Mode;
import com.emc.storageos.volumecontroller.impl.utils.VirtualPoolCapabilityValuesWrapper;
import com.emc.storageos.volumecontroller.placement.ExportPathUpdater;
import com.emc.storageos.vplexcontroller.VPlexDeviceController;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
@Path("/block/volumes")
@DefaultPermissions(readRoles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, readAcls = { ACL.OWN, ACL.ALL }, writeRoles = {
Role.TENANT_ADMIN }, writeAcls = { ACL.OWN, ACL.ALL })
@SuppressWarnings({ "unchecked", "deprecation", "rawtypes" })
public class BlockService extends TaskResourceService {
private static final String SEARCH_VARRAY = "virtual_array";
private static final String SEARCH_WWN = "wwn";
private static final String SEARCH_PERSONALITY = "personality";
private static final String SEARCH_PROTECTION = "protection";
private static final String SRDF = "srdf";
private static final String RP = "rp";
private static final String VPLEX = "vplex";
private static final String HA = "ha";
private static final String TRUE_STR = "true";
private static final String FALSE_STR = "false";
private static final String MIRRORS = "Mirrors";
private static final String SIZE = "size";
// Protection operations that are allowed with /block/volumes/{id}/protection/continuous-copies/
public static enum ProtectionOp {
FAILOVER("failover", ResourceOperationTypeEnum.PERFORM_PROTECTION_ACTION_FAILOVER),
FAILOVER_TEST("failover-test", ResourceOperationTypeEnum.PERFORM_PROTECTION_ACTION_FAILOVER_TEST),
FAILOVER_TEST_CANCEL("failover-test-cancel", ResourceOperationTypeEnum.PERFORM_PROTECTION_ACTION_FAILOVER_TEST_CANCEL),
FAILOVER_CANCEL("failover-cancel", ResourceOperationTypeEnum.PERFORM_PROTECTION_ACTION_FAILOVER_CANCEL),
SWAP("swap", ResourceOperationTypeEnum.PERFORM_PROTECTION_ACTION_SWAP),
SYNC("sync", ResourceOperationTypeEnum.PERFORM_PROTECTION_ACTION_SYNC),
START("start", ResourceOperationTypeEnum.PERFORM_PROTECTION_ACTION_START),
STOP("stop", ResourceOperationTypeEnum.PERFORM_PROTECTION_ACTION_STOP),
PAUSE("pause", ResourceOperationTypeEnum.PERFORM_PROTECTION_ACTION_PAUSE),
SUSPEND("suspend", ResourceOperationTypeEnum.PERFORM_PROTECTION_ACTION_SUSPEND),
RESUME("resume", ResourceOperationTypeEnum.PERFORM_PROTECTION_ACTION_RESUME),
CHANGE_COPY_MODE("change-copy-mode", ResourceOperationTypeEnum.PERFORM_PROTECTION_ACTION_CHANGE_COPY_MODE),
CHANGE_ACCESS_MODE("change-access-mode", ResourceOperationTypeEnum.PERFORM_PROTECTION_ACTION_CHANGE_ACCESS_MODE),
UNKNOWN("unknown", ResourceOperationTypeEnum.PERFORM_PROTECTION_ACTION);
private final String op;
private final ResourceOperationTypeEnum resourceType;
ProtectionOp(String op, ResourceOperationTypeEnum resourceType) {
this.op = op;
this.resourceType = resourceType;
}
// The rest URI operation
public String getRestOp() {
return op;
}
// The resource type, which contains a good name and description
public ResourceOperationTypeEnum getResourceType() {
return resourceType;
}
private static final ProtectionOp[] copyOfValues = values();
public static String getProtectionOpDisplayName(String op) {
for (ProtectionOp opValue : copyOfValues) {
if (opValue.getRestOp().contains(op)) {
return opValue.getResourceType().getName();
}
}
return ProtectionOp.UNKNOWN.name();
}
public static ResourceOperationTypeEnum getResourceOperationTypeEnum(String restOp) {
for (ProtectionOp opValue : copyOfValues) {
if (opValue.getRestOp().contains(restOp)) {
return opValue.getResourceType();
}
}
return ResourceOperationTypeEnum.PERFORM_PROTECTION_ACTION;
}
}
@SuppressWarnings("unused")
private static class DiscoverProtectionSetJobExec implements AsyncTaskExecutorIntf {
private final ProtectionController _controller;
DiscoverProtectionSetJobExec(ProtectionController controller) {
_controller = controller;
}
@Override
public void executeTasks(AsyncTask[] tasks) throws ControllerException {
_controller.discover(tasks);
}
@Override
public ResourceOperationTypeEnum getOperation() {
return ResourceOperationTypeEnum.DISCOVER_STORAGE_SYSTEM;
}
}
static final Logger _log = LoggerFactory.getLogger(BlockService.class);
public static final String EVENT_SERVICE_TYPE = "block";
private static final int GB = 1024 * 1024 * 1024;
private static final int MAX_VOLUME_COUNT = 100;
private TenantsService _tenantsService;
PlacementManager _placementManager;
public void setPlacementManager(PlacementManager placementManager) {
_placementManager = placementManager;
}
public void setTenantsService(TenantsService tenantsService) {
_tenantsService = tenantsService;
}
@Override
public String getServiceType() {
return EVENT_SERVICE_TYPE;
}
private static double THIN_VOLUME_MAX_LIMIT = 240;
// Block service implementations
static volatile private Map<String, BlockServiceApi> _blockServiceApis;
static public void setBlockServiceApis(Map<String, BlockServiceApi> serviceInterfaces) {
_blockServiceApis = serviceInterfaces;
}
static public BlockServiceApi getBlockServiceImpl(String type) {
return _blockServiceApis.get(type);
}
@Override
public Class<Volume> getResourceClass() {
return Volume.class;
}
/**
* Retrieve volume representations based on input ids.
*
* @return list of volume representations.
*/
@Override
public VolumeBulkRep queryBulkResourceReps(List<URI> ids) {
Iterator<Volume> _dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids);
return new VolumeBulkRep(BulkList.wrapping(_dbIterator, MapVolume.getInstance(_dbClient)));
}
@Override
protected BulkRestRep queryFilteredBulkResourceReps(
List<URI> ids) {
Iterator<Volume> _dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids);
BulkList.ResourceFilter<Volume> filter = new BulkList.ProjectResourceFilter<Volume>(
getUserFromContext(), _permissionsHelper);
return new VolumeBulkRep(BulkList.wrapping(_dbIterator, MapVolume.getInstance(_dbClient), filter));
}
/**
* Start continuous copies. Continuous copies will be created when <i>NATIVE</i> type is specified and
* <i>copyID</i> fields are omitted.
*
* @prereq none
*
* @param id URN of a ViPR Source volume
* @param param List of copies to start or create.
*
* @brief Start or create continuous copies.
*
* @return TaskList
* @throws ControllerException
*
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/continuous-copies/start")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskList startContinuousCopies(@PathParam("id") URI id, CopiesParam param)
throws ControllerException {
TaskResourceRep taskResp = null;
TaskList taskList = new TaskList();
// Validate the source volume URI
ArgValidator.checkFieldUriType(id, Volume.class, "id");
Volume volume = _dbClient.queryObject(Volume.class, id);
// Make sure that we don't have some pending
// operation against the volume
checkForPendingTasks(Arrays.asList(volume.getTenant().getURI()), Arrays.asList(volume));
// Don't operate on ingested volumes.
VolumeIngestionUtil.checkOperationSupportedOnIngestedVolume(volume,
ResourceOperationTypeEnum.CREATE_VOLUME_MIRROR, _dbClient);
Volume sourceVolume = queryVolumeResource(id);
validateSourceVolumeHasExported(sourceVolume);
// Validate the list of copies
ArgValidator.checkFieldNotEmpty(param.getCopies(), "copies");
// Verify that the copy IDs are either all specified or none are specified
// for a particular protection type. Combinations are not allowed
verifyCopyIDs(param);
// Process the list of copies
for (Copy copy : param.getCopies()) {
// Validate a copy type was passed
ArgValidator.checkFieldNotEmpty(copy.getType(), "type");
URI copyID = copy.getCopyID();
// If copyID is null all copies are started
if (copy.getType().equalsIgnoreCase(TechnologyType.RP.toString())) {
// If copyID is not set all copies are started
if (!URIUtil.isValid(copyID)) {
copyID = null;
}
taskResp = performProtectionAction(id, copy, ProtectionOp.START.getRestOp());
taskList.getTaskList().add(taskResp);
// If copyID is null, we have already started all copies
if (copyID == null) {
return taskList;
}
} else if (copy.getType().equalsIgnoreCase(TechnologyType.SRDF.toString())) {
id = VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, id);
copy.setCopyID(VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, copy.getCopyID()));
taskResp = performSRDFProtectionAction(id, copy, ProtectionOp.START.getRestOp());
taskList.getTaskList().add(taskResp);
} else if (copy.getType().equalsIgnoreCase(TechnologyType.NATIVE.toString())) {
if (URIUtil.isValid(copyID) && URIUtil.isType(copyID, BlockMirror.class)) {
/*
* To establish group relationship between volume group and mirror group
*/
taskResp = establishVolumeMirrorGroupRelation(id, copy, ProtectionOp.START.getRestOp());
taskList.getTaskList().add(taskResp);
} else {
NativeContinuousCopyCreate mirror = new NativeContinuousCopyCreate(
copy.getName(), copy.getCount());
taskList = startMirrors(id, mirror);
}
} else {
throw APIException.badRequests.invalidCopyType(copy.getType());
}
}
return taskList;
}
/**
* Stop continuous copies.
*
*
* @prereq none
*
* @param id
* the URN of a ViPR Source volume
* @param param
* List of copies to stop
*
* @brief Stop continuous copies.
* @return TaskList
*
* @throws ControllerException
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/continuous-copies/stop")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskList stopContinuousCopies(@PathParam("id") URI id, CopiesParam param)
throws ControllerException {
TaskResourceRep taskResp = null;
TaskList taskList = new TaskList();
// Validate the source volume URI
ArgValidator.checkFieldUriType(id, Volume.class, "id");
Volume volume = _dbClient.queryObject(Volume.class, id);
// Make sure that we don't have some pending
// operation against the volume
checkForPendingTasks(Arrays.asList(volume.getTenant().getURI()), Arrays.asList(volume));
boolean vplexVolume = checkIfVolumeIsForVplex(id);
// Validate the list of copies
ArgValidator.checkFieldNotEmpty(param.getCopies(), "copies");
// Verify that the copy IDs are either all specified or none are specified
// for a particular protection type. Combinations are not allowed
verifyCopyIDs(param);
// Validate for SRDF Stop operation
validateSRDFStopOperation(id, param);
// Process the list of copies
for (Copy copy : param.getCopies()) {
// If copyID is not set all copies are stopped
URI copyID = copy.getCopyID();
if (!URIUtil.isValid(copyID)) {
copyID = null;
}
// Validate a copy type was passed
ArgValidator.checkFieldNotEmpty(copy.getType(), "type");
// If copyID is null all copies are stopped
if (copy.getType().equalsIgnoreCase(TechnologyType.RP.toString())) {
taskResp = performProtectionAction(id, copy, ProtectionOp.STOP.getRestOp());
taskList.getTaskList().add(taskResp);
} else if (!vplexVolume && copy.getType().equalsIgnoreCase(TechnologyType.NATIVE.toString())) {
taskList = stopMirrors(id, copyID);
} else if (vplexVolume && copy.getType().equalsIgnoreCase(TechnologyType.NATIVE.toString())) {
taskList = stopVplexMirrors(id, copyID);
} else if (copy.getType().equalsIgnoreCase(TechnologyType.SRDF.toString())) {
id = VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, id);
copy.setCopyID(VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, copy.getCopyID()));
taskResp = performSRDFProtectionAction(id, copy, ProtectionOp.STOP.getRestOp());
taskList.getTaskList().add(taskResp);
} else {
throw APIException.badRequests.invalidCopyType(copy.getType());
}
// If copyID is null, we have already stopped all copies
if (copyID == null) {
return taskList;
}
}
return taskList;
}
/**
* Cant Perform SRDF STOP operation Sync/Async with CG if it has active snap or clone.
*
* @param id
* @param param
*/
private void validateSRDFStopOperation(URI id, CopiesParam param) {
List<URI> srdfVolumeURIList = new ArrayList<URI>();
Volume srdfSourceVolume = _dbClient.queryObject(Volume.class, id);
if (srdfSourceVolume.checkForSRDF() && srdfSourceVolume.hasConsistencyGroup()) {
srdfVolumeURIList.add(id);
for (Copy copy : param.getCopies()) {
URI copyID = copy.getCopyID();
if (URIUtil.isType(copyID, Volume.class) && URIUtil.isValid(copyID)) {
srdfVolumeURIList.add(copyID);
break;
}
}
for (URI srdfVolURI : srdfVolumeURIList) {
Volume volume = _dbClient.queryObject(Volume.class, srdfVolURI);
URIQueryResultList list = new URIQueryResultList();
Constraint constraint = ContainmentConstraint.Factory
.getVolumeSnapshotConstraint(srdfVolURI);
_dbClient.queryByConstraint(constraint, list);
Iterator<URI> it = list.iterator();
while (it.hasNext()) {
URI snapshotID = it.next();
BlockSnapshot snapshot = _dbClient.queryObject(BlockSnapshot.class, snapshotID);
if (snapshot != null && !snapshot.getInactive()) {
throw APIException.badRequests.cannotStopSRDFBlockSnapShotExists(volume
.getLabel());
}
}
// Also check for snapshot sessions.
List<BlockSnapshotSession> snapSessions = CustomQueryUtility.queryActiveResourcesByConstraint(_dbClient,
BlockSnapshotSession.class, ContainmentConstraint.Factory.getParentSnapshotSessionConstraint(srdfVolURI));
if (!snapSessions.isEmpty()) {
throw APIException.badRequests.cannotStopSRDFBlockSnapShotExists(volume
.getLabel());
}
// For a volume that is a full copy or is the source volume for
// full copies deleting the volume may not be allowed.
if (!getFullCopyManager().volumeCanBeDeleted(volume)) {
throw APIException.badRequests.cantStopSRDFFullCopyNotDetached(volume
.getLabel());
}
}
}
}
/**
* Create a full copy of the specified volume.
*
*
* @prereq none
*
* @param id
* the URN of a ViPR Source volume
* @param param
* POST data containing full copy creation information
*
* @brief Create full copies
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskList createFullCopy(@PathParam("id") URI id,
VolumeFullCopyCreateParam param) throws InternalException {
return getFullCopyManager().createFullCopy(id, param);
}
/**
* Activate a full copy.
* <p>
* This method is deprecated. Use /block/full-copies/{id}/activate instead with {id} representing full copy URI id
*
* @prereq Create full copy as inactive
*
* @param id
* the URN of a ViPR Source volume
* @param fullCopyId
* Full copy URI
*
* @brief Activate full copy. This method is deprecated. Use /block/full-copies/{id}/activate instead with {id}
* representing full copy
* URI id
*
* @return TaskResourceRep
*/
@Deprecated
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies/{pid}/activate")
public TaskResourceRep activateFullCopy(@PathParam("id") URI id,
@PathParam("pid") URI fullCopyId) throws InternalException {
return getFullCopyManager().activateFullCopy(id, fullCopyId).getTaskList().get(0);
}
/**
* Show synchronization progress for a full copy.
*
* <p>
* This method is deprecated. Use /block/full-copies/{id}/check-progress instead with {id} representing full copy URI id
*
* @prereq none
*
* @param id
* the URN of a ViPR Source volume
* @param fullCopyId
* Full copy URI
*
* @brief Show full copy synchronization progress
*
* @return VolumeRestRep
*/
@Deprecated
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies/{pid}/check-progress")
public VolumeRestRep getFullCopyProgressCheck(@PathParam("id") URI id,
@PathParam("pid") URI fullCopyId) throws InternalException {
return getFullCopyManager().checkFullCopyProgress(id, fullCopyId);
}
/**
* Detach a full copy from its source volume.
*
* <p>
* This method is deprecated. Use /block/full-copies/{id}/detach instead with {id} representing full copy URI id
*
* @prereq Create full copy as inactive
* @prereq Activate full copy
*
* @param id
* the URN of a ViPR Source volume
* @param id
* the URN of Full copy volume
*
* @brief Detach full copy
*
* @return TaskResourceRep
*/
@Deprecated
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies/{pid}/detach")
public TaskResourceRep detachFullCopy(@PathParam("id") URI id,
@PathParam("pid") URI fullCopyId) throws InternalException {
return getFullCopyManager().detachFullCopy(id, fullCopyId).getTaskList().get(0);
}
/**
* 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, _tenantsService);
return fcManager;
}
/**
* 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;
}
/**
* The fundamental abstraction in the Block Store is a
* volume. A volume is a unit of block storage capacity that has been
* allocated by a consumer to a project. This API allows the user to
* create one or more volumes. The volumes are created in the same
* storage pool.
*
* NOTE: This is an asynchronous operation.
*
*
* @prereq none
*
* @param param
* POST data containing the volume creation information.
*
* @brief Create volume
* @return A reference to a BlockTaskList containing a list of
* TaskResourceRep references specifying the task data for the
* volume creation tasks.
* @throws InternalException
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public TaskList createVolume(VolumeCreate param) throws InternalException {
ArgValidator.checkFieldNotNull(param, "volume_create");
// CQECC00604134
ArgValidator.checkFieldUriType(param.getProject(), Project.class, "project");
// Get and validate the project.
Project project = _permissionsHelper.getObjectById(param.getProject(), Project.class);
ArgValidator.checkEntity(project, param.getProject(), isIdEmbeddedInURL(param.getProject()));
// Verify the user is authorized.
BlockServiceUtils.verifyUserIsAuthorizedForRequest(project, getUserFromContext(), _permissionsHelper);
// Get and validate the varray
ArgValidator.checkFieldUriType(param.getVarray(), VirtualArray.class, "varray");
VirtualArray varray = BlockServiceUtils.verifyVirtualArrayForRequest(project,
param.getVarray(), uriInfo, _permissionsHelper, _dbClient);
ArgValidator.checkEntity(varray, param.getVarray(), isIdEmbeddedInURL(param.getVarray()));
// Get and validate the VirtualPool.
VirtualPool vpool = getVirtualPoolForVolumeCreateRequest(project, param);
VirtualPoolCapabilityValuesWrapper capabilities = new VirtualPoolCapabilityValuesWrapper();
// Get the count indicating the number of volumes to create. If not
// passed
// assume 1. Then get the volume placement recommendations.
Integer volumeCount = 1;
Long volumeSize = 0L;
if (param.getCount() != null) {
if (param.getCount() <= 0) {
throw APIException.badRequests.parameterMustBeGreaterThan("count", 0);
}
if (param.getCount() > MAX_VOLUME_COUNT) {
throw APIException.badRequests.exceedingLimit("count", MAX_VOLUME_COUNT);
}
volumeCount = param.getCount();
capabilities.put(VirtualPoolCapabilityValuesWrapper.RESOURCE_COUNT, volumeCount);
}
if (param.getSize() != null) {
// Validate the requested volume size is greater then 0.
volumeSize = SizeUtil.translateSize(param.getSize());
if (volumeSize <= 0) {
throw APIException.badRequests.parameterMustBeGreaterThan(SIZE, 0);
}
capabilities.put(VirtualPoolCapabilityValuesWrapper.SIZE, volumeSize);
}
if (null != vpool.getThinVolumePreAllocationPercentage()
&& 0 < vpool.getThinVolumePreAllocationPercentage()) {
capabilities.put(VirtualPoolCapabilityValuesWrapper.THIN_VOLUME_PRE_ALLOCATE_SIZE, VirtualPoolUtil
.getThinVolumePreAllocationSize(vpool.getThinVolumePreAllocationPercentage(), volumeSize));
}
if (VirtualPool.ProvisioningType.Thin.toString().equalsIgnoreCase(
vpool.getSupportedProvisioningType())) {
capabilities.put(VirtualPoolCapabilityValuesWrapper.THIN_PROVISIONING, Boolean.TRUE);
}
// Does vpool supports dedup
if (null != vpool.getDedupCapable() && vpool.getDedupCapable()) {
capabilities.put(VirtualPoolCapabilityValuesWrapper.DEDUP, Boolean.TRUE);
}
// Find the implementation that services this vpool and volume request
BlockServiceApi blockServiceImpl = getBlockServiceImpl(vpool, _dbClient);
BlockConsistencyGroup consistencyGroup = null;
final Boolean isMultiVolumeConsistencyOn = vpool.getMultivolumeConsistency() == null ? FALSE
: vpool.getMultivolumeConsistency();
/*
* Validate Consistency Group:
* 1. CG should be active in the database
* 2. CG project and Volume project should match
* 3. The storage system that the CG is bonded to is associated to the
* request virtual array
*/
ArrayList<String> requestedTypes = new ArrayList<String>();
final URI actualId = project.getId();
if (param.getConsistencyGroup() != null) {
// Get and validate consistency group
consistencyGroup = queryConsistencyGroup(param.getConsistencyGroup());
// Check that the Volume project and the CG project are the same
final URI expectedId = consistencyGroup.getProject().getURI();
checkProjectsMatch(expectedId, actualId);
// If the Consistency Group was provided, MultiVolumeConsistency
// attribute should be true
if (!isMultiVolumeConsistencyOn) {
throw APIException.badRequests.invalidParameterConsistencyGroupProvidedButVirtualPoolHasNoMultiVolumeConsistency(
param.getConsistencyGroup(), param.getVpool());
}
// Find all volumes assigned to the group
final List<Volume> activeCGVolumes = blockServiceImpl.getActiveCGVolumes(consistencyGroup);
// Validate that the number of volumes in the group plus the number
// to be added by this request does not exceed the maximum volumes
// in a CG.
int cgMaxVolCount = blockServiceImpl
.getMaxVolumesForConsistencyGroup(consistencyGroup);
if ((activeCGVolumes.size() + volumeCount.intValue()) > cgMaxVolCount) {
throw APIException.badRequests.requestedVolumeCountExceedsLimitsForCG(
volumeCount.intValue(), cgMaxVolCount, consistencyGroup.getLabel());
}
// Get the requested types for provisioning (RP, VPlex, etc.)
requestedTypes = getRequestedTypes(vpool);
// If the consistency group is not yet created, verify the name is OK.
if (!consistencyGroup.created()) {
blockServiceImpl.validateConsistencyGroupName(consistencyGroup, requestedTypes);
}
// Consistency Group is already a Target, hence cannot be used to create source volume
if (consistencyGroup.srdfTarget()) {
throw APIException.badRequests.consistencyGroupBelongsToTarget(consistencyGroup.getId());
}
if (VirtualPool.vPoolSpecifiesSRDF(vpool)
&& (consistencyGroup.getLabel().length() > 8 || !isAlphaNumeric(consistencyGroup.getLabel()))) {
throw APIException.badRequests.groupNameCannotExceedEightCharactersoronlyAlphaNumericAllowed();
}
if (!VirtualPool.vPoolSpecifiesSRDF(vpool) && consistencyGroup.checkForType(Types.SRDF)) {
throw APIException.badRequests.nonSRDFVolumeCannotbeAddedToSRDFCG();
}
if (VirtualPool.vPoolSpecifiesSRDF(vpool)) {
List<Volume> nativeVolumesInCG = BlockConsistencyGroupUtils.getActiveNativeVolumesInCG(consistencyGroup, _dbClient);
for (Volume nativeVolume : nativeVolumesInCG) {
// Cannot add volumes if in swapped state. This is a limitation that will eventually be removed.
if (Volume.LinkStatus.SWAPPED.name().equals(nativeVolume.getLinkStatus())) {
throw BadRequestException.badRequests.cannotAddVolumesToSwappedCG(consistencyGroup.getLabel());
}
}
}
// check if CG's storage system is associated to the requested virtual array
validateCGValidWithVirtualArray(consistencyGroup, varray);
// Validate the CG type. We want to make sure the volume create request is appropriate
// the CG's previously requested types.
if (consistencyGroup.creationInitiated()) {
if (!consistencyGroup.getRequestedTypes().containsAll(requestedTypes)) {
throw APIException.badRequests.consistencyGroupIsNotCompatibleWithRequest(
consistencyGroup.getId(), consistencyGroup.getRequestedTypes().toString(), requestedTypes.toString());
}
}
Volume existingRpSourceVolume = null;
// RP consistency group validation
if (VirtualPool.vPoolSpecifiesProtection(vpool)) {
// Check to see if the CG has any RecoverPoint provisioned volumes. This is done by looking at the protectionSet field.
// The protectionSet field won't be set until the volume is provisioned in RP so this check allows concurrent
// requests to go through.
boolean cgHasRpProvisionedVolumes = false;
if (activeCGVolumes != null && !activeCGVolumes.isEmpty()) {
for (Volume vol : activeCGVolumes) {
if (!NullColumnValueGetter.isNullNamedURI(vol.getProtectionSet())) {
_log.info(String.format("Determined that consistency group %s contains RP provisioned volumes.",
consistencyGroup.getId()));
cgHasRpProvisionedVolumes = true;
break;
}
}
}
// Ensure the CG is either empty or has been tagged for RP and contains properly provisioned RP volumes.
if (cgHasRpProvisionedVolumes && !consistencyGroup.getTypes().contains(BlockConsistencyGroup.Types.RP.toString())) {
throw APIException.badRequests.consistencyGroupMustBeEmptyOrContainRpVolumes(consistencyGroup.getId());
}
if (!activeCGVolumes.isEmpty()) {
// Find the first existing source volume for source/target varray comparison.
for (Volume cgVolume : activeCGVolumes) {
if (cgVolume.getPersonality() != null &&
cgVolume.getPersonality().equals(Volume.PersonalityTypes.SOURCE.toString())) {
existingRpSourceVolume = cgVolume;
break;
}
}
if (existingRpSourceVolume != null) {
VirtualPool existingVpool = _dbClient.queryObject(
VirtualPool.class, existingRpSourceVolume.getVirtualPool());
VirtualPool requestedVpool = _dbClient.queryObject(
VirtualPool.class, param.getVpool());
// The source virtual arrays must much
if (existingVpool.getVirtualArrays().size() != requestedVpool.getVirtualArrays().size()
|| !existingVpool.getVirtualArrays().containsAll(requestedVpool.getVirtualArrays())) {
// The source virtual arrays are not compatible with the CG
throw APIException.badRequests.vPoolSourceVarraysNotCompatibleForCG(consistencyGroup.getLabel());
}
// Validate that the request is not attempting to mix VPlex Metro volumes and
// MetroPoint volumes in the same CG.
if (VirtualPool.vPoolSpecifiesHighAvailability(existingVpool) &&
VirtualPool.vPoolSpecifiesHighAvailability(requestedVpool)) {
// If the requested and CG assigned virtual pools both specify VPlex, ensure
// that we are not trying to mix MetroPoint volumes with Metro volumes.
if ((!VirtualPool.vPoolSpecifiesMetroPoint(requestedVpool) &&
VirtualPool.vPoolSpecifiesMetroPoint(existingVpool)) ||
(VirtualPool.vPoolSpecifiesMetroPoint(requestedVpool) &&
!VirtualPool.vPoolSpecifiesMetroPoint(existingVpool))) {
throw APIException.badRequests.cannotMixMetroPointAndNonMetroPointVolumes(consistencyGroup.getLabel());
}
}
// Check the target virtual arrays
StringMap existingProtectionVarraySettings = existingVpool.getProtectionVarraySettings();
if (existingProtectionVarraySettings == null) {
// The existing CG source volume's protection settings are null. This can only happen when a
// swap is performed on the CG. This would have been a former target volume so its virtual
// pool references a target virtual pool, which has no protection settings defined. We do
// not support adding a volume to an existing CG whose volumes have been swapped.
// NOTE: This will be supported in the future through Jira CTRL-10129
throw APIException.badRequests.cannotAddVolumesToSwappedCG(consistencyGroup.getLabel());
}
StringMap requestedProtectionVarraySettings = requestedVpool.getProtectionVarraySettings();
if (existingProtectionVarraySettings.size() != requestedProtectionVarraySettings.size()) {
// The target virtual arrays are not compatible with the CG
throw APIException.badRequests.vPoolTargetVarraysNotCompatibleForCG(consistencyGroup.getLabel());
}
for (String targetVarray : requestedProtectionVarraySettings.keySet()) {
if (!existingProtectionVarraySettings.containsKey(targetVarray)) {
// The target virtual arrays are not compatible with the CG
throw APIException.badRequests.vPoolTargetVarraysNotCompatibleForCG(consistencyGroup.getLabel());
}
}
// Ensure the replication mode is logically equivalent
String requestedRpCopyMode = NullColumnValueGetter.isNullValue(requestedVpool.getRpCopyMode())
? RPCopyMode.ASYNCHRONOUS.name() : requestedVpool.getRpCopyMode();
String existingRpCopyMode = NullColumnValueGetter.isNullValue(existingVpool.getRpCopyMode())
? RPCopyMode.ASYNCHRONOUS.name() : existingVpool.getRpCopyMode();
if (!requestedRpCopyMode.equalsIgnoreCase(existingRpCopyMode)) {
throw APIException.badRequests.vPoolRPCopyModeNotCompatibleForCG(consistencyGroup.getLabel());
}
}
}
}
// Creating new volumes in 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.
if (!activeCGVolumes.isEmpty()) {
// Pass in an active CG volume for validation. If we are dealing with a RecoverPoint
// consistency group, we need to use an RP source volume. Otherwise we can use any arbitrary
// CG volume.
Volume activeCGVolume = existingRpSourceVolume == null ? activeCGVolumes.get(0) : existingRpSourceVolume;
if (!BlockServiceUtils.checkCGVolumeCanBeAddedOrRemoved(consistencyGroup, activeCGVolume, _dbClient)) {
checkCGForMirrors(consistencyGroup, activeCGVolumes);
checkCGForSnapshots(consistencyGroup);
getFullCopyManager().verifyNewVolumesCanBeCreatedInConsistencyGroup(consistencyGroup,
activeCGVolumes);
}
}
capabilities.put(VirtualPoolCapabilityValuesWrapper.BLOCK_CONSISTENCY_GROUP,
param.getConsistencyGroup());
} else if (VirtualPool.vPoolSpecifiesProtection(vpool)) {
// The consistency group param is null and RP protection has been specified. When RP
// protection is specified, a consistency group must be selected.
throw APIException.badRequests.consistencyGroupMissingForRpProtection();
}
// verify quota
long size = volumeCount * SizeUtil.translateSize(param.getSize());
TenantOrg tenant = _dbClient.queryObject(TenantOrg.class, project.getTenantOrg().getURI());
ArgValidator.checkEntity(tenant, project.getTenantOrg().getURI(), false);
CapacityUtils.validateQuotasForProvisioning(_dbClient, vpool, project, tenant, size, "volume");
// set compute param
URI computeURI = param.getComputeResource();
if (!NullColumnValueGetter.isNullURI(computeURI)) {
capabilities.put(VirtualPoolCapabilityValuesWrapper.COMPUTE, computeURI.toString());
}
// COP-14028
// Changing the return of a TaskList to return immediately while the underlying tasks are
// being built up. Steps:
// 1. Create a task object ahead of time and persist it for each requested volume.
// 2. Fire off a thread that does the placement and preparation of the volumes, which will use the pre-created
// task/volume objects during their source volume creations.
// 3. Return to the caller the new Task objects that is in the pending state.
String task = UUID.randomUUID().toString();
TaskList taskList = createVolumeTaskList(param.getSize(), project, varray, vpool, param.getName(), task, volumeCount);
// This is causing exceptions when run in the thread.
auditOp(OperationTypeEnum.CREATE_BLOCK_VOLUME, true, AuditLogManager.AUDITOP_BEGIN,
param.getName(), volumeCount, varray.getId().toString(), actualId.toString());
// call thread that does the work.
CreateVolumeSchedulingThread.executeApiTask(this, _asyncTaskService.getExecutorService(), _dbClient, varray,
project, vpool, capabilities, taskList, task,
consistencyGroup, requestedTypes, param, blockServiceImpl);
_log.info("Kicked off thread to perform placement and scheduling. Returning " + taskList.getTaskList().size() + " tasks");
return taskList;
}
/**
* Returns the types (RP, VPLEX, SRDF or LOCAL) that will be created based on the vpool
*
* @param vpool The vpool to find requested types
* @return All requested types for the vpool
*/
private ArrayList<String> getRequestedTypes(VirtualPool vpool) {
ArrayList<String> requestedTypes = new ArrayList<String>();
if (VirtualPool.vPoolSpecifiesProtection(vpool)) {
requestedTypes.add(Types.RP.name());
}
// Note that for ingested VPLEX CGs or CGs created in releases
// prior to 2.2, there will be no corresponding native
// consistency group. We don't necessarily want to fail
// volume creations in these CGs, so we don't require the
// LOCAL type.
if (VirtualPool.vPoolSpecifiesHighAvailability(vpool)) {
requestedTypes.add(Types.VPLEX.name());
}
if (VirtualPool.vPoolSpecifiesSRDF(vpool)) {
requestedTypes.add(Types.SRDF.name());
}
if (!VirtualPool.vPoolSpecifiesProtection(vpool)
&& !VirtualPool.vPoolSpecifiesHighAvailability(vpool)
&& !VirtualPool.vPoolSpecifiesSRDF(vpool)
&& vpool.getMultivolumeConsistency()) {
requestedTypes.add(Types.LOCAL.name());
}
return requestedTypes;
}
/**
* A method that pre-creates task and volume objects to return to the caller of the API.
*
* @param size
* size of the volume
* @param project
* project of the volume
* @param varray
* virtual array of the volume
* @param vpool
* virtual pool of the volume
* @param label
* label
* @param task
* task string
* @param volumeCount
* number of volumes requested
* @return a list of tasks associated with this request
*/
private TaskList createVolumeTaskList(String size, Project project, VirtualArray varray, VirtualPool vpool, String label, String task,
Integer volumeCount) {
TaskList taskList = new TaskList();
try {
// For each volume requested, pre-create a volume object/task object
long lsize = SizeUtil.translateSize(size);
for (int i = 0; i < volumeCount; i++) {
Volume volume = StorageScheduler.prepareEmptyVolume(_dbClient, lsize, project, varray, vpool, label, i, volumeCount);
Operation op = _dbClient.createTaskOpStatus(Volume.class, volume.getId(),
task, ResourceOperationTypeEnum.CREATE_BLOCK_VOLUME);
volume.getOpStatus().put(task, op);
TaskResourceRep volumeTask = toTask(volume, task, op);
taskList.getTaskList().add(volumeTask);
_log.info(String.format("Volume and Task Pre-creation Objects [Init]-- Source Volume: %s, Task: %s, Op: %s",
volume.getId(), volumeTask.getId(), task));
}
} catch (APIException ex) {
// Mark the dummy objects inactive
String errMsg = "Caught Exception while creating Volume and Task objects. Marking pre-created Objects inactive";
_log.error(errMsg, ex);
for (TaskResourceRep taskObj : taskList.getTaskList()) {
taskObj.setMessage(String.format("%s. %s", errMsg, ex.getMessage()));
taskObj.setState(Operation.Status.error.name());
URI volumeURI = taskObj.getResource().getId();
_dbClient.error(Volume.class, volumeURI, task, ex);
// Set the volumes to inactive
Volume volume = _dbClient.queryObject(Volume.class, volumeURI);
volume.setInactive(true);
_dbClient.updateObject(volume);
}
// throw the Exception to the caller
throw ex;
}
return taskList;
}
private boolean isAlphaNumeric(String consistencyGroupName) {
String pattern = "^[a-zA-Z0-9]*$";
if (consistencyGroupName.matches(pattern)) {
return true;
}
return false;
}
/**
* Checks existing CG for non-RP snapshots. If non-RP snapshots exist,
* we cannot create/add a volume to the CG.
*
* @param consistencyGroup
* the consistency group to validate.
*/
private void checkCGForSnapshots(BlockConsistencyGroup consistencyGroup) {
// If the Consistency Group has Snapshot(s), then Volume can not be created.
final URIQueryResultList cgSnapshotsResults = new URIQueryResultList();
_dbClient.queryByConstraint(getBlockSnapshotByConsistencyGroup(consistencyGroup.getId()),
cgSnapshotsResults);
Iterator<BlockSnapshot> blockSnapshotIterator = _dbClient.queryIterativeObjects(BlockSnapshot.class, cgSnapshotsResults);
while (blockSnapshotIterator.hasNext()) {
BlockSnapshot next = blockSnapshotIterator.next();
// RP BlockSnapshots do not prevent other volumes from being created/added to the
// consistency group.
if (!next.getTechnologyType().equalsIgnoreCase(TechnologyType.RP.name())) {
throw APIException.badRequests.cannotCreateVolumeAsConsistencyGroupHasSnapshots(consistencyGroup.getLabel(),
consistencyGroup.getId());
}
}
}
/**
* Verify that new volumes can be created in the passed consistency group.
*
* @param consistencyGroup
* A reference to the consistency group.
* @param cgVolumes
* The volumes in the consistency group.
*/
private void checkCGForMirrors(BlockConsistencyGroup consistencyGroup, List<Volume> cgVolumes) {
// If volumes in CG have mirrors, then new volume cannot be created.
for (Volume volume : cgVolumes) {
StringSet mirrors = volume.getMirrors();
if (mirrors != null && !mirrors.isEmpty()) {
throw APIException.badRequests.cannotCreateVolumeAsConsistencyGroupHasMirrors(consistencyGroup.getLabel(),
consistencyGroup.getId());
}
}
}
private void checkProjectsMatch(final URI expectedId, final URI actualId) {
final boolean condition = actualId.equals(expectedId);
if (!condition) {
throw APIException.badRequests.invalidProjectConflict(expectedId);
}
}
/**
* Returns the bean responsible for servicing the request
*
* @param vpool
* Virtual Pool
* @return block service implementation object
*/
private static BlockServiceApi getBlockServiceImpl(VirtualPool vpool, DbClient dbClient) {
// Mutually exclusive logic that selects an implementation of the block service
if (VirtualPool.vPoolSpecifiesProtection(vpool)) {
return getBlockServiceImpl(DiscoveredDataObject.Type.rp.name());
} else if (VirtualPool.vPoolSpecifiesHighAvailability(vpool)) {
return getBlockServiceImpl(DiscoveredDataObject.Type.vplex.name());
} else if (VirtualPool.vPoolSpecifiesSRDF(vpool)) {
return getBlockServiceImpl(DiscoveredDataObject.Type.srdf.name());
} else if (VirtualPool.vPoolSpecifiesMirrors(vpool, dbClient)) {
return getBlockServiceImpl("mirror");
} else if (vpool.getMultivolumeConsistency() != null && vpool.getMultivolumeConsistency()) {
return getBlockServiceImpl("group");
}
return getBlockServiceImpl("default");
}
/**
* Returns the bean responsible for servicing the request
*
* @param volume
* block volume
* @return block service implementation object
*/
private BlockServiceApi getBlockServiceImpl(Volume volume) {
return getBlockServiceImpl(volume, _dbClient);
}
/**
* Returns the bean responsible for servicing the request
*
* @param volume
* block volume
* @return block service implementation object
*/
public static BlockServiceApi getBlockServiceImpl(Volume volume, DbClient dbClient) {
// RP volumes may not be in an RP CoS (like after failover), so look to the volume properties
if (!isNullURI(volume.getProtectionController())
&& volume.checkForRp()) {
return getBlockServiceImpl(DiscoveredDataObject.Type.rp.name());
}
if (Volume.checkForSRDF(dbClient, volume.getId())) {
return getBlockServiceImpl(DiscoveredDataObject.Type.srdf.name());
}
// Otherwise the volume sent in is assigned to a virtual pool that tells us what block service to return
VirtualPool vPool = dbClient.queryObject(VirtualPool.class, volume.getVirtualPool());
// Mutually exclusive logic that selects an implementation of the block service
if (VirtualPool.vPoolSpecifiesHighAvailability(vPool)) {
return getBlockServiceImpl(DiscoveredDataObject.Type.vplex.name());
} else if (VirtualPool.vPoolSpecifiesSRDF(vPool)) {
return getBlockServiceImpl(DiscoveredDataObject.Type.srdf.name());
} else if (VirtualPool.vPoolSpecifiesMirrors(vPool, dbClient)) {
return getBlockServiceImpl("mirror");
} else if (vPool.getMultivolumeConsistency() != null && vPool.getMultivolumeConsistency()) {
return getBlockServiceImpl("group");
}
return getBlockServiceImpl("default");
}
/**
* Gets and verifies the VirtualPool passed in the request.
*
* @param project
* A reference to the project.
* @param param
* The volume create post data.
*
* @return A reference to the VirtualPool.
*/
private VirtualPool getVirtualPoolForVolumeCreateRequest(Project project, VolumeCreate param) {
ArgValidator.checkUri(param.getVpool());
VirtualPool cos = _dbClient.queryObject(VirtualPool.class, param.getVpool());
ArgValidator.checkEntity(cos, param.getVpool(), false);
if (!VirtualPool.Type.block.name().equals(cos.getType())) {
throw APIException.badRequests.virtualPoolNotForFileBlockStorage(VirtualPool.Type.block.name());
}
_permissionsHelper.checkTenantHasAccessToVirtualPool(project.getTenantOrg().getURI(), cos);
return cos;
}
/**
* Gets and verifies the consistency group passed in the request.
*
* @param consistencyGroupUri
* URI of the Consistency Group
*
* @return A reference to the BlockConsistencyGroup.
*/
private BlockConsistencyGroup queryConsistencyGroup(final URI consistencyGroupUri) {
ArgValidator.checkFieldNotNull(consistencyGroupUri, "consistency_group");
ArgValidator.checkUri(consistencyGroupUri);
final BlockConsistencyGroup consistencyGroup = _dbClient.queryObject(BlockConsistencyGroup.class, consistencyGroupUri);
ArgValidator.checkEntity(consistencyGroup, consistencyGroupUri, isIdEmbeddedInURL(consistencyGroupUri));
return consistencyGroup;
}
/**
* Request to expand volume capacity to the specified size.
*
* NOTE: This is an asynchronous operation.
*
*
* @prereq none
*
* @param id
* the URN of a ViPR storage volume.
* @param param
* Specifies requested size for volume expansion.
*
* @brief Expand volume capacity
* @return Task resource representation
*
* @throws InternalException
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/expand")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskResourceRep expandVolume(@PathParam("id") URI id, VolumeExpandParam param) throws InternalException {
// Get the volume.
ArgValidator.checkFieldUriType(id, Volume.class, "id");
Volume volume = queryVolumeResource(id);
// Verify that the volume is 'expandable'
VirtualPool virtualPool = _dbClient.queryObject(VirtualPool.class, volume.getVirtualPool());
if (!virtualPool.getExpandable()) {
throw APIException.badRequests.volumeNotExpandable(volume.getLabel());
}
// Don't operate on VPLEX backend or RP Journal volumes.
BlockServiceUtils.validateNotAnInternalBlockObject(volume, false);
// Don't operate on ingested volumes
VolumeIngestionUtil.checkOperationSupportedOnIngestedVolume(volume,
ResourceOperationTypeEnum.EXPAND_BLOCK_VOLUME, _dbClient);
// Verify if there are any restrictions on volume expansion for
// full copy volumes or full copy source volumes.
if (((BlockFullCopyUtils.isVolumeFullCopy(volume, _dbClient)) ||
(BlockFullCopyUtils.isVolumeFullCopySource(volume, _dbClient))) &&
(!getFullCopyManager().volumeCanBeExpanded(volume))) {
throw APIException.badRequests.fullCopyExpansionNotAllowed(volume.getLabel());
}
// Check for an SRDF volume with snapshots which cannot be expanded.
if (VirtualPool.vPoolSpecifiesSRDF(virtualPool)) {
validateExpandingSrdfVolume(volume);
}
// Make sure that we don't have some pending
// operation against the volume
checkForPendingTasks(Arrays.asList(volume.getTenant().getURI()), Arrays.asList(volume));
// Get the new size.
Long newSize = SizeUtil.translateSize(param.getNewSize());
// When newSize is the same as current size of the volume, this can be recovery attempt from failing previous
// expand to cleanup
// dangling meta members created for failed expansion
if (newSize.equals(volume.getCapacity()) && volume.getMetaVolumeMembers() != null && !(volume.getMetaVolumeMembers().isEmpty())) {
_log.info(String
.format(
"expandVolume --- Zero capacity expansion: allowed as a recovery to cleanup dangling members from previous expand failure.\n"
+
"VolumeId id: %s, Current size: %d, New size: %d, Dangling volumes: %s ",
id, volume.getCapacity(), newSize, volume.getMetaVolumeMembers()));
} else if (newSize <= volume.getCapacity()) {
_log.info(String.format(
"expandVolume: VolumeId id: %s, Current size: %d, New size: %d ", id, volume.getCapacity(), newSize));
throw APIException.badRequests.newSizeShouldBeLargerThanOldSize("volume");
}
_log.info(String.format(
"expandVolume --- VolumeId id: %s, Current size: %d, New size: %d", id,
volume.getCapacity(), newSize));
// Get the Block service implementation for this volume.
BlockServiceApi blockServiceApi = getBlockServiceImpl(volume);
// Verify that the volume can be expanded.
blockServiceApi.verifyVolumeExpansionRequest(volume, newSize);
// verify quota
if (newSize > volume.getProvisionedCapacity()) {
long size = newSize - volume.getProvisionedCapacity();
TenantOrg tenant = _dbClient.queryObject(TenantOrg.class, volume.getTenant().getURI());
ArgValidator.checkEntity(tenant, volume.getTenant().getURI(), false);
Project project = _dbClient.queryObject(Project.class, volume.getProject().getURI());
ArgValidator.checkEntity(project, volume.getProject().getURI(), false);
VirtualPool cos = _dbClient.queryObject(VirtualPool.class, volume.getVirtualPool());
ArgValidator.checkEntity(cos, volume.getVirtualPool(), false);
CapacityUtils.validateQuotasForProvisioning(_dbClient, cos, project, tenant, size, "volume");
}
// Create a task for the volume expansion.
String taskId = UUID.randomUUID().toString();
Operation op = _dbClient.createTaskOpStatus(Volume.class, volume.getId(),
taskId, ResourceOperationTypeEnum.EXPAND_BLOCK_VOLUME);
// Try and expand the volume.
try {
blockServiceApi.expandVolume(volume, newSize, taskId);
} catch (final ControllerException e) {
_log.error("Controller Error", e);
String errMsg = String.format("Controller Error: %s", e.getMessage());
op = new Operation(Operation.Status.error.name(), errMsg);
_dbClient.updateTaskOpStatus(Volume.class, id, taskId, op);
throw e;
}
auditOp(OperationTypeEnum.EXPAND_BLOCK_VOLUME, true, AuditLogManager.AUDITOP_BEGIN,
volume.getId().toString(), volume.getCapacity(), newSize);
return toTask(volume, taskId, op);
}
/**
* Request to test failover of the protection link associated with the param.copyID.
*
* NOTE: This is an asynchronous operation.
*
* If volume is srdf protected, then invoking failover-test ends in no-op.
* failoverTest is being replaced by failover
*
* @prereq none
*
* @param id
* the URN of a ViPR Source volume
* @param param
* Copy to test failover on
*
* @brief Test volume protection link failover
* @return TaskList
*
* @throws ControllerException
*
* @deprecated failoverTest is being replaced by failover.
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/continuous-copies/failover-test")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
@Deprecated
public TaskList failoverTest(@PathParam("id") URI id, CopiesParam param) throws ControllerException {
TaskResourceRep taskResp = null;
TaskList taskList = new TaskList();
// Validate the source volume URI
ArgValidator.checkFieldUriType(id, Volume.class, "id");
boolean vplexVolume = checkIfVolumeIsForVplex(id);
// Validate the list of copies
ArgValidator.checkFieldNotEmpty(param.getCopies(), "copies");
List<Copy> copies = param.getCopies();
if (copies.size() != 1) {
throw APIException.badRequests.failoverCopiesParamCanOnlyBeOne();
}
Copy copy = copies.get(0);
if (vplexVolume && copy.getType().equalsIgnoreCase(TechnologyType.NATIVE.toString())) {
throw APIException.badRequests.actionNotApplicableForVplexVolumeMirrors(ProtectionOp.FAILOVER_TEST.getRestOp());
}
ArgValidator.checkFieldUriType(copy.getCopyID(), Volume.class, "id");
ArgValidator.checkFieldNotEmpty(copy.getType(), "type");
if (copy.getType().equalsIgnoreCase(TechnologyType.RP.toString())) {
taskResp = performProtectionAction(id, copy, ProtectionOp.FAILOVER_TEST.getRestOp());
taskList.getTaskList().add(taskResp);
} else if (copy.getType().equalsIgnoreCase(TechnologyType.SRDF.toString())) {
id = VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, id);
copy.setCopyID(VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, copy.getCopyID()));
taskResp = performSRDFProtectionAction(id, copy, ProtectionOp.FAILOVER_TEST.getRestOp());
taskList.getTaskList().add(taskResp);
} else {
throw APIException.badRequests.invalidCopyType(copy.getType());
}
return taskList;
}
/**
* Request to reverse the replication direction, i.e. R1 and R2 are interchanged..
*
* @prereq none
*
* @param id
* the URN of a ViPR Source volume
* @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, Volume.class, "id");
// Validate the list of copies
ArgValidator.checkFieldNotEmpty(param.getCopies(), "copies");
List<Copy> copies = param.getCopies();
if (copies.size() > 1) {
throw APIException.badRequests.swapCopiesParamCanOnlyBeOne();
}
Copy copy = copies.get(0);
ArgValidator.checkFieldUriType(copy.getCopyID(), Volume.class, "id");
ArgValidator.checkFieldNotEmpty(copy.getType(), "type");
if (copy.getType().equalsIgnoreCase(TechnologyType.RP.toString())) {
taskResp = performProtectionAction(id, copy, ProtectionOp.SWAP.getRestOp());
taskList.getTaskList().add(taskResp);
} else if (copy.getType().equalsIgnoreCase(TechnologyType.SRDF.toString())) {
id = VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, id);
copy.setCopyID(VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, copy.getCopyID()));
taskResp = performSRDFProtectionAction(id, copy, ProtectionOp.SWAP.getRestOp());
taskList.getTaskList().add(taskResp);
} else {
throw APIException.badRequests.invalidCopyType(copy.getType());
}
return taskList;
}
/**
* Request to cancel fail over on already failed over volumes.
*
* @prereq none
*
* @param id
* the URN of a ViPR Source volume
* @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, Volume.class, "id");
// Validate the list of copies
ArgValidator.checkFieldNotEmpty(param.getCopies(), "copies");
List<Copy> copies = param.getCopies();
if (copies.size() > 1) {
throw APIException.badRequests.failOverCancelCopiesParamCanOnlyBeOne();
}
Copy copy = copies.get(0);
ArgValidator.checkFieldUriType(copy.getCopyID(), Volume.class, "id");
ArgValidator.checkFieldNotEmpty(copy.getType(), "type");
if (copy.getType().equalsIgnoreCase(TechnologyType.RP.toString())) {
taskResp = performProtectionAction(id, copy, ProtectionOp.FAILOVER_CANCEL.getRestOp());
taskList.getTaskList().add(taskResp);
} else if (copy.getType().equalsIgnoreCase(TechnologyType.SRDF.toString())) {
id = VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, id);
copy.setCopyID(VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, copy.getCopyID()));
taskResp = performSRDFProtectionAction(id, copy, ProtectionOp.FAILOVER_CANCEL.getRestOp());
taskList.getTaskList().add(taskResp);
} else {
throw APIException.badRequests.invalidCopyType(copy.getType());
}
return taskList;
}
/**
* Request to cancel a prior test failover of the protection link associated with the param.copyID.
*
* NOTE: This is an asynchronous operation.
*
* If volume is srdf protected, then its a no-op
* <p>
* This method is deprecated. Use /block/volumes/{id}/protection/continuous-copies/failover-cancel instead.
*
* @prereq none
*
* @param id
* the URN of a ViPR Source volume
* @param param
* Copy to cancel the failover to
*
* @brief Cancel volume protection link failover test
* @return TaskList
*
* @throws ControllerException
*
* @deprecated failoverTestCancel is being replaced by failover-cancel.
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/continuous-copies/failover-test-cancel")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
@Deprecated
public TaskList failoverTestCancel(@PathParam("id") URI id, CopiesParam param) throws ControllerException {
TaskResourceRep taskResp = null;
TaskList taskList = new TaskList();
// Validate the source volume URI
ArgValidator.checkFieldUriType(id, Volume.class, "id");
boolean vplexVolume = checkIfVolumeIsForVplex(id);
// Validate the list of copies
ArgValidator.checkFieldNotEmpty(param.getCopies(), "copies");
List<Copy> copies = param.getCopies();
if (copies.size() != 1) {
throw APIException.badRequests.failoverCopiesParamCanOnlyBeOne();
}
Copy copy = copies.get(0);
if (vplexVolume && copy.getType().equalsIgnoreCase(TechnologyType.NATIVE.toString())) {
throw APIException.badRequests.actionNotApplicableForVplexVolumeMirrors(ProtectionOp.FAILOVER_TEST_CANCEL.getRestOp());
}
ArgValidator.checkFieldUriType(copy.getCopyID(), Volume.class, "id");
ArgValidator.checkFieldNotEmpty(copy.getType(), "type");
if (copy.getType().equalsIgnoreCase(TechnologyType.RP.toString())) {
taskResp = performProtectionAction(id, copy, ProtectionOp.FAILOVER_TEST_CANCEL.getRestOp());
taskList.getTaskList().add(taskResp);
} else if (copy.getType().equalsIgnoreCase(TechnologyType.SRDF.toString())) {
id = VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, id);
copy.setCopyID(VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, copy.getCopyID()));
taskResp = performSRDFProtectionAction(id, copy, ProtectionOp.FAILOVER_TEST_CANCEL.getRestOp());
taskList.getTaskList().add(taskResp);
} else {
throw APIException.badRequests.invalidCopyType(copy.getType());
}
return taskList;
}
/**
*
* Request to failover the protection link associated with the 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 URN of a ViPR Source volume
* @param param
* Copy to failover to
*
* @brief Failover the volume 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 source volume URI
ArgValidator.checkFieldUriType(id, Volume.class, "id");
boolean vplexVolume = checkIfVolumeIsForVplex(id);
// Validate the list of copies
ArgValidator.checkFieldNotEmpty(param.getCopies(), "copies");
List<Copy> copies = param.getCopies();
if (copies.size() != 1) {
throw APIException.badRequests.failoverCopiesParamCanOnlyBeOne();
}
Copy copy = copies.get(0);
if (vplexVolume && copy.getType().equalsIgnoreCase(TechnologyType.NATIVE.toString())) {
throw APIException.badRequests.actionNotApplicableForVplexVolumeMirrors(ProtectionOp.FAILOVER.getRestOp());
}
ArgValidator.checkFieldNotEmpty(copy.getType(), "type");
if (copy.getType().equalsIgnoreCase(TechnologyType.RP.toString())) {
taskResp = performProtectionAction(id, copy, ProtectionOp.FAILOVER.getRestOp());
taskList.getTaskList().add(taskResp);
} else if (copy.getType().equalsIgnoreCase(TechnologyType.SRDF.toString())) {
id = VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, id);
copy.setCopyID(VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, copy.getCopyID()));
taskResp = performSRDFProtectionAction(id, copy, ProtectionOp.FAILOVER.getRestOp());
taskList.getTaskList().add(taskResp);
} else {
throw APIException.badRequests.invalidCopyType(copy.getType());
}
return taskList;
}
/**
*
* Sync continuous copies.
*
*
* @prereq none
*
* @param id
* the URI of a ViPR Source volume
* @param param
* List of copies to sync
*
* @brief Sync continuous copies.
* @return TaskList
*
* @throws ControllerException
*
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/continuous-copies/sync")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskList syncContinuousCopies(@PathParam("id") URI id, CopiesParam param)
throws ControllerException {
TaskResourceRep taskResp = null;
TaskList taskList = new TaskList();
// Validate the source volume URI
ArgValidator.checkFieldUriType(id, Volume.class, "id");
// Validate the list of copies
ArgValidator.checkFieldNotEmpty(param.getCopies(), "copies");
// Verify that the copy IDs are either all specified or none are specified
// for a particular protection type. Combinations are not allowed
verifyCopyIDs(param);
// Process the list of copies
for (Copy copy : param.getCopies()) {
// Validate a copy type was passed
ArgValidator.checkFieldNotEmpty(copy.getType(), "type");
// If copyID is null all copies are paused
if (copy.getType().equalsIgnoreCase(TechnologyType.RP.toString())) {
// If copyID is not set all copies are sync'd
URI copyID = copy.getCopyID();
if (!URIUtil.isValid(copyID)) {
copyID = null;
}
taskResp = performProtectionAction(id, copy, ProtectionOp.SYNC.getRestOp());
taskList.getTaskList().add(taskResp);
// If copyID is null, we have already synced all copies
if (copyID == null) {
return taskList;
}
} else if (copy.getType().equalsIgnoreCase(TechnologyType.SRDF.toString())) {
id = VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, id);
copy.setCopyID(VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, copy.getCopyID()));
taskResp = performSRDFProtectionAction(id, copy, ProtectionOp.SYNC.getRestOp());
taskList.getTaskList().add(taskResp);
} else {
throw APIException.badRequests.invalidCopyType(copy.getType());
}
}
return taskList;
}
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/continuous-copies/copymode")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskList changeCopyMode(@PathParam("id") URI id, CopiesParam param)
throws ControllerException {
TaskResourceRep taskResp = null;
TaskList taskList = new TaskList();
// Validate the source volume URI
ArgValidator.checkFieldUriType(id, Volume.class, "id");
// Validate the list of copies
ArgValidator.checkFieldNotEmpty(param.getCopies(), "copies");
// Verify that the copy IDs are either all specified or none are specified
// for a particular protection type. Combinations are not allowed
verifyCopyIDs(param);
// Process the list of copies
for (Copy copy : param.getCopies()) {
// Validate a copy type was passed
ArgValidator.checkFieldNotEmpty(copy.getType(), "type");
String copyMode = copy.getCopyMode();
// Validate a copy mode was passed
ArgValidator.checkFieldNotEmpty(copyMode, "copyMode");
Volume volume = queryVolumeResource(id);
ArgValidator.checkEntity(volume, id, true);
if (volume.hasConsistencyGroup()) {
if (TechnologyType.SRDF.name().equalsIgnoreCase(copy.getType())) {
id = VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, id);
copy.setCopyID(VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, copy.getCopyID()));
if (RemoteDirectorGroup.SupportedCopyModes.ASYNCHRONOUS.name().equalsIgnoreCase(copyMode)
|| RemoteDirectorGroup.SupportedCopyModes.SYNCHRONOUS.name().equalsIgnoreCase(copyMode)
|| RemoteDirectorGroup.SupportedCopyModes.ADAPTIVECOPY.name().equalsIgnoreCase(copyMode)) {
taskResp = performSRDFProtectionAction(id, copy, ProtectionOp.CHANGE_COPY_MODE.getRestOp());
taskList.getTaskList().add(taskResp);
} else {
throw APIException.badRequests.invalidSRDFCopyMode(copy.getType());
}
} else {
throw APIException.badRequests.invalidCopyType(copy.getType());
}
} else {
/**
* As of now ViPR supports change copy mode operations only for volumes with CG.
*/
throw APIException.badRequests.invalidSRDFCopyMode(volume.getNativeId());
}
}
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 source volume URI
ArgValidator.checkFieldUriType(id, Volume.class, "id");
boolean vplexVolume = checkIfVolumeIsForVplex(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);
if (vplexVolume && copy.getType().equalsIgnoreCase(TechnologyType.NATIVE.toString())) {
throw APIException.badRequests.actionNotApplicableForVplexVolumeMirrors(ProtectionOp.CHANGE_ACCESS_MODE.getRestOp());
}
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;
}
/**
* Get the details of a specific volume
*
*
* @prereq none
*
* @param id
* the URN of a ViPR volume to query
*
* @brief Show volume
* @return Volume details
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public VolumeRestRep getVolume(@PathParam("id") URI id) {
// TODO queryResource permits queries for Volume and BlockMirror types - is this validation too restrictive?
Class<? extends DataObject> type = Volume.class;
if (URIUtil.isType(id, BlockMirror.class)) {
type = BlockMirror.class;
}
ArgValidator.checkFieldUriType(id, type, "id");
Volume volume = queryVolumeResource(id);
return map(_dbClient, volume);
}
/**
* Get the storage pool of a specific volume
*
*
* @prereq none
*
* @param id
* the URN of a ViPR volume to query
*
* @brief Show volume storage pool
* @return Storage pool details
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/storage-pool")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.SYSTEM_ADMIN })
public RelatedStoragePool getStoragePool(@PathParam("id") URI id) {
ArgValidator.checkFieldUriType(id, Volume.class, "id");
Volume volume = queryVolumeResource(id);
StoragePool pool = _dbClient.queryObject(StoragePool.class, volume.getPool());
if (pool != null) {
return new RelatedStoragePool(
toNamedRelatedResource(ResourceTypeEnum.STORAGE_POOL,
volume.getPool(), pool.getPoolName()));
} else {
// the related storage pool may be null in the case of a virtual volume
// so, return an empty related resource
return RelatedStoragePool.EMPTY;
}
}
@Override
protected DataObject queryResource(URI id) {
ArgValidator.checkUri(id);
Class<? extends DataObject> blockClazz = Volume.class;
if (URIUtil.isType(id, BlockMirror.class)) {
blockClazz = BlockMirror.class;
}
if (URIUtil.isType(id, VplexMirror.class)) {
blockClazz = VplexMirror.class;
}
DataObject dataObject = _permissionsHelper.getObjectById(id, blockClazz);
ArgValidator.checkEntityNotNull(dataObject, id, isIdEmbeddedInURL(id));
return dataObject;
}
private Volume queryVolumeResource(URI id) {
return (Volume) queryResource(id);
}
@Override
protected URI getTenantOwner(URI id) {
Volume volume = (Volume) queryResource(id);
return volume.getTenant().getURI();
}
/**
* Return all the export information related to this volume.
* This will be in the form of a list of initiator / target pairs
* for all the initiators that have been paired with a target
* storage port.
*
*
* @prereq none
*
* @param id
* the URN of a ViPR Volume
*
* @brief Show export information for volume
* @return List of exports
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/exports")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public ITLRestRepList getVolumeExports(@PathParam("id") URI id) {
queryResource(id);
return ExportUtils.getBlockObjectInitiatorTargets(id, _dbClient, isIdEmbeddedInURL(id));
}
/**
* Deactivate a volume, will result in permanent deletion of the requested volume(s) from the storage system it was created on and will
* move the volume to a "marked-for-delete" state after the deletion happens on the array side. The volume will be
* deleted from the database when all references to this volume of type BlockSnapshot and ExportGroup are deleted.
*
* If "?force=true" is added to the path, it will force the delete of internal
* volumes that have the SUPPORTS_FORCE flag.
*
* If "?type=VIPR_ONLY" is added to the path, it will delete volumes only from ViPR data base and leaves the volume on storage array as
* it is.
* Possible value for the attribute type : FULL, VIPR_ONLY
* FULL : Deletes the volumes permanently on array and ViPR data base.
* VIPR_ONLY : Deletes the volumes only from ViPR data base and leaves the volumes on array as it is.
*
* NOTE: This is an asynchronous operation.
*
*
* @prereq Dependent volume resources such as snapshots and export groups must be deleted
*
* @param id
* the URN of a ViPR volume to delete
* @param force {@link DefaultValue} false
* @param type {@link DefaultValue} FULL
*
* @brief Delete volume
* @return Volume information
*
* @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 deleteVolume(@PathParam("id") URI id,
@DefaultValue("false") @QueryParam("force") boolean force,
@DefaultValue("FULL") @QueryParam("type") String type)
throws InternalException {
// Reuse implementation for deleting multiple volumes.
BulkDeleteParam deleteParam = new BulkDeleteParam();
deleteParam.setIds(Lists.newArrayList(id));
TaskList taskList = deleteVolumes(deleteParam, force, type);
return taskList.getTaskList().get(0);
}
/**
* This API allows the user to deactivate multiple volumes in a single request.
* There is no restriction on the the volumes specified in the request. The volumes
* can reside in multiple storage pools on multiple storage systems. The
* response will contain a task resource for each volume to be
* deactivated. The volumes will be deleted from the database when
* all references to the volumes of type BlockSnapshot and
* ExportGroup are deleted.
*
* If "?force=true" is added to the path, it will force the delete of internal
* volumes that have the SUPPORTS_FORCE flag.
*
* If "?type=VIPR_ONLY" is added to the path, it will delete volumes only from ViPR data base and leaves the volume on storage array as
* it is.
* Possible value for the attribute type : FULL, VIPR_ONLY
* FULL : Deletes the volumes permanently on array and ViPR data base.
* VIPR_ONLY : Deletes the volumes only from ViPR data base and leaves the volumes on array as it is.
*
*
* NOTE: This is an asynchronous operation.
*
*
* @prereq Dependent volume resources such as snapshots and export groups must be deleted
*
* @param volumeURIs
* The POST data specifying the ids of the volume(s) to be
* deleted.
* @param force {@link DefaultValue} false
* @param type {@link DefaultValue} FULL
*
* @brief Delete multiple volumes
* @return A reference to a BlockTaskList containing a list of
* TaskResourceRep instances specifying the task data for each
* volume delete task.
* @throws InternalException
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/deactivate")
public TaskList deleteVolumes(BulkDeleteParam volumeURIs,
@DefaultValue("false") @QueryParam("force") boolean force,
@DefaultValue("FULL") @QueryParam("type") String type) throws InternalException {
// Verify the some volumes were passed in the request.
BlockService.checkVolumesParameter(volumeURIs);
// For volume operations, user need to has TENANT_ADMIN role or proper ACLs etc.
StorageOSUser user = getUserFromContext();
Iterator<Volume> dbVolumeIter = _dbClient.queryIterativeObjects(getResourceClass(), volumeURIs.getIds());
Set<URI> tenantSet = new HashSet<>();
List<Volume> volumes = new ArrayList<Volume>();
while (dbVolumeIter.hasNext()) {
Volume vol = dbVolumeIter.next();
// Don't operate on VPLEX backend or RP Journal volumes (unless forced to).
BlockServiceUtils.validateNotAnInternalBlockObject(vol, force);
// Don't operate on volumes with boot volume tags (unless forced to).
BlockServiceUtils.validateNotABootVolume(vol, force);
if (!_permissionsHelper.userHasGivenRole(user, vol.getTenant().getURI(), Role.TENANT_ADMIN)
&& !_permissionsHelper.userHasGivenACL(user, vol.getProject().getURI(), ACL.OWN, ACL.ALL)) {
throw APIException.forbidden.insufficientPermissionsForUser(user.getName());
}
tenantSet.add(vol.getTenant().getURI());
volumes.add(vol);
}
// Make sure that we don't have some pending
// operation against the volume
if (!force) {
checkForPendingTasks(tenantSet, volumes);
}
// Volumes on different storage systems need to be deleted with
// separate, individual calls to the controller. Therefore, we
// need to map the volumes passed to the storage systems on
// which they reside.
Map<URI, List<URI>> systemVolumesMap = new HashMap<URI, List<URI>>();
// We will create a task resource response for each volume to
// be deleted and initialize it to the pending state. If there
// is a controller error deleting the volumes on a given storage
// system, we need to update the responses associated with these
// volumes to specify an error state.
Map<URI, List<TaskResourceRep>> systemTaskResourceRepsMap = new HashMap<URI, List<TaskResourceRep>>();
// We create a global task list containing the task resource response
// for all volumes, which will be returned as the request response.
TaskList taskList = new TaskList();
// CTRL-8839
// Initial validations for the volumes. We want to make sure the basic validations
// are done before we start creating any Tasks. Otherwise, we will get Tasks that
// seem to be "stuck".
for (Volume volume : volumes) {
URI volumeURI = volume.getId();
ArgValidator.checkEntity(volume, volumeURI, isIdEmbeddedInURL(volumeURI));
BlockServiceApi blockServiceApi = getBlockServiceImpl(volume);
/**
* Delete volume api call will delete the replica objects as part of volume delete call for vmax using SMI
* 8.0.3.
* Hence we don't require reference check for vmax.
*/
if ((VolumeDeleteTypeEnum.VIPR_ONLY.name().equals(type)) || (!volume.isInCG())
|| (!BlockServiceUtils.checkCGVolumeCanBeAddedOrRemoved(null, volume, _dbClient))) {
List<Class<? extends DataObject>> excludeTypes = null;
if (VolumeDeleteTypeEnum.VIPR_ONLY.name().equals(type)) {
// For ViPR-only delete of exported volumes, We will clean up any
// export groups/masks if the snapshot is exported.
excludeTypes = new ArrayList<>();
excludeTypes.add(ExportGroup.class);
excludeTypes.add(ExportMask.class);
}
// If we are deleting a boot volume, there may still be a reference to the volume
// in the decommissioned host. Since the force flag is used, we will clear out this
// reference in the host so the volume can be deleted.
List<Host> hosts = CustomQueryUtility.queryActiveResourcesByRelation(_dbClient, volume.getId(),
Host.class, "bootVolumeId");
if (hosts != null) {
for (Host host : hosts) {
host.setBootVolumeId(NullColumnValueGetter.getNullURI());
_log.info("Removing boot volume ID from host: {}", host.forDisplay());
_dbClient.updateObject(host);
}
}
ArgValidator.checkReference(Volume.class, volumeURI, blockServiceApi.checkForDelete(volume, excludeTypes));
}
// For a volume that is a full copy or is the source volume for
// full copies deleting the volume may not be allowed.
if ((!VolumeDeleteTypeEnum.VIPR_ONLY.name().equals(type)) && (!getFullCopyManager().volumeCanBeDeleted(volume))) {
throw APIException.badRequests.cantDeleteFullCopyNotDetached(volume.getLabel());
}
}
// since we issue one controller request per storage system, we must give each storage system
// a separate task id. Otherwise, we will create multiple workflows with the same task id
// which is not allowed.
// this maps task ids to their storage systems
Map<URI, String> systemURITaskIdMap = new HashMap<URI, String>();
// Now loop over the volumes, initializing the above constructs.
for (Volume volume : volumes) {
URI volumeURI = volume.getId();
// If the volume has active associated volumes, try to deactivate regardless
// of native ID or inactive state. This basically means it's a VPLEX volume.
boolean forceDeactivate = checkIfVplexVolumeHasActiveAssociatedVolumes(volume);
if (forceDeactivate || (!Strings.isNullOrEmpty(volume.getNativeId()) && !volume.getInactive())) {
URI systemURI = null;
if (!isNullURI(volume.getProtectionController())) {
systemURI = volume.getProtectionController();
} else {
systemURI = volume.getStorageController();
}
if (systemURITaskIdMap.get(systemURI) == null) {
systemURITaskIdMap.put(systemURI, UUID.randomUUID().toString());
}
String task = systemURITaskIdMap.get(systemURI);
// Create a task resource response for this volume and
// set the initial task state to pending.
// Initialize volume delete task status.
Operation op = _dbClient.createTaskOpStatus(Volume.class, volume.getId(),
task, ResourceOperationTypeEnum.DELETE_BLOCK_VOLUME);
TaskResourceRep volumeTaskResourceRep = toTask(volume, task, op);
List<URI> systemVolumeURIs = systemVolumesMap.get(systemURI);
if (systemVolumeURIs == null) {
// Create a list to hold the volumes for the
// system, add the volume to the list, and put
// the list in the system volumes map.
systemVolumeURIs = new ArrayList<URI>();
systemVolumeURIs.add(volumeURI);
systemVolumesMap.put(systemURI, systemVolumeURIs);
// Build a list to hold the task resource responses for
// the system. Create a task resource response for
// this volume and add it to the list for the system.
// Put the list for the system into the map.
List<TaskResourceRep> systemTaskResourceReps = new ArrayList<TaskResourceRep>();
systemTaskResourceReps.add(volumeTaskResourceRep);
systemTaskResourceRepsMap.put(systemURI, systemTaskResourceReps);
} else if (!systemVolumeURIs.contains(volumeURI)) {
// Add the volume to the system's volume list if it has
// not already been added. Duplicates are just ignored.
systemVolumeURIs.add(volumeURI);
List<TaskResourceRep> systemTaskResourceReps = systemTaskResourceRepsMap
.get(systemURI);
systemTaskResourceReps.add(volumeTaskResourceRep);
}
// Add the task resource response for the volume to the global list
// to be returned.
taskList.getTaskList().add(volumeTaskResourceRep);
} else if (!volume.getInactive()) {
// somehow no nativeId is set on volume, but it was active. Set it to not active
volume.setInactive(true);
_dbClient.persistObject(volume);
}
}
// Try and delete the volumes on each system.
Iterator<URI> systemsURIIter = systemVolumesMap.keySet().iterator();
while (systemsURIIter.hasNext()) {
URI systemURI = systemsURIIter.next();
String task = systemURITaskIdMap.get(systemURI);
try {
List<URI> systemVolumes = systemVolumesMap.get(systemURI);
BlockServiceApi blockServiceApi = getBlockServiceImpl(queryVolumeResource(systemVolumes.get(0)));
blockServiceApi.deleteVolumes(systemURI, systemVolumes, type, task);
} catch (APIException | InternalException e) {
if (_log.isErrorEnabled()) {
_log.error("Delete error", e);
}
List<TaskResourceRep> systemTaskResourceReps = systemTaskResourceRepsMap.get(systemURI);
for (TaskResourceRep volumeTask : systemTaskResourceReps) {
volumeTask.setState(Operation.Status.error.name());
volumeTask.setMessage(e.getMessage());
_dbClient.updateTaskOpStatus(Volume.class, volumeTask
.getResource().getId(), task, new Operation(
Operation.Status.error.name(), e.getMessage()));
}
}
}
auditOp(OperationTypeEnum.DELETE_BLOCK_VOLUME, true, AuditLogManager.AUDITOP_MULTI_BEGIN);
return taskList;
}
/**
* This method is used during delete volume to check if a volume has active associated
* volumes with nativeId.
*
* TODO : This method can be moved to some utility class post 2.0, once we figure out
* which class is suitable for this.
*
* @param volume
* A reference to the volume.
* @return true if volume has active associated volumes with nativeId else returns false
*/
private boolean checkIfVplexVolumeHasActiveAssociatedVolumes(Volume volume) {
boolean activeAssociatedVolumes = false;
if (volume != null && volume.getAssociatedVolumes() != null) {
for (String associatedVolumeUri : volume.getAssociatedVolumes()) {
Volume associatedVolume = _dbClient.queryObject(Volume.class, URI.create(associatedVolumeUri));
if (associatedVolume != null && !associatedVolume.getInactive()) {
_log.warn("volume {} has active associated volume {}",
volume.getLabel(), associatedVolume.getLabel());
if (associatedVolume.getNativeId() == null) {
_log.warn("associated volume with Id {} has no native Id. Native Id is {}. So mark this volume for deletion. ",
associatedVolume.getId(), associatedVolume.getNativeId());
_dbClient.markForDeletion(associatedVolume);
} else {
activeAssociatedVolumes = true;
}
}
}
}
return activeAssociatedVolumes;
}
/**
* A snapshot is a point-in-time copy of a volume. Snapshots are intended for short-term
* operational recovery and are typically implemented using lightweight, fast capabilities
* native to the underlying storage platforms.
* Like a volume, a snapshot can be exported to initiators, and you can delete it.
* A snapshots lifetime is tied to the original volume. When the original volume is deleted
* all of its snapshots will also be deleted.
* A snapshot is associated with the same project as the original volume.
* A volume may be restored in place based on a snapshot. The snapshot must have come from the volume.
* A new volume may be created using a snapshot as a template.
*
* See multi-volume consistent snapshots for a description of an advanced feature to snapshot multiple volumes at
* once.
*
* NOTE: This is an asynchronous operation.
*
*
* @prereq Virtual pool must specify non-zero value for max_snapshots
*
* @param id
* the URN of a ViPR Volume to snapshot
* @param param
* Volume snapshot parameters
*
* @brief Create volume snapshot
* @return List of snapshots information
*
* @throws InternalException
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshots")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList createSnapshot(@PathParam("id") URI id, VolumeSnapshotParam param) throws InternalException {
// Validate and get the volume being snapped.
ArgValidator.checkFieldUriType(id, Volume.class, "id");
Volume requestedVolume = queryVolumeResource(id);
VolumeIngestionUtil.checkOperationSupportedOnIngestedVolume(requestedVolume,
ResourceOperationTypeEnum.CREATE_VOLUME_SNAPSHOT, _dbClient);
// Don't operate on VPLEX backend volumes or RP journal volumes.
BlockServiceUtils.validateNotAnInternalBlockObject(requestedVolume, false);
// Set default type, if not set at all.
if (param.getType() == null) {
param.setType(TechnologyType.NATIVE.toString());
}
String snapshotType = param.getType();
validateSourceVolumeHasExported(requestedVolume);
// Make sure that we don't have some pending
// operation against the volume
checkForPendingTasks(Arrays.asList(requestedVolume.getTenant().getURI()), Arrays.asList(requestedVolume));
// validate the volume is not part of a RP or VPlex CG that is part of an application
validateCGIsNotInApplication(requestedVolume, snapshotType);
// Set whether or not the snapshot be activated when created.
Boolean createInactive = Boolean.FALSE;
if (param.getCreateInactive() != null) {
createInactive = param.getCreateInactive();
}
// Set whether the snapshot should be read only
Boolean readOnly = Boolean.FALSE;
if (param.getReadOnly() != null) {
readOnly = param.getReadOnly();
}
// Get the block service implementation for the volume. The manner
// in which snapshots are created an initialized can be different
// based on the volume being snapped.
BlockServiceApi blockServiceApiImpl = getBlockServiceImpl(requestedVolume);
// Determine the list of volumes to be snapped. If the volume
// is in a consistency group, a snapshot will be created for
// each volume in the CG.
List<Volume> volumesToSnap = new ArrayList<Volume>();
volumesToSnap.addAll(blockServiceApiImpl.getVolumesToSnap(requestedVolume, snapshotType));
// Validate the snapshot creation request parameters for the volume(s)
// to be snapped.
String snapshotNamePattern = param.getName();
String snapshotName = TimeUtils.formatDateForCurrent(snapshotNamePattern);
blockServiceApiImpl.validateCreateSnapshot(requestedVolume, volumesToSnap,
snapshotType, snapshotName, readOnly, getFullCopyManager());
// Create the snapshots for the volume(s) being snapped and
// initialize the task list.
String taskId = UUID.randomUUID().toString();
List<URI> snapshotURIs = new ArrayList<URI>();
List<BlockSnapshot> snapshots = blockServiceApiImpl.prepareSnapshots(
volumesToSnap, snapshotType, snapshotName, snapshotURIs, taskId);
TaskList response = new TaskList();
for (BlockSnapshot snapshot : snapshots) {
response.getTaskList().add(toTask(snapshot, taskId));
}
// Update the task status for the volumes task.
_dbClient.createTaskOpStatus(Volume.class, requestedVolume.getId(),
taskId, ResourceOperationTypeEnum.CREATE_VOLUME_SNAPSHOT);
// Invoke the block service API implementation to create the snapshot
blockServiceApiImpl.createSnapshot(requestedVolume, snapshotURIs, snapshotType,
createInactive, readOnly, taskId);
// Record a message in the audit log.
auditOp(OperationTypeEnum.CREATE_VOLUME_SNAPSHOT, true,
AuditLogManager.AUDITOP_BEGIN, snapshotName, requestedVolume.getId()
.toString());
return response;
}
/**
* validates that the volume is not part of a RP or VPlex CG that is part of an application
*
* @param requestedVolume
* @param snapshotType
* indicates if this is an array snapshot or RP bookmark request
*/
private void validateCGIsNotInApplication(Volume requestedVolume, String snapshotType) {
// validation should only apply to non-RP snapshots
if (TechnologyType.RP.toString().equalsIgnoreCase(snapshotType)) {
return;
}
if (NullColumnValueGetter.isNotNullValue(requestedVolume.getReplicationGroupInstance())
&& (VPlexUtil.isVplexVolume(requestedVolume, _dbClient)
|| NullColumnValueGetter.isNullURI(requestedVolume.getProtectionController()))) {
VolumeGroup application = requestedVolume.getApplication(_dbClient);
if (application != null) {
throw APIException.badRequests.cannotCreateSnapshotCgPartOfApplication(application.getLabel());
}
}
}
/**
* Create an array snapshot of the volume with the passed Id. Creating a
* snapshot session simply creates and array snapshot point-in-time copy
* of the volume. It does not automatically create a single target volume
* and link it to the array snapshot as is done with the existing create
* snapshot API. It allows array snapshots to be created with out any linked
* target volumes, or multiple linked target volumes depending on the
* data passed in the request. This API is only supported on a limited
* number of platforms that support this capability.
*
* @brief Create volume snapshot session
*
* @prereq Virtual pool for the volume must specify non-zero value for max_snapshots
*
* @param id
* The URI of a ViPR Volume.
* @param param
* Volume snapshot parameters
*
* @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.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList createSnapshotSession(@PathParam("id") URI id, SnapshotSessionCreateParam param) {
return getSnapshotSessionManager().createSnapshotSession(id, param, getFullCopyManager());
}
/**
* List volume snapshots
*
*
* @prereq none
*
* @param id
* the URN of a ViPR Volume to list snapshots
*
* @brief List volume snapshots
* @return Volume snapshot response containing list of snapshot identifiers
*/
@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 getSnapshots(@PathParam("id") URI id) {
ArgValidator.checkFieldUriType(id, Volume.class, "id");
Volume requestedVolume = queryVolumeResource(id);
// Get the block service implementation for the volume.
BlockServiceApi blockServiceApiImpl = getBlockServiceImpl(requestedVolume);
// Get and return the snapshots.
List<BlockSnapshot> snapList = blockServiceApiImpl.getSnapshots(requestedVolume);
SnapshotList list = new SnapshotList();
list.setSnapList(new ArrayList<NamedRelatedResourceRep>());
List<NamedRelatedResourceRep> activeSnaps = new ArrayList<NamedRelatedResourceRep>();
List<NamedRelatedResourceRep> inactiveSnaps = new ArrayList<NamedRelatedResourceRep>();
for (BlockSnapshot snap : snapList) {
if (snap.getInactive()) {
inactiveSnaps.add(toNamedRelatedResource(snap));
} else {
activeSnaps.add(toNamedRelatedResource(snap));
}
}
list.getSnapList().addAll(activeSnaps);
list.getSnapList().addAll(inactiveSnaps);
return list;
}
/**
* List volume snapshot sessions.
*
* @brief List volume snapshot sessions.
*
* @prereq none
*
* @param id
* The URI of a ViPR Volume.
*
* @return Volume snapshot response containing list of snapshot sessions
*/
@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 getSnapshotSessions(@PathParam("id") URI id) {
return getSnapshotSessionManager().getSnapshotSessionsForSource(id);
}
/**
* List volume mirrors
*
*
* @prereq none
*
* @param id
* the URN of a ViPR Volume to list mirrors
*
* @brief List volume mirrors
* @return Volume mirror response containing a list of mirror identifiers
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/continuous-copies")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public MirrorList getNativeContinuousCopies(@PathParam("id") URI id) {
MirrorList list = new MirrorList();
ArgValidator.checkFieldUriType(id, Volume.class, "id");
Volume sourceVolume = queryVolumeResource(id);
boolean vplexVolume = checkIfVolumeIsForVplex(id);
StringSet sourceVolumeMirrors = sourceVolume.getMirrors();
if (sourceVolumeMirrors == null || sourceVolumeMirrors.isEmpty()) {
return list;
}
for (String uriStr : sourceVolumeMirrors) {
if (vplexVolume) {
VplexMirror vplexMirror = _dbClient.queryObject(VplexMirror.class, URI.create(uriStr));
if (vplexMirror == null || vplexMirror.getInactive()) {
_log.warn("Stale mirror {} found for volume {}", uriStr, sourceVolume.getId());
continue;
}
list.getMirrorList().add(toNamedRelatedResource(vplexMirror));
} else {
BlockMirror blockMirror = _dbClient.queryObject(BlockMirror.class, URI.create(uriStr));
if (blockMirror == null || blockMirror.getInactive()) {
_log.warn("Stale mirror {} found for volume {}", uriStr, sourceVolume.getId());
continue;
}
list.getMirrorList().add(toNamedRelatedResource(blockMirror));
}
}
return list;
}
/**
* Show details for a specific continuous copy
*
*
* @prereq none
*
* @param id
* the URN of a ViPR Source volume
* @param mid
* Continuous copy URI
*
* @brief Show continuous copy
* @return BlockMirrorRestRep
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/continuous-copies/{mid}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public BlockMirrorRestRep getMirror(@PathParam("id") URI id, @PathParam("mid") URI mid) {
ArgValidator.checkFieldUriType(id, Volume.class, "id");
boolean vplexVolume = checkIfVolumeIsForVplex(id);
BlockMirrorRestRep mirrorRestRep = null;
if (vplexVolume) {
ArgValidator.checkFieldUriType(mid, VplexMirror.class, "mid");
VplexMirror mirror = queryVplexMirror(mid);
if (!mirror.getSource().getURI().equals(id)) {
throw APIException.badRequests.invalidParameterVolumeMirrorMismatch(mid, id);
}
mirrorRestRep = map(mirror);
} else {
queryResource(id);
ArgValidator.checkFieldUriType(mid, BlockMirror.class, "mid");
BlockMirror mirror = queryMirror(mid);
if (!mirror.getSource().getURI().equals(id)) {
throw APIException.badRequests.invalidParameterVolumeMirrorMismatch(mid, id);
}
mirrorRestRep = map(_dbClient, mirror);
}
return mirrorRestRep;
}
/**
* Returns a list of the full copy volume references associated with a given volume.
*
*
* @prereq none
*
* @param id
* the URN of a ViPR source volume from which to retrieve associated full copies
*
* @brief List full copies
* @return full copy volume response containing a list of full copy identifiers
*/
@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 getFullCopies(@PathParam("id") URI id) {
return getFullCopyManager().getFullCopiesForSource(id);
}
/**
* Pause continuous copies for given source volume
*
* NOTE: This is an asynchronous operation.
*
*
* @prereq none
*
* @param id
* the URN of a ViPR Source volume
* @param param
* List of copies to pause
*
* @brief Pause continuous copies
* @return TaskList
*
* @throws ControllerException
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/continuous-copies/pause")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskList pauseContinuousCopies(@PathParam("id") URI id, CopiesParam param) throws ControllerException {
TaskResourceRep taskResp = null;
TaskList taskList = new TaskList();
// Validate the source volume URI
ArgValidator.checkFieldUriType(id, Volume.class, "id");
boolean vplexVolume = checkIfVolumeIsForVplex(id);
// Validate the list of copies
ArgValidator.checkFieldNotEmpty(param.getCopies(), "copies");
// Verify that the copy IDs are either all specified or none are specified
// for a particular protection type. Combinations are not allowed
verifyCopyIDs(param);
// Process the list of copies
for (Copy copy : param.getCopies()) {
// If copyID is not set all copies are paused
URI copyID = copy.getCopyID();
if (!URIUtil.isValid(copyID)) {
copyID = null;
}
// Validate a copy type was passed
ArgValidator.checkFieldNotEmpty(copy.getType(), "type");
if (copy.getType().equalsIgnoreCase(TechnologyType.RP.toString())) {
taskResp = performProtectionAction(id, copy, ProtectionOp.PAUSE.getRestOp());
taskList.getTaskList().add(taskResp);
} else if (!vplexVolume && copy.getType().equalsIgnoreCase(TechnologyType.NATIVE.toString())) {
TaskList pauseTaskList = pauseMirrors(id, copy.getSync(), copyID);
taskList.getTaskList().addAll(pauseTaskList.getTaskList());
} else if (copy.getType().equalsIgnoreCase(TechnologyType.SRDF.toString())) {
id = VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, id);
copy.setCopyID(VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, copy.getCopyID()));
taskResp = performSRDFProtectionAction(id, copy, ProtectionOp.PAUSE.getRestOp());
taskList.getTaskList().add(taskResp);
} else if (vplexVolume && copy.getType().equalsIgnoreCase(TechnologyType.NATIVE.toString())) {
throw APIException.badRequests.actionNotApplicableForVplexVolumeMirrors(ProtectionOp.PAUSE.getRestOp());
} else {
throw APIException.badRequests.invalidCopyType(copy.getType());
}
// If copyID is null, we have already paused all copies
if (copyID == null) {
return taskList;
}
}
return taskList;
}
/**
* Resume continuous copies for given source volume
*
* NOTE: This is an asynchronous operation.
*
*
* @prereq none
*
* @param id
* the URN of a ViPR Source volume
* @param param
* List of copies to resume
*
* @brief Resume continuous copies
* @return TaskList
*
* @throws ControllerException
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/continuous-copies/resume")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskList resumeContinuousCopies(@PathParam("id") URI id, CopiesParam param)
throws ControllerException {
TaskResourceRep taskResp = null;
TaskList taskList = new TaskList();
// Validate the source volume URI
ArgValidator.checkFieldUriType(id, Volume.class, "id");
boolean vplexVolume = checkIfVolumeIsForVplex(id);
// Validate the list of copies
ArgValidator.checkFieldNotEmpty(param.getCopies(), "copies");
// Verify that the copy IDs are either all specified or none are specified
// for a particular protection type. Combinations are not allowed
verifyCopyIDs(param);
// Process the list of copies
for (Copy copy : param.getCopies()) {
// If copyID is not set all copies are resumed
URI copyID = copy.getCopyID();
if (!URIUtil.isValid(copyID)) {
copyID = null;
}
// Validate a copy type was passed
ArgValidator.checkFieldNotEmpty(copy.getType(), "type");
// If copyID is null all copies are paused
if (copy.getType().equalsIgnoreCase(TechnologyType.RP.toString())) {
taskResp = performProtectionAction(id, copy, ProtectionOp.RESUME.getRestOp());
taskList.getTaskList().add(taskResp);
} else if (!vplexVolume && copy.getType().equalsIgnoreCase(TechnologyType.NATIVE.toString())) {
TaskList resumeTaskList = resumeMirrors(id, copyID);
taskList.getTaskList().addAll(resumeTaskList.getTaskList());
} else if (copy.getType().equalsIgnoreCase(TechnologyType.SRDF.toString())) {
id = VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, id);
copy.setCopyID(VPlexSrdfUtil.getSrdfIdFromVolumeId(_dbClient, copy.getCopyID()));
taskResp = performSRDFProtectionAction(id, copy, ProtectionOp.RESUME.getRestOp());
taskList.getTaskList().add(taskResp);
} else if (vplexVolume && copy.getType().equalsIgnoreCase(TechnologyType.NATIVE.toString())) {
throw APIException.badRequests.actionNotApplicableForVplexVolumeMirrors(ProtectionOp.RESUME.getRestOp());
} else {
throw APIException.badRequests.invalidCopyType(copy.getType());
}
// If copyID is null, we have already resumed all copies
if (copyID == null) {
return taskList;
}
}
return taskList;
}
/**
* Deactivate continuous copies for given source volume
*
* NOTE: This is an asynchronous operation.
*
*
* @prereq none
*
* @param id
* the URN of a ViPR Source volume
* @param param
* List of copies to deactivate
* @param type {@link DefaultValue} FULL
* Possible type of deletion
* <ul>
* <li>FULL</li>
* <li>VIPR_ONLY</li>
* </ul>
* if type is FULL, ViPR deletes the continuous copy from storage array and removes from ViPR data base.
* if type is VIPR_ONLY, ViPR removes the continuous copy only from ViPR data base and leaves the continuous copy on storage
* array as it is.
*
* @brief Delete continuous copies
* @return TaskList
*
* @throws ControllerException
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/continuous-copies/deactivate")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskList deactivateMirror(@PathParam("id") URI id, CopiesParam param,
@DefaultValue("FULL") @QueryParam("type") String deleteType) throws ControllerException {
TaskList taskList = new TaskList();
// Validate the source volume URI
ArgValidator.checkFieldUriType(id, Volume.class, "id");
Volume volume = _dbClient.queryObject(Volume.class, id);
// Make sure that we don't have some pending
// operation against the volume
checkForPendingTasks(Arrays.asList(volume.getTenant().getURI()), Arrays.asList(volume));
// Validate the list of copies
ArgValidator.checkFieldNotEmpty(param.getCopies(), "copies");
boolean vplexVolume = checkIfVolumeIsForVplex(id);
// Process the list of copies
for (Copy copy : param.getCopies()) {
// Validate the copy ID
URI copyID = copy.getCopyID();
ArgValidator.checkUri(copyID);
// Validate a copy type was passed
ArgValidator.checkFieldNotEmpty(copy.getType(), "type");
if (TechnologyType.NATIVE.toString().equalsIgnoreCase(copy.getType())) {
String task = UUID.randomUUID().toString();
StorageSystem device;
String mirrorLabel;
URI mirrorURI;
BlockServiceApi blockServiceApi;
if (vplexVolume) {
VplexMirror mirror = queryVplexMirror(copyID);
ArgValidator.checkEntity(mirror, mirror.getId(), isIdEmbeddedInURL(copyID));
if (!mirror.getSource().getURI().equals(id)) {
throw APIException.badRequests.mirrorDoesNotBelongToVolume(copyID, id);
}
mirrorLabel = mirror.getLabel();
mirrorURI = mirror.getId();
device = _dbClient.queryObject(StorageSystem.class, mirror.getStorageController());
blockServiceApi = getBlockServiceImpl(DiscoveredDataObject.Type.vplex.name());
} else {
BlockMirror mirror = queryMirror(copyID);
ArgValidator.checkEntity(mirror, mirror.getId(), isIdEmbeddedInURL(copyID));
if (!mirror.getSource().getURI().equals(id)) {
throw APIException.badRequests.mirrorDoesNotBelongToVolume(copyID, id);
}
mirrorLabel = mirror.getLabel();
mirrorURI = mirror.getId();
device = _dbClient.queryObject(StorageSystem.class, mirror.getStorageController());
blockServiceApi = getBlockServiceImpl("mirror");
}
// Deactivate the mirror
TaskList deactivateTaskList = blockServiceApi.deactivateMirror(device, mirrorURI, task, deleteType);
// Create the audit log message
String opStage = VolumeDeleteTypeEnum.VIPR_ONLY.name().equals(deleteType) ? null : AuditLogManager.AUDITOP_BEGIN;
boolean opStatus = true;
for (TaskResourceRep resultTask : deactivateTaskList.getTaskList()) {
if (Operation.Status.error.name().equals(resultTask.getState())) {
opStatus = false;
break;
}
}
auditOp(OperationTypeEnum.DEACTIVATE_VOLUME_MIRROR, opStatus, opStage, copyID.toString(), mirrorLabel);
// Add tasks for this copy
taskList.getTaskList().addAll(deactivateTaskList.getTaskList());
} else {
throw APIException.badRequests.invalidCopyType(copy.getType());
}
}
return taskList;
}
/**
* perform SRDF Protection APIs
*
* @param id
* the URN of a ViPR volume associated
* @param copy
* @param op
* @return
* @throws InternalException
*/
private TaskResourceRep performSRDFProtectionAction(URI id, Copy copy, String op)
throws InternalException {
URI copyID = copy.getCopyID();
ArgValidator.checkFieldUriType(copyID, Volume.class, "copyID");
// Get the volume associated with the URI
Volume volume = queryVolumeResource(id);
Volume copyVolume = null;
if (null == copyID) {
copyVolume = volume;
} else {
copyVolume = queryVolumeResource(copyID);
}
ArgValidator.checkEntity(volume, id, true);
ArgValidator.checkEntity(copyVolume, copyID, true);
// check if the passed in target volume is indeed the target of the
// passed in source volume
if (!copyVolume.getSrdfParent().getURI().equals(id)
&& !copyVolume.getId().equals(id)) {
throw APIException.badRequests.protectionVolumeInvalidTargetOfVolume(copyID, id);
}
// Make sure that we don't have some pending
// operation against the volume
checkForPendingTasks(Arrays.asList(volume.getTenant().getURI()), Arrays.asList(volume));
if (Volume.isSRDFProtectedVolume(copyVolume)) {
if (op.equalsIgnoreCase(ProtectionOp.FAILOVER_TEST_CANCEL.getRestOp()) ||
op.equalsIgnoreCase(ProtectionOp.FAILOVER_TEST.getRestOp())) {
String task = UUID.randomUUID().toString();
Operation status = new Operation();
status.setResourceType(ProtectionOp.getResourceOperationTypeEnum(op));
_dbClient.createTaskOpStatus(Volume.class, volume.getId(), task, status);
_dbClient.ready(Volume.class, volume.getId(), task);
return toTask(volume, task, status);
}
if (PersonalityTypes.SOURCE.name().equalsIgnoreCase(
copyVolume.getPersonality())) {
if (op.equalsIgnoreCase(ProtectionOp.FAILOVER_CANCEL.getRestOp()) || op.equalsIgnoreCase(ProtectionOp.FAILOVER.getRestOp())
|| op.equalsIgnoreCase(ProtectionOp.SWAP.getRestOp())) {
throw new ServiceCodeException(
ServiceCode.IO_ERROR,
"Expected SRDF Target R2 volume, instead R1 {0} is being passed, hence cannot proceed with failover or failback.",
new Object[] { copyVolume.getNativeGuid() });
} else if (copyVolume.getSrdfTargets() == null
|| copyVolume.getSrdfTargets().isEmpty()) {
throw new ServiceCodeException(
ServiceCode.IO_ERROR,
"Target Volume Empty for a given source R1 {0}, hence cannot proceed with failover or failback.",
new Object[] { copyVolume.getNativeGuid() });
} else if (PersonalityTypes.TARGET.name().equalsIgnoreCase(copyVolume.getPersonality()) &&
RemoteDirectorGroup.SupportedCopyModes.ADAPTIVECOPY.name().equalsIgnoreCase(copyVolume.getSrdfCopyMode())) {
if (ProtectionOp.CHANGE_COPY_MODE.getRestOp().equalsIgnoreCase(op)) {
validateVpoolCopyModeSetting(volume, copy.getCopyMode());
}
}
}
// COP-25377. We need to block failover and swap operations for SRDF ACTIVE COPY MODE
if (Mode.ACTIVE.toString().equalsIgnoreCase(copyVolume.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(Volume.class, volume.getId(), 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 (isSuspendCopyRequest(op, copy)) {
op = ProtectionOp.SUSPEND.getRestOp();
}
ProtectionOrchestrationController protectionController = getController(ProtectionOrchestrationController.class,
ProtectionOrchestrationController.PROTECTION_ORCHESTRATION_DEVICE);
StorageSystem system = _dbClient.queryObject(StorageSystem.class,
copyVolume.getStorageController());
protectionController.performSRDFProtectionOperation(system.getId(), copy, op, task);
return toTask(volume, task, status);
} else {
throw new ServiceCodeException(ServiceCode.IO_ERROR,
"Volume {0} is not SRDF protected",
new Object[] { copyVolume.getNativeGuid() });
}
}
private void validateVpoolCopyModeSetting(Volume srcVolume, String newCopyMode) {
if (srcVolume != null) {
URI virtualPoolURI = srcVolume.getVirtualPool();
VirtualPool virtualPool = _dbClient.queryObject(VirtualPool.class, virtualPoolURI);
if (virtualPool != null) {
StringMap remoteCopySettingsMap = virtualPool.getProtectionRemoteCopySettings();
if (remoteCopySettingsMap != null) {
for (Map.Entry<URI, VpoolRemoteCopyProtectionSettings> entry : VirtualPool.getRemoteProtectionSettings(virtualPool,
_dbClient).entrySet()) {
VpoolRemoteCopyProtectionSettings copySetting = entry.getValue();
if (!newCopyMode.equalsIgnoreCase(copySetting.getCopyMode())) {
throw APIException.badRequests.invalidCopyModeOp(newCopyMode, copySetting.getCopyMode());
}
}
}
}
}
}
private TaskResourceRep establishVolumeMirrorGroupRelation(URI id, Copy copy, String op)
throws InternalException {
URI copyID = copy.getCopyID();
ArgValidator.checkFieldUriType(copyID, BlockMirror.class, "copyID");
// Get the volume associated with the URI
Volume volume = queryVolumeResource(id);
BlockMirror mirror = queryMirror(copyID);
ArgValidator.checkEntity(volume, id, true);
ArgValidator.checkEntity(mirror, copyID, true);
StringSet mirrors = volume.getMirrors();
if (mirrors == null || mirrors.isEmpty()) {
throw APIException.badRequests.invalidParameterVolumeHasNoContinuousCopies(id);
}
if (!mirror.getSource().getURI().equals(id)) {
throw APIException.badRequests.invalidParameterBlockCopyDoesNotBelongToVolume(copyID, id);
}
if (!volume.hasConsistencyGroup() ||
!mirror.hasConsistencyGroup()) {
throw APIException.badRequests.blockObjectHasNoConsistencyGroup();
}
String task = UUID.randomUUID().toString();
Operation status = new Operation();
status.setResourceType(ProtectionOp.getResourceOperationTypeEnum(op));
_dbClient.createTaskOpStatus(Volume.class, volume.getId(), task, status);
auditOp(OperationTypeEnum.ESTABLISH_VOLUME_MIRROR, true, AuditLogManager.AUDITOP_BEGIN,
mirrors);
StorageSystem system = _dbClient.queryObject(StorageSystem.class,
volume.getStorageController());
BlockServiceApi blockServiceApi = getBlockServiceImpl("mirror");
return blockServiceApi.establishVolumeAndNativeContinuousCopyGroupRelation(system, volume,
mirror, task);
}
/**
* 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 id
* the URN of a ViPR source volume
* @param copyID
* id of the target volume
* @param pointInTime
* any point in time used for failover, specified in UTC.
* Allowed values: "yyyy-MM-dd_HH:mm:ss" formatted date or datetime in milliseconds. Can be
* null.
* @param op
* operation to perform (pause, stop, failover, etc)
* @return task resource rep
* @throws InternalException
*/
private TaskResourceRep performProtectionAction(URI id, Copy copy, String op)
throws InternalException {
ArgValidator.checkFieldUriType(copy.getCopyID(), Volume.class, "copyID");
// Get the volume associated with the URI
Volume volume = queryVolumeResource(id);
Volume copyVolume = queryVolumeResource(copy.getCopyID());
ArgValidator.checkEntity(volume, id, true);
ArgValidator.checkEntity(copyVolume, copy.getCopyID(), true);
if (op.equalsIgnoreCase(ProtectionOp.SWAP.getRestOp()) && !NullColumnValueGetter.isNullURI(volume.getConsistencyGroup())) {
ExportUtils.validateConsistencyGroupBookmarksExported(_dbClient, volume.getConsistencyGroup());
}
// Make sure the source and failover target share the same consistency group (applies to change access mode, failover, and failover
// cancel operations)
if ((op.equalsIgnoreCase(ProtectionOp.CHANGE_ACCESS_MODE.getRestOp()) || op.equalsIgnoreCase(ProtectionOp.FAILOVER.getRestOp()) || op
.equalsIgnoreCase(ProtectionOp.FAILOVER_CANCEL.getRestOp()))
&& !NullColumnValueGetter.isNullURI(volume.getConsistencyGroup())
&& !volume.getConsistencyGroup().equals(copyVolume.getConsistencyGroup())) {
throw APIException.badRequests.invalidConsistencyGroupsForProtectionOperation();
}
// 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());
}
if (isNullURI(volume.getProtectionController())) {
throw new ServiceCodeException(ServiceCode.IO_ERROR,
"Attempt to do protection link management on unprotected volume: {0}",
new Object[] { volume.getWWN() });
}
// Make sure that we don't have some pending
// operation against the volume
checkForPendingTasks(Arrays.asList(volume.getTenant().getURI()), Arrays.asList(volume));
String task = UUID.randomUUID().toString();
Operation status = new Operation();
status.setResourceType(ProtectionOp.getResourceOperationTypeEnum(op));
_dbClient.createTaskOpStatus(Volume.class, volume.getId(), task, status);
_log.info(String.format("Protection %s --- VolumeId id: %s on Protection Appliance: %s",
task, id, volume.getProtectionController()));
ProtectionSystem system = _dbClient.queryObject(ProtectionSystem.class,
volume.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(), id, copy.getCopyID(), 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(volume, task, status);
}
/**
* Helper method for querying a mirror
*
* @param id
* the URN of a ViPR mirror to query
* @return BlockMirror instance
*/
private BlockMirror queryMirror(URI id) {
ArgValidator.checkUri(id);
BlockMirror mirror = _permissionsHelper.getObjectById(id, BlockMirror.class);
ArgValidator.checkEntityNotNull(mirror, id, isIdEmbeddedInURL(id));
return mirror;
}
/**
* Helper method for querying a vplex mirror
*
* @param id
* the URN of a ViPR mirror to query
* @return VplexMirror instance
*/
private VplexMirror queryVplexMirror(URI id) {
ArgValidator.checkUri(id);
VplexMirror mirror = _permissionsHelper.getObjectById(id, VplexMirror.class);
ArgValidator.checkEntityNotNull(mirror, id, isIdEmbeddedInURL(id));
return mirror;
}
/**
* Returns all potential virtual pools for a virtual pool change of the
* volume specified in the request. Note that not all virtual pool returned
* by the request will be a valid virtual pool change for the volume. The
* virtual pools returned are based on the connectivity of the storage
* system on which the volume resides, the storage pools available to that
* storage system, and the virtual pools defined in the system that can be
* supported by those storage pools. For each virtual pool returned, the
* response identifies whether or not a change to the virtual pool is
* allowed, and when not allowed, the reason the change is not allowed.
*
*
* @prereq none
*
* @param id
* the URN of a ViPR volume.
*
* @brief Show potential virtual pools
* @return A VirtualPoolChangeList that identifies each potential virtual
* pool, whether or not a change is allowed for the virtual pool,
* and if not, the reason why.
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/vpool-change/vpool")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public VirtualPoolChangeList getVirtualPoolForVirtualPoolChange(@PathParam("id") URI id) {
// Get the volume.
ArgValidator.checkFieldUriType(id, Volume.class, "id");
Volume volume = queryVolumeResource(id);
_log.info("Found volume");
// Get the block service implementation for this volume.
BlockServiceApi blockServiceApi = getBlockServiceImpl(volume);
_log.info("Got BlockServiceApi for volume");
// Return the list of potential VirtualPool for a VirtualPool change for this volume.
return blockServiceApi.getVirtualPoolForVirtualPoolChange(volume);
}
/**
* Allows the caller to change the virtual pool for the volume identified in
* the request. Currently, the only virtual pool changes that are supported
* are as follows:
*
* Change the virtual pool for a VPLEX virtual volume. This virtual pool
* change would allow the caller to change the types of drives, for example,
* used for the backend volume(s) that are used by the virtual volume.
*
* Change the virtual pool for a VPLEX virtual volume, such that a local
* VPLEX virtual volumes becomes a distributed VPLEX virtual volume.
*
* Change the virtual pool of a VMAX or VNX Block volume to make the volume
* a local or distributed VPLEX virtual volume. Essentially, the volume
* becomes the backend volume for a VPLEX virtual volume. Similar to
* creating a virtual volume, but instead of creating a new backend volume,
* using the volume identified in the request. The VMAX or VNX volume cannot
* currently be exported for this change.
*
* Change the virtual pool of a VMAX or VNX Block volume to make the volume
* a RecoverPoint protected volume. The volume must be able to stay put, and
* ViPR will build a protection around it.
*
* Change the virtual pool of a VMAX or VNX Block volume to allow native
* continuous copies to be created for it.
*
* Change the virtual pool of a volume to increase the export path parameter max_paths.
* The number of paths will be upgraded if possible for all Export Groups / Export Masks
* containing this volume. If the volume is not currently exported, max_paths can be
* decreased or paths_per_initiator can be changed. Note that changing max_paths does
* not have any effect on the export of BlockSnapshots that were created from this volume.
*
* Change the virtual pool of a VMAX and VNX volume to allow change of Auto-tiering policy
* associated with it.
* <p>
* Since this method has been deprecated use POST /block/volumes/vpool-change
*
* @brief Change the virtual pool for a volume.
*
* @prereq none
*
* @param id
* the URN of a ViPR volume.
* @param param
* The parameter specifying the new virtual pool.
* @return A TaskResourceRep representing the virtual pool change for the
* volume.
* @throws InternalException,
* APIException
*/
@PUT
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/vpool")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
@Deprecated
public TaskResourceRep changeVolumeVirtualPool(@PathParam("id") URI id,
VirtualPoolChangeParam param) throws InternalException, APIException {
_log.info("Request to change VirtualPool for volume {}", id);
// Get the volume.
ArgValidator.checkFieldUriType(id, Volume.class, "id");
Volume volume = queryVolumeResource(id);
_log.info("Found volume");
// Don't operate on VPLEX backend or RP Journal volumes.
BlockServiceUtils.validateNotAnInternalBlockObject(volume, false);
// Don't operate on ingested volumes.
VolumeIngestionUtil.checkOperationSupportedOnIngestedVolume(volume,
ResourceOperationTypeEnum.CHANGE_BLOCK_VOLUME_VPOOL, _dbClient);
// Get the project.
URI projectURI = volume.getProject().getURI();
Project project = _permissionsHelper.getObjectById(projectURI, Project.class);
ArgValidator.checkEntity(project, projectURI, false);
_log.info("Found volume project {}", projectURI);
// Verify the user is authorized for the volume's project.
BlockServiceUtils.verifyUserIsAuthorizedForRequest(project, getUserFromContext(), _permissionsHelper);
_log.info("User is authorized for volume's project");
// Get the VirtualPool for the request and verify that the
// project's tenant has access to the VirtualPool.
VirtualPool vpool = getVirtualPoolForRequest(project, param.getVirtualPool(), _dbClient,
_permissionsHelper);
_log.info("Found new VirtualPool {}", vpool.getId());
// Verify that the VirtualPool change is allowed for the
// requested volume and VirtualPool.
verifyVirtualPoolChangeSupportedForVolumeAndVirtualPool(volume, vpool);
_log.info("VirtualPool change is supported for requested volume and VirtualPool");
verifyAllVolumesInCGRequirement(Arrays.asList(volume), vpool);
// verify quota
if (!CapacityUtils.validateVirtualPoolQuota(_dbClient, vpool, volume.getProvisionedCapacity())) {
throw APIException.badRequests.insufficientQuotaForVirtualPool(vpool.getLabel(), "volume");
}
// Create a unique task id.
String taskId = UUID.randomUUID().toString();
Operation op = _dbClient.createTaskOpStatus(Volume.class, id,
taskId, ResourceOperationTypeEnum.CHANGE_BLOCK_VOLUME_VPOOL);
// Get the required block service API implementation to
// make the desired VirtualPool change on this volume. This
// essentially determines the controller that will be used
// execute the VirtualPool update on the volume.
try {
BlockServiceApi blockServiceAPI = getBlockServiceImplForVirtualPoolChange(volume, vpool);
_log.info("Got block service implementation for VirtualPool change request");
blockServiceAPI.changeVolumeVirtualPool(Arrays.asList(volume), vpool, param, taskId);
_log.info("Executed VirtualPool change for volume.");
} catch (InternalException | APIException e) {
String errorMsg = String.format("Volume VirtualPool change error: %s", e.getMessage());
op = new Operation(Operation.Status.error.name(), errorMsg);
_dbClient.updateTaskOpStatus(Volume.class, id, taskId, op);
throw e;
}
auditOp(OperationTypeEnum.CHANGE_VOLUME_VPOOL, true, AuditLogManager.AUDITOP_BEGIN,
volume.getLabel(), 1, volume.getVirtualArray().toString(), volume.getProject().toString());
return toTask(volume, taskId, op);
}
/**
* Allows the caller to change the virtual pool for the volumes identified in
* the request. Currently, the only virtual pool changes that are supported via
* this method are as follows:
*
*
* Change the virtual pool for a VPLEX virtual volume. This virtual pool
* change would allow the caller to change the types of drives, for example,
* used for the backend volume(s) that are used by the virtual volume.
*
* Change the virtual pool for a VPLEX virtual volume, such that a local
* VPLEX virtual volumes becomes a distributed VPLEX virtual volume.
*
* Change the virtual pool of a VMAX or VNX Block volume to make the volume
* a local or distributed VPLEX virtual volume. Essentially, the volume
* becomes the backend volume for a VPLEX virtual volume. Similar to
* creating a virtual volume, but instead of creating a new backend volume,
* using the volume identified in the request. The VMAX or VNX volume cannot
* currently be exported for this change.
*
* Change the virtual pool of a VMAX or VNX Block volume to make the volume
* a RecoverPoint protected volume. The volume must be able to stay put, and
* ViPR will build a protection around it.
*
* Change the virtual pool of a VMAX or VNX Block volume to allow native
* continuous copies to be created for it.
*
* Change the virtual pool of a volume to increase the export path parameter max_paths.
* The number of paths will be upgraded if possible for all Export Groups / Export Masks
* containing this volume. If the volume is not currently exported, max_paths can be
* decreased or paths_per_initiator can be changed. Note that changing max_paths does
* not have any effect on the export of BlockSnapshots that were created from this volume.
*
* Change the virtual pool of a VMAX and VNX volumes to allow change of Auto-tiering policy
* associated with it.
*
*
* Note: Operations other than Auto-tiering Policy change will call the
* internal single volume method (BlockServiceApiImpl) in a loop.
*
* @brief Change the virtual pool for the given volumes.
*
* @param param
* the VolumeVirtualPoolChangeParam
* @return A List of TaskResourceRep representing the virtual pool change for the
* volumes.
* @throws InternalException,
* APIException
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/vpool-change")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskList changeVolumesVirtualPool(VolumeVirtualPoolChangeParam param)
throws InternalException, APIException {
// verify volume ids list is provided.
List<URI> ids = param.getVolumes();
ArgValidator.checkFieldNotEmpty(ids, "volumes");
_log.info("Request to change VirtualPool for volumes {}", ids);
List<Volume> volumes = new ArrayList<Volume>();
TaskList taskList = new TaskList();
for (URI id : ids) {
// Get the volume.
ArgValidator.checkFieldUriType(id, Volume.class, "volume");
Volume volume = queryVolumeResource(id);
volumes.add(volume);
// Make sure that we don't have some pending
// operation against the volume
checkForPendingTasks(Arrays.asList(volume.getTenant().getURI()), Arrays.asList(volume));
}
_log.info("Found volumes");
/**
* verify that all volumes belong to same vPool.
*
* If so and vPool change detects it as Auto-tiering policy change,
* then they are of same system type.
*
* Special case: If the request contains a VMAX volume and a VNX volume
* belonging to a generic vPool and the target vPool has some VMAX FAST policy,
* the below verifyVirtualPoolChangeSupportedForVolumeAndVirtualPool() check will
* throw error for VNX volume (saying it does not come under any valid change).
*/
verifyAllVolumesBelongToSameVpool(volumes);
// target vPool
VirtualPool vPool = null;
// total provisioned capacity to check for vPool quota.
long totalProvisionedCapacity = 0;
for (Volume volume : volumes) {
_log.info("Checking on volume: {}", volume.getId());
// Don't operate on VPLEX backend or RP Journal volumes.
BlockServiceUtils.validateNotAnInternalBlockObject(volume, param.getForceFlag());
// Don't operate on ingested volumes.
VolumeIngestionUtil.checkOperationSupportedOnIngestedVolume(volume,
ResourceOperationTypeEnum.CHANGE_BLOCK_VOLUME_VPOOL, _dbClient);
// Get the project.
URI projectURI = volume.getProject().getURI();
Project project = _permissionsHelper.getObjectById(projectURI,
Project.class);
ArgValidator.checkEntity(project, projectURI, false);
_log.info("Found volume project {}", projectURI);
// Verify the user is authorized for the volume's project.
BlockServiceUtils.verifyUserIsAuthorizedForRequest(project, getUserFromContext(), _permissionsHelper);
_log.info("User is authorized for volume's project");
// Get the VirtualPool for the request and verify that the
// project's tenant has access to the VirtualPool.
vPool = getVirtualPoolForRequest(project, param.getVirtualPool(),
_dbClient, _permissionsHelper);
_log.info("Found new VirtualPool {}", vPool.getId());
// Verify that the VirtualPool change is allowed for the
// requested volume and VirtualPool.
verifyVirtualPoolChangeSupportedForVolumeAndVirtualPool(volume, vPool);
_log.info("VirtualPool change is supported for requested volume and VirtualPool");
totalProvisionedCapacity += volume.getProvisionedCapacity()
.longValue();
}
verifyAllVolumesInCGRequirement(volumes, vPool);
// verify target vPool quota
if (!CapacityUtils.validateVirtualPoolQuota(_dbClient, vPool,
totalProvisionedCapacity)) {
throw APIException.badRequests.insufficientQuotaForVirtualPool(
vPool.getLabel(), "volume");
}
// Create a unique task id.
String taskId = UUID.randomUUID().toString();
// if this vpool request change has a consistency group, set its requested types
if (param.getConsistencyGroup() != null) {
BlockConsistencyGroup cg = _dbClient.queryObject(BlockConsistencyGroup.class, param.getConsistencyGroup());
if (cg != null && !cg.getInactive()) {
cg.getRequestedTypes().addAll(getRequestedTypes(vPool));
_dbClient.updateObject(cg);
}
}
// Get the required block service API implementation to
// make the desired VirtualPool change on this volume. This
// essentially determines the controller that will be used
// to execute the VirtualPool update on the volume.
try {
/**
* If it is Auto-tiering policy change, the system type remains same
* between source and target vPools.
* Volumes from single vPool would be of same characteristics and
* all would specify same operation.
*/
BlockServiceApi blockServiceAPI = getBlockServiceImplForVirtualPoolChange(
volumes.get(0), vPool);
_log.info("Got block service implementation for VirtualPool change request");
VirtualPoolChangeParam oldParam = convertNewVirtualPoolChangeParamToOldParam(param);
TaskList taskList2 = blockServiceAPI.changeVolumeVirtualPool(volumes, vPool,
oldParam, taskId);
if (taskList2 != null && !taskList2.getTaskList().isEmpty()) {
taskList.getTaskList().addAll(taskList2.getTaskList());
}
_log.info("Executed VirtualPool change for given volumes.");
} catch (Exception e) {
String errorMsg = String.format(
"Volume VirtualPool change error: %s", e.getMessage());
_log.error(errorMsg, e);
if (!taskList.getTaskList().isEmpty()) {
for (TaskResourceRep volumeTask : taskList.getTaskList()) {
volumeTask.setState(Operation.Status.error.name());
volumeTask.setMessage(errorMsg);
_dbClient.updateTaskOpStatus(Volume.class, volumeTask
.getResource().getId(), taskId,
new Operation(Operation.Status.error.name(), errorMsg));
}
} else {
for (Volume volume : volumes) {
_dbClient.updateTaskOpStatus(Volume.class, volume.getId(), taskId,
new Operation(Operation.Status.error.name(), errorMsg));
}
}
throw e;
}
// Record Audit operation.
for (Volume volume : volumes) {
auditOp(OperationTypeEnum.CHANGE_VOLUME_VPOOL, true,
AuditLogManager.AUDITOP_BEGIN, volume.getLabel(), 1, volume
.getVirtualArray().toString(),
volume.getProject()
.toString());
}
return taskList;
}
/**
* Verify that all volumes belong to same vpool.
*
* @param volumes
* the volumes
*/
private void verifyAllVolumesBelongToSameVpool(List<Volume> volumes) {
URI vPool = null;
for (Volume volume : volumes) {
if (vPool != null
&& !vPool.toString().equalsIgnoreCase(
volume.getVirtualPool().toString())) {
throw APIException.badRequests.volumesShouldBelongToSameVpool();
}
vPool = volume.getVirtualPool();
}
}
/**
* Copy the contents from new virtual pool change param to old param.
*
* Old param is passed as an argument in multiple methods and it is not advisable
* to create over-loaded methods for all those.
* When we remove the old deprecated param class, we can change the argument in
* all those methods to take the new param.
*
* @param param
* the param
* @return the virtual pool change param
*/
private VirtualPoolChangeParam convertNewVirtualPoolChangeParamToOldParam(
VolumeVirtualPoolChangeParam newParam) {
VirtualPoolChangeParam oldParam = new VirtualPoolChangeParam();
oldParam.setVirtualPool(newParam.getVirtualPool());
oldParam.setProtection(newParam.getProtection());
oldParam.setConsistencyGroup(newParam.getConsistencyGroup());
oldParam.setTransferSpeedParam(newParam.getTransferSpeedParam());
oldParam.setMigrationSuspendBeforeCommit(newParam.isMigrationSuspendBeforeCommit());
oldParam.setMigrationSuspendBeforeDeleteSource(newParam.isMigrationSuspendBeforeDeleteSource());
return oldParam;
}
/**
*
* @param projectURI
* @param varrayURI
* @return Get Volume for Virtual Array Change
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/varray-change")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public NamedVolumesList getVolumesForVirtualArrayChange(
@QueryParam("project") URI projectURI, @QueryParam("targetVarray") URI varrayURI) {
NamedVolumesList volumeList = new NamedVolumesList();
// Get the project.
ArgValidator.checkFieldUriType(projectURI, Project.class, "project");
Project project = _permissionsHelper.getObjectById(projectURI, Project.class);
ArgValidator.checkEntity(project, projectURI, false);
_log.info("Found project {}:{}", projectURI);
// Verify the user is authorized for the project.
BlockServiceUtils.verifyUserIsAuthorizedForRequest(project, getUserFromContext(), _permissionsHelper);
_log.info("User is authorized for project");
// Get the target virtual array.
ArgValidator.checkFieldUriType(varrayURI, VirtualArray.class, "targetVarray");
VirtualArray tgtVarray = _permissionsHelper.getObjectById(varrayURI, VirtualArray.class);
ArgValidator.checkEntity(tgtVarray, varrayURI, false);
_log.info("Found target virtual array {}:{}", tgtVarray.getLabel(), varrayURI);
// Determine all volumes in the project that could potentially
// be moved to the target virtual array.
URIQueryResultList volumeIds = new URIQueryResultList();
_dbClient.queryByConstraint(ContainmentConstraint.Factory.getProjectVolumeConstraint(projectURI), volumeIds);
Iterator<Volume> volumeItr = _dbClient.queryIterativeObjects(Volume.class, volumeIds);
while (volumeItr.hasNext()) {
Volume volume = volumeItr.next();
try {
// Don't operate on VPLEX backend, RP Journal volumes,
// or other internal volumes.
BlockServiceUtils.validateNotAnInternalBlockObject(volume, false);
// Don't operate on ingested volumes.
VolumeIngestionUtil.checkOperationSupportedOnIngestedVolume(volume,
ResourceOperationTypeEnum.CHANGE_BLOCK_VOLUME_VARRAY, _dbClient);
// Can't change to the same varray.
if (volume.getVirtualArray().equals(varrayURI)) {
_log.info("Virtual array change not supported for volume {} already in the target varray",
volume.getId());
continue;
}
// Get the appropriate block service implementation.
BlockServiceApi blockServiceAPI = getBlockServiceImpl(volume);
// Verify that the virtual array change is allowed for the
// volume and target virtual array.
blockServiceAPI.verifyVarrayChangeSupportedForVolumeAndVarray(volume, tgtVarray);
// If so, add it to the list.
volumeList.getVolumes().add(toNamedRelatedResource(volume));
} catch (Exception e) {
_log.info("Virtual array change not supported for volume {}:{}",
volume.getId(), e.getMessage());
}
}
return volumeList;
}
/**
* Allows the caller to change the virtual array of the passed volume. Currently,
* this is only possible for a local VPlex virtual volumes. Additionally, the
* volume must not be exported. The volume can be migrated to the other cluster
* in the VPlex Metro configuration or a new varray in the same cluster. Since this method has been
* deprecated use POST /block/volumes/varray-change
*
* @prereq Volume must not be exported
*
* @param id
* The URN of a ViPR volume.
* @param varrayChangeParam
* The varray change parameters.
*
* @brief Change the virtual array for the specified volume.
*
* @return A TaskResourceRep representing the NH change for the volume.
*
* @throws InternalException,
* APIException
*/
@PUT
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/varray")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
@Deprecated
public TaskResourceRep changeVolumeVirtualArray(@PathParam("id") URI id,
VirtualArrayChangeParam varrayChangeParam) throws InternalException, APIException {
_log.info("Request to change varray for volume {}", id);
TaskList taskList = changeVirtualArrayForVolumes(Arrays.asList(id),
varrayChangeParam.getVirtualArray());
return taskList.getTaskList().get(0);
}
/**
* Allows the caller to change the virtual array of the passed volumes.
* Currently, this is only possible for local VPlex virtual volumes.
* Additionally, the volumes must not be exported. The volume can be
* migrated to the other cluster in the VPlex Metro configuration or a new
* varray in the same cluster.
*
* @brief Change the virtual array for the given volumes.
*
* @prereq Volumes must not be exported
*
* @param param
* The varray change parameters.
*
* @return A TaskList representing the varray change for the volumes.
*
* @throws InternalException,
* APIException
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/varray-change")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskList changeVolumesVirtualArray(VolumeVirtualArrayChangeParam param)
throws InternalException, APIException {
_log.info("Request to change varray for volumes {}", param.getVolumes());
return changeVirtualArrayForVolumes(param.getVolumes(), param.getVirtualArray());
}
/**
* Changes the virtual array for the passed volumes to the passed
* target virtual array.
*
* @param volumeURIs
* The URIs of the volumes to move
* @param tgtVarrayURI
* The URI of the target virtual array
*
* @return A TaskList of the tasks associated with each volume being moved.
*
* @throws InternalException,
* APIException
*/
private TaskList changeVirtualArrayForVolumes(List<URI> volumeURIs, URI tgtVarrayURI)
throws InternalException, APIException {
// Create the result.
TaskList taskList = new TaskList();
// Create a unique task id.
String taskId = UUID.randomUUID().toString();
// Validate that each of the volumes passed in is eligible
// for the varray change.
VirtualArray tgtVarray = null;
BlockConsistencyGroup cg = null;
BlockServiceApi blockServiceAPI = null;
List<Volume> volumes = new ArrayList<Volume>();
List<Volume> cgVolumes = new ArrayList<Volume>();
boolean foundVolumeNotInCG = false;
for (URI volumeURI : volumeURIs) {
// Get and verify the volume.
ArgValidator.checkFieldUriType(volumeURI, Volume.class, "volume");
Volume volume = queryVolumeResource(volumeURI);
ArgValidator.checkEntity(volume, volumeURI, false);
_log.info("Found volume {}", volumeURI);
// Don't operate on VPLEX backend or RP Journal volumes.
BlockServiceUtils.validateNotAnInternalBlockObject(volume, false);
// Don't operate on ingested volumes.
VolumeIngestionUtil.checkOperationSupportedOnIngestedVolume(volume,
ResourceOperationTypeEnum.CHANGE_BLOCK_VOLUME_VARRAY, _dbClient);
// Get and validate the volume's project.
URI projectURI = volume.getProject().getURI();
Project project = _permissionsHelper.getObjectById(projectURI, Project.class);
ArgValidator.checkEntity(project, projectURI, false);
_log.info("Found volume project {}", projectURI);
// Verify the user is authorized for the volume's project.
BlockServiceUtils.verifyUserIsAuthorizedForRequest(project, getUserFromContext(), _permissionsHelper);
_log.info("User is authorized for volume's project");
// Verify the current and requested virtual arrays are not the same.
if (volume.getVirtualArray().equals(tgtVarrayURI)) {
throw APIException.badRequests.currentAndRequestedVArrayAreTheSame();
}
// Get and validate the target virtual array.
if (tgtVarray == null) {
tgtVarray = BlockServiceUtils.verifyVirtualArrayForRequest(project,
tgtVarrayURI, uriInfo, _permissionsHelper, _dbClient);
_log.info("Found new VirtualArray {}", tgtVarrayURI);
}
// Make sure that we don't have some pending
// operation against the volume
checkForPendingTasks(Arrays.asList(volume.getTenant().getURI()), Arrays.asList(volume));
// Get the appropriate block service implementation for the
// volume. Note that this same implementation is used to
// execute the change. If it is possible that volumes
// with multiple implementations can be selected for a
// varray change, then we would need a map of the
// implementation to use for a given volume. However,
// currently only VPLEX volumes can be moved, so valid
// volumes for a varray change will always have the same
// implementation.
blockServiceAPI = getBlockServiceImpl(volume);
// Verify that the virtual array change is allowed for the
// requested volume and virtual array.
blockServiceAPI.verifyVarrayChangeSupportedForVolumeAndVarray(volume, tgtVarray);
_log.info("Virtual array change is supported for requested volume and varray");
// All volumes must be a CG or none of the volumes can be
// in a CG. After processing individual volumes, if the
// volumes are in a CG, then we make sure all volumes in the
// CG and only the volumes in the CG are passed.
URI cgURI = volume.getConsistencyGroup();
if ((cg == null) && (!foundVolumeNotInCG)) {
if (!isNullURI(cgURI)) {
cg = _permissionsHelper.getObjectById(cgURI, BlockConsistencyGroup.class);
_log.info("All volumes should be in CG {}:{}", cgURI, cg.getLabel());
cgVolumes.addAll(blockServiceAPI.getActiveCGVolumes(cg));
} else {
_log.info("No volumes should be in CGs");
foundVolumeNotInCG = true;
}
} else if (((cg != null) && (isNullURI(cgURI))) ||
((foundVolumeNotInCG) && (!isNullURI(cgURI)))) {
// A volume was in a CG, so all volumes must be in a CG.
if (cg != null) {
// Volumes should all be in the CG and this one is not.
_log.error("Volume {}:{} is not in the CG", volumeURI, volume.getLabel());
} else {
_log.error("Volume {}:{} is in CG {}", new Object[] { volumeURI,
volume.getLabel(), cgURI });
}
throw APIException.badRequests.mixedVolumesinCGForVarrayChange();
}
// Add the volume to the list
volumes.add(volume);
}
// If the volumes are in a CG verify that they are
// all in the same CG and all volumes are passed.
if (cg != null) {
// all volume in CG must have been passed.
_log.info("Verify all volumes in CG {}:{}", cg.getId(), cg.getLabel());
URI storageId = cg.getStorageController();
if (!NullColumnValueGetter.isNullURI(storageId)) {
StorageSystem storage = _dbClient.queryObject(StorageSystem.class, storageId);
if (DiscoveredDataObject.Type.vplex.name().equals(storage.getSystemType())) {
// For VPlex, the volumes should include all volumes, which are in the same backend storage system,
// in the CG.
if (!VPlexUtil.verifyVolumesInCG(volumes, cgVolumes, _dbClient)) {
throw APIException.badRequests.cantChangeVarrayNotAllCGVolumes();
}
} else {
verifyVolumesInCG(volumes, cgVolumes);
}
} else {
verifyVolumesInCG(volumes, cgVolumes);
}
}
// Create a task for each volume and set the initial
// task state to pending.
for (Volume volume : volumes) {
Operation op = _dbClient.createTaskOpStatus(Volume.class, volume.getId(), taskId,
ResourceOperationTypeEnum.CHANGE_BLOCK_VOLUME_VARRAY);
TaskResourceRep resourceTask = toTask(volume, taskId, op);
taskList.addTask(resourceTask);
}
// Now execute the varray change for the volumes.
if (cg != null) {
try {
// When the volumes are part of a CG, executed as a single workflow.
blockServiceAPI.changeVirtualArrayForVolumes(volumes, cg, cgVolumes, tgtVarray, taskId);
_log.info("Executed virtual array change for volumes");
} catch (InternalException | APIException e) {
// Fail all the tasks.
String errorMsg = String.format("Volume virtual array change error: %s", e.getMessage());
_log.error(errorMsg);
for (TaskResourceRep resourceTask : taskList.getTaskList()) {
resourceTask.setState(Operation.Status.error.name());
resourceTask.setMessage(errorMsg);
_dbClient.error(Volume.class, resourceTask.getResource().getId(), taskId, e);
}
} catch (Exception e) {
// Fail all the tasks.
String errorMsg = String.format("Volume virtual array change error: %s", e.getMessage());
_log.error(errorMsg);
for (TaskResourceRep resourceTask : taskList.getTaskList()) {
resourceTask.setState(Operation.Status.error.name());
resourceTask.setMessage(errorMsg);
_dbClient.error(Volume.class, resourceTask.getResource().getId(), taskId,
InternalServerErrorException.internalServerErrors
.unexpectedErrorDuringVarrayChange(e));
}
}
} else {
// When the volumes are not in a CG, then execute as individual workflows.
for (Volume volume : volumes) {
try {
blockServiceAPI.changeVirtualArrayForVolumes(Arrays.asList(volume), cg, cgVolumes, tgtVarray, taskId);
_log.info("Executed virtual array change for volume {}", volume.getId());
} catch (InternalException | APIException e) {
String errorMsg = String.format("Volume virtual array change error: %s", e.getMessage());
_log.error(errorMsg);
for (TaskResourceRep resourceTask : taskList.getTaskList()) {
// Fail the correct task.
if (resourceTask.getResource().getId().equals(volume.getId())) {
resourceTask.setState(Operation.Status.error.name());
resourceTask.setMessage(errorMsg);
_dbClient.error(Volume.class, resourceTask.getResource().getId(), taskId, e);
}
}
} catch (Exception e) {
// Fail all the tasks.
String errorMsg = String.format("Volume virtual array change error: %s", e.getMessage());
_log.error(errorMsg);
for (TaskResourceRep resourceTask : taskList.getTaskList()) {
// Fail the correct task.
if (resourceTask.getResource().getId().equals(volume.getId())) {
resourceTask.setState(Operation.Status.error.name());
resourceTask.setMessage(errorMsg);
_dbClient.error(Volume.class, resourceTask.getResource().getId(), taskId,
InternalServerErrorException.internalServerErrors
.unexpectedErrorDuringVarrayChange(e));
}
}
}
}
}
return taskList;
}
/**
* Verifies that the passed volumes correspond to the passed volumes from
* a consistency group.
*
* @param volumes
* The volumes to verify
* @param cgVolumes
* The list of active volumes in a CG.
*/
private void verifyVolumesInCG(List<Volume> volumes, List<Volume> cgVolumes) {
// The volumes counts must match. If the number of volumes
// is less, then not all volumes in the CG were passed.
if (volumes.size() < cgVolumes.size()) {
throw APIException.badRequests.cantChangeVarrayNotAllCGVolumes();
}
// Make sure only the CG volumes are selected.
for (Volume volume : volumes) {
boolean found = false;
for (Volume cgVolume : cgVolumes) {
if (volume.getId().equals(cgVolume.getId())) {
found = true;
break;
}
}
if (!found) {
_log.error("Volume {}:{} not found in CG", volume.getId(), volume.getLabel());
throw APIException.badRequests.cantChangeVarrayVolumeIsNotInCG();
}
}
}
/**
* Returns a list of the migrations associated with the volume identified by
* the id specified in the request.
*
*
* @prereq none
*
* @param id
* the URN of a ViPR volume.
*
* @brief Show volume migrations
* @return A list specifying the id, name, and self link of the migrations
* associated with the volume
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/migrations")
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.SYSTEM_MONITOR, Role.TENANT_ADMIN })
public MigrationList getVolumeMigrations(@PathParam("id") URI id) {
ArgValidator.checkFieldUriType(id, Volume.class, "id");
MigrationList volumeMigrations = new MigrationList();
URIQueryResultList migrationURIs = new URIQueryResultList();
_dbClient.queryByConstraint(ContainmentConstraint.Factory.getMigrationVolumeConstraint(id), migrationURIs);
Iterator<URI> migrationURIsIter = migrationURIs.iterator();
while (migrationURIsIter.hasNext()) {
URI migrationURI = migrationURIsIter.next();
Migration migration = _permissionsHelper.getObjectById(migrationURI,
Migration.class);
if (BulkList.MigrationFilter.isUserAuthorizedForMigration(migration, getUserFromContext(), _permissionsHelper)) {
volumeMigrations.getMigrations().add(toNamedRelatedResource(migration, migration.getLabel()));
}
}
return volumeMigrations;
}
/**
* Retrieve resource representations based on input ids.
*
*
* @prereq none
*
* @param param
* POST data containing the id list.
*
* @brief List data of volume resources
* @return list of representations.
*/
@Override
@POST
@Path("/bulk")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public VolumeBulkRep getBulkResources(BulkIdParam param) {
return (VolumeBulkRep) super.getBulkResources(param);
}
/**
* Return all the export information related to volume ids passed.
* This will be in the form of a list of initiator / target pairs
* for all the initiators that have been paired with a target
* storage port.
*
*
* @prereq none
*
* @param param
* POST data containing the id list.
*
* @brief Show export information for volumes
* @return List of exports
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/exports/bulk")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public ITLBulkRep getVolumesExports(BulkIdParam param) {
List<URI> volumeIdList = param.getIds();
ITLBulkRep list = new ITLBulkRep();
for (URI volumeId : volumeIdList) {
ArgValidator.checkFieldUriType(volumeId, Volume.class, "id");
queryResource(volumeId);
list.getExportList().addAll(
ExportUtils.getBlockObjectInitiatorTargets(volumeId, _dbClient, isIdEmbeddedInURL(volumeId)).getExportList());
}
return list;
}
/**
* Determines whether or not the passed VirtualPool change for the passed Volume is
* supported. Throws a ServiceCodeException when the vpool change is not
* supported.
*
* @param volume
* A reference to the volume.
* @param newVpool
* A reference to the new VirtualPool.
*/
private void verifyVirtualPoolChangeSupportedForVolumeAndVirtualPool(Volume volume, VirtualPool newVpool) {
// Currently, Vpool change is only supported for volumes on
// VPlex storage systems and volumes (both regular and VPLEX i.e. RP+VPLEX) that are currently
// unprotected by RP to a Vpool that has RP, as long as the source volume doesn't have to move.
VirtualPool currentVpool = _dbClient.queryObject(VirtualPool.class, volume.getVirtualPool());
URI systemURI = volume.getStorageController();
StorageSystem system = _dbClient.queryObject(StorageSystem.class, systemURI);
String systemType = system.getSystemType();
StringBuffer notSuppReasonBuff = new StringBuffer();
notSuppReasonBuff.setLength(0);
/**
* Do not support following vpool change operations for the volume part of application
* 1. Move into Vplex
* 2. Add RecoverPoint
* 3. Remove RecoverPoint
* 4. Add SRDF
*/
if (volume.getApplication(_dbClient) != null) {
// Move into VPLEX
if (!VirtualPool.vPoolSpecifiesHighAvailability(currentVpool) && VirtualPool.vPoolSpecifiesHighAvailability(newVpool)) {
notSuppReasonBuff.append("Non VPLEX volumes in applications cannot be moved into VPLEX pools");
throw APIException.badRequests.changeToVirtualPoolNotSupported(newVpool.getLabel(),
notSuppReasonBuff.toString());
}
// Add recoverPoint
if (!VirtualPool.vPoolSpecifiesProtection(currentVpool)
&& VirtualPool.vPoolSpecifiesProtection(newVpool)) {
notSuppReasonBuff.append("Non RP volumes in applications cannot be moved into RP pools");
throw APIException.badRequests.changeToVirtualPoolNotSupported(newVpool.getLabel(),
notSuppReasonBuff.toString());
}
// Remove RecoverPoint
if (VirtualPool.vPoolSpecifiesProtection(currentVpool)
&& !VirtualPool.vPoolSpecifiesProtection(newVpool)) {
notSuppReasonBuff.append("RP volumes in applications cannot be moved into non RP pools");
throw APIException.badRequests.changeToVirtualPoolNotSupported(newVpool.getLabel(),
notSuppReasonBuff.toString());
}
// Add SRDF
if (!VirtualPool.vPoolSpecifiesSRDF(currentVpool) && VirtualPool.vPoolSpecifiesSRDF(newVpool)) {
notSuppReasonBuff.append("volumes in applications cannot be moved into SRDF pools");
throw APIException.badRequests.changeToVirtualPoolNotSupported(newVpool.getLabel(),
notSuppReasonBuff.toString());
}
}
// Check if an Export Path Params change.
if (VirtualPoolChangeAnalyzer.isSupportedPathParamsChange(volume, currentVpool, newVpool,
_dbClient, notSuppReasonBuff)) {
ExportPathUpdater updater = new ExportPathUpdater(_dbClient);
ExportPathParams newParam = new ExportPathParams(newVpool.getNumPaths(),
newVpool.getMinPaths(), newVpool.getPathsPerInitiator());
updater.validateChangePathParams(volume.getId(), newParam);
_log.info("New VPool specifies an Export Path Params change");
return;
}
// Check if it is an Auto-tiering policy change.
notSuppReasonBuff.setLength(0);
if (VirtualPoolChangeAnalyzer.isSupportedAutoTieringPolicyAndLimitsChange(volume, currentVpool, newVpool,
_dbClient, notSuppReasonBuff)) {
_log.info("New VPool specifies an Auto-tiering policy change");
return;
}
if (VirtualPoolChangeAnalyzer.isSupportedReplicationModeChange(currentVpool, newVpool,
notSuppReasonBuff)) {
_log.info("New VPool specifies a replication mode change");
return;
}
if (DiscoveredDataObject.Type.vplex.name().equals(systemType)) {
_log.info("Volume is a VPlex virtual volume.");
// Vpool must specify a valid VPlex high availability.
// The volume will still be highly available, but maybe
// the Vpool specifies a different grade of disk drives.
if (!VirtualPool.vPoolSpecifiesHighAvailability(newVpool)) {
_log.info("New VirtualPool does not specify VPlex high availability.");
throw new ServiceCodeException(ServiceCode.API_VOLUME_VPOOL_CHANGE_DISRUPTIVE,
"New VirtualPool {0} does not specify vplex high availability",
new Object[] { newVpool.getId() });
} else {
notSuppReasonBuff.setLength(0);
// If this a RP+VPLEX Journal check to see if a straight up VPLEX Data migration is
// allowed.
//
// RP+VPLEX Journals are normally hidden in the UI since they are internal volumes, however they
// can be exposed in the Migration Services catalog to support RP+VPLEX Data Migrations.
if (volume.checkPersonality(Volume.PersonalityTypes.METADATA)) {
if (VirtualPoolChangeAnalyzer.vpoolChangeRequiresMigration(currentVpool, newVpool)) {
verifyVPlexVolumeForDataMigration(volume, currentVpool, newVpool, _dbClient);
return;
}
}
// Check to see if this is a RP protected VPLEX volume and
// if the request is trying to remove RP protection.
if (volume.checkForRp()
&& VirtualPool.vPoolSpecifiesProtection(currentVpool)
&& !VirtualPool.vPoolSpecifiesProtection(newVpool)) {
notSuppReasonBuff.setLength(0);
if (!VirtualPoolChangeAnalyzer.isSupportedRPRemoveProtectionVirtualPoolChange(volume, currentVpool, newVpool,
_dbClient, notSuppReasonBuff)) {
throw APIException.badRequests.changeToVirtualPoolNotSupported(newVpool.getLabel(),
notSuppReasonBuff.toString());
}
} else if (VirtualPool.vPoolSpecifiesRPVPlex(newVpool)) {
notSuppReasonBuff.setLength(0);
// Check to see if any of the operations for protected vpool to protected vpool changes are supported
if (VirtualPool.vPoolSpecifiesRPVPlex(currentVpool)) {
if (VirtualPoolChangeAnalyzer.isSupportedRPVPlexMigrationVirtualPoolChange(volume, currentVpool, newVpool,
_dbClient, notSuppReasonBuff, null)) {
verifyVPlexVolumeForDataMigration(volume, currentVpool, newVpool, _dbClient);
} else if (!VirtualPoolChangeAnalyzer.isSupportedUpgradeToMetroPointVirtualPoolChange(volume, currentVpool,
newVpool,
_dbClient, notSuppReasonBuff)) {
_log.warn("RP Change Protection VirtualPool change for volume is not supported: {}",
notSuppReasonBuff.toString());
throw APIException.badRequests.changeToVirtualPoolNotSupported(newVpool.getLabel(),
notSuppReasonBuff.toString());
}
}
// Otherwise, check to see if we're trying to protect a VPLEX volume.
else if (!VirtualPoolChangeAnalyzer.isSupportedAddRPProtectionVirtualPoolChange(volume, currentVpool, newVpool,
_dbClient, notSuppReasonBuff)) {
_log.warn("RP+VPLEX VirtualPool change for volume is not supported: {}",
notSuppReasonBuff.toString());
throw APIException.badRequests.changeToVirtualPoolNotSupported(newVpool.getLabel(),
notSuppReasonBuff.toString());
} else if (BlockFullCopyUtils.volumeHasFullCopySession(volume, _dbClient)) {
// Full copies not supported for RP protected volumes.
throw APIException.badRequests.volumeForRPVpoolChangeHasFullCopies(volume.getLabel());
}
} else {
VirtualPoolChangeOperationEnum vplexVpoolChangeOperation = VirtualPoolChangeAnalyzer
.getSupportedVPlexVolumeVirtualPoolChangeOperation(volume,
currentVpool, newVpool, _dbClient, notSuppReasonBuff);
if (vplexVpoolChangeOperation == null) {
_log.warn("VPlex volume VirtualPool change not supported {}", notSuppReasonBuff.toString());
throw APIException.badRequests.changeToVirtualPoolNotSupported(newVpool.getLabel(),
notSuppReasonBuff.toString());
} else if (VPlexUtil.isVolumeBuiltOnBlockSnapshot(_dbClient, volume)) {
// We will not allow virtual pool change for a VPLEX volume that was
// created using the target volume of a block snapshot.
throw APIException.badRequests.vpoolChangeNotAllowedVolumeIsExposedSnapshot(volume.getId().toString());
} else if (vplexVpoolChangeOperation == VirtualPoolChangeOperationEnum.VPLEX_DATA_MIGRATION) {
verifyVPlexVolumeForDataMigration(volume, currentVpool, newVpool, _dbClient);
}
}
}
} else if (DiscoveredDataObject.Type.vmax.name().equals(systemType)
|| DiscoveredDataObject.Type.vnxblock.name().equals(systemType)
|| DiscoveredDataObject.Type.hds.name().equals(systemType)
|| DiscoveredDataObject.Type.xtremio.name().equals(systemType)
|| DiscoveredDataObject.Type.ibmxiv.name().equals(systemType)
|| DiscoveredDataObject.Type.unity.name().equals(systemType)) {
if (VirtualPool.vPoolSpecifiesHighAvailability(newVpool)) {
// VNX/VMAX import to VPLEX cases
notSuppReasonBuff.setLength(0);
if (!VirtualPoolChangeAnalyzer.isVPlexImport(volume, currentVpool, newVpool, notSuppReasonBuff)
|| (!VirtualPoolChangeAnalyzer.doesVplexVpoolContainVolumeStoragePool(volume, newVpool, notSuppReasonBuff))) {
_log.warn("VNX/VMAX cos change for volume is not supported: {}",
notSuppReasonBuff.toString());
throw APIException.badRequests.changeToVirtualPoolNotSupported(newVpool.getLabel(),
notSuppReasonBuff.toString());
}
if (volume.isVolumeExported(_dbClient)) {
throw APIException.badRequests.cannotImportExportedVolumeToVplex(volume.getId());
} else if (BlockFullCopyUtils.volumeHasFullCopySession(volume, _dbClient)) {
// The backend would have a full copy, but the VPLEX volume would not.
throw APIException.badRequests.volumeForVpoolChangeHasFullCopies(volume.getLabel());
} else {
// Can't be imported if it has snapshot sessions, because we
// don't currently support these behind VPLEX.
List<BlockSnapshotSession> snapSessions = CustomQueryUtility.queryActiveResourcesByConstraint(_dbClient,
BlockSnapshotSession.class, ContainmentConstraint.Factory.getParentSnapshotSessionConstraint(volume.getId()));
if (!snapSessions.isEmpty()) {
throw APIException.badRequests.cannotImportVolumeWithSnapshotSessions(volume.getLabel());
}
}
} else if (VirtualPool.vPoolSpecifiesProtection(newVpool)) {
// VNX/VMAX import to RP cases (currently one)
notSuppReasonBuff.setLength(0);
if (!VirtualPoolChangeAnalyzer.isSupportedAddRPProtectionVirtualPoolChange(volume, currentVpool, newVpool, _dbClient,
notSuppReasonBuff)) {
_log.warn("VirtualPool change to Add RP Protection for volume is not supported: {}",
notSuppReasonBuff.toString());
throw APIException.badRequests.changeToVirtualPoolNotSupported(newVpool.getLabel(),
notSuppReasonBuff.toString());
} else if (BlockFullCopyUtils.volumeHasFullCopySession(volume, _dbClient)) {
// Full copies not supported for RP protected volumes.
throw APIException.badRequests.volumeForRPVpoolChangeHasFullCopies(volume.getLabel());
} else {
// Can't add RP if it has snapshot sessions, because we
// don't currently support these for RP protected volumes.
List<BlockSnapshotSession> snapSessions = CustomQueryUtility.queryActiveResourcesByConstraint(_dbClient,
BlockSnapshotSession.class, ContainmentConstraint.Factory.getParentSnapshotSessionConstraint(volume.getId()));
if (!snapSessions.isEmpty()) {
throw APIException.badRequests.volumeForRPVpoolChangeHasSnapshotSessions(volume.getLabel());
}
}
} else if (VirtualPool.vPoolSpecifiesProtection(currentVpool)
&& !VirtualPool.vPoolSpecifiesProtection(newVpool)) {
notSuppReasonBuff.setLength(0);
if (!VirtualPoolChangeAnalyzer.isSupportedRPRemoveProtectionVirtualPoolChange(volume, currentVpool, newVpool,
_dbClient, notSuppReasonBuff)) {
throw APIException.badRequests.changeToVirtualPoolNotSupported(newVpool.getLabel(),
notSuppReasonBuff.toString());
}
} else if (VirtualPool.vPoolSpecifiesSRDF(newVpool)) {
// VMAX import to SRDF cases (currently one)
notSuppReasonBuff.setLength(0);
if (!VirtualPoolChangeAnalyzer.isSupportedSRDFVolumeVirtualPoolChange(volume, currentVpool, newVpool, _dbClient,
notSuppReasonBuff)) {
_log.warn("VMAX VirtualPool change for volume is not supported: {}",
notSuppReasonBuff.toString());
throw APIException.badRequests.changeToVirtualPoolNotSupported(newVpool.getLabel(),
notSuppReasonBuff.toString());
} else if (BlockFullCopyUtils.volumeHasFullCopySession(volume, _dbClient)) {
// Full copy not supported for volumes with asynchronous copy mode.
Map<URI, VpoolRemoteCopyProtectionSettings> remoteCopySettingsMap = VirtualPool.getRemoteProtectionSettings(newVpool,
_dbClient);
VpoolRemoteCopyProtectionSettings remoteCopyProtectionSettings = remoteCopySettingsMap.values().iterator().next();
if (SupportedCopyModes.ASYNCHRONOUS.toString().equalsIgnoreCase(remoteCopyProtectionSettings.getCopyMode())) {
throw APIException.badRequests.volumeForSRDFVpoolChangeHasFullCopies(volume.getLabel());
}
}
} else if (!NullColumnValueGetter.isNullNamedURI(volume.getSrdfParent())
|| (volume.getSrdfTargets() != null && !volume.getSrdfTargets().isEmpty())) {
// Cannot move SRDF Volume to non SRDF VPool
throw APIException.badRequests.srdfVolumeVPoolChangeToNonSRDFVPoolNotSupported(volume.getId());
} else if (VirtualPool.vPoolSpecifiesMirrors(newVpool, _dbClient)) {
notSuppReasonBuff.setLength(0);
if (!VirtualPoolChangeAnalyzer.isSupportedAddMirrorsVirtualPoolChange(volume, currentVpool, newVpool,
_dbClient, notSuppReasonBuff)) {
_log.warn("VirtualPool change to add continuous copies for volume {} is not supported: {}",
volume.getId(), notSuppReasonBuff.toString());
throw APIException.badRequests.changeToVirtualPoolNotSupported(newVpool.getLabel(),
notSuppReasonBuff.toString());
}
} else {
String errMsg = "there was an invalid property mismatch between source and target vPools.";
_log.error(errMsg);
notSuppReasonBuff.append(errMsg);
throw APIException.badRequests.changeToVirtualPoolNotSupported(newVpool.getLabel(),
notSuppReasonBuff.toString());
}
} else {
_log.info("VirtualPool change volume is not a vplex, vmax or vnxblock volume");
throw new ServiceCodeException(
ServiceCode.API_VOLUME_VPOOL_CHANGE_DISRUPTIVE,
"VirtualPool change is not supported for volume {0}",
new Object[] { volume.getId() });
}
}
/**
* Performs verification on the VPLEX volume to ensure it is a candidate for migration.
*
* @param volume VPLEX volume to check
* @param currentVpool The current vpool where the volume is placed
* @param newVpool The target vpool where the volume will be placed after migration
*/
public static void verifyVPlexVolumeForDataMigration(Volume volume, VirtualPool currentVpool, VirtualPool newVpool, DbClient _dbClient) {
_log.info(String.format("Verifying that the VPlex volume[%s](%s) qualifies for Data Migration"
+ " moving from current vpool [%s](%s) to new vpool [%s](%s).",
volume.getLabel(), volume.getId(),
currentVpool.getLabel(), currentVpool.getId(),
newVpool.getLabel(), newVpool.getId()));
// Determine if source side will be migrated.
boolean migrateSourceVolume = VirtualPoolChangeAnalyzer
.vpoolChangeRequiresMigration(currentVpool, newVpool);
// Determine if HA side will be migrated.
boolean migrateHAVolume = false;
VirtualPool currentHaVpool = VirtualPoolChangeAnalyzer
.getHaVpool(currentVpool, _dbClient);
if (currentHaVpool != null) {
VirtualPool newHaVpool = VirtualPoolChangeAnalyzer
.getNewHaVpool(currentVpool, newVpool, _dbClient);
migrateHAVolume = VirtualPoolChangeAnalyzer
.vpoolChangeRequiresMigration(currentHaVpool, newHaVpool);
}
// Verify the VPLEX volume structure. Ingested volumes
// can only be migrated if the component structure of
// the volume is supported by ViPR.
verifyVPlexVolumeStructureForDataMigration(volume, currentVpool,
migrateSourceVolume, migrateHAVolume, _dbClient);
// Check for snaps, mirrors, and full copies
if (migrateSourceVolume) {
// The vpool change is a data migration and the source
// side backend volume will be migrated. If the volume
// has snapshots, then the vpool change will not be
// allowed because VPLEX snapshots are just snapshots
// of this backend volume. The user would lose all
// snapshots if we allowed the vpool change. The user
// must explicitly go and delete their snapshots first.
// the same is true for volumes that have full copies
// from which they are not detached and also full copy
// volumes that are not detached from their source. If
// not detached a full copy session still exists between
// this backend volume and some other volume.
//
// Note: We make this validation here instead of in the
// verification VirtualPoolChangeAnalyzer method
// "getSupportedVPlexVolumeVirtualPoolChangeOperation"
// because this method is called from not only the API
// to change the volume virtual pool, but also the API
// that determines the virtual pools to which a volume
// can be changed. The latter API is used by the UI to
// populate the list of volumes. We want volumes with
// snaps to appear in the list, so that the user will
// know that if they remove the snapshots, they can
// perform the vpool change.
Volume srcVolume = VPlexUtil.getVPLEXBackendVolume(volume, true, _dbClient, false);
if (srcVolume != null) {
// Has a source volume, so not ingested.
List<BlockSnapshot> snapshots = CustomQueryUtility
.queryActiveResourcesByConstraint(_dbClient,
BlockSnapshot.class, ContainmentConstraint.Factory
.getVolumeSnapshotConstraint(srcVolume.getId()));
if (!snapshots.isEmpty()) {
throw APIException.badRequests
.volumeForVpoolChangeHasSnaps(volume.getId().toString());
}
// Check for snapshot sessions for the volume.
if (BlockSnapshotSessionUtils.volumeHasSnapshotSession(srcVolume, _dbClient)) {
throw APIException.badRequests.volumeForVpoolChangeHasSnaps(volume.getLabel());
}
// Can't migrate the source side backend volume if it is
// has full copy sessions.
if (BlockFullCopyUtils.volumeHasFullCopySession(srcVolume, _dbClient)) {
throw APIException.badRequests.volumeForVpoolChangeHasFullCopies(volume.getLabel());
}
}
// If the volume has mirrors then Vpool change will not
// be allowed. User needs to explicitly delete mirrors first.
// This is applicable for both Local and Distributed volumes.
// For distributed volume getMirrors will get mirror if any
// on source or HA side.
StringSet mirrorURIs = volume.getMirrors();
if (mirrorURIs != null && !mirrorURIs.isEmpty()) {
List<VplexMirror> mirrors = _dbClient.queryObject(VplexMirror.class,
StringSetUtil.stringSetToUriList(mirrorURIs));
if (mirrors != null && !mirrors.isEmpty()) {
throw APIException.badRequests
.volumeForVpoolChangeHasMirrors(volume.getId().toString(), volume.getLabel());
}
}
}
}
/**
* Verifies that the component structure of an ingested VPLEX volume is such
* that ViPR can support a data migration of the backend volumes. The
* structure must be in the 1-1-1 format in which VGIPR creates VPLEX
* volumes. That is, 1 backend storage volumes is consumed by 1 extent,
* which in turn is consumed buy 1 local device. Note that this function
* will make calls to the VPLEX to determine the volume's structure.
*
* @param volume
* A reference to a VPLEX volume.
* @param currentVpool
* The vpool for the VPLEX volume.
* @param migrateSourceVolume
* true if the source side requires migration.
* @param migrateHAVolume
* true if the HA side requires migration.
*/
private static void verifyVPlexVolumeStructureForDataMigration(Volume volume,
VirtualPool currentVpool, boolean migrateSourceVolume, boolean migrateHAVolume, DbClient _dbClient) {
boolean structureOK = true;
if (volume.isIngestedVolumeWithoutBackend(_dbClient)) {
if (migrateSourceVolume && migrateHAVolume) {
structureOK = VPlexDeviceController.migrationSupportedForVolume(volume,
null, _dbClient);
} else if (migrateSourceVolume) {
structureOK = VPlexDeviceController.migrationSupportedForVolume(volume,
volume.getVirtualArray(), _dbClient);
} else if (migrateHAVolume) {
structureOK = VPlexDeviceController.migrationSupportedForVolume(volume,
VirtualPoolChangeAnalyzer.getHaVarrayURI(currentVpool),
_dbClient);
}
}
if (!structureOK) {
throw APIException.badRequests.invalidStructureForIngestedVolume(volume
.getLabel());
}
}
/**
* Returns a reference to the BlockServiceApi that should be used to execute
* change the VirtualPool for the passed volume to the passed VirtualPool.
*
* @param volume
* A reference to the volume.
* @param vpool
* A reference to the VirtualPool.
*
* @return A reference to the BlockServiceApi that should be used execute
* the VirtualPool change for the volume.
*/
private BlockServiceApi getBlockServiceImplForVirtualPoolChange(Volume volume, VirtualPool vpool) {
URI protectionSystemURI = isNullURI(volume.getProtectionController()) ? null : volume
.getProtectionController();
URI storageSystemURI = volume.getStorageController();
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storageSystemURI);
String systemType = storageSystem.getSystemType();
if (protectionSystemURI != null
|| VirtualPool.vPoolSpecifiesProtection(vpool)
|| (volume.checkForRp() && !VirtualPool.vPoolSpecifiesProtection(vpool))) {
// Assume RP for now if the volume is associated with an
// RP controller regardless of the VirtualPool change.
// Also if the volume is unprotected currently and the vpool specifies protection.
// Or if the volume is protected by RP and we're looking to move to a vpool without
// protection.
_log.info("Returning RP block service implementation.");
return _blockServiceApis.get(DiscoveredDataObject.Type.rp.name());
} else {
if ((DiscoveredDataObject.Type.vplex.name().equals(systemType)) ||
(VirtualPool.vPoolSpecifiesHighAvailability(vpool))) {
_log.info("Returning VPlex block service implementation.");
return getBlockServiceImpl(DiscoveredDataObject.Type.vplex.name());
} else if ((DiscoveredDataObject.Type.vnxblock.name().equals(systemType) ||
DiscoveredDataObject.Type.vmax.name().equals(systemType)) &&
VirtualPool.vPoolSpecifiesSRDF(vpool)) {
_log.info("Returning SRDF service implementation.");
return getBlockServiceImpl(SRDF);
} else if ((DiscoveredDataObject.Type.vnxblock.name().equals(systemType) ||
DiscoveredDataObject.Type.vmax.name().equals(systemType)) &&
VirtualPool.vPoolSpecifiesMirrors(vpool, _dbClient)) {
_log.info("Returning mirror service implementation.");
return getBlockServiceImpl("mirror");
} else {
// If it's not a VPlex volume and the VirtualPool does not
// specify high availability, then just assume the
// default implementation for now.
_log.info("Returning default service implementation.");
return getBlockServiceImpl("default");
}
}
}
/**
* Gets and verifies the VirtualPool passed in the request.
*
* TODO: Reuse the existing function (getVirtualPoolForVolumeCreateRequest) once the
* capabilities removal is completed by Stalin, but rename the function to just
* (getVirtualPoolForRequest).
*
* @param project
* A reference to the project.
* @param cosURI
* The URI of the VirtualPool.
* @param dbClient
* Reference to a database client.
* @param permissionsHelper
* Reference to a permissions helper.
*
* @return A reference to the VirtualPool.
*/
public static VirtualPool getVirtualPoolForRequest(Project project, URI cosURI, DbClient dbClient,
PermissionsHelper permissionsHelper) {
ArgValidator.checkUri(cosURI);
VirtualPool cos = dbClient.queryObject(VirtualPool.class, cosURI);
ArgValidator.checkEntity(cos, cosURI, false);
if (!VirtualPool.Type.block.name().equals(cos.getType())) {
throw APIException.badRequests.virtualPoolNotForFileBlockStorage(VirtualPool.Type.block.name());
}
permissionsHelper.checkTenantHasAccessToVirtualPool(project.getTenantOrg().getURI(), cos);
return cos;
}
/**
* Volume is not a zone level resource
*/
@Override
protected boolean isZoneLevelResource() {
return false;
}
@Override
protected ResourceTypeEnum getResourceType() {
return ResourceTypeEnum.VOLUME;
}
/**
* Get search results by name in zone or project.
*
* @return SearchedResRepList
*/
@Override
protected SearchedResRepList getNamedSearchResults(String name, URI projectId) {
SearchedResRepList resRepList = new SearchedResRepList(getResourceType());
if (projectId == null) {
_dbClient.queryByConstraint(
PrefixConstraint.Factory.getLabelPrefixConstraint(getResourceClass(), name),
resRepList);
} else {
_dbClient.queryByConstraint(
ContainmentPrefixConstraint.Factory.getVolumeUnderProjectConstraint(
projectId, name),
resRepList);
}
return resRepList;
}
/**
* Get search results by project alone.
*
* @return SearchedResRepList
*/
@Override
protected SearchedResRepList getProjectSearchResults(URI projectId) {
SearchedResRepList resRepList = new SearchedResRepList(getResourceType());
_dbClient.queryByConstraint(
ContainmentConstraint.Factory.getProjectVolumeConstraint(projectId),
resRepList);
return resRepList;
}
/**
* Additional search criteria for a volume.
*
* If a matching volume is not found, an empty list is returned.
*
* Parameters - wwn String - WWN of the volume
* - virtual_array String - URI of the source virtual array
* - personality String - source, target, metadata
*/
@Override
protected SearchResults getOtherSearchResults(Map<String, List<String>> parameters, boolean authorized) {
SearchResults result = new SearchResults();
String[] searchCriteria = { SEARCH_WWN, SEARCH_VARRAY, SEARCH_PERSONALITY, SEARCH_PROTECTION };
// Make sure the parameters passed in contain at least one of our search criteria
// Here we search by wwn or virtual_array
boolean found = false;
for (String search : searchCriteria) {
if (parameters.containsKey(search)) {
found = true;
}
}
if (!found) {
throw APIException.badRequests.invalidParameterSearchMissingParameter(getResourceClass().getName(), searchCriteria.toString());
}
// Make sure all parameters are our parameters, otherwise post an exception because we don't support other
// search criteria than our
// own.
String nonVolumeKey = null;
for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
found = false;
for (String search : searchCriteria) {
if (entry.getKey().equals(search)) {
found = true;
}
}
if (!found) {
nonVolumeKey = entry.getKey();
}
}
if (nonVolumeKey != null) {
throw APIException.badRequests.parameterForSearchCouldNotBeCombinedWithAnyOtherParameter(getResourceClass().getName(),
searchCriteria.toString(), nonVolumeKey);
}
boolean simpleSearch = false;
// Now perform individual searches based on the input criteria. These results are stored and joined later if
// there were
// multiple search criteria.
List<List<SearchResultResourceRep>> resRepLists = new ArrayList<List<SearchResultResourceRep>>();
for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
for (String searchValue : entry.getValue()) {
SearchedResRepList resRepList = new SearchedResRepList(getResourceType());
if (entry.getKey().equals(SEARCH_WWN)) {
simpleSearch = true;
String wwn = searchValue;
_dbClient.queryByConstraint(
AlternateIdConstraint.Factory.getVolumeWwnConstraint(wwn.toUpperCase()),
resRepList);
} else if (entry.getKey().equals(SEARCH_VARRAY)) {
simpleSearch = true;
String varrayId = searchValue;
_dbClient.queryByConstraint(
AlternateIdConstraint.Factory.getConstraint(Volume.class, "varray", varrayId),
resRepList);
} else if (entry.getKey().equals(SEARCH_PERSONALITY)) {
simpleSearch = true;
String personality = searchValue;
// Validate the personality type
boolean valid = false;
for (PersonalityTypes personalityType : Volume.PersonalityTypes.values()) {
if (personalityType.toString().equals(personality)) {
valid = true;
}
}
if (!valid) {
throw APIException.badRequests.parameterForSearchHasInvalidSearchValueWithSuggestions(getResourceClass().getName(),
entry.getKey(),
personality,
PersonalityTypes.values());
}
_dbClient.queryByConstraint(
AlternateIdConstraint.Factory.getConstraint(Volume.class, "personality", personality),
resRepList);
}
// Convert to a list; SearchedResRepList is immutable and not really made for what we're doing here.
List<SearchResultResourceRep> repList = new ArrayList<SearchResultResourceRep>();
if (resRepList.iterator() != null) {
for (SearchResultResourceRep res : resRepList) {
repList.add(res);
}
resRepLists.add(repList);
}
}
}
// Now perform a "join" on the resRepList entries to create a single set of resources
Set<SearchResultResourceRep> resRepSet = new HashSet<SearchResultResourceRep>();
for (List<SearchResultResourceRep> resList : resRepLists) {
for (SearchResultResourceRep res : resList) {
resRepSet.add(res);
}
}
// Remove entries that aren't in every collection
for (List<SearchResultResourceRep> resList : resRepLists) {
resRepSet.retainAll(resList);
}
//
// Non-Indexed (manual) query result business logic goes here, after we've already reduced the list
//
boolean advancedQuery = false;
if (parameters.containsKey(SEARCH_PROTECTION)) {
// Prevent the expensive advanced query unless you see certain parameters
advancedQuery = true;
}
// Apply protection search criteria, if applicable
// Keep the volumes in the outer loop so we will never call the DB more than O(n) times where n is the size of
// resRepSet
if (advancedQuery) {
// If no simple Query was run above, then we have to start here. If the caller likes good performance, they
// would include other
// simple parameters, like personality=SOURCE to pre-fill a much smaller list of objects and avoid this.
if (!simpleSearch) {
List<URI> volumes = _dbClient.queryByType(Volume.class, true);
for (URI volumeId : volumes) {
RestLinkRep selfLink = new RestLinkRep("self", RestLinkFactory.newLink(ResourceTypeEnum.VOLUME, volumeId));
resRepSet.add(new SearchResultResourceRep(volumeId, selfLink, null));
}
_log.warn(String.format(
"Performance of Volume search is poor when specifying only %s with no additional search parameters." +
"Search performs faster when combined with other parameters such as %s, %s",
SEARCH_PROTECTION, SEARCH_PERSONALITY, SEARCH_VARRAY));
}
List<SearchResultResourceRep> resToInclude = new ArrayList<SearchResultResourceRep>();
for (SearchResultResourceRep res : resRepSet) {
Volume volume = _dbClient.queryObject(Volume.class, res.getId());
boolean personalityIsSource = volume.getPersonality() == null
|| volume.getPersonality().equalsIgnoreCase(Volume.PersonalityTypes.SOURCE.toString());
boolean ha = volume.getAssociatedVolumes() != null && !volume.getAssociatedVolumes().isEmpty();
boolean srdf = volume.getSrdfTargets() != null && !volume.getSrdfTargets().isEmpty();
boolean rp = volume.getRpTargets() != null && !volume.getRpTargets().isEmpty();
boolean isProtected = personalityIsSource && (ha || srdf || rp);
boolean includeResource = true;
for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
for (String searchValue : entry.getValue()) {
if (entry.getKey().equals(SEARCH_PROTECTION)) {
// Validate the protection parameter
boolean valid = false;
String validProtection[] = { TRUE_STR, FALSE_STR, RP, SRDF, VPLEX, HA };
for (String validValue : validProtection) {
if (validValue.toString().equalsIgnoreCase(searchValue)) {
valid = true;
}
}
if (!valid) {
throw APIException.badRequests.parameterForSearchHasInvalidSearchValueWithSuggestions(getResourceClass()
.getName(),
entry.getKey(),
searchValue,
validProtection);
}
if ((searchValue.equals(TRUE_STR) && !isProtected) ||
(searchValue.equals(FALSE_STR) && isProtected)) {
includeResource = false;
} else if (searchValue.equalsIgnoreCase(RP) && !rp) {
includeResource = false;
} else if ((searchValue.equalsIgnoreCase(VPLEX) || searchValue.equalsIgnoreCase(HA)) && !ha) {
includeResource = false;
} else if (searchValue.equalsIgnoreCase(SRDF) && !srdf) {
includeResource = false;
}
}
}
}
if (includeResource) {
resToInclude.add(res);
}
}
// Reduce the set
resRepSet.retainAll(resToInclude);
}
// Convert to a format consumable by our utilities
List<SearchResultResourceRep> resRepList = new ArrayList<SearchResultResourceRep>();
for (SearchResultResourceRep res : resRepSet) {
resRepList.add(res);
}
if (!authorized) {
Iterator<SearchResultResourceRep> _queryResultIterator = resRepList.iterator();
ResRepFilter<SearchResultResourceRep> resrepFilter = (ResRepFilter<SearchResultResourceRep>) getPermissionFilter(
getUserFromContext(), _permissionsHelper);
SearchedResRepList filteredResRepList = new SearchedResRepList();
filteredResRepList.setResult(
new FilterIterator<SearchResultResourceRep>(_queryResultIterator, resrepFilter));
result.setResource(filteredResRepList);
} else {
result.setResource(resRepList);
}
return result;
}
/**
* Get object specific permissions filter
*
*/
@Override
protected ResRepFilter<? extends RelatedResourceRep> getPermissionFilter(StorageOSUser user,
PermissionsHelper permissionsHelper) {
return new ProjOwnedResRepFilter(user, permissionsHelper, Volume.class);
}
/**
* Get info for protectionSet including owner, parent protectionSet, and child protectionSets
*
*
* @prereq none
*
* @param id
* Volume identifier
* @param pid
* the URN of a ViPR ProtectionSet
*
* @brief Show protection set
* @return ProtectionSet details
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/protection-sets/{pid}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public ProtectionSetRestRep getProtectionSet(@PathParam("id") URI id, @PathParam("pid") URI pid) {
validateProtectionSetUri(id, pid);
_log.info("Getting protection set for ID: " + pid);
ProtectionSet protectionSet = queryProtectionSetResource(pid);
_log.info("Protection set status: " + protectionSet.getProtectionStatus());
return map(protectionSet);
}
/**
* This api allows the user to add new journal volume(s) to a recoverpoint
* consistency group copy
*
* @param param
* POST data containing the journal volume(s) creation information.
*
* @brief Add journal volume(s) to the exiting recoverpoint CG copy
* @return A reference to a BlockTaskList containing a list of
* TaskResourceRep references specifying the task data for the
* journal volume creation tasks.
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/protection/addJournalCapacity")
public TaskList addJournalCapacity(VolumeCreate param) throws InternalException {
ArgValidator.checkFieldNotNull(param, "volume_create");
ArgValidator.checkFieldNotNull(param.getName(), "name");
ArgValidator.checkFieldNotNull(param.getSize(), "size");
ArgValidator.checkFieldNotNull(param.getCount(), "count");
ArgValidator.checkFieldUriType(param.getProject(), Project.class, "project");
// Get and validate the project.
Project project = _permissionsHelper.getObjectById(param.getProject(), Project.class);
ArgValidator.checkEntity(project, param.getProject(), isIdEmbeddedInURL(param.getProject()));
final URI actualId = project.getId();
// Verify the user is authorized.
BlockServiceUtils.verifyUserIsAuthorizedForRequest(project, getUserFromContext(), _permissionsHelper);
// Get and validate the varray
ArgValidator.checkFieldUriType(param.getVarray(), VirtualArray.class, "varray");
VirtualArray varray = BlockServiceUtils.verifyVirtualArrayForRequest(project,
param.getVarray(), uriInfo, _permissionsHelper, _dbClient);
ArgValidator.checkEntity(varray, param.getVarray(), isIdEmbeddedInURL(param.getVarray()));
// Get and validate the journal vPool.
VirtualPool vpool = getVirtualPoolForVolumeCreateRequest(project, param);
VirtualPoolCapabilityValuesWrapper capabilities = new VirtualPoolCapabilityValuesWrapper();
capabilities.put(VirtualPoolCapabilityValuesWrapper.ADD_JOURNAL_CAPACITY, Boolean.TRUE);
// Get the count indicating the number of journal volumes to add. If not
// passed assume 1.
Integer volumeCount = 1;
Long volumeSize = 0L;
if (param.getCount() <= 0) {
throw APIException.badRequests.parameterMustBeGreaterThan("count", 0);
}
if (param.getCount() > MAX_VOLUME_COUNT) {
throw APIException.badRequests.exceedingLimit("count", MAX_VOLUME_COUNT);
}
volumeCount = param.getCount();
capabilities.put(VirtualPoolCapabilityValuesWrapper.RESOURCE_COUNT, volumeCount);
// Validate the requested volume size is greater then 0.
volumeSize = SizeUtil.translateSize(param.getSize());
// Validate the requested volume size is at least 1 GB.
if (volumeSize < GB) {
throw APIException.badRequests.leastVolumeSize("1");
}
capabilities.put(VirtualPoolCapabilityValuesWrapper.SIZE, volumeSize);
// verify quota
long size = volumeCount * SizeUtil.translateSize(param.getSize());
TenantOrg tenant = _dbClient.queryObject(TenantOrg.class, project.getTenantOrg().getURI());
ArgValidator.checkEntity(tenant, project.getTenantOrg().getURI(), false);
CapacityUtils.validateQuotasForProvisioning(_dbClient, vpool, project, tenant, size, "volume");
if (null != vpool.getThinVolumePreAllocationPercentage()
&& 0 < vpool.getThinVolumePreAllocationPercentage()) {
capabilities.put(VirtualPoolCapabilityValuesWrapper.THIN_VOLUME_PRE_ALLOCATE_SIZE, VirtualPoolUtil
.getThinVolumePreAllocationSize(vpool.getThinVolumePreAllocationPercentage(), volumeSize));
}
if (VirtualPool.ProvisioningType.Thin.toString().equalsIgnoreCase(
vpool.getSupportedProvisioningType())) {
capabilities.put(VirtualPoolCapabilityValuesWrapper.THIN_PROVISIONING, Boolean.TRUE);
}
// Get and validate the BlockConsistencyGroup
BlockConsistencyGroup consistencyGroup = queryConsistencyGroup(param.getConsistencyGroup());
// Check that the project and the CG project are the same
final URI expectedId = consistencyGroup.getProject().getURI();
checkProjectsMatch(expectedId, project.getId());
// Validate the CG type is RP
if (!consistencyGroup.getRequestedTypes().contains(BlockConsistencyGroup.Types.RP.toString())) {
throw APIException.badRequests.consistencyGroupIsNotCompatibleWithRequest(
consistencyGroup.getId(), consistencyGroup.getTypes().toString(), BlockConsistencyGroup.Types.RP.toString());
}
capabilities.put(VirtualPoolCapabilityValuesWrapper.BLOCK_CONSISTENCY_GROUP, consistencyGroup.getId());
// Create a unique task id if one is not passed in the request.
String task = UUID.randomUUID().toString();
auditOp(OperationTypeEnum.ADD_JOURNAL_VOLUME, true, AuditLogManager.AUDITOP_BEGIN,
param.getName(), volumeCount, varray.getId().toString(), actualId.toString());
// add the journal capacity to the CG
RPBlockServiceApiImpl blockServiceImpl = (RPBlockServiceApiImpl) getBlockServiceImpl(DiscoveredDataObject.Type.rp.name());
return blockServiceImpl.addJournalCapacity(param, project, varray, vpool, consistencyGroup, capabilities, task);
}
/**
* Perform simple validation-- make sure this volume owns the protection set.
*
* @param vid
* volume ID
* @param id
* the URN of a ViPR protection set
*/
private void validateProtectionSetUri(URI vid, URI id) {
ArgValidator.checkUri(vid);
ArgValidator.checkUri(id);
Volume volume = _dbClient.queryObject(Volume.class, vid);
ArgValidator.checkEntity(volume, vid, isIdEmbeddedInURL(vid));
if (volume.getProtectionSet() == null || !(volume.getProtectionSet().getURI().equals(id))) {
throw APIException.badRequests.invalidVolumeForProtectionSet();
}
}
/**
* queryResource(), but for protection set.
*
* @param id
* the URN of a ViPR ID of protection set
* @return protection set object
*/
private ProtectionSet queryProtectionSetResource(URI id) {
if (id == null) {
return null;
}
ProtectionSet ret = _permissionsHelper.getObjectById(id, ProtectionSet.class);
ArgValidator.checkEntityNotNull(ret, id, isIdEmbeddedInURL(id));
return ret;
}
public static void checkVolumesParameter(final BulkDeleteParam volumeURIs) {
if (volumeURIs == null || volumeURIs.getIds().isEmpty()) {
throw APIException.badRequests.noVolumesSpecifiedInRequest();
}
}
private void validateContinuousCopyName(String name, Integer count, Volume sourceVolume) {
if (sourceVolume.getMirrors() == null || sourceVolume.getMirrors().isEmpty()) {
return;
}
boolean vplexVolume = checkIfVolumeIsForVplex(sourceVolume.getId());
List<String> dupList = new ArrayList<String>();
for (String mirrorURI : sourceVolume.getMirrors()) {
if (vplexVolume) {
VplexMirror mirror = _dbClient.queryObject(VplexMirror.class, URI.create(mirrorURI));
if (!mirror.getInactive() &&
((count > 1 && mirror.getLabel().matches("^" + name + "\\-\\d+$")) ||
(count == 1 && name.equals(mirror.getLabel())))) {
dupList.add(mirror.getLabel());
}
} else {
BlockMirror mirror = _dbClient.queryObject(BlockMirror.class, URI.create(mirrorURI));
if (null != mirror && !mirror.getInactive() &&
((count > 1 && mirror.getLabel().matches("^" + name + "\\-\\d+$")) ||
(count == 1 && name.equals(mirror.getLabel())))) {
dupList.add(mirror.getLabel());
}
}
}
if (!dupList.isEmpty()) {
throw APIException.badRequests.blockSourceAlreadyHasContinuousCopiesOfSameName(dupList);
}
}
/**
* Validate that none of the passed URIs represents an internal
* volume such as, a VPLEX volume. If so, throw a bad request
* exception unless the SUPPORTS_FORCE flag is present AND force is
* true.
*
* @param dbClient
* Reference to a database client
* @param blockObjectURIs
* A list of blockObject URIs to verify.
* @param force
* boolean value representing whether or not we want to force the operation
*/
public static void validateNoInternalBlockObjects(DbClient dbClient,
List<URI> blockObjectURIs, boolean force) {
if (blockObjectURIs != null) {
for (URI blockObjectURI : blockObjectURIs) {
BlockObject blockObject = BlockObject.fetch(dbClient, blockObjectURI);
BlockServiceUtils.validateNotAnInternalBlockObject(blockObject, force);
}
}
}
/**
* Stop the specified mirror(s) for the source volume
*
*
*
* @param id
* the URN of a ViPR Source volume
* @param copyID
* Copy volume ID, none specified stops all copies
*
* @return TaskList
*/
private TaskList stopMirrors(URI id, URI copyID) {
ArgValidator.checkFieldUriType(id, Volume.class, "id");
Volume sourceVolume = queryVolumeResource(id);
ArgValidator.checkEntity(sourceVolume, id, true);
StringSet mirrors = sourceVolume.getMirrors();
if (mirrors == null || mirrors.isEmpty()) {
throw APIException.badRequests.invalidParameterVolumeHasNoContinuousCopies(sourceVolume.getId());
}
ArrayList<URI> mirrorList = null;
if (copyID != null) {
ArgValidator.checkFieldUriType(copyID, BlockMirror.class, "copyID");
BlockMirror mirror = queryMirror(copyID);
ArgValidator.checkEntity(mirror, copyID, true);
if (!mirror.getSource().getURI().equals(id)) {
throw APIException.badRequests.invalidParameterBlockCopyDoesNotBelongToVolume(copyID, id);
} else {
mirrorList = new ArrayList();
mirrorList.add(mirror.getId());
}
}
String task = UUID.randomUUID().toString();
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, sourceVolume.getStorageController());
BlockServiceApi blockServiceApi = getBlockServiceImpl("mirror");
return blockServiceApi.stopNativeContinuousCopies(storageSystem, sourceVolume, mirrorList, task);
}
/**
* Stop the specified vplex mirror(s) for the source volume
*
* @param id
* the URN of a ViPR Source volume
* @param copyID
* Copy volume ID, none specified stops all copies
*
* @return TaskList
*/
private TaskList stopVplexMirrors(URI id, URI copyID) {
ArgValidator.checkFieldUriType(id, Volume.class, "id");
Volume sourceVolume = queryVolumeResource(id);
ArgValidator.checkEntity(sourceVolume, id, true);
StringSet mirrors = sourceVolume.getMirrors();
if (mirrors == null || mirrors.isEmpty()) {
throw APIException.badRequests.invalidParameterVolumeHasNoContinuousCopies(sourceVolume.getId());
}
ArrayList<URI> mirrorList = null;
if (copyID != null) {
ArgValidator.checkFieldUriType(copyID, VplexMirror.class, "copyID");
VplexMirror mirror = queryVplexMirror(copyID);
ArgValidator.checkEntity(mirror, copyID, true);
if (!mirror.getSource().getURI().equals(id)) {
throw APIException.badRequests.invalidParameterBlockCopyDoesNotBelongToVolume(copyID, id);
} else {
mirrorList = new ArrayList();
mirrorList.add(mirror.getId());
}
}
String task = UUID.randomUUID().toString();
BlockServiceApi blockServiceApi;
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, sourceVolume.getStorageController());
blockServiceApi = getBlockServiceImpl(DiscoveredDataObject.Type.vplex.name());
return blockServiceApi.stopNativeContinuousCopies(storageSystem, sourceVolume, mirrorList, task);
}
/**
* Start the specified mirror(s) for the source volume
*
* @param id
* the URN of a ViPR Source volume
* @param copy
* copyID Copy volume ID, none specified starts all copies
*
* @return TaskList
*/
private TaskList startMirrors(URI id, NativeContinuousCopyCreate copy) {
String taskId = UUID.randomUUID().toString();
int count = 1;
if (copy.getCount() != null) {
count = copy.getCount();
}
Volume sourceVolume = queryVolumeResource(id);
// Don't operate on VPLEX backend or RP Journal volumes.
BlockServiceUtils.validateNotAnInternalBlockObject(sourceVolume, false);
// Make sure that we don't have some pending
// operation against the volume
checkForPendingTasks(Arrays.asList(sourceVolume.getTenant().getURI()), Arrays.asList(sourceVolume));
if (count <= 0) {
throw APIException.badRequests.invalidParameterRangeLessThanMinimum("count", count, 1);
}
ArgValidator.checkEntity(sourceVolume, id, isIdEmbeddedInURL(id));
validateContinuousCopyName(copy.getName(), count, sourceVolume);
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, sourceVolume.getStorageController());
VirtualPool sourceVPool = _dbClient.queryObject(VirtualPool.class, sourceVolume.getVirtualPool());
validateMirrorCount(sourceVolume, sourceVPool, count);
// validate VMAX3 source volume for active snap sessions.
if (storageSystem != null && storageSystem.checkIfVmax3()) {
BlockServiceUtils.validateVMAX3ActiveSnapSessionsExists(sourceVolume.getId(), _dbClient, MIRRORS);
}
VirtualPoolCapabilityValuesWrapper capabilities = new VirtualPoolCapabilityValuesWrapper();
capabilities.put(VirtualPoolCapabilityValuesWrapper.RESOURCE_COUNT, count);
capabilities.put(VirtualPoolCapabilityValuesWrapper.SIZE, sourceVolume.getCapacity());
capabilities.put(VirtualPoolCapabilityValuesWrapper.THIN_PROVISIONING, sourceVolume.getThinlyProvisioned());
capabilities.put(VirtualPoolCapabilityValuesWrapper.THIN_VOLUME_PRE_ALLOCATE_SIZE,
sourceVolume.getThinVolumePreAllocationSize());
BlockServiceApi serviceApi = null;
if ((storageSystem != null)
&& (DiscoveredDataObject.Type.vplex.name().equals(storageSystem
.getSystemType()))) {
serviceApi = getBlockServiceImpl(storageSystem.getSystemType());
} else {
serviceApi = getBlockServiceImpl("mirror");
}
return serviceApi.startNativeContinuousCopies(storageSystem, sourceVolume,
sourceVPool, capabilities, copy, taskId);
}
/**
* This method validates if the count requested by user to create
* mirror(s) for a volume is valid.
*
* @param sourceVolume
* The reference to volume for which mirrors needs to be created
* @param sourceVPool
* The reference to virtual pool to which volume is is associated
* @param count
* The number of mirrors requested to be created
*/
private void validateMirrorCount(Volume sourceVolume, VirtualPool sourceVPool, int count) {
int currentMirrorCount = (sourceVolume.getMirrors() == null || sourceVolume.getMirrors().isEmpty()) ? 0
: sourceVolume.getMirrors().size();
int requestedMirrorCount = currentMirrorCount + count;
if (sourceVPool.getHighAvailability() != null
&& sourceVPool.getHighAvailability().equals(VirtualPool.HighAvailabilityType.vplex_distributed.name())) {
VPlexUtil.validateMirrorCountForVplexDistVolume(sourceVolume, sourceVPool, count, currentMirrorCount, requestedMirrorCount,
_dbClient);
} else if (sourceVPool.getMaxNativeContinuousCopies() < requestedMirrorCount) {
throw APIException.badRequests.invalidParameterBlockMaximumCopiesForVolumeExceeded(sourceVPool.getMaxNativeContinuousCopies(),
sourceVolume.getLabel(), sourceVolume.getId(), currentMirrorCount);
}
}
/**
* Pause the specified mirror(s) for the source volume
*
*
*
* @param id
* the URN of a ViPR Source volume
* @param sync
* flag for pause operation; true=split, false=fracture
* @param copyID
* copyID Copy volume ID, none specified pauses all copies
*
* @return TaskList
*/
private TaskList pauseMirrors(URI id, String sync, URI copyID) {
Volume sourceVolume = queryVolumeResource(id);
ArgValidator.checkEntity(sourceVolume, id, true);
StringSet mirrors = sourceVolume.getMirrors();
if (mirrors == null || mirrors.isEmpty()) {
throw APIException.badRequests.invalidParameterVolumeHasNoContinuousCopies(sourceVolume.getId());
}
ArrayList<BlockMirror> mirrorList = null;
if (copyID != null) {
BlockMirror mirror = queryMirror(copyID);
ArgValidator.checkEntity(mirror, copyID, true);
if (!mirror.getSource().getURI().equals(id)) {
throw APIException.badRequests.invalidParameterBlockCopyDoesNotBelongToVolume(copyID, id);
} else {
mirrorList = new ArrayList();
mirrorList.add(mirror);
}
}
if (sync != null) {
ArgValidator.checkFieldValueFromArrayIgnoreCase(sync, ProtectionOp.SYNC.getRestOp(), TRUE_STR, FALSE_STR);
}
Boolean syncParam = Boolean.parseBoolean(sync);
String task = UUID.randomUUID().toString();
StorageSystem device = _dbClient.queryObject(StorageSystem.class, sourceVolume.getStorageController());
BlockServiceApi blockServiceApi = getBlockServiceImpl("mirror");
auditOp(OperationTypeEnum.FRACTURE_VOLUME_MIRROR, true, AuditLogManager.AUDITOP_BEGIN,
mirrorList == null ? mirrors : mirrorList, sync);
return blockServiceApi.pauseNativeContinuousCopies(device, sourceVolume, mirrorList, syncParam, task);
}
/**
* Resume the specified mirror(s) for the source volume
*
*
*
* @param id
* the URN of a ViPR Source volume
* @param copyID
* Copy volume ID, none specified resumes all copies
*
* @return TaskList
*/
private TaskList resumeMirrors(URI id, URI copyID) {
ArgValidator.checkFieldUriType(id, Volume.class, "id");
Volume sourceVolume = queryVolumeResource(id);
ArgValidator.checkEntity(sourceVolume, id, true);
StringSet mirrors = sourceVolume.getMirrors();
if (mirrors == null || mirrors.isEmpty()) {
throw APIException.badRequests.invalidParameterVolumeHasNoContinuousCopies(sourceVolume.getId());
}
ArrayList<BlockMirror> mirrorList = null;
if (copyID != null) {
ArgValidator.checkFieldUriType(copyID, BlockMirror.class, "copyID");
BlockMirror mirror = queryMirror(copyID);
ArgValidator.checkEntity(mirror, copyID, true);
if (!mirror.getSource().getURI().equals(id)) {
throw APIException.badRequests.invalidParameterBlockCopyDoesNotBelongToVolume(copyID, id);
} else {
mirrorList = new ArrayList();
mirrorList.add(mirror);
}
}
String task = UUID.randomUUID().toString();
StorageSystem device = _dbClient.queryObject(StorageSystem.class, sourceVolume.getStorageController());
BlockServiceApi blockServiceApi = getBlockServiceImpl("mirror");
auditOp(OperationTypeEnum.RESUME_VOLUME_MIRROR, true, AuditLogManager.AUDITOP_BEGIN,
mirrors);
return blockServiceApi.resumeNativeContinuousCopies(device, sourceVolume, mirrorList, task);
}
/**
* Verify that all the copy IDs passed for a protection type are either
* set to valid URIs, or none are set. A combination of the two is not allowed.
* When none are set the operation is performed on all copies for the specified source volume.
*
* @param param
* List of copies to verify
*/
private void verifyCopyIDs(CopiesParam param) {
boolean rpEmpty = false;
boolean rpSet = false;
boolean nativeEmpty = false;
boolean nativeSet = false;
boolean srdfEmpty = false;
boolean srdfSet = false;
// Process the list of copies to ensure either all are set or all are empty
for (Copy copy : param.getCopies()) {
URI copyID = copy.getCopyID();
if (URIUtil.isValid(copyID)) {
if (copy.getType().equalsIgnoreCase(TechnologyType.RP.toString())) {
rpEmpty = true;
} else if (copy.getType().equalsIgnoreCase(TechnologyType.NATIVE.toString())) {
nativeEmpty = true;
} else if (copy.getType().equalsIgnoreCase(TechnologyType.SRDF.toString())) {
srdfEmpty = true;
}
} else {
if (copy.getType().equalsIgnoreCase(TechnologyType.RP.toString())) {
rpSet = true;
} else if (copy.getType().equalsIgnoreCase(TechnologyType.NATIVE.toString())) {
nativeSet = true;
} else if (copy.getType().equalsIgnoreCase(TechnologyType.SRDF.toString())) {
srdfSet = true;
}
}
}
if (rpEmpty && rpSet) {
throw APIException.badRequests.invalidCopyIDCombination(TechnologyType.RP.toString());
} else if (nativeEmpty && nativeSet) {
throw APIException.badRequests.invalidCopyIDCombination(TechnologyType.NATIVE.toString());
} else if (srdfEmpty && srdfSet) {
throw APIException.badRequests.invalidCopyIDCombination(TechnologyType.SRDF.toString());
}
}
private boolean checkIfVolumeIsForVplex(URI id) {
Volume volume = queryVolumeResource(id);
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, volume.getStorageController());
if ((storageSystem != null)
&& (DiscoveredDataObject.Type.vplex.name().equals(storageSystem
.getSystemType()))) {
return true;
} else {
return false;
}
}
/*
* Validate if the physical array that the consistency group bonded to is associated
* with the virtual array
*
* @param consistencyGroup
*
* @param varray virtual array
*/
private void validateCGValidWithVirtualArray(BlockConsistencyGroup consistencyGroup,
VirtualArray varray) {
URI storageSystemUri = consistencyGroup.getStorageController();
if (isNullURI(storageSystemUri)) {
return;
}
URIQueryResultList storagePortURIs = new URIQueryResultList();
URIQueryResultList assignedStoragePortURIs = new URIQueryResultList();
boolean isAssociated = false;
String systemString = storageSystemUri.toString();
// get all assigned storage ports
_dbClient.queryByConstraint(AlternateIdConstraint.Factory
.getVirtualArrayStoragePortsConstraint(varray.getId().toString()),
assignedStoragePortURIs);
isAssociated = anyPortsMatchStorageDevice(assignedStoragePortURIs, systemString);
if (!isAssociated) {
// get all network storage ports in the virtual array
_dbClient.queryByConstraint(AlternateIdConstraint.Factory
.getImplicitVirtualArrayStoragePortsConstraint(varray.getId().toString()),
storagePortURIs);
isAssociated = anyPortsMatchStorageDevice(storagePortURIs, systemString);
}
if (!isAssociated) {
throw APIException.badRequests.invalidParameterConsistencyGroupStorageSystemMismatchVirtualArray(
consistencyGroup.getId(), varray.getId());
}
}
/**
* Check if any port in the list is from the storage system
*
* @param ports
* @param systemUri
* @return
*/
private boolean anyPortsMatchStorageDevice(URIQueryResultList ports, String systemUri) {
boolean isMatched = false;
for (URI spUri : ports) {
StoragePort storagePort = _dbClient.queryObject(StoragePort.class, spUri);
if (storagePort != null) {
URI system = storagePort.getStorageDevice();
if (system != null && system.toString().equals(systemUri)) {
isMatched = true;
break;
}
}
}
return isMatched;
}
protected static boolean isSuspendCopyRequest(String op, Copy copy) {
return ProtectionOp.PAUSE.getRestOp().equalsIgnoreCase(op) &&
(parseBoolean(copy.getSync()) == false);
}
/**
* Determines if the passed volume is an exported HDS volume and if not
* throws an bad request APIException
*
* @param volume
* A reference to a volume.
*/
private void validateSourceVolumeHasExported(Volume volume) {
URI id = volume.getId();
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class,
volume.getStorageController());
if (storageSystem != null
&& DiscoveredDataObject.Type.hds.name().equals(storageSystem.getSystemType())) {
if (!volume.isVolumeExported(_dbClient)) {
throw APIException.badRequests.sourceNotExported(id);
}
}
}
/**
* Determines the class of the Block resources based on its URI.
* This is because, BlockService implements Volume and
* Mirror (BlockMirror and VplexMirror) resources. To query the
* respective objects from DB, we should use the right class type.
*
* @param uriStr
* the uri to determine the right resource class type.
*
* @return returns the correct resource type of the resource.
*/
public static Class<? extends DataObject> getBlockServiceResourceClass(String uriStr) {
Class<? extends DataObject> blockResourceClass = Volume.class;
if (URIUtil.isType(URI.create(uriStr), BlockMirror.class)) {
blockResourceClass = BlockMirror.class;
} else if (URIUtil.isType(URI.create(uriStr), VplexMirror.class)) {
blockResourceClass = VplexMirror.class;
}
return blockResourceClass;
}
/**
* Given a list of volumes, verify that any consistency groups associated with its volumes
* are fully specified, i.e. the list contains all the members of a consistency group.
*
* @param volumes
* @param targetVPool
*/
private void verifyAllVolumesInCGRequirement(List<Volume> volumes, VirtualPool targetVPool) {
StringBuilder errorMsg = new StringBuilder();
boolean failure = false;
Collection<URI> volIds = transform(volumes, fctnDataObjectToID());
Map<URI, Volume> cgId2Volume = new HashMap<>();
try {
// Build map of consistency groups to a single group member representative
for (Volume volume : volumes) {
URI cgId = volume.getConsistencyGroup();
if (!isNullURI(cgId) && !cgId2Volume.containsKey(cgId)) {
cgId2Volume.put(cgId, volume);
}
}
// Verify that all consistency groups are fully specified
for (Map.Entry<URI, Volume> entry : cgId2Volume.entrySet()) {
// Currently, we only care about verifying CG's when adding SRDF protection
if (!isAddingSRDFProtection(entry.getValue(), targetVPool)) {
continue;
}
List<URI> memberIds = _dbClient.queryByConstraint(getVolumesByConsistencyGroup(entry.getKey()));
memberIds.removeAll(volIds);
if (!memberIds.isEmpty()) {
failure = true;
errorMsg.append(entry.getValue().getLabel())
.append(" is missing other consistency group members.\n");
}
}
} finally {
if (failure) {
throw APIException.badRequests.cannotAddSRDFProtectionToPartialCG(errorMsg.toString());
}
}
}
private boolean isAddingSRDFProtection(Volume v, VirtualPool targetVPool) {
return v.getSrdfTargets() == null && VirtualPool.vPoolSpecifiesSRDF(targetVPool);
}
/**
* Validate volume being expanded is not an SRDF volume with snapshots attached,
* which isn't handled.
* Also make sure that the SRDF Copy Mode is not Active.
*
* @param volume
* -- Volume being expanded
* @throws Exception
* if cannot be expanded
*/
private void validateExpandingSrdfVolume(Volume volume) {
if (ControllerUtils.checkIfVolumeHasSnapshot(volume, _dbClient)
|| BlockSnapshotSessionUtils.volumeHasSnapshotSession(volume, _dbClient)) {
throw BadRequestException.badRequests.cannotExpandSRDFVolumeWithSnapshots(volume.getLabel());
}
Volume srdfVolume = volume;
if (volume.isVPlexVolume(_dbClient)) {
// Find associated SRDF volume
srdfVolume = VPlexSrdfUtil.getSrdfVolumeFromVplexVolume(_dbClient, volume);
if (srdfVolume != null) {
if (ControllerUtils.checkIfVolumeHasSnapshot(srdfVolume, _dbClient)
|| BlockSnapshotSessionUtils.volumeHasSnapshotSession(srdfVolume, _dbClient)) {
throw BadRequestException.badRequests.cannotExpandSRDFVolumeWithSnapshots(srdfVolume.getLabel());
}
}
}
// Check target volumes
if (srdfVolume.getSrdfTargets() != null) {
for (String target : srdfVolume.getSrdfTargets()) {
Volume targetVolume = _dbClient.queryObject(Volume.class, URI.create(target));
if (BlockSnapshotSessionUtils.volumeHasSnapshotSession(targetVolume, _dbClient)
|| BlockSnapshotSessionUtils.volumeHasSnapshotSession(targetVolume, _dbClient)) {
throw BadRequestException.badRequests.cannotExpandSRDFVolumeWithSnapshots(targetVolume.getLabel());
}
if (Mode.ACTIVE.equals(Mode.valueOf(targetVolume.getSrdfCopyMode()))) {
throw BadRequestException.badRequests.cannotExpandSRDFActiveVolume(srdfVolume.getLabel());
}
}
}
}
}