/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.api.service.impl.resource;
import static com.emc.storageos.api.mapper.BlockMapper.map;
import static com.emc.storageos.api.mapper.DbObjectMapper.toNamedRelatedResource;
import static com.emc.storageos.api.mapper.TaskMapper.toTask;
import static com.emc.storageos.db.client.constraint.AlternateIdConstraint.Factory.getVolumesByAssociatedId;
import static com.emc.storageos.db.client.util.NullColumnValueGetter.isNullURI;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import javax.ws.rs.Consumes;
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.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.api.mapper.BlockMapper;
import com.emc.storageos.api.mapper.DbObjectMapper;
import com.emc.storageos.api.mapper.TaskMapper;
import com.emc.storageos.api.service.impl.placement.PlacementManager;
import com.emc.storageos.api.service.impl.resource.fullcopy.BlockFullCopyManager;
import com.emc.storageos.api.service.impl.resource.fullcopy.BlockFullCopyUtils;
import com.emc.storageos.api.service.impl.resource.snapshot.BlockSnapshotSessionManager;
import com.emc.storageos.api.service.impl.resource.snapshot.BlockSnapshotSessionUtils;
import com.emc.storageos.api.service.impl.resource.utils.BlockServiceUtils;
import com.emc.storageos.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.ContainmentConstraint;
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.BlockSnapshotSession;
import com.emc.storageos.db.client.model.Cluster;
import com.emc.storageos.db.client.model.DataObject;
import com.emc.storageos.db.client.model.DataObject.Flag;
import com.emc.storageos.db.client.model.DiscoveredDataObject;
import com.emc.storageos.db.client.model.DiscoveredDataObject.Type;
import com.emc.storageos.db.client.model.Host;
import com.emc.storageos.db.client.model.Operation;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.model.Task;
import com.emc.storageos.db.client.model.VirtualArray;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.model.VolumeGroup;
import com.emc.storageos.db.client.model.VolumeGroup.VolumeGroupRole;
import com.emc.storageos.db.client.model.util.TaskUtils;
import com.emc.storageos.db.client.util.CustomQueryUtility;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.db.client.util.ResourceOnlyNameGenerator;
import com.emc.storageos.model.NamedRelatedResourceRep;
import com.emc.storageos.model.ResourceOperationTypeEnum;
import com.emc.storageos.model.ResourceTypeEnum;
import com.emc.storageos.model.SnapshotList;
import com.emc.storageos.model.TaskList;
import com.emc.storageos.model.TaskResourceRep;
import com.emc.storageos.model.application.VolumeGroupCopySetList;
import com.emc.storageos.model.application.VolumeGroupCopySetParam;
import com.emc.storageos.model.application.VolumeGroupCreateParam;
import com.emc.storageos.model.application.VolumeGroupFullCopyActivateParam;
import com.emc.storageos.model.application.VolumeGroupFullCopyCreateParam;
import com.emc.storageos.model.application.VolumeGroupFullCopyDetachParam;
import com.emc.storageos.model.application.VolumeGroupFullCopyRestoreParam;
import com.emc.storageos.model.application.VolumeGroupFullCopyResynchronizeParam;
import com.emc.storageos.model.application.VolumeGroupList;
import com.emc.storageos.model.application.VolumeGroupRestRep;
import com.emc.storageos.model.application.VolumeGroupSnapshotCreateParam;
import com.emc.storageos.model.application.VolumeGroupSnapshotOperationParam;
import com.emc.storageos.model.application.VolumeGroupSnapshotSessionCreateParam;
import com.emc.storageos.model.application.VolumeGroupSnapshotSessionDeactivateParam;
import com.emc.storageos.model.application.VolumeGroupSnapshotSessionLinkTargetsParam;
import com.emc.storageos.model.application.VolumeGroupSnapshotSessionOperationParam;
import com.emc.storageos.model.application.VolumeGroupSnapshotSessionRelinkTargetsParam;
import com.emc.storageos.model.application.VolumeGroupSnapshotSessionRestoreParam;
import com.emc.storageos.model.application.VolumeGroupSnapshotSessionUnlinkTargetsParam;
import com.emc.storageos.model.application.VolumeGroupUpdateParam;
import com.emc.storageos.model.block.BlockConsistencyGroupSnapshotCreate;
import com.emc.storageos.model.block.BlockSnapshotRestRep;
import com.emc.storageos.model.block.BlockSnapshotSessionList;
import com.emc.storageos.model.block.BlockSnapshotSessionRestRep;
import com.emc.storageos.model.block.NamedVolumeGroupsList;
import com.emc.storageos.model.block.NamedVolumesList;
import com.emc.storageos.model.block.SnapshotSessionCreateParam;
import com.emc.storageos.model.block.SnapshotSessionLinkTargetsParam;
import com.emc.storageos.model.block.SnapshotSessionRelinkTargetsParam;
import com.emc.storageos.model.block.SnapshotSessionUnlinkTargetParam;
import com.emc.storageos.model.block.SnapshotSessionUnlinkTargetsParam;
import com.emc.storageos.model.block.VolumeRestRep;
import com.emc.storageos.model.host.HostList;
import com.emc.storageos.model.host.cluster.ClusterList;
import com.emc.storageos.protectioncontroller.impl.recoverpoint.RPHelper;
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.model.ServiceCoded;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import com.emc.storageos.svcs.errorhandling.resources.InternalException;
import com.emc.storageos.util.VPlexUtil;
import com.emc.storageos.volumecontroller.impl.ControllerUtils;
import com.emc.storageos.volumecontroller.impl.smis.SmisConstants;
import com.google.common.base.Joiner;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import com.google.common.collect.Table.Cell;
/**
* APIs to view, create, modify and remove volume groups
*/
@Path("/volume-groups/block")
@DefaultPermissions(readRoles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, readAcls = { ACL.OWN, ACL.ALL }, writeRoles = {
Role.TENANT_ADMIN }, writeAcls = { ACL.OWN, ACL.ALL })
public class VolumeGroupService extends TaskResourceService {
private static final String VOLUME_GROUP_NAME = "name";
private static final String VOLUME_GROUP_ROLES = "roles";
private static final String MIGRATION_GROUP_BY = "migration_group_by";
private static final String MIGRATION_TYPE = "migration_type";
private static final String EVENT_SERVICE_TYPE = "application";
private static final String ADD_CLUSTERS = "add_clusters";
private static final String REMOVE_CLUSTERS = "remove_clusters";
private static final String ADD_HOSTS = "add_hosts";
private static final String REMOVE_HOSTS = "remove_hosts";
private static final String ADD_VOLUMES = "add_volumes";
private static final String REMOVE_VOLUMES = "remove_volumes";
private static final Set<String> ALLOWED_SYSTEM_TYPES = new HashSet<String>(Arrays.asList(
DiscoveredDataObject.Type.vnxblock.name(),
DiscoveredDataObject.Type.vplex.name(),
DiscoveredDataObject.Type.vmax.name(),
DiscoveredDataObject.Type.xtremio.name(),
DiscoveredDataObject.Type.scaleio.name(),
DiscoveredDataObject.Type.rp.name(),
DiscoveredDataObject.Type.ibmxiv.name(),
DiscoveredDataObject.Type.unity.name()));
private static final Set<String> PENDING_TASK_NAMES = new HashSet<String>(Arrays.asList(
ResourceOperationTypeEnum.UPDATE_VOLUME_GROUP.getName(),
ResourceOperationTypeEnum.RESTORE_CONSISTENCY_GROUP_FULL_COPY.getName(),
ResourceOperationTypeEnum.RESTORE_VOLUME_FULL_COPY.getName(),
ResourceOperationTypeEnum.RESTORE_CONSISTENCY_GROUP_SNAPSHOT.getName(),
ResourceOperationTypeEnum.RESTORE_VOLUME_SNAPSHOT.getName(),
ResourceOperationTypeEnum.RESTORE_SNAPSHOT_SESSION.getName(),
ResourceOperationTypeEnum.DEACTIVATE_VOLUME_SNAPSHOT.getName(),
ResourceOperationTypeEnum.DEACTIVATE_CONSISTENCY_GROUP_SNAPSHOT.getName(),
ResourceOperationTypeEnum.DELETE_SNAPSHOT_SESSION.getName(),
ResourceOperationTypeEnum.DELETE_CONSISTENCY_GROUP_SNAPSHOT_SESSION.getName(),
ResourceOperationTypeEnum.DETACH_VOLUME_FULL_COPY.getName(),
ResourceOperationTypeEnum.DETACH_CONSISTENCY_GROUP_FULL_COPY.getName(),
ResourceOperationTypeEnum.DELETE_BLOCK_VOLUME.getName()));
private static final Set<String> FULL_COPY_NOT_SUPPORTED_SYSTEM_TYPES = new HashSet<String>(Arrays.asList(
Type.xtremio.name(),
Type.unity.name()));
private static final String BLOCK = "block";
private static final String ID_FIELD = "id";
private static final String NAME_FIELD = "name";
private static final String RG_NAME_FIELD = "replication_group_name";
private static final String VOLUMES_FIELD = "volumes";
private static final String VOLUME_FIELD = "volume";
private static final String COPY_SET_NAME_FIELD = "copy_set_name";
private static final String SNAPSHOT_ID_FIELD = "sid";
private static final String SNAPSHOTS_FIELD = "snapshots";
private static final String SNAPSHOT_FIELD = "snapshot";
private static final String SNAPSHOT_SESSIONS_FIELD = "snapshot_sessions";
private static final String SNAPSHOT_SESSION_FIELD = "snapshot_session";
private static enum ReplicaTypeEnum {
FULL_COPY("Full copy"),
SNAPSHOT("Snapshot"),
SNAPSHOT_SESSION("Snapshot Session"),
MIRROR("Continuous copy");
private final String _name;
private ReplicaTypeEnum(String name) {
this._name = name;
}
@Override
public String toString() {
return _name;
}
};
static final Logger log = LoggerFactory.getLogger(VolumeGroupService.class);
// A reference to the placement manager.
private PlacementManager _placementManager;
// A reference to the block consistency group service.
private BlockConsistencyGroupService _blockConsistencyGroupService;
// A reference to the block snapshot service.
private BlockSnapshotService _blockSnapshotService;
// Block service implementations
private static Map<String, BlockServiceApi> _blockServiceApis;
/**
* Setter for the placement manager.
*
* @param placementManager A reference to the placement manager.
*/
public void setPlacementManager(PlacementManager placementManager) {
_placementManager = placementManager;
}
/**
* Setter for the block consistency group service.
*
* @param blockConsistencyGroupService A reference to the block consistency group service.
*/
public void setBlockConsistencyGroupService(BlockConsistencyGroupService blockConsistencyGroupService) {
_blockConsistencyGroupService = blockConsistencyGroupService;
}
/**
* @param blockSnapshotService the blockSnapshotService to set
*/
public void setBlockSnapshotService(BlockSnapshotService blockSnapshotService) {
this._blockSnapshotService = blockSnapshotService;
}
public void setBlockServiceApis(final Map<String, BlockServiceApi> serviceInterfaces) {
_blockServiceApis = serviceInterfaces;
}
private static BlockServiceApi getBlockServiceImpl(final String type) {
return _blockServiceApis.get(type);
}
@Override
protected DataObject queryResource(URI id) {
ArgValidator.checkFieldUriType(id, VolumeGroup.class, "id");
VolumeGroup volumeGroup = _permissionsHelper.getObjectById(id, VolumeGroup.class);
ArgValidator.checkEntityNotNull(volumeGroup, id, isIdEmbeddedInURL(id));
return volumeGroup;
}
@Override
protected ResourceTypeEnum getResourceType() {
return ResourceTypeEnum.VOLUME_GROUP;
}
@Override
protected URI getTenantOwner(final URI id) {
return null;
}
@Override
public String getServiceType() {
return EVENT_SERVICE_TYPE;
}
/**
* Create a volume group
*
* @param param Parameters for creating a volume group
* @return created volume group
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public VolumeGroupRestRep createVolumeGroup(VolumeGroupCreateParam param) {
ArgValidator.checkFieldNotEmpty(param.getName(), VOLUME_GROUP_NAME);
checkDuplicateLabel(VolumeGroup.class, param.getName());
Set<String> roles = param.getRoles();
ArgValidator.checkFieldNotEmpty(roles, VOLUME_GROUP_ROLES);
for (String role : roles) {
ArgValidator.checkFieldValueFromEnum(role, VOLUME_GROUP_ROLES, VolumeGroup.VolumeGroupRole.class);
}
VolumeGroup volumeGroup = new VolumeGroup();
volumeGroup.setId(URIUtil.createId(VolumeGroup.class));
volumeGroup.setLabel(param.getName());
volumeGroup.setDescription(param.getDescription());
volumeGroup.addRoles(param.getRoles());
// add parent if specified
String msg = setParent(volumeGroup, param.getParent());
if (msg != null && !msg.isEmpty()) {
throw APIException.badRequests.volumeGroupCantBeCreated(volumeGroup.getLabel(), msg);
}
if (param.getRoles().contains(VolumeGroup.VolumeGroupRole.MOBILITY.name())) {
ArgValidator.checkFieldNotEmpty(param.getMigrationType(), MIGRATION_TYPE);
ArgValidator.checkFieldNotEmpty(param.getMigrationGroupBy(), MIGRATION_GROUP_BY);
ArgValidator.checkFieldValueFromEnum(param.getMigrationType(), MIGRATION_TYPE,
VolumeGroup.MigrationType.class);
ArgValidator.checkFieldValueFromEnum(param.getMigrationGroupBy(), MIGRATION_GROUP_BY,
VolumeGroup.MigrationGroupBy.class);
volumeGroup.setMigrationType(param.getMigrationType());
volumeGroup.setMigrationGroupBy(param.getMigrationGroupBy());
}
_dbClient.createObject(volumeGroup);
auditOp(OperationTypeEnum.CREATE_VOLUME_GROUP, true, null, volumeGroup.getId().toString(),
volumeGroup.getLabel());
return DbObjectMapper.map(volumeGroup);
}
/**
* List a volume group
*
* @param id volume group Id
* @return ApplicationRestRep
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}")
public VolumeGroupRestRep getVolumeGroup(@PathParam("id") URI id) {
ArgValidator.checkFieldUriType(id, VolumeGroup.class, "id");
VolumeGroup volumeGroup = (VolumeGroup) queryResource(id);
StorageOSUser user = getUserFromContext();
if (!_permissionsHelper.userHasGivenRole(user, null, Role.SYSTEM_MONITOR, Role.TENANT_ADMIN, Role.SECURITY_ADMIN)) {
// Check if the application tenant is the same as the user tenant
List<Volume> volumes = ControllerUtils.getVolumeGroupVolumes(_dbClient, volumeGroup);
if (volumes != null && !volumes.isEmpty()) {
URI tenant = URI.create(user.getTenantId());
Volume firstVol = volumes.get(0);
URI volTenant = firstVol.getTenant().getURI();
if (!volTenant.equals(tenant)) {
APIException.forbidden.insufficientPermissionsForUser(user.getName());
}
}
}
VolumeGroupRestRep resp = DbObjectMapper.map(volumeGroup);
resp.setReplicationGroupNames(CopyVolumeGroupUtils.getReplicationGroupNames(volumeGroup, _dbClient));
resp.setVirtualArrays(CopyVolumeGroupUtils.getVirtualArrays(volumeGroup, _dbClient));
return resp;
}
/**
* List volume groups.
*
* @return A reference to VolumeGroupList.
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public VolumeGroupList getVolumeGroups() {
VolumeGroupList volumeGroupList = new VolumeGroupList();
List<URI> ids = _dbClient.queryByType(VolumeGroup.class, true);
Iterator<VolumeGroup> iter = _dbClient.queryIterativeObjects(VolumeGroup.class, ids);
StorageOSUser user = getUserFromContext();
if (_permissionsHelper.userHasGivenRole(user, null, Role.SYSTEM_MONITOR, Role.TENANT_ADMIN, Role.SECURITY_ADMIN)) {
while (iter.hasNext()) {
VolumeGroup vg = iter.next();
volumeGroupList.getVolumeGroups().add(toNamedRelatedResource(vg));
}
} else {
log.info("checking tenant");
// otherwise, filter by only authorized to use
URI tenant = URI.create(user.getTenantId());
while (iter.hasNext()) {
VolumeGroup vg = iter.next();
List<Volume> volumes = ControllerUtils.getVolumeGroupVolumes(_dbClient, vg);
if (volumes == null || volumes.isEmpty()) {
// if no volume in the application yet, the application is visible to all tenants
volumeGroupList.getVolumeGroups().add(toNamedRelatedResource(vg));
} else {
Volume firstVol = volumes.get(0);
URI volTenant = firstVol.getTenant().getURI();
if (volTenant.equals(tenant)) {
volumeGroupList.getVolumeGroups().add(toNamedRelatedResource(vg));
}
}
}
}
return volumeGroupList;
}
/**
* Get application volumes
*
* @param id Application Id
* @return NamedVolumesList
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/volumes")
public NamedVolumesList getVolumes(@PathParam("id") URI id) {
ArgValidator.checkFieldUriType(id, VolumeGroup.class, "id");
VolumeGroup volumeGroup = (VolumeGroup) queryResource(id);
NamedVolumesList result = new NamedVolumesList();
List<Volume> volumes = ControllerUtils.getVolumeGroupVolumes(_dbClient, volumeGroup);
for (Volume volume : volumes) {
result.getVolumes().add(toNamedRelatedResource(volume));
}
return result;
}
/**
* Get application hosts
*
* @param id Application Id
* @return HostList
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/hosts")
public HostList getHosts(@PathParam("id") URI id) {
ArgValidator.checkFieldUriType(id, VolumeGroup.class, "id");
VolumeGroup volumeGroup = _dbClient.queryObject(VolumeGroup.class, id);
HostList result = new HostList();
List<Host> hosts = getVolumeGroupHosts(_dbClient, volumeGroup);
for (Host host : hosts) {
result.getHosts().add(toNamedRelatedResource(host));
}
return result;
}
/**
* Get application clusters
*
* @param id Application Id
* @return ClusterList
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/clusters")
public ClusterList getClusters(@PathParam("id") URI id) {
ArgValidator.checkFieldUriType(id, VolumeGroup.class, "id");
VolumeGroup volumeGroup = _dbClient.queryObject(VolumeGroup.class, id);
ClusterList result = new ClusterList();
List<Cluster> clusters = getVolumeGroupClusters(_dbClient, volumeGroup);
for (Cluster cluster : clusters) {
result.getClusters().add(toNamedRelatedResource(cluster));
}
return result;
}
/**
* Get the list of child volume groups
*
* @param id
* @return
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/volume-groups")
public NamedVolumeGroupsList getChildrenVolumeGroups(@PathParam("id") URI id) {
ArgValidator.checkFieldUriType(id, VolumeGroup.class, "id");
VolumeGroup volumeGroup = _dbClient.queryObject(VolumeGroup.class, id);
NamedVolumeGroupsList result = new NamedVolumeGroupsList();
List<VolumeGroup> volumeGroups = getVolumeGroupChildren(_dbClient, volumeGroup);
for (VolumeGroup group : volumeGroups) {
result.getVolumeGroups().add(toNamedRelatedResource(group));
}
return result;
}
/**
* Delete the volume group.
* When a volume group is deleted it will move to a "marked for deletion" state.
*
* @param id the URN of the volume group
* @brief Deactivate application
* @return No data returned in response body
*/
@POST
@Path("/{id}/deactivate")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public Response deactivateVolumeGroup(@PathParam("id") URI id) {
ArgValidator.checkFieldUriType(id, VolumeGroup.class, "id");
VolumeGroup volumeGroup = (VolumeGroup) queryResource(id);
if (!ControllerUtils.getVolumeGroupVolumes(_dbClient, volumeGroup).isEmpty()) {
// application could not be deleted if it has volumes
throw APIException.badRequests.volumeGroupWithVolumesCantBeDeleted(volumeGroup.getLabel());
}
if (!getVolumeGroupHosts(_dbClient, volumeGroup).isEmpty()) {
// application could not be deleted if it has hosts
throw APIException.badRequests.volumeGroupWithHostsCantBeDeleted(volumeGroup.getLabel());
}
if (!getVolumeGroupClusters(_dbClient, volumeGroup).isEmpty()) {
// application could not be deleted if it has clusters
throw APIException.badRequests.volumeGroupWithClustersCantBeDeleted(volumeGroup.getLabel());
}
if (!getVolumeGroupChildren(_dbClient, volumeGroup).isEmpty()) {
// application could not be deleted if it has child volume groups
throw APIException.badRequests.volumeGroupWithChildrenCantBeDeleted(volumeGroup.getLabel());
}
// check for any other references to this volume group
ArgValidator.checkReference(VolumeGroup.class, id, checkForDelete(volumeGroup));
_dbClient.markForDeletion(volumeGroup);
auditOp(OperationTypeEnum.DELETE_CONFIG, true, null, id.toString(),
volumeGroup.getLabel());
return Response.ok().build();
}
/**
* update a volume group
*
* @param id volume group id
* @param param volume group update parameters
* @return
*/
@PUT
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.OWN, ACL.ALL })
public TaskList updateVolumeGroup(@PathParam("id") final URI id,
final VolumeGroupUpdateParam param) {
ArgValidator.checkFieldUriType(id, VolumeGroup.class, "id");
VolumeGroup volumeGroup = (VolumeGroup) queryResource(id);
if (volumeGroup.getInactive()) {
throw APIException.badRequests.volumeGroupCantBeUpdated(volumeGroup.getLabel(), "The Volume Group has been deleted");
}
boolean isChanged = false;
String vgName = param.getName();
if (vgName != null && !vgName.isEmpty() && !vgName.equalsIgnoreCase(volumeGroup.getLabel())) {
checkDuplicateLabel(VolumeGroup.class, vgName);
volumeGroup.setLabel(vgName);
isChanged = true;
}
String description = param.getDescription();
if (description != null && !description.isEmpty()) {
volumeGroup.setDescription(description);
isChanged = true;
}
String parent = param.getParent();
if (parent != null && !parent.isEmpty()) {
String msg = setParent(volumeGroup, parent);
if (msg != null && !msg.isEmpty()) {
throw APIException.badRequests.volumeGroupCantBeUpdated(volumeGroup.getLabel(), msg);
}
isChanged = true;
}
if (isChanged) {
_dbClient.updateObject(volumeGroup);
}
String taskId = UUID.randomUUID().toString();
TaskList taskList = new TaskList();
Operation op = null;
if (!param.hasEitherAddOrRemoveVolumes() && !param.hasEitherAddOrRemoveHosts() && !param.hasEitherAddOrRemoveClusters()) {
op = new Operation();
op.setResourceType(ResourceOperationTypeEnum.UPDATE_VOLUME_GROUP);
op.ready();
volumeGroup.getOpStatus().createTaskStatus(taskId, op);
_dbClient.updateObject(volumeGroup);
TaskResourceRep task = toTask(volumeGroup, taskId, op);
taskList.getTaskList().add(task);
return taskList;
}
List<VolumeGroupUtils> utils = getVolumeGroupUtils(volumeGroup);
for (VolumeGroupUtils util : utils) {
util.validateUpdateVolumesInVolumeGroup(_dbClient, param, volumeGroup);
}
for (VolumeGroupUtils util : utils) {
util.updateVolumesInVolumeGroup(_dbClient, param, volumeGroup, taskId, taskList);
}
auditOp(OperationTypeEnum.UPDATE_VOLUME_GROUP, true, AuditLogManager.AUDITOP_BEGIN, volumeGroup.getId().toString(),
volumeGroup.getLabel());
return taskList;
}
/**
* Creates a volume group full copy
* - Creates full copy for all the array replication groups within this Application.
* - If partial flag is specified, it creates full copy only for set of array replication groups.
* A Volume from each array replication group can be provided to indicate which array replication
* groups are required to take full copy.
*
* @prereq none
*
* @param volumeGroupId the URI of the Volume Group
* - Volume group URI
* @param param VolumeGroupFullCopyCreateParam
*
* @brief Create volume group fullcopy
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies")
@CheckPermission(roles = { Role.SYSTEM_ADMIN }, acls = { ACL.ANY })
public TaskList createVolumeGroupFullCopy(@PathParam("id") final URI volumeGroupId,
VolumeGroupFullCopyCreateParam param) {
ArgValidator.checkFieldUriType(volumeGroupId, VolumeGroup.class, "id");
// Query Volume Group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
ArgValidator.checkEntityNotNull(volumeGroup, volumeGroupId, isIdEmbeddedInURL(volumeGroupId));
// validate replica operation for volume group
validateCopyOperationForVolumeGroup(volumeGroup, ReplicaTypeEnum.FULL_COPY);
TaskList taskList = new TaskList();
// get all volumes
List<Volume> volumes = ControllerUtils.getVolumeGroupVolumes(_dbClient, volumeGroup);
// validate that there should be some volumes in VolumeGroup
if (volumes.isEmpty()) {
throw APIException.badRequests.replicaOperationNotAllowedOnEmptyVolumeGroup(volumeGroup.getLabel(), ReplicaTypeEnum.FULL_COPY.toString());
}
List<URI> partialVolumeList = new ArrayList<URI>();
boolean partial = isPartialRequest(param, volumeGroup, partialVolumeList);
if (partial) {
log.info("Full Copy requested for subset of array groups in Application.");
// validate that at least one volume URI is provided
ArgValidator.checkFieldNotEmpty(partialVolumeList, "volumes");
// validate that provided volumes
Set<String> arrayGroupNames = new HashSet<String>();
List<Volume> volumesInRequest = new ArrayList<Volume>();
for (URI volumeURI : partialVolumeList) {
ArgValidator.checkFieldUriType(volumeURI, Volume.class, "volume");
// Get the Volume.
Volume volume = (Volume) BlockFullCopyUtils.queryFullCopyResource(volumeURI,
uriInfo, true, _dbClient);
String arrayGroupName = volume.getReplicationGroupInstance();
if (volume.isVPlexVolume(_dbClient)) {
// get backend source volume to get RG name
Volume backedVol = VPlexUtil.getVPLEXBackendVolume(volume, true, _dbClient);
if (backedVol != null) {
arrayGroupName = backedVol.getReplicationGroupInstance();
}
}
// this shouldn't happen, but just in case, skip if replicationGroupInstance is null
if (NullColumnValueGetter.isNullValue(arrayGroupName)) {
log.info("Skipping volume {} because replicationGroupInstance is null", volume.getLabel());
continue;
}
// skip repeated array groups
if (arrayGroupNames.contains(arrayGroupName)) {
log.info("Skipping repetitive request for Volume array group {}. Volume: {}",
arrayGroupName, volume.getLabel());
continue;
}
arrayGroupNames.add(arrayGroupName);
// validate that provided volumes are part of Volume Group
if (!volume.getVolumeGroupIds().contains(volumeGroupId.toString())) {
throw APIException.badRequests
.replicaOperationNotAllowedVolumeNotInVolumeGroup(ReplicaTypeEnum.FULL_COPY.toString(), volume.getLabel());
}
volumesInRequest.add(volume);
}
checkForApplicationPendingTasks(volumeGroup, _dbClient, false);
// check for full copy not supported volumes
for (String groupName : arrayGroupNames) {
checkForFullCopyNotSupportedVolumes(CustomQueryUtility.queryActiveResourcesByConstraint(_dbClient, Volume.class,
AlternateIdConstraint.Factory.getVolumeReplicationGroupInstanceConstraint(groupName)));
}
for (Volume volume : volumesInRequest) {
// set Flag in Volume so that we will know about partial request during processing.
volume.addInternalFlags(Flag.VOLUME_GROUP_PARTIAL_REQUEST);
_dbClient.updateObject(volume);
// Create full copy. Note that it will take into account the
// fact that the volume is in a ReplicationGroup
// and full copies will be created for all volumes in that ReplicationGroup.
// In case of partial request, Tasks will be generated for each Array group
// and they cannot be monitored together.
// append replication group name to requested full copy name
// to make the requested name unique across array replication groups
try {
taskList.getTaskList().addAll(getFullCopyManager().createFullCopy(volume.getId(), param).getTaskList());
} catch (Exception e) {
volume.clearInternalFlags(Flag.VOLUME_GROUP_PARTIAL_REQUEST);
_dbClient.updateObject(volume);
throw e;
}
}
} else {
log.info("Full Copy requested for entire Application");
checkForApplicationPendingTasks(volumeGroup, _dbClient, false);
// make sure there are no xtremio/unity volumes in the application
checkForFullCopyNotSupportedVolumes(volumes);
auditOp(OperationTypeEnum.CREATE_VOLUME_GROUP_FULL_COPY, true, AuditLogManager.AUDITOP_BEGIN, volumeGroup.getId().toString(),
param.getName(), param.getCount());
// Full copy will be created for all volumes in Application
taskList = getFullCopyManager().createFullCopy(volumes.get(0).getId(), param);
}
return taskList;
}
/**
* determines if the full copy create request is partial or full; also gets the partial list of volumes if the request is partial
*
* @param param full copy create parameters
* @param application application containing the source volumes
* @param partialVolumeList output partial list of volumes
* @return true if partial
*/
private boolean isPartialRequest(VolumeGroupFullCopyCreateParam param, final VolumeGroup application,
List<URI> partialVolumeList) {
return isPartialRequest(param.getPartial(), param.getVolumes(), param.getSubGroups(), application, partialVolumeList);
}
/**
* determines if the snapshot create request is partial or full; also gets the partial list of volumes if the request is partial
*
* @param param snapshot create parameters
* @param application application containing the source volumes
* @param partialVolumeList output partial list of volumes
* @return true if partial
*/
private boolean isPartialRequest(VolumeGroupSnapshotCreateParam param, final VolumeGroup application,
List<URI> partialVolumeList) {
return isPartialRequest(param.getPartial(), param.getVolumes(), param.getSubGroups(), application, partialVolumeList);
}
/**
* determines if the snapshot session create request is partial or full; also gets the partial list of volumes if the request is partial
*
* @param param snapshot session create parameters
* @param application application containing the source volumes
* @param partialVolumeList output partial list of volumes
* @return true if partial
*/
private boolean isPartialRequest(VolumeGroupSnapshotSessionCreateParam param, final VolumeGroup application,
List<URI> partialVolumeList) {
return isPartialRequest(param.getPartial(), param.getVolumes(), param.getSubGroups(), application, partialVolumeList);
}
/**
* determines if the full copy, snapshot or snapshot session create request is partial or full; also gets the partial list of volumes if the request is partial
*
* @param partialRequest request from REST params object
* @param volumes volume URI's from REST params object
* @param subGroups subgroups from REST params object
* @param application application containing the source volumes
* @param partialVolumeList output partial list of volumes
* @return true if partial
*/
private boolean isPartialRequest(boolean partialRequest, List<URI> volumes, List<String> subGroups, final VolumeGroup application,
List<URI> partialVolumeList) {
boolean partial = false;
if (partialRequest) {
partial = true;
if (volumes != null && !volumes.isEmpty()) {
partialVolumeList.addAll(volumes);
}
} else {
if (subGroups != null && !subGroups.isEmpty()) {
partial = validateSubGroupsParam(subGroups, application);
}
if (partial) {
Map<String, Volume> groupToVolume = groupVolumesByReplicationGroup(ControllerUtils.getVolumeGroupVolumes(_dbClient, application));
// get one volume per sub group
for (Entry<String, Volume> entry : groupToVolume.entrySet()) {
if (subGroups.contains(entry.getKey())) {
partialVolumeList.add(entry.getValue().getId());
}
}
} else {
Map<String, Volume> groupToVolume = groupVolumesByReplicationGroup(ControllerUtils.getVolumeGroupVolumes(_dbClient, application));
// if there are RP volumes in the application, treat is as partial
if (!groupToVolume.values().isEmpty() && !NullColumnValueGetter.isNullURI(groupToVolume.values().iterator().next().getProtectionController())) {
for (Entry<String, Volume> entry : groupToVolume.entrySet()) {
if (NullColumnValueGetter.isNotNullValue(entry.getValue().getPersonality()) &&
entry.getValue().getPersonality().equals(Volume.PersonalityTypes.SOURCE.toString()) ) {
partialVolumeList.add(entry.getValue().getId());
}
}
partial = true;
}
}
}
return partial;
}
/**
* validates that the sub groups are part of the application
*
* @param subGroups
* @param application
* @return false if all subgroups are requested; otherwise true
*/
private boolean validateSubGroupsParam(List<String> subGroups, VolumeGroup application) {
Set<String> appSubGroups = CopyVolumeGroupUtils.getReplicationGroupNames(application, _dbClient);
// validate the sub groups; make sure they're all part of the application
if (!appSubGroups.containsAll(subGroups)) {
throw APIException.badRequests.invalidCopySetNamesProvided(StringUtils.join(subGroups.iterator(), ","), ReplicaTypeEnum.FULL_COPY.toString());
}
// not partial if all sub groups are listed
return (appSubGroups.size() != subGroups.size());
}
/**
* checks the list of volumes to see if any is on FULL_COPY_NOT_SUPPORTED_SYSTEM_TYPES; handles vplex;
*
* @param volumes
*/
private void checkForFullCopyNotSupportedVolumes(List<Volume> volumes) {
// getVolumeByAssociatedVolumesConstraint
Set<URI> virtualVolAlreadyChecked = new HashSet<URI>();
for (Volume volume : volumes) {
Volume checkVolume = volume;
// check to see if the volume is a vplex virtual volume
Volume vplexBackendVol = VPlexUtil.getVPLEXBackendVolume(volume, true, _dbClient, false);
if (vplexBackendVol != null) {
checkVolume = vplexBackendVol;
} else {
// check to see if this volume is a backing volume for a vplex virtual volume
List<Volume> vplexSrcVols = CustomQueryUtility.queryActiveResourcesByConstraint(_dbClient, Volume.class,
AlternateIdConstraint.Factory.getVolumeByAssociatedVolumesConstraint(checkVolume.getId().toString()));
if (vplexSrcVols != null && !vplexSrcVols.isEmpty()) {
if (virtualVolAlreadyChecked.contains(vplexSrcVols.get(0).getId())) {
continue;
} else {
checkVolume = VPlexUtil.getVPLEXBackendVolume(vplexSrcVols.get(0), true, _dbClient, false);
virtualVolAlreadyChecked.add(vplexSrcVols.get(0).getId());
}
}
}
StorageSystem storage = _dbClient.queryObject(StorageSystem.class, checkVolume.getStorageController());
if (storage != null) {
if (FULL_COPY_NOT_SUPPORTED_SYSTEM_TYPES.contains(storage.getSystemType())) {
throw APIException.badRequests.replicaOperationNotAllowedApplication(ReplicaTypeEnum.FULL_COPY.toString(),
storage.getSystemType());
}
}
}
}
/**
* List full copies for a volume group
*
* @prereq none
*
* @param volumeGroupId The URI of the volume group.
*
* @brief List full copies for a volume group
*
* @return The list of full copies for the volume group
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public NamedVolumesList getVolumeGroupFullCopies(@PathParam("id") final URI volumeGroupId) {
ArgValidator.checkFieldUriType(volumeGroupId, VolumeGroup.class, "id");
// Query Volume Group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
// get all volumes
List<Volume> volumes = ControllerUtils.getVolumeGroupVolumes(_dbClient, volumeGroup);
// Cycle over the volumes in the volume group and
// get the full copies for each volume in the group.
NamedVolumesList fullCopyList = new NamedVolumesList();
for (Volume volume : volumes) {
NamedVolumesList volumeFullCopies = getFullCopyManager().getFullCopiesForSource(volume.getId());
fullCopyList.getVolumes().addAll(volumeFullCopies.getVolumes());
}
return fullCopyList;
}
/**
* List full copy set names for a volume group
*
* @param volumeGroupId The URI of the volume group.
*
* @brief List full copy set names for a volume group
*
* @return The list of full copy set names for the volume group
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies/copy-sets")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public VolumeGroupCopySetList getVolumeGroupFullCopySets(@PathParam("id") final URI volumeGroupId) {
ArgValidator.checkFieldUriType(volumeGroupId, VolumeGroup.class, "id");
// Query Volume Group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
// get all volumes
List<Volume> volumes = ControllerUtils.getVolumeGroupVolumes(_dbClient, volumeGroup);
// Cycle over the volumes in the volume group and
// get the full copies for each volume in the group.
VolumeGroupCopySetList fullCopySets = new VolumeGroupCopySetList();
for (Volume volume : volumes) {
StringSet fullCopyIds = volume.getFullCopies();
if (fullCopyIds != null) {
for (String fullCopyId : fullCopyIds) {
Volume fullCopyVolume = _dbClient.queryObject(Volume.class,
URI.create(fullCopyId));
if (fullCopyVolume == null || fullCopyVolume.getInactive()) {
log.warn("Stale full copy {} found for volume {}", fullCopyId,
volume.getLabel());
continue;
}
String setName = fullCopyVolume.getFullCopySetName();
if (NullColumnValueGetter.isNullValue(setName)) { // This should not happen
log.warn(String.format("skipping volume %s becuase fullCopySetName is null", fullCopyVolume.getLabel()));
continue;
}
fullCopySets.getCopySets().add(setName);
}
}
}
return fullCopySets;
}
/**
* List full copies for a volume group belonging to the provided copy set name
*
* @param volumeGroupId The URI of the volume group.
* @param param VolumeGroupCopySetParam containing the copy set name
*
* @brief List full copies for a volume group belonging to the provided copy set name
*
* @return The list of full copies for the volume group belonging to the provided copy set name
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies/copy-sets")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public NamedVolumesList getVolumeGroupFullCopiesForSet(@PathParam("id") final URI volumeGroupId,
final VolumeGroupCopySetParam param) {
ArgValidator.checkFieldUriType(volumeGroupId, VolumeGroup.class, "id");
// Query Volume Group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
// validate that full copy set name is provided
String fullCopySetName = param.getCopySetName();
ArgValidator.checkFieldNotEmpty(fullCopySetName, COPY_SET_NAME_FIELD);
// validate that the provided set name actually belongs to this Application
VolumeGroupCopySetList fullCopySetNames = getVolumeGroupFullCopySets(volumeGroupId);
if (!fullCopySetNames.getCopySets().contains(fullCopySetName)) {
throw APIException.badRequests.
setNameDoesNotBelongToVolumeGroup("Full Copy Set name", fullCopySetName, volumeGroup.getLabel());
}
NamedVolumesList fullCopyList = new NamedVolumesList();
List<Volume> fullCopiesForSet = getClonesBySetName(fullCopySetName, volumeGroupId);
for (Volume fullCopy : fullCopiesForSet) {
fullCopyList.getVolumes().add(toNamedRelatedResource(fullCopy));
}
return fullCopyList;
}
/**
* Get the specified volume group full copy.
*
* @prereq none
* @param volumeGroupId The URI of the volume group.
* @param fullCopyURI The URI of the full copy.
*
* @brief Get the specified volume group full copy.
*
* @return The full copy volume.
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies/{fcid}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public VolumeRestRep getVolumeGroupFullCopy(@PathParam("id") final URI volumeGroupId,
@PathParam("fcid") URI fullCopyURI) {
ArgValidator.checkFieldUriType(volumeGroupId, VolumeGroup.class, "id");
// Query Volume Group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
// validate replica operation for volume group
validateCopyOperationForVolumeGroup(volumeGroup, ReplicaTypeEnum.FULL_COPY);
Volume fullCopyVolume = (Volume) BlockFullCopyUtils.queryFullCopyResource(
fullCopyURI, uriInfo, false, _dbClient);
verifyReplicaForCopyRequest(fullCopyVolume, volumeGroupId);
// Get and return the full copy.
return BlockMapper.map(_dbClient, fullCopyVolume);
}
/**
* Activate the specified Volume group full copy.
* - Activates full copy for all the array replication groups within this Application.
* - If partial flag is specified, it activates full copy only for set of array replication groups.
* A Full Copy from each array replication group can be provided to indicate which array replication
* groups's full copies needs to be activated.
*
* @prereq Create Volume group full copy as inactive.
*
* @param volumeGroupId The URI of the Volume group.
* @param fullCopyURI The URI of the full copy.
*
* @brief Activate Volume group full copy.
*
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies/activate")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList activateVolumeGroupFullCopy(@PathParam("id") final URI volumeGroupId,
final VolumeGroupFullCopyActivateParam param) {
ArgValidator.checkFieldUriType(volumeGroupId, VolumeGroup.class, "id");
// Query Volume Group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
TaskList taskList = new TaskList();
// validate replica operation for volume group
validateCopyOperationForVolumeGroup(volumeGroup, ReplicaTypeEnum.FULL_COPY);
// validate the requested full copies
List<Volume> fullCopyVolumesInRequest = new ArrayList<Volume>();
boolean partial = validateFullCopiesInRequest(fullCopyVolumesInRequest, param.getFullCopies(), param.getCopySetName(), param.getSubGroups(), volumeGroupId);
/**
* 1. VolumeGroupService Clone API accepts a Clone URI (to identify clone set and RG)
* - then get All full copies belonging to same full copy set
* - get full copy set name from the requested full copy
* 2. If partial, there will be a List of Clone URIs (one from each RG)
* 3. Group the full copies by Replication Group(RG)
* 4. For each RG, invoke the ConsistencyGroup full copy API (CG uri, clone uri)
* - a. Skip the CG/RG calls when thrown error and continue with other entries; create 'ERROR' Task for this call
* - b. Finally return the Task List (RG tasks may finish at different times as they are different calls)
*/
if (!partial) {
Volume fullCopy = fullCopyVolumesInRequest.get(0);
log.info("Full Copy operation requested for entire Application, Considering full copy {} in request.",
fullCopy.getLabel());
fullCopyVolumesInRequest.clear();
fullCopyVolumesInRequest.addAll(getClonesBySetName(fullCopy.getFullCopySetName(), volumeGroup.getId()));
} else {
log.info("Full Copy operation requested for subset of array replication groups in Application.");
}
Map<String, Volume> repGroupToFullCopyMap = groupVolumesByReplicationGroup(fullCopyVolumesInRequest);
for (Map.Entry<String, Volume> entry : repGroupToFullCopyMap.entrySet()) {
String replicationGroup = entry.getKey();
Volume fullCopy = entry.getValue();
log.info("Processing Array Replication Group {}, Full Copy {}", replicationGroup, fullCopy.getLabel());
try {
// get CG URI
URI cgURI = getConsistencyGroupForFullCopy(fullCopy);
// Activate the full copy. Note that it will take into account the
// fact that the volume is in a ReplicationGroup
// and all volumes in that ReplicationGroup will be activated.
taskList.getTaskList().addAll(
_blockConsistencyGroupService.activateConsistencyGroupFullCopy(cgURI, fullCopy.getId())
.getTaskList());
} catch (InternalException | APIException e) {
String errMsg = String.format("Error activating Array Replication Group %s, Full Copy %s",
replicationGroup, fullCopy.getLabel());
log.error(errMsg, e);
TaskResourceRep task = BlockServiceUtils.createFailedTaskOnVolume(_dbClient, fullCopy,
ResourceOperationTypeEnum.ACTIVATE_VOLUME_FULL_COPY, e);
taskList.addTask(task);
}
}
if (!partial) {
auditOp(OperationTypeEnum.ACTIVATE_VOLUME_GROUP_FULL_COPY, true, AuditLogManager.AUDITOP_BEGIN,
volumeGroup.getId().toString(), fullCopyVolumesInRequest.get(0).getLabel());
}
return taskList;
}
/**
* Detach the specified Volume group full copy.
* - Detaches full copy for all the array replication groups within this Application.
* - If partial flag is specified, it detaches full copy only for set of array replication groups.
* A Full Copy from each array replication group can be provided to indicate which array replication
* groups's full copies needs to be detached.
*
* @prereq Create Volume group full copy as active.
*
* @param volumeGroupId The URI of the Volume group.
* @param fullCopyURI The URI of the full copy.
*
* @brief Detach Volume group full copy.
*
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies/detach")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList detachVolumeGroupFullCopy(@PathParam("id") final URI volumeGroupId,
final VolumeGroupFullCopyDetachParam param) {
ArgValidator.checkFieldUriType(volumeGroupId, VolumeGroup.class, "id");
// Query Volume Group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
TaskList taskList = new TaskList();
// validate replica operation for volume group
validateCopyOperationForVolumeGroup(volumeGroup, ReplicaTypeEnum.FULL_COPY);
// validate the requested full copies
List<Volume> fullCopyVolumesInRequest = new ArrayList<Volume>();
boolean partial = validateFullCopiesInRequest(fullCopyVolumesInRequest, param.getFullCopies(), param.getCopySetName(), param.getSubGroups(), volumeGroupId);
/**
* 1. VolumeGroupService Clone API accepts a Clone URI (to identify clone set and RG)
* - then get All full copies belonging to same full copy set
* - get full copy set name from the requested full copy
* 2. If partial, there will be a List of Clone URIs (one from each RG)
* 3. Group the full copies by Replication Group(RG)
* 4. For each RG, invoke the ConsistencyGroup full copy API (CG uri, clone uri)
* - a. Skip the CG/RG calls when thrown error and continue with other entries; create 'ERROR' Task for this call
* - b. Finally return the Task List (RG tasks may finish at different times as they are different calls)
*/
if (!partial) {
Volume fullCopy = fullCopyVolumesInRequest.get(0);
log.info("Full Copy operation requested for entire Application, Considering full copy {} in request.",
fullCopy.getLabel());
fullCopyVolumesInRequest.clear();
fullCopyVolumesInRequest.addAll(getClonesBySetName(fullCopy.getFullCopySetName(), volumeGroup.getId()));
} else {
log.info("Full Copy operation requested for subset of array replication groups in Application.");
}
checkForApplicationPendingTasks(volumeGroup, _dbClient, true);
Map<String, Volume> repGroupToFullCopyMap = groupVolumesByReplicationGroup(fullCopyVolumesInRequest);
for (Map.Entry<String, Volume> entry : repGroupToFullCopyMap.entrySet()) {
String replicationGroup = entry.getKey();
Volume fullCopy = entry.getValue();
log.info("Processing Array Replication Group {}, Full Copy {}", replicationGroup, fullCopy.getLabel());
try {
// get CG URI
URI cgURI = getConsistencyGroupForFullCopy(fullCopy);
// Detach the full copy. Note that it will take into account the
// fact that the volume is in a ReplicationGroup
// and all volumes in that ReplicationGroup will be detached.
taskList.getTaskList().addAll(
_blockConsistencyGroupService.detachConsistencyGroupFullCopy(cgURI, fullCopy.getId())
.getTaskList());
} catch (InternalException | APIException e) {
String errMsg = String.format("Error detaching Array Replication Group %s, Full Copy %s",
replicationGroup, fullCopy.getLabel());
log.error(errMsg, e);
TaskResourceRep task = BlockServiceUtils.createFailedTaskOnVolume(_dbClient, fullCopy,
ResourceOperationTypeEnum.DETACH_VOLUME_FULL_COPY, e);
taskList.addTask(task);
}
}
if (!partial) {
auditOp(OperationTypeEnum.DETACH_VOLUME_GROUP_FULL_COPY, true, AuditLogManager.AUDITOP_BEGIN,
volumeGroup.getId().toString(), fullCopyVolumesInRequest.get(0).getLabel());
}
return taskList;
}
/**
* Restore the specified Volume group full copy.
* - Restores full copy for all the array replication groups within this Application.
* - If partial flag is specified, it restores full copy only for set of array replication groups.
* A Full Copy from each array replication group can be provided to indicate which array replication
* groups's full copies needs to be restored.
*
* @prereq Create Volume group full copy as active.
*
* @param volumeGroupId The URI of the Volume group.
* @param fullCopyURI The URI of the full copy.
*
* @brief Restore Volume group full copy.
*
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies/restore")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList restoreVolumeGroupFullCopy(@PathParam("id") final URI volumeGroupId,
final VolumeGroupFullCopyRestoreParam param) {
ArgValidator.checkFieldUriType(volumeGroupId, VolumeGroup.class, "id");
// Query Volume Group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
TaskList taskList = new TaskList();
// validate replica operation for volume group
validateCopyOperationForVolumeGroup(volumeGroup, ReplicaTypeEnum.FULL_COPY);
// validate the requested full copies
List<Volume> fullCopyVolumesInRequest = new ArrayList<Volume>();
boolean partial = validateFullCopiesInRequest(fullCopyVolumesInRequest, param.getFullCopies(), param.getCopySetName(), param.getSubGroups(), volumeGroupId);
/**
* 1. VolumeGroupService Clone API accepts a Clone URI (to identify clone set and RG)
* - then get All full copies belonging to same full copy set
* - get full copy set name from the requested full copy
* 2. If partial, there will be a List of Clone URIs (one from each RG)
* 3. Group the full copies by Replication Group(RG)
* 4. For each RG, invoke the ConsistencyGroup full copy API (CG uri, clone uri)
* - a. Skip the CG/RG calls when thrown error and continue with other entries; create 'ERROR' Task for this call
* - b. Finally return the Task List (RG tasks may finish at different times as they are different calls)
*/
if (!partial) {
Volume fullCopy = fullCopyVolumesInRequest.get(0);
log.info("Full Copy operation requested for entire Application, Considering full copy {} in request.",
fullCopy.getLabel());
fullCopyVolumesInRequest.clear();
fullCopyVolumesInRequest.addAll(getClonesBySetName(fullCopy.getFullCopySetName(), volumeGroup.getId()));
} else {
log.info("Full Copy operation requested for subset of array replication groups in Application.");
}
checkForApplicationPendingTasks(volumeGroup, _dbClient, true);
Map<String, Volume> repGroupToFullCopyMap = groupVolumesByReplicationGroup(fullCopyVolumesInRequest);
for (Map.Entry<String, Volume> entry : repGroupToFullCopyMap.entrySet()) {
String replicationGroup = entry.getKey();
Volume fullCopy = entry.getValue();
log.info("Processing Array Replication Group {}, Full Copy {}", replicationGroup, fullCopy.getLabel());
try {
// get CG URI
URI cgURI = getConsistencyGroupForFullCopy(fullCopy);
// Restore the full copy. Note that it will take into account the
// fact that the volume is in a ReplicationGroup
// and all volumes in that ReplicationGroup will be restored.
taskList.getTaskList().addAll(
_blockConsistencyGroupService.restoreConsistencyGroupFullCopy(cgURI, fullCopy.getId())
.getTaskList());
} catch (InternalException | APIException e) {
String errMsg = String.format("Error restoring Array Replication Group %s, Full Copy %s",
replicationGroup, fullCopy.getLabel());
log.error(errMsg, e);
TaskResourceRep task = BlockServiceUtils.createFailedTaskOnVolume(_dbClient, fullCopy,
ResourceOperationTypeEnum.RESTORE_VOLUME_FULL_COPY, e);
taskList.addTask(task);
}
}
if (!partial) {
auditOp(OperationTypeEnum.RESTORE_VOLUME_GROUP_FULL_COPY, true, AuditLogManager.AUDITOP_BEGIN,
volumeGroup.getId().toString(), fullCopyVolumesInRequest.get(0).getLabel());
}
return taskList;
}
/**
* Resynchronize the specified Volume group full copy.
* - Resynchronizes full copy for all the array replication groups within this Application.
* - If partial flag is specified, it resynchronizes full copy only for set of array replication groups.
* A Full Copy from each array replication group can be provided to indicate which array replication
* groups's full copies needs to be resynchronized.
*
* @prereq Create Volume group full copy as active.
*
* @param volumeGroupId The URI of the Volume group.
* @param fullCopyURI The URI of the full copy.
*
* @brief Resynchronize Volume group full copy.
*
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/full-copies/resynchronize")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList resynchronizeVolumeGroupFullCopy(@PathParam("id") final URI volumeGroupId,
final VolumeGroupFullCopyResynchronizeParam param) {
ArgValidator.checkFieldUriType(volumeGroupId, VolumeGroup.class, "id");
// Query Volume Group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
TaskList taskList = new TaskList();
// validate replica operation for volume group
validateCopyOperationForVolumeGroup(volumeGroup, ReplicaTypeEnum.FULL_COPY);
// validate the requested full copies
List<Volume> fullCopyVolumesInRequest = new ArrayList<Volume>();
boolean partial = validateFullCopiesInRequest(fullCopyVolumesInRequest, param.getFullCopies(), param.getCopySetName(), param.getSubGroups(), volumeGroupId);
/**
* 1. VolumeGroupService Clone API accepts a Clone URI (to identify clone set and RG)
* - then get All full copies belonging to same full copy set
* - get full copy set name from the requested full copy
* 2. If partial, there will be a List of Clone URIs (one from each RG)
* 3. Group the full copies by Replication Group(RG)
* 4. For each RG, invoke the ConsistencyGroup full copy API (CG uri, clone uri)
* - a. Skip the CG/RG calls when thrown error and continue with other entries; create 'ERROR' Task for this call
* - b. Finally return the Task List (RG tasks may finish at different times as they are different calls)
*/
if (!partial) {
Volume fullCopy = fullCopyVolumesInRequest.get(0);
log.info("Full Copy operation requested for entire Application, Considering full copy {} in request.",
fullCopy.getLabel());
fullCopyVolumesInRequest.clear();
fullCopyVolumesInRequest.addAll(getClonesBySetName(fullCopy.getFullCopySetName(), volumeGroup.getId()));
} else {
log.info("Full Copy operation requested for subset of array replication groups in Application.");
}
checkForApplicationPendingTasks(volumeGroup, _dbClient, false);
Map<String, Volume> repGroupToFullCopyMap = groupVolumesByReplicationGroup(fullCopyVolumesInRequest);
for (Map.Entry<String, Volume> entry : repGroupToFullCopyMap.entrySet()) {
String replicationGroup = entry.getKey();
Volume fullCopy = entry.getValue();
log.info("Processing Array Replication Group {}, Full Copy {}", replicationGroup, fullCopy.getLabel());
try {
// get CG URI
URI cgURI = getConsistencyGroupForFullCopy(fullCopy);
// Resynchronize the full copy. Note that it will take into account the
// fact that the volume is in a ReplicationGroup
// and all volumes in that ReplicationGroup will be resynchronized.
taskList.getTaskList().addAll(
_blockConsistencyGroupService.resynchronizeConsistencyGroupFullCopy(cgURI, fullCopy.getId())
.getTaskList());
} catch (InternalException | APIException e) {
String errMsg = String.format("Error resynchronizing Array Replication Group %s, Full Copy %s",
replicationGroup, fullCopy.getLabel());
log.error(errMsg, e);
TaskResourceRep task = BlockServiceUtils.createFailedTaskOnVolume(_dbClient, fullCopy,
ResourceOperationTypeEnum.RESYNCHRONIZE_VOLUME_FULL_COPY, e);
taskList.addTask(task);
}
}
if (!partial) {
auditOp(OperationTypeEnum.RESYNCHRONIZE_VOLUME_GROUP_FULL_COPY, true, AuditLogManager.AUDITOP_BEGIN,
volumeGroup.getId().toString(), fullCopyVolumesInRequest.get(0).getLabel());
}
return taskList;
}
/**
* Validate or creates the list of full copies in the request
*
* @param fullCopyVolumesInRequest the list of all full copy volumes to be processed as part of the request
* @param fullCopyURIsInRequest the list of full copy URI's from the request object
* @param copySetName the name of the copy set that identifies the set of full copies in the request
* @param subGroups the name of the sub group that identifies the set of full copies in the request
* @param volumeGroupUri the volume group URI
* @return true if the request is partial, false if the request is full (includes all volumes in the application)
*/
private boolean validateFullCopiesInRequest(List<Volume> fullCopyVolumesInRequest, final List<URI> fullCopyURIsInRequest, String copySetName, List<String> subGroups, URI volumeGroupUri) {
boolean partial = false;
// validate that either copySetName or a volume list was sent in
if (copySetName == null && (fullCopyURIsInRequest == null || fullCopyURIsInRequest.isEmpty())) {
throw APIException.badRequests.invalidApplicationCopyOperationInput(ReplicaTypeEnum.FULL_COPY.toString());
}
VolumeGroup application = _dbClient.queryObject(VolumeGroup.class, volumeGroupUri);
// if copy set name is used, get one clone volume from each sub group
if (fullCopyURIsInRequest == null || fullCopyURIsInRequest.isEmpty()) {
if (subGroups != null && !subGroups.isEmpty()) {
partial = validateSubGroupsParam(subGroups, application);
}
Map<String, Volume> copySetVolumeMap = new HashMap<String, Volume>();
List<Volume> volumesInApplication = ControllerUtils.getVolumeGroupVolumes(_dbClient, application);
for (Volume volume : volumesInApplication) {
String repGroupName = volume.getReplicationGroupInstance();
if(volume.isVPlexVolume(_dbClient)){
Volume backedVol = VPlexUtil.getVPLEXBackendVolume(volume, true, _dbClient);
if (backedVol != null) {
repGroupName = backedVol.getReplicationGroupInstance();
}
}
if (volume.getFullCopies() != null) {
for (String fullCopyUri : volume.getFullCopies()) {
Volume fullCopy = _dbClient.queryObject(Volume.class, URI.create(fullCopyUri));
if (StringUtils.equals(fullCopy.getFullCopySetName(), copySetName)
&& (!partial || subGroups.contains(repGroupName)) ) {
copySetVolumeMap.put(repGroupName, fullCopy);
}
}
}
}
if (copySetVolumeMap.isEmpty()) {
throw APIException.badRequests.invalidCopySetNamesProvided(copySetName, ReplicaTypeEnum.FULL_COPY.toString());
}
fullCopyVolumesInRequest.addAll(copySetVolumeMap.values());
} else {
List<String> arrayGroupNames = new ArrayList<String>();
Set<String> setNames = new HashSet<String>();
for (URI fullCopyURI : fullCopyURIsInRequest) {
String repGroupName = null;
ArgValidator.checkFieldUriType(fullCopyURI, Volume.class, "volume");
// Get the full copy.
Volume fullCopyVolume = (Volume) BlockFullCopyUtils.queryFullCopyResource(
fullCopyURI, uriInfo, false, _dbClient);
if(fullCopyVolume.isVPlexVolume(_dbClient)){
Volume backedVol = VPlexUtil.getVPLEXBackendVolume(fullCopyVolume, true, _dbClient);
if (backedVol != null) {
repGroupName = backedVol.getReplicationGroupInstance();
}
} else{
repGroupName = fullCopyVolume.getReplicationGroupInstance();
}
// skip repeated array groups
if (arrayGroupNames.contains(repGroupName)) {
log.info("Skipping repetitive request for Full Copy array group {}. Full Copy: {}",
repGroupName, fullCopyVolume.getLabel());
continue;
}
arrayGroupNames.add(repGroupName);
verifyReplicaForCopyRequest(fullCopyVolume, volumeGroupUri);
fullCopyVolumesInRequest.add(fullCopyVolume);
setNames.add(fullCopyVolume.getFullCopySetName());
}
if (setNames.size() > 1) {
throw APIException.badRequests.multipleSetNamesProvided(ReplicaTypeEnum.FULL_COPY.toString());
}
Set<String> appSubGroups = CopyVolumeGroupUtils.getReplicationGroupNames(application, _dbClient);
if (!appSubGroups.containsAll(arrayGroupNames)) {
partial = true;
}
}
return partial;
}
/**
* Returns a map of replication group name to one of the volumes in the group.
*
* @param volumeList the list of volumes
* @return the map of replication group to volume
*/
private Map<String, Volume> groupVolumesByReplicationGroup(List<Volume> volumeList) {
Map<String, Volume> repGroupToVolumeMap = new HashMap<String, Volume>();
for (Volume volume : volumeList) {
String repGroupName = volume.getReplicationGroupInstance();
if (volume.isVPlexVolume(_dbClient)) {
// get backend source volume to get RG name
Volume backedVol = VPlexUtil.getVPLEXBackendVolume(volume, true, _dbClient);
if (backedVol != null) {
repGroupName = backedVol.getReplicationGroupInstance();
}
}
// duplicate group names will be overwritten
repGroupToVolumeMap.put(repGroupName, volume);
}
return repGroupToVolumeMap;
}
/**
* Gets the consistency group for full copy.
*
* @param fullCopy the full copy
* @return the consistency group for full copy
*/
private URI getConsistencyGroupForFullCopy(Volume fullCopy) {
if (NullColumnValueGetter.isNullURI(fullCopy.getAssociatedSourceVolume())) {
// Full Copy may already be Detached
throw APIException.badRequests
.replicaOperationNotAllowedNotAReplica(ReplicaTypeEnum.FULL_COPY.toString(), fullCopy.getLabel());
}
Volume srcVolume = _dbClient.queryObject(Volume.class, fullCopy.getAssociatedSourceVolume());
return srcVolume != null ? srcVolume.getConsistencyGroup() : null;
}
/**
* allow replica operation only for COPY type VolumeGroup.
*
* @param volumeGroup
* @param replicaType
*/
private void validateCopyOperationForVolumeGroup(VolumeGroup volumeGroup, ReplicaTypeEnum replicaType) {
if (!volumeGroup.getRoles().contains(VolumeGroupRole.COPY.name())) {
throw APIException.badRequests.replicaOperationNotAllowedForNonCopyTypeVolumeGroup(volumeGroup.getLabel(), replicaType.toString());
}
}
/**
* 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;
}
/**
* Creates and returns an instance of the block full copy manager to handle
* a full copy request.
*
* @return BlockFullCopyManager
*/
private BlockFullCopyManager getFullCopyManager() {
BlockFullCopyManager fcManager = new BlockFullCopyManager(_dbClient,
_permissionsHelper, _auditMgr, _coordinator, _placementManager, sc, uriInfo,
_request, null);
return fcManager;
}
/**
*
* Verifies that the passed replica URI and ensure that it represents a replica for a volume in volume group represented by
* the passed
* in volume group id.
*
* @param replica
* the replica (Clone/Snapshot/Mirror)
* @param volumeGroupUri
* @return The URI of the replica's source.
*/
private URI verifyReplicaForCopyRequest(BlockObject replica, URI volumeGroupUri) {
URI sourceURI = getSourceIdForFullCopy(replica);
if (NullColumnValueGetter.isNullURI(sourceURI)) {
throw APIException.badRequests.replicaOperationNotAllowedNotAReplica(getReplicaType(replica),
replica.getLabel());
}
Volume sourceVol = _dbClient.queryObject(Volume.class, sourceURI);
if (sourceVol != null && !sourceVol.getInactive() && sourceVol.getVolumeGroupIds() != null
&& sourceVol.getVolumeGroupIds().contains(volumeGroupUri.toString())) {
return sourceURI;
}
throw APIException.badRequests.replicaOperationNotAllowedSourceNotInVolumeGroup(getReplicaType(replica),
replica.getLabel());
}
/*
* get all snapshot sets associated with the volume group
*/
private VolumeGroupCopySetList getVolumeGroupSnapshotSets(VolumeGroup volumeGroup) {
// get all volumes
List<Volume> volumes = ControllerUtils.getVolumeGroupVolumes(_dbClient, volumeGroup);
VolumeGroupCopySetList copySetList = new VolumeGroupCopySetList();
Set<String> copySets = copySetList.getCopySets();
// get snapshots for each volume in the group
for (Volume volume : volumes) {
if (volume.isVPlexVolume(_dbClient)) {
volume = VPlexUtil.getVPLEXBackendVolume(volume, true, _dbClient);
if (volume == null || volume.getInactive()) {
log.warn("Cannot find backend volume for VPLEX volume {}", volume.getLabel());
continue;
}
}
URIQueryResultList snapshotURIs = new URIQueryResultList();
_dbClient.queryByConstraint(ContainmentConstraint.Factory.getVolumeSnapshotConstraint(
volume.getId()), snapshotURIs);
if (!snapshotURIs.iterator().hasNext()) {
continue;
}
List<BlockSnapshot> snapshots = _dbClient.queryObject(BlockSnapshot.class, snapshotURIs);
for (BlockSnapshot snapshot : snapshots) {
if (snapshot != null && !snapshot.getInactive()) {
String snapsetLabel = snapshot.getSnapsetLabel();
if (NullColumnValueGetter.isNotNullValue(snapsetLabel)) {
copySets.add(snapsetLabel);
}
}
}
}
return copySetList;
}
/**
* Creates a volume group snapshot
* Creates snapshot for all the array replication groups within this Application.
* If partial flag is specified, it creates snapshot only for set of array replication groups.
* A Volume from each array replication group can be provided to indicate which array replication
* groups are required to take snapshot.
*
* @prereq none
*
* @param volumeGroupId the URI of the Volume Group
* - Volume group URI
* @param param VolumeGroupSnapshotCreateParam
*
* @brief Create volume group snapshot
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshots")
@CheckPermission(roles = { Role.SYSTEM_ADMIN }, acls = { ACL.ANY })
public TaskList createVolumeGroupSnapshot(@PathParam("id") final URI volumeGroupId,
VolumeGroupSnapshotCreateParam param) {
// Query volume group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
// validate replica operation for volume group
validateCopyOperationForVolumeGroup(volumeGroup, ReplicaTypeEnum.SNAPSHOT);
// validate name
String name = param.getName();
ArgValidator.checkFieldNotEmpty(name, NAME_FIELD);
name = TimeUtils.formatDateForCurrent(name);
// snapsetLabel is normalized in RP, do it here too to avoid potential mismatch
name = ResourceOnlyNameGenerator.removeSpecialCharsForName(name, SmisConstants.MAX_SNAPSHOT_NAME_LENGTH);
if (StringUtils.isEmpty(name)) {
// original name has special chars only
throw APIException.badRequests.invalidCopySetName(param.getName(),
ReplicaTypeEnum.SNAPSHOT.toString());
}
// check name provided is not duplicate
VolumeGroupCopySetList copySetList = getVolumeGroupSnapshotSets(volumeGroup);
if (copySetList.getCopySets().contains(name)) {
// duplicate name
throw APIException.badRequests.duplicateCopySetName(param.getName(),
ReplicaTypeEnum.SNAPSHOT.toString());
}
// volumes to be processed
List<Volume> volumes = new ArrayList<Volume>();
List<URI> partialVolumeList = new ArrayList<URI>();
boolean partial = isPartialRequest(param, volumeGroup, partialVolumeList);
if (partial) {
log.info("Snapshot requested for subset of array groups in Application.");
// validate that at least one volume URI is provided
ArgValidator.checkFieldNotEmpty(partialVolumeList, VOLUMES_FIELD);
// validate that provided volumes
for (URI volumeURI : partialVolumeList) {
ArgValidator.checkFieldUriType(volumeURI, Volume.class, VOLUME_FIELD);
// Get the volume
Volume volume = _dbClient.queryObject(Volume.class, volumeURI);
ArgValidator.checkEntity(volume, volumeURI, isIdEmbeddedInURL(volumeURI));
// validate that provided volume is part of Volume Group
if (!volume.getVolumeGroupIds().contains(volumeGroupId.toString())) {
throw APIException.badRequests
.replicaOperationNotAllowedVolumeNotInVolumeGroup(ReplicaTypeEnum.SNAPSHOT.toString(), volume.getLabel());
}
volumes.add(volume);
}
} else {
log.info("Snapshot creation for entire Application");
// get all volumes
volumes.addAll(ControllerUtils.getVolumeGroupVolumes(_dbClient, volumeGroup));
// validate that there should be some volumes in VolumeGroup
if (volumes.isEmpty()) {
throw APIException.badRequests.replicaOperationNotAllowedOnEmptyVolumeGroup(volumeGroup.getLabel(), ReplicaTypeEnum.SNAPSHOT.toString());
}
}
// Check for pending tasks
checkForApplicationPendingTasks(volumeGroup, _dbClient, false);
auditOp(OperationTypeEnum.CREATE_VOLUME_GROUP_SNAPSHOT, true, AuditLogManager.AUDITOP_BEGIN, volumeGroupId.toString(),
name);
TaskList taskList = new TaskList();
/**
* If there are VMAX3 volumes in the request, we need to create snap session for them.
* For others, create snapshot.
*
* vmax3Volumes - block VMAX3 or backend VMAX3 for VPLEX based on copy side requested
* volumes - except volumes filtered out for above case
*/
// TODO consider copyOnHaSide from user's request once the underlying implementation supports it.
List<Volume> vmax3Volumes = getVMAX3Volumes(volumes, false);
if (!vmax3Volumes.isEmpty()) {
// check snap session name provided is not duplicate
VolumeGroupCopySetList sessionSet = getVolumeGroupSnapsetSessionSets(volumeGroup);
if (sessionSet.getCopySets().contains(name)) {
// duplicate name
throw APIException.badRequests.duplicateCopySetName(name, ReplicaTypeEnum.SNAPSHOT_SESSION.toString());
}
}
// create snapshot
Map<URI, List<URI>> cgToVolUris = ControllerUtils.groupVolumeURIsByCG(volumes);
Set<Entry<URI, List<URI>>> entrySet = cgToVolUris.entrySet();
for (Entry<URI, List<URI>> entry : entrySet) {
URI cgUri = entry.getKey();
log.info("Create snapshot with consistency group {}", cgUri);
try {
BlockConsistencyGroupSnapshotCreate cgSnapshotParam = new BlockConsistencyGroupSnapshotCreate(
name, entry.getValue(), param.getCreateInactive(), param.getReadOnly());
TaskList cgTaskList = _blockConsistencyGroupService.createConsistencyGroupSnapshot(cgUri, cgSnapshotParam);
List<TaskResourceRep> taskResourceRepList = cgTaskList.getTaskList();
if (taskResourceRepList != null && !taskResourceRepList.isEmpty()) {
for (TaskResourceRep taskResRep : taskResourceRepList) {
taskList.addTask(taskResRep);
}
}
} catch (InternalException | APIException e) {
log.error("Exception when creating snapshot for consistency group {}", cgUri, e);
BlockConsistencyGroup cg = _dbClient.queryObject(BlockConsistencyGroup.class, cgUri);
TaskResourceRep task = BlockServiceUtils.createFailedTaskOnCG(_dbClient, cg,
ResourceOperationTypeEnum.CREATE_CONSISTENCY_GROUP_SNAPSHOT, e);
taskList.addTask(task);
} catch (Exception ex) {
log.error("Unexpected Exception occurred when creating snapshot for consistency group {}",
cgUri, ex);
}
}
// create snapshot session for VMAX3
Map<URI, List<URI>> cgToV3VolUris = ControllerUtils.groupVolumeURIsByCG(vmax3Volumes);
Set<Entry<URI, List<URI>>> entrySetV3 = cgToV3VolUris.entrySet();
for (Entry<URI, List<URI>> entry : entrySetV3) {
URI cgUri = entry.getKey();
log.info("Create snapshot session for consistency group {}, volumes {}",
cgUri, Joiner.on(',').join(entry.getValue()));
try {
// create snap session with No targets
SnapshotSessionCreateParam cgSnapshotSessionParam = new SnapshotSessionCreateParam(
name, null, entry.getValue());
taskList.getTaskList().addAll(
_blockConsistencyGroupService.createConsistencyGroupSnapshotSession(cgUri, cgSnapshotSessionParam)
.getTaskList());
} catch (InternalException | APIException e) {
log.error("Exception while creating snapshot session for consistency group {}: {}", cgUri, e);
BlockConsistencyGroup cg = _dbClient.queryObject(BlockConsistencyGroup.class, cgUri);
TaskResourceRep task = BlockServiceUtils.createFailedTaskOnCG(_dbClient, cg,
ResourceOperationTypeEnum.CREATE_CONSISTENCY_GROUP_SNAPSHOT_SESSION, e);
taskList.addTask(task);
} catch (Exception ex) {
log.error("Unexpected Exception occurred while creating snapshot session for consistency group {}: {}",
cgUri, ex);
}
}
auditOp(OperationTypeEnum.CREATE_VOLUME_GROUP_SNAPSHOT, true, AuditLogManager.AUDITOP_END, volumeGroupId.toString(),
name);
return taskList;
}
/**
* List snapshots for a volume group
*
* @prereq none
* @param volumeGroupId The URI of the volume group.
* @brief List snapshots for a volume group
* @return SnapshotList
*/
@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 getVolumeGroupSnapshots(@PathParam("id") final URI volumeGroupId) {
// query volume group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
// validate replica operation for volume group
validateCopyOperationForVolumeGroup(volumeGroup, ReplicaTypeEnum.SNAPSHOT);
// get all volumes
List<Volume> volumes = ControllerUtils.getVolumeGroupVolumes(_dbClient, volumeGroup);
// get the snapshots for each volume in the group
SnapshotList snapshotList = new SnapshotList();
for (Volume volume : volumes) {
if (volume.isVPlexVolume(_dbClient)) {
volume = VPlexUtil.getVPLEXBackendVolume(volume, true, _dbClient);
if (volume == null || volume.getInactive()) {
log.warn("Cannot find backend volume for VPLEX volume {}", volume.getLabel());
}
}
URIQueryResultList snapshotURIs = new URIQueryResultList();
_dbClient.queryByConstraint(ContainmentConstraint.Factory.getVolumeSnapshotConstraint(
volume.getId()), snapshotURIs);
if (!snapshotURIs.iterator().hasNext()) {
continue;
}
List<BlockSnapshot> snapshots = _dbClient.queryObject(BlockSnapshot.class, snapshotURIs);
for (BlockSnapshot snapshot : snapshots) {
if (snapshot != null && !snapshot.getInactive()) {
snapshotList.getSnapList().add(toNamedRelatedResource(snapshot));
}
}
}
return snapshotList;
}
/**
* Get the specified volume group snapshot
*
* @prereq none
* @param volumeGroupId The URI of the volume group
* @param snapshotId The URI of the snapshot
* @brief Get the specified volume group snapshot
* @return BlockSnapshotRestRep
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshots/{sid}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public BlockSnapshotRestRep getVolumeGroupSnapshot(@PathParam("id") final URI volumeGroupId,
@PathParam("sid") URI snapshotId) {
// query Volume Group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
// validate replica operation for volume group
validateCopyOperationForVolumeGroup(volumeGroup, ReplicaTypeEnum.SNAPSHOT);
// validate snapshot ID
ArgValidator.checkFieldUriType(snapshotId, BlockSnapshot.class, SNAPSHOT_ID_FIELD);
// get snapshot
BlockSnapshot snapshot = _dbClient.queryObject(BlockSnapshot.class, snapshotId);
ArgValidator.checkEntity(snapshot, snapshotId,isIdEmbeddedInURL(snapshotId), true);
// validate that source of the provided snapshot is part of the volume group
Volume volume = _dbClient.queryObject(Volume.class, snapshot.getParent().getURI());
if (volume != null && Volume.checkForVplexBackEndVolume(_dbClient, volume)) {
// get VPLEX virtual volume to check for volume group reference
volume = Volume.fetchVplexVolume(_dbClient, volume);
}
if (volume == null || volume.getInactive() || !volume.getVolumeGroupIds().contains(volumeGroupId.toString())) {
throw APIException.badRequests
.replicaOperationNotAllowedSourceNotInVolumeGroup(ReplicaTypeEnum.SNAPSHOT.toString(),
snapshot.getLabel());
}
return BlockMapper.map(_dbClient, snapshot);
}
/**
* Get all snapshot set names for a volume group
*
* @prereq none
* @param volumeGroupId The URI of the volume group
* @brief List snapshot set names for a volume group
* @return The list of snapshot set names for the volume group
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshots/copy-sets")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public VolumeGroupCopySetList getVolumeGroupSnapshotSets(@PathParam("id") final URI volumeGroupId) {
// query volume group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
return getVolumeGroupSnapshotSets(volumeGroup);
}
/**
* List snapshots in a snapshot set for a volume group
*
* @prereq none
* @param volumeGroupId The URI of the volume group
* @brief List snapshots in snapshot set for a volume group
* @return SnapshotList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshots/copy-sets")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public SnapshotList getVolumeGroupSnapshotsForSet(@PathParam("id") final URI volumeGroupId, final VolumeGroupCopySetParam param) {
// query volume group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
// validate replica operation for volume group
validateCopyOperationForVolumeGroup(volumeGroup, ReplicaTypeEnum.SNAPSHOT);
// validate copy set name
String copySetName = param.getCopySetName();
ArgValidator.checkFieldNotEmpty(copySetName, COPY_SET_NAME_FIELD);
// get all volumes
List<Volume> volumes = ControllerUtils.getVolumeGroupVolumes(_dbClient, volumeGroup);
// get the snapshots for each volume in the group
SnapshotList snapshotList = new SnapshotList();
for (Volume volume : volumes) {
if (volume.isVPlexVolume(_dbClient)) {
volume = VPlexUtil.getVPLEXBackendVolume(volume, true, _dbClient);
if (volume == null || volume.getInactive()) {
log.warn("Cannot find backend volume for VPLEX volume {}", volume.getLabel());
}
}
URIQueryResultList snapshotURIs = new URIQueryResultList();
_dbClient.queryByConstraint(ContainmentConstraint.Factory.getVolumeSnapshotConstraint(
volume.getId()), snapshotURIs);
if (!snapshotURIs.iterator().hasNext()) {
continue;
}
List<BlockSnapshot> snapshots = _dbClient.queryObject(BlockSnapshot.class, snapshotURIs);
for (BlockSnapshot snapshot : snapshots) {
if (snapshot != null && !snapshot.getInactive()) {
if (copySetName.equals(snapshot.getSnapsetLabel())) {
snapshotList.getSnapList().add(toNamedRelatedResource(snapshot));
}
}
}
}
return snapshotList;
}
/**
* Validate resources and group snapshots by snapsetLabel
*
* If partial, group snapshots in VolumeGroupSnapshotOperationParam by snapsetLabel
* If full, find all the snapshots for each snapsetLabel that the snapshots in the param belong to
*
* @param volumeGroupId
* @param param
* @return map snapsetLabel to snapshots
*/
private Map<String, List<BlockSnapshot>> getSnapshotsGroupedBySnapset(final URI volumeGroupId, VolumeGroupSnapshotOperationParam param) {
// Query volume group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
// validate replica operation for volume group
validateCopyOperationForVolumeGroup(volumeGroup, ReplicaTypeEnum.SNAPSHOT);
List<URI> snapshotsInRequest = new ArrayList<URI>();
boolean partialRequest = false;
if (param.getSnapshots() != null && !param.getSnapshots().isEmpty()) {
// validate only one snapshot URI is provided for full request
if (!param.getPartial() && param.getSnapshots().size() > 1) {
throw APIException.badRequests.invalidNumberOfReplicas(ReplicaTypeEnum.SNAPSHOT.toString());
}
snapshotsInRequest.addAll(param.getSnapshots());
partialRequest = param.getPartial();
} else {
ArgValidator.checkFieldNotEmpty(param.getCopySetName(), COPY_SET_NAME_FIELD);
if (param.getSubGroups() != null && !param.getSubGroups().isEmpty()) {
partialRequest = validateSubGroupsParam(param.getSubGroups(), volumeGroup);
}
URIQueryResultList snapshotIds = new URIQueryResultList();
_dbClient.queryByConstraint(AlternateIdConstraint.Factory.getBlockSnapshotsBySnapsetLabel(param.getCopySetName()), snapshotIds);
Iterator<BlockSnapshot> iter = _dbClient.queryIterativeObjects(BlockSnapshot.class, snapshotIds);
if (partialRequest) {
// get one snapshot per sub group requested
List<Volume> volumesInApplication = ControllerUtils.getVolumeGroupVolumes(_dbClient, volumeGroup);
Map<URI, Volume> volumesMap = new HashMap<URI, Volume>();
for (Volume volume : volumesInApplication) {
if (volume.getAssociatedVolumes() != null) {
List<URI> assocVolIds = new ArrayList<URI>();
for(String s : volume.getAssociatedVolumes()) {
assocVolIds.add(URI.create(s));
}
List<Volume> assocVols = _dbClient.queryObject(Volume.class, assocVolIds);
for (Volume assocVol : assocVols) {
volumesMap.put(assocVol.getId(), assocVol);
}
} else {
volumesMap.put(volume.getId(), volume);
}
}
while (iter.hasNext()) {
BlockSnapshot snapshot = iter.next();
Volume parent = volumesMap.get(snapshot.getParent().getURI());
if (parent != null && param.getSubGroups().contains(parent.getReplicationGroupInstance())) {
snapshotsInRequest.add(snapshot.getId());
}
}
} else {
// for non-partial, we only need one snapshot per sub group
Map<String, List<URI>> subGroupSnapshotMap = new HashMap<String, List<URI>>();
while (iter.hasNext()) {
BlockSnapshot snapshot = iter.next();
if (NullColumnValueGetter.isNotNullValue(snapshot.getReplicationGroupInstance())) {
if (subGroupSnapshotMap.get(snapshot.getReplicationGroupInstance()) == null) {
subGroupSnapshotMap.put(snapshot.getReplicationGroupInstance(), new ArrayList<URI>());
}
subGroupSnapshotMap.get(snapshot.getReplicationGroupInstance()).add(snapshot.getId());
}
}
for (Entry<String, List<URI>> entry : subGroupSnapshotMap.entrySet()) {
snapshotsInRequest.add(entry.getValue().get(0));
}
}
}
Map<String, List<BlockSnapshot>> snapsetToSnapshots = new HashMap<String, List<BlockSnapshot>>();
for (URI snapshotURI : snapshotsInRequest) {
ArgValidator.checkFieldUriType(snapshotURI, BlockSnapshot.class, SNAPSHOT_FIELD);
// Get the snapshot
BlockSnapshot snapshot = BlockServiceUtils.querySnapshotResource(snapshotURI, uriInfo, _dbClient);
if (NullColumnValueGetter.isNullValue(snapshot.getReplicationGroupInstance())) {
throw APIException.badRequests.noReplicationGroupForReplica(snapshot.getLabel());
}
// validate that source of the provided snapshot is part of the volume group
if (!ControllerUtils.isSourceInVoumeGroup(snapshot, volumeGroupId, _dbClient)) {
throw APIException.badRequests.replicaOperationNotAllowedSourceNotInVolumeGroup(ReplicaTypeEnum.SNAPSHOT.toString(),
snapshot.getLabel());
}
String snapsetLabel = snapshot.getSnapsetLabel();
List<BlockSnapshot> snapshots = snapsetToSnapshots.get(snapsetLabel);
if (snapshots == null) {
if (partialRequest) {
snapshots = new ArrayList<BlockSnapshot>();
snapshots.add(snapshot);
} else {
snapshots = ControllerUtils.getVolumeGroupSnapshots(volumeGroup.getId(), snapsetLabel, _dbClient);
}
snapsetToSnapshots.put(snapsetLabel, snapshots);
} else if (partialRequest) {
snapshots.add(snapshot);
}
}
return snapsetToSnapshots;
}
/*
* Wrapper of BlockConsistencyGroupService methods for snapshot operations
*
* @param volumeGroupId
* @param param
* @return a TaskList
*/
private TaskList performVolumeGroupSnapshotOperation(final URI volumeGroupId, final VolumeGroupSnapshotOperationParam param, OperationTypeEnum opType) {
Map<String, List<BlockSnapshot>> snapsetToSnapshots = getSnapshotsGroupedBySnapset(volumeGroupId, param);
// validate that it's ok to do the snapshot operation
validateSnapshotOperation(volumeGroupId, snapsetToSnapshots, opType);
auditOp(opType, true, AuditLogManager.AUDITOP_BEGIN,
volumeGroupId.toString(), param.getSnapshots());
TaskList taskList = new TaskList();
Set<Entry<String, List<BlockSnapshot>>> entrySet = snapsetToSnapshots.entrySet();
for (Entry<String, List<BlockSnapshot>> entry : entrySet) {
Table<URI, String, BlockSnapshot> storageRgToSnapshot = ControllerUtils.getSnapshotForStorageReplicationGroup(entry.getValue());
for (Cell<URI, String, BlockSnapshot> cell : storageRgToSnapshot.cellSet()) {
log.info("{} for replication group {}", opType.getDescription(), cell.getColumnKey());
try {
BlockSnapshot snapshot = cell.getValue();
URI cgUri = snapshot.getConsistencyGroup();
URI snapshotUri = snapshot.getId();
switch (opType) {
case ACTIVATE_VOLUME_GROUP_SNAPSHOT:
taskList.addTask(_blockConsistencyGroupService.activateConsistencyGroupSnapshot(cgUri, snapshotUri));
break;
case RESTORE_VOLUME_GROUP_SNAPSHOT:
taskList.addTask(_blockConsistencyGroupService.restoreConsistencyGroupSnapshot(cgUri, snapshotUri));
break;
case RESYNCHRONIZE_VOLUME_GROUP_SNAPSHOT:
taskList.addTask(_blockConsistencyGroupService.resynchronizeConsistencyGroupSnapshot(cgUri, snapshotUri));
break;
case DEACTIVATE_VOLUME_GROUP_SNAPSHOT:
TaskList cgTaskList = _blockConsistencyGroupService.deactivateConsistencyGroupSnapshot(cgUri, snapshotUri);
List<TaskResourceRep> taskResourceRepList = cgTaskList.getTaskList();
if (taskResourceRepList != null && !taskResourceRepList.isEmpty()) {
for (TaskResourceRep taskResRep : taskResourceRepList) {
taskList.addTask(taskResRep);
}
}
break;
default:
log.error("Unsupported operation {}", opType.getDescription());
break;
}
} catch (InternalException | APIException e) {
log.error("Exception on {} for replication group {}: {}", opType.getDescription(), cell.getColumnKey(), e.getMessage());
}
}
}
auditOp(opType, true, AuditLogManager.AUDITOP_END, volumeGroupId.toString(), param.getSnapshots());
return taskList;
}
/**
* Validates a snapshot operation and throws and exception if the operation cannot be done:
* - checks for pending tasks
* - for deactivate snapshot, check for any exported snapshots
*
* @param volumeGroupId
* @param snapsetToSnapshots
* @param opType
*/
private void validateSnapshotOperation(URI volumeGroupId, Map<String, List<BlockSnapshot>> snapsetToSnapshots, OperationTypeEnum opType) {
// Check for pending tasks
VolumeGroup volumeGroup = _dbClient.queryObject(VolumeGroup.class, volumeGroupId);
if (opType == OperationTypeEnum.RESTORE_VOLUME_GROUP_SNAPSHOT) {
checkForApplicationPendingTasks(volumeGroup, _dbClient, true);
} else {
checkForApplicationPendingTasks(volumeGroup, _dbClient, false);
if (opType == OperationTypeEnum.DEACTIVATE_VOLUME_GROUP_SNAPSHOT) {
for (Entry<String, List<BlockSnapshot>> entry : snapsetToSnapshots.entrySet()) {
String snapsetName = entry.getKey();
List<BlockSnapshot> snapshotList = entry.getValue();
for (BlockSnapshot snapshot : snapshotList) {
if (snapshot.isSnapshotExported(_dbClient)) {
throw APIException.badRequests.cannotDeleteApplicationSnapshotExportExists(volumeGroup.getLabel(), snapsetName);
}
}
}
}
}
}
/**
* Activate the specified Volume group snapshot
* Activates snapshot for all the array replication groups within this Application.
* If partial flag is specified, it activates snapshot only for set of array replication groups.
* A snapshot from each array replication group can be provided to indicate which array replication
* groups's snapshots needs to be activated.
*
* @prereq Create volume group snapshot
*
* @param volumeGroupId The URI of the volume group
* @param param VolumeGroupSnapshotOperationParam
*
* @brief Activate volume group snapshot
*
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshots/activate")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList activateVolumeGroupSnapshot(@PathParam("id") final URI volumeGroupId,
final VolumeGroupSnapshotOperationParam param) {
return performVolumeGroupSnapshotOperation(volumeGroupId, param, OperationTypeEnum.ACTIVATE_VOLUME_GROUP_SNAPSHOT);
}
/**
* Deactivate the specified Volume group snapshot
* Deactivates snapshot for all the array replication groups within this Application.
* If partial flag is specified, it deactivates snapshot only for set of array replication groups.
* A snapshot from each array replication group can be provided to indicate which array replication
* groups's snapshots needs to be deactivated.
*
* @prereq Create volume group snapshot
*
* @param volumeGroupId The URI of the volume group
* @param param VolumeGroupSnapshotOperationParam
*
* @brief Deactivate volume group snapshot
*
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshots/deactivate")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList deactivateVolumeGroupSnapshot(@PathParam("id") final URI volumeGroupId,
final VolumeGroupSnapshotOperationParam param) {
return performVolumeGroupSnapshotOperation(volumeGroupId, param, OperationTypeEnum.DEACTIVATE_VOLUME_GROUP_SNAPSHOT);
}
/**
* Restore the specified Volume group snapshot
* Restores snapshot for all the array replication groups within this Application.
* If partial flag is specified, it restores snapshot only for set of array replication groups.
* A snapshot from each array replication group can be provided to indicate which array replication
* groups's snapshots needs to be restored.
*
* @prereq Create volume group snapshot
*
* @param volumeGroupId The URI of the volume group
* @param param VolumeGroupSnapshotOperationParam
*
* @brief Restore volume group snapshot
*
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshots/restore")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList restoreVolumeGroupSnapshot(@PathParam("id") final URI volumeGroupId,
final VolumeGroupSnapshotOperationParam param) {
return performVolumeGroupSnapshotOperation(volumeGroupId, param, OperationTypeEnum.RESTORE_VOLUME_GROUP_SNAPSHOT);
}
/**
* Resynchronize the specified Volume group snapshot
* Resynchronizes snapshot for all the array replication groups within this Application.
* If partial flag is specified, it resynchronizes snapshot only for set of array replication groups.
* A snapshot from each array replication group can be provided to indicate which array replication
* groups's snapshots needs to be resynchronized.
*
* @prereq Create volume group snapshot
*
* @param volumeGroupId The URI of the volume group
* @param param VolumeGroupSnapshotOperationParam
*
* @brief Resynchronize volume group snapshot
*
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshots/resynchronize")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList resynchronizeVolumeGroupSnapshot(@PathParam("id") final URI volumeGroupId,
final VolumeGroupSnapshotOperationParam param) {
return performVolumeGroupSnapshotOperation(volumeGroupId, param, OperationTypeEnum.RESYNCHRONIZE_VOLUME_GROUP_SNAPSHOT);
}
/**
* Exposes the target volumes associated with application BlockSnapshot instances
* as ViPR Volumes. The BlockSnapshot instances must represent snapshots
* whose parent volumes are the backend volumes for VPLEX volumes. That is, it must be a
* VPLEX snapshot. The purpose is to expose the backend snapshot as a VPLEX volume so
* that access to the snapshot is via the VPLEX rather than directly via the backend
* storage system.
*
* The volumes created are not added to the application
*
* @prereq Create volume group snapshot
*
* @param volumeGroupId The URI of the volume group
* @param param VolumeGroupSnapshotOperationParam
*
* @brief Expose application snapshots as vplex volumes
*
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshots/expose")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList exposeVolumeGroupSnapshotAsVolume(@PathParam("id") final URI volumeGroupId,
final VolumeGroupSnapshotOperationParam param) {
Map<String, List<BlockSnapshot>> snapsetToSnapshots = getSnapshotsGroupedBySnapset(volumeGroupId, param);
// Check for pending tasks
VolumeGroup volumeGroup = _dbClient.queryObject(VolumeGroup.class, volumeGroupId);
checkForApplicationPendingTasks(volumeGroup, _dbClient, false);
TaskList taskList = new TaskList();
for (List<BlockSnapshot> snapshots : snapsetToSnapshots.values()) {
for (BlockSnapshot snapshot : snapshots) {
taskList.addTask(_blockSnapshotService.exposeSnapshotAsVolume(snapshot.getId()));
}
}
return taskList;
}
/**
* Separates the VMAX3 volumes from the given list.
*
* If there are VMAX3 volumes in the request, we need to create snap session for them.
* For others, create snapshot.
* VMAX could be either the 'block' or back-end for VPLEX virtual volume.
*
* @param vgVolumes the volume group volumes
* @param copyOnHighAvailabilitySide side where to take snap in case of VPLEX Distributed volumes
* @return the VMAX3 volumes
*/
private List<Volume> getVMAX3Volumes(List<Volume> vgVolumes, boolean copyOnHighAvailabilitySide) {
List<Volume> vmax3Volumes = new ArrayList<Volume>();
Iterator<Volume> itr = vgVolumes.iterator();
while (itr.hasNext()) {
Volume volume = itr.next();
URI systemURI = volume.getStorageController();
if (volume.isVPlexVolume(_dbClient)) {
Volume backedVol = null;
if (copyOnHighAvailabilitySide) {
// get backend HA volume, copy is requested on HA side of VPLEX
backedVol = VPlexUtil.getVPLEXBackendVolume(volume, false, _dbClient);
if (backedVol == null || backedVol.getInactive()) {
throw APIException.badRequests.noHAVolumeFoundForVPLEX(volume.getLabel());
}
} else {
// get backend source volume
backedVol = VPlexUtil.getVPLEXBackendVolume(volume, true, _dbClient);
}
systemURI = backedVol.getStorageController();
}
StorageSystem system = _permissionsHelper.getObjectById(systemURI, StorageSystem.class);
if (system.checkIfVmax3()) {
vmax3Volumes.add(volume);
// remove vmax3 volume from given list
itr.remove();
}
}
return vmax3Volumes;
}
/*
* get all snapshot session set names associated with the volume group
*/
private VolumeGroupCopySetList getVolumeGroupSnapsetSessionSets(VolumeGroup volumeGroup) {
VolumeGroupCopySetList copySetList = new VolumeGroupCopySetList();
Set<String> copySets = copySetList.getCopySets();
// get all snapshot sessions for the volume group
List<BlockSnapshotSession> volumeGroupSessions = getVolumeGroupSnapshotSessions(volumeGroup);
for (BlockSnapshotSession session : volumeGroupSessions) {
String sessionsetLabel = session.getSessionSetName();
if (NullColumnValueGetter.isNotNullValue(sessionsetLabel)) {
copySets.add(sessionsetLabel);
}
}
return copySetList;
}
/**
* Creates a volume group snapshot session
* - Creates snapshot session for all the array replication groups within this Application.
* - If partial flag is specified, it creates snapshot session only for set of array replication groups.
* A Volume from each array replication group can be provided to indicate which array replication
* groups are required to take snapshot session.
*
* @prereq none
*
* @param volumeGroupId the URI of the Volume Group
* - Volume group URI
* @param param VolumeGroupSnapshotSessionCreateParam
*
* @brief Create volume group snapshot session
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshot-sessions")
@CheckPermission(roles = { Role.SYSTEM_ADMIN }, acls = { ACL.ANY })
public TaskList createVolumeGroupSnapshotSession(@PathParam("id") final URI volumeGroupId,
VolumeGroupSnapshotSessionCreateParam param) {
ArgValidator.checkFieldUriType(volumeGroupId, VolumeGroup.class, ID_FIELD);
// Query volume group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
// validate replica operation for volume group
validateCopyOperationForVolumeGroup(volumeGroup, ReplicaTypeEnum.SNAPSHOT_SESSION);
// validate name
String name = TimeUtils.formatDateForCurrent(param.getName());
ArgValidator.checkFieldNotEmpty(name, NAME_FIELD);
name = ResourceOnlyNameGenerator.removeSpecialCharsForName(name, SmisConstants.MAX_SNAPSHOT_NAME_LENGTH);
if (StringUtils.isEmpty(name)) {
// original name has special chars only
throw APIException.badRequests.invalidCopySetName(param.getName(),
ReplicaTypeEnum.SNAPSHOT_SESSION.toString());
}
// check name provided is not duplicate
VolumeGroupCopySetList sessionSet = getVolumeGroupSnapsetSessionSets(volumeGroup);
if (sessionSet.getCopySets().contains(name)) {
// duplicate name
throw APIException.badRequests.duplicateCopySetName(param.getName(),
ReplicaTypeEnum.SNAPSHOT_SESSION.toString());
}
// volumes to be processed
List<Volume> volumes = new ArrayList<Volume>();
List<URI> partialVolumeList = new ArrayList<URI>();
boolean partial = isPartialRequest(param, volumeGroup, partialVolumeList);
if (partial) {
log.info("Snapshot Session requested for subset of array groups in Application.");
// validate that at least one volume URI is provided
ArgValidator.checkFieldNotEmpty(partialVolumeList, VOLUMES_FIELD);
// validate the provided volumes
for (URI volumeURI : partialVolumeList) {
ArgValidator.checkFieldUriType(volumeURI, Volume.class, VOLUME_FIELD);
// Get the volume
Volume volume = _dbClient.queryObject(Volume.class, volumeURI);
ArgValidator.checkEntity(volume, volumeURI, isIdEmbeddedInURL(volumeURI));
// validate that provided volume is part of Volume Group
if (!volume.getVolumeGroupIds().contains(volumeGroupId.toString())) {
throw APIException.badRequests
.replicaOperationNotAllowedVolumeNotInVolumeGroup(ReplicaTypeEnum.SNAPSHOT_SESSION.toString(),
volume.getLabel());
}
volumes.add(volume);
}
} else {
log.info("Snapshot Session creation for entire Application");
// get all volumes
volumes.addAll(ControllerUtils.getVolumeGroupVolumes(_dbClient, volumeGroup));
// validate that there should be some volumes in VolumeGroup
if (volumes.isEmpty()) {
throw APIException.badRequests.replicaOperationNotAllowedOnEmptyVolumeGroup(volumeGroup.getLabel(),
ReplicaTypeEnum.SNAPSHOT_SESSION.toString());
}
}
// Check for pending tasks
checkForApplicationPendingTasks(volumeGroup, _dbClient, false);
auditOp(OperationTypeEnum.CREATE_VOLUME_GROUP_SNAPSHOT_SESSION, true, AuditLogManager.AUDITOP_BEGIN, volumeGroupId.toString(),
name);
TaskList taskList = new TaskList();
Map<URI, List<URI>> cgToVolUris = ControllerUtils.groupVolumeURIsByCG(volumes);
Set<Entry<URI, List<URI>>> entrySet = cgToVolUris.entrySet();
for (Entry<URI, List<URI>> entry : entrySet) {
URI cgUri = entry.getKey();
log.info("Create snapshot session for consistency group {}, volumes {}",
cgUri, Joiner.on(',').join(entry.getValue()));
try {
SnapshotSessionCreateParam cgSnapshotSessionParam = new SnapshotSessionCreateParam(
name, param.getNewLinkedTargets(), entry.getValue());
taskList.getTaskList().addAll(
_blockConsistencyGroupService.createConsistencyGroupSnapshotSession(cgUri, cgSnapshotSessionParam)
.getTaskList());
} catch (Exception ex) {
log.error("Unexpected Exception occurred when creating snapshot session for consistency group {}",
cgUri, ex);
}
}
auditOp(OperationTypeEnum.CREATE_VOLUME_GROUP_SNAPSHOT_SESSION, true, AuditLogManager.AUDITOP_END, volumeGroupId.toString(),
name);
return taskList;
}
/**
* List snapshot sessions for a volume group
*
* @param volumeGroupId The URI of the volume group.
* @brief List snapshot session for a volume group
* @return BlockSnapshotSessionList
*/
@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 getVolumeGroupSnapshotSessions(@PathParam("id") final URI volumeGroupId) {
ArgValidator.checkFieldUriType(volumeGroupId, VolumeGroup.class, ID_FIELD);
// query volume group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
// get the snapshot sessions for each CG in the volume group
BlockSnapshotSessionList snapshotSessionList = new BlockSnapshotSessionList();
// get all snapshot sessions for the volume group
List<BlockSnapshotSession> volumeGroupSessions = getVolumeGroupSnapshotSessions(volumeGroup);
for (BlockSnapshotSession session : volumeGroupSessions) {
snapshotSessionList.getSnapSessionRelatedResourceList().
add(toNamedRelatedResource(session));
}
return snapshotSessionList;
}
/**
* Gets the volume group snapshot sessions.
*
* @param volumeGroup the volume group
* @return the volume group snapshot sessions
*/
private List<BlockSnapshotSession> getVolumeGroupSnapshotSessions(final VolumeGroup volumeGroup) {
// get all volumes
List<Volume> volumes = ControllerUtils.getVolumeGroupVolumes(_dbClient, volumeGroup);
// get all RG names
Set<String> rgNames = CopyVolumeGroupUtils.getReplicationGroupNames(volumeGroup, _dbClient);
/**
* Get all CGs involved
* Query all snap sessions for CGs
* Filter Sessions whose RG name does not match with volumes' RG names in VolumeGroup
*/
Set<URI> cgIds = ControllerUtils.groupVolumeURIsByCG(volumes).keySet();
List<BlockSnapshotSession> volumeGroupSessions = new ArrayList<BlockSnapshotSession>();
for (URI cgId : cgIds) {
BlockConsistencyGroup consistencyGroup = _permissionsHelper.getObjectById(cgId,
BlockConsistencyGroup.class);
List<BlockSnapshotSession> cgSessions = getSnapshotSessionManager().
getSnapshotSessionsForCG(consistencyGroup);
// filter Sessions for RGs which are not part of this application
for (BlockSnapshotSession session : cgSessions) {
if (!session.getInactive() &&
rgNames.contains(session.getReplicationGroupInstance())) {
volumeGroupSessions.add(session);
}
}
}
return volumeGroupSessions;
}
/**
* Get the specified volume group snapshot session.
*
* @param volumeGroupId The URI of the volume group.
* @param snapshotIsnapshotSessionIdd The URI of the snapshot session.
* @brief Get the specified volume group snapshot session.
* @return BlockSnapshotSessionRestRep.
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshot-sessions/{sid}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public BlockSnapshotSessionRestRep getVolumeGroupSnapshotSession(@PathParam("id") final URI volumeGroupId,
@PathParam("sid") final URI snapshotSessionId) {
ArgValidator.checkFieldUriType(volumeGroupId, VolumeGroup.class, ID_FIELD);
// query Volume Group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
// validate replica operation for volume group
validateCopyOperationForVolumeGroup(volumeGroup, ReplicaTypeEnum.SNAPSHOT);
// get snapshot session
BlockSnapshotSession snapSession = BlockSnapshotSessionUtils.querySnapshotSession(snapshotSessionId, uriInfo, _dbClient, true);
// validate that the provided snapshot session is part of the volume group
validateSnapSessionBelongsToApplication(snapSession, volumeGroup);
return map(_dbClient, snapSession);
}
/**
* Get all snapshot session set names for a volume group.
*
* @param volumeGroupId The URI of the volume group
*
* @brief List snapsetLabels for a volume group
*
* @return VolumeGroupCopySetList
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshot-sessions/copy-sets")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public VolumeGroupCopySetList getVolumeGroupSnapsetSessionSets(@PathParam("id") final URI volumeGroupId) {
ArgValidator.checkFieldUriType(volumeGroupId, VolumeGroup.class, ID_FIELD);
// query volume group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
return getVolumeGroupSnapsetSessionSets(volumeGroup);
}
/**
* List snapshot sessions in a session set for a volume group.
*
* @param volumeGroupId The URI of the volume group
* @param param the VolumeGroupCopySetParam containing set name
* @return BlockSnapshotSessionList
* @brief List snapshot sessions in session set for a volume group
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshot-sessions/copy-sets")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public BlockSnapshotSessionList getVolumeGroupSnapshotSessionsByCopySet(@PathParam("id") final URI volumeGroupId,
final VolumeGroupCopySetParam param) {
ArgValidator.checkFieldUriType(volumeGroupId, VolumeGroup.class, ID_FIELD);
// query volume group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
// validate snap session set name
String sessionsetName = param.getCopySetName();
ArgValidator.checkFieldNotNull(sessionsetName, COPY_SET_NAME_FIELD);
// get the snapshot sessions for the given set name in the volume group
BlockSnapshotSessionList snapshotSessionList = new BlockSnapshotSessionList();
// validate that the provided set name actually belongs to this Application
VolumeGroupCopySetList copySetList = getVolumeGroupSnapsetSessionSets(volumeGroup);
if (copySetList.getCopySets().contains(sessionsetName)) {
// get the snapshot sessions for the volume group
List<BlockSnapshotSession> volumeGroupSessions = getVolumeGroupSnapshotSessions(volumeGroup);
for (BlockSnapshotSession session : volumeGroupSessions) {
if (sessionsetName.equals(session.getSessionSetName())) {
snapshotSessionList.getSnapSessionRelatedResourceList().
add(toNamedRelatedResource(session));
}
}
}
return snapshotSessionList;
}
/**
* Validate resources and return a list of snapshot sessions for set name
*
* If partial, validate snapshot sessions in VolumeGroupSnapshotOperationParam belonging to multiple sets
* If full, find all snapshot sessions for the set name in the provided snapshot session
*
* @param volumeGroupId
* @param param VolumeGroupSnapshotSessionOperationParam
* @return list of snapshot sessions
*/
private List<BlockSnapshotSession> getSnapshotSessionsGroupedBySnapSessionset(final URI volumeGroupId,
VolumeGroupSnapshotSessionOperationParam param) {
ArgValidator.checkFieldUriType(volumeGroupId, VolumeGroup.class, "id");
// Query volume group
final VolumeGroup volumeGroup = (VolumeGroup) queryResource(volumeGroupId);
// validate replica operation for volume group
validateCopyOperationForVolumeGroup(volumeGroup, ReplicaTypeEnum.SNAPSHOT_SESSION);
List<URI> snapshotsInRequest = new ArrayList<URI>();
boolean partialRequest = false;
if (param.getSnapshotSessions() != null && !param.getSnapshotSessions().isEmpty()) {
// validate only one snapshot URI is provided for full request
if (!param.getPartial() && param.getSnapshotSessions().size() > 1) {
throw APIException.badRequests.invalidNumberOfReplicas(ReplicaTypeEnum.SNAPSHOT.toString());
}
snapshotsInRequest.addAll(param.getSnapshotSessions());
partialRequest = param.getPartial();
} else {
ArgValidator.checkFieldNotEmpty(param.getCopySetName(), COPY_SET_NAME_FIELD);
if (param.getSubGroups() != null && !param.getSubGroups().isEmpty()) {
partialRequest = validateSubGroupsParam(param.getSubGroups(), volumeGroup);
}
URIQueryResultList snapshotIds = new URIQueryResultList();
_dbClient.queryByConstraint(AlternateIdConstraint.Factory.getBlockSnapshotSessionsBySetName(param.getCopySetName()), snapshotIds);
Iterator<BlockSnapshotSession> iter = _dbClient.queryIterativeObjects(BlockSnapshotSession.class, snapshotIds);
if (partialRequest) {
// get one snapshot per sub group requested
List<Volume> volumesInApplication = ControllerUtils.getVolumeGroupVolumes(_dbClient, volumeGroup);
Map<URI, Volume> volumesMap = new HashMap<URI, Volume>();
for (Volume volume : volumesInApplication) {
volumesMap.put(volume.getId(), volume);
}
while (iter.hasNext()) {
BlockSnapshotSession snapshot = iter.next();
Volume parent = volumesMap.get(snapshot.getParent().getURI());
if (parent != null && param.getSubGroups().contains(parent.getReplicationGroupInstance())) {
snapshotsInRequest.add(iter.next().getId());
}
}
} else {
// for non-partial, we only need one snapshot in the snapshot set
if (iter.hasNext()) {
snapshotsInRequest.add(iter.next().getId());
}
}
}
List<BlockSnapshotSession> snapSessions = new ArrayList<BlockSnapshotSession>();
Set<String> setNames = new HashSet<String>();
for (URI sessionURI : snapshotsInRequest) {
ArgValidator.checkFieldUriType(sessionURI, BlockSnapshotSession.class, SNAPSHOT_SESSION_FIELD);
// Get the snapshot session
BlockSnapshotSession session = BlockSnapshotSessionUtils.querySnapshotSession(sessionURI, uriInfo, _dbClient, true);
// validate that source of the provided snapshot session is part of the volume group
validateSnapSessionBelongsToApplication(session, volumeGroup);
String setName = session.getSessionSetName();
setNames.add(setName);
if (partialRequest) {
snapSessions.add(session);
} else {
log.info("Snapshot Session operation requested for entire Application, Considering session {} in request.",
session.getLabel());
// get the snapshot sessions for the volume group
List<BlockSnapshotSession> volumeGroupSessions = getVolumeGroupSnapshotSessions(volumeGroup);
for (BlockSnapshotSession vgSession : volumeGroupSessions) {
if (setName.equals(vgSession.getSessionSetName())) {
snapSessions.add(vgSession);
}
}
}
}
if (setNames.size() > 1) {
throw APIException.badRequests.multipleSetNamesProvided(ReplicaTypeEnum.SNAPSHOT_SESSION.toString());
}
return snapSessions;
}
/*
* Wrapper of BlockConsistencyGroupService methods for snapshot session operations
*
* @param volumeGroupId
*
* @param param
*
* @return a TaskList
*/
private TaskList performVolumeGroupSnapshotSessionOperation(final URI volumeGroupId,
final VolumeGroupSnapshotSessionOperationParam param,
OperationTypeEnum opType) {
List<BlockSnapshotSession> snapSessions = getSnapshotSessionsGroupedBySnapSessionset(volumeGroupId, param);
// Check for pending tasks
VolumeGroup volumeGroup = _dbClient.queryObject(VolumeGroup.class, volumeGroupId);
if (opType == OperationTypeEnum.RESTORE_VOLUME_GROUP_SNAPSHOT_SESSION) {
checkForApplicationPendingTasks(volumeGroup, _dbClient, true);
} else {
checkForApplicationPendingTasks(volumeGroup, _dbClient, false);
}
auditOp(opType, true, AuditLogManager.AUDITOP_BEGIN,
volumeGroupId.toString(), param.getSnapshotSessions());
TaskList taskList = new TaskList();
Table<URI, String, BlockSnapshotSession> storageRgToSnapshot = ControllerUtils.
getSnapshotSessionForStorageReplicationGroup(snapSessions, _dbClient);
for (Cell<URI, String, BlockSnapshotSession> cell : storageRgToSnapshot.cellSet()) {
BlockSnapshotSession session = cell.getValue();
log.info("{} for replication group {}", opType.getDescription(), cell.getColumnKey());
ResourceOperationTypeEnum oprEnum = null;
try {
URI cgUri = session.getConsistencyGroup(); // should not be null
URI sessionUri = session.getId();
log.info("CG: {}, Session: {}", cgUri, session.getLabel());
switch (opType) {
case RESTORE_VOLUME_GROUP_SNAPSHOT_SESSION:
oprEnum = ResourceOperationTypeEnum.RESTORE_SNAPSHOT_SESSION;
taskList.addTask(
_blockConsistencyGroupService.restoreConsistencyGroupSnapshotSession(cgUri, sessionUri));
break;
case DELETE_VOLUME_GROUP_SNAPSHOT_SESSION:
oprEnum = ResourceOperationTypeEnum.DELETE_CONSISTENCY_GROUP_SNAPSHOT_SESSION;
taskList.getTaskList().addAll(
_blockConsistencyGroupService.deactivateConsistencyGroupSnapshotSession(cgUri, sessionUri)
.getTaskList());
break;
case LINK_VOLUME_GROUP_SNAPSHOT_SESSION_TARGET:
oprEnum = ResourceOperationTypeEnum.LINK_SNAPSHOT_SESSION_TARGETS;
SnapshotSessionLinkTargetsParam linkParam = new SnapshotSessionLinkTargetsParam(
((VolumeGroupSnapshotSessionLinkTargetsParam) param).getNewLinkedTargets());
taskList.getTaskList().addAll(
_blockConsistencyGroupService.linkTargetVolumes(cgUri, sessionUri, linkParam)
.getTaskList());
break;
case RELINK_VOLUME_GROUP_SNAPSHOT_SESSION_TARGET:
oprEnum = ResourceOperationTypeEnum.RELINK_CONSISTENCY_GROUP_SNAPSHOT_SESSION_TARGETS;
SnapshotSessionRelinkTargetsParam relinkParam = new SnapshotSessionRelinkTargetsParam(
getRelinkTargetIdsForSession((VolumeGroupSnapshotSessionRelinkTargetsParam) param, session,
snapSessions.size()));
taskList.getTaskList().addAll(
_blockConsistencyGroupService.relinkTargetVolumes(cgUri, sessionUri, relinkParam)
.getTaskList());
break;
case UNLINK_VOLUME_GROUP_SNAPSHOT_SESSION_TARGET:
oprEnum = ResourceOperationTypeEnum.UNLINK_SNAPSHOT_SESSION_TARGETS;
SnapshotSessionUnlinkTargetsParam unlinkParam = new SnapshotSessionUnlinkTargetsParam(
getUnlinkTargetIdsForSession((VolumeGroupSnapshotSessionUnlinkTargetsParam) param, session));
taskList.addTask(
_blockConsistencyGroupService.unlinkTargetVolumesForSession(cgUri, sessionUri, unlinkParam));
break;
default:
log.error("Unsupported operation {}", opType.getDescription());
break;
}
} catch (InternalException | APIException e) {
String errMsg = String.format("Exception occurred while performing %s on Replication group %s",
opType.getDescription(), cell.getColumnKey());
log.error(errMsg, e);
TaskResourceRep task = BlockServiceUtils.createFailedTaskOnSnapshotSession(_dbClient, session, oprEnum, e);
taskList.addTask(task);
} catch (Exception ex) {
String errMsg = String.format("Unexpected Exception occurred while performing %s on Replication group %s",
opType.getDescription(), cell.getColumnKey());
log.error(errMsg, ex);
}
}
auditOp(opType, true, AuditLogManager.AUDITOP_END, volumeGroupId.toString(), param.getSnapshotSessions());
return taskList;
}
/**
* Gets the relink target ids for the given session.
*
* @param param the VolumeGroupSnapshotSessionRelinkTargetsParam
* @param session the snap session
* @param numberOfRequestedSessions the number of requested sessions
* @return the relink target ids for session
*/
private List<URI> getRelinkTargetIdsForSession(final VolumeGroupSnapshotSessionRelinkTargetsParam param,
BlockSnapshotSession session, int numberOfRequestedSessions) {
List<URI> targetIds = new ArrayList<URI>();
if (param.getLinkedTargetIds() != null && !param.getLinkedTargetIds().isEmpty() && numberOfRequestedSessions == 1) {
/**
* It could be a request from user to re-link targets to different snap sessions.
* So, don't filter given targets based on snap session.
*
* Re-linking different targets to a snap session can be done for only one
* Replication group at a time.
*/
targetIds = param.getLinkedTargetIds();
} else {
/**
* Re-link to same linked targets
*/
StringSet sessionTargets = session.getLinkedTargets();
if (param.getLinkedTargetIds() != null && !param.getLinkedTargetIds().isEmpty()) {
for (URI snapURI : param.getLinkedTargetIds()) {
// Snapshot session targets are represented by BlockSnapshot instances in ViPR.
ArgValidator.checkFieldUriType(snapURI, BlockSnapshot.class, "id");
BlockSnapshot snap = _dbClient.queryObject(BlockSnapshot.class, snapURI);
ArgValidator.checkEntityNotNull(snap, snapURI, isIdEmbeddedInURL(snapURI));
if (sessionTargets != null && sessionTargets.contains(snapURI.toString())) {
targetIds.add(snapURI);
}
}
} else {
ArgValidator.checkFieldNotEmpty(param.getTargetName(), "target_name");
for (String linkedTgtId : session.getLinkedTargets()) {
URI snapURI = URI.create(linkedTgtId);
BlockSnapshot snap = _dbClient.queryObject(BlockSnapshot.class, snapURI);
if (StringUtils.equals(param.getTargetName(), snap.getSnapsetLabel())) {
targetIds.add(snapURI);
}
}
}
}
log.info(String.format("Target ids for snapshot session %s : %s",
session.getLabel(), Joiner.on(',').join(targetIds)));
if (targetIds.isEmpty()) {
// None of the provided target belong to this snapshot session.
throw APIException.badRequests.snapshotSessionDoesNotHaveAnyTargetsInGivenList(session.getId().toString());
}
return targetIds;
}
/**
* Gets the unlink target ids for the given session.
*
* @param param the VolumeGroupSnapshotSessionUnlinkTargetsParam
* @param session the snap session
* @return the unlink target id params for session
*/
private List<SnapshotSessionUnlinkTargetParam> getUnlinkTargetIdsForSession(final VolumeGroupSnapshotSessionUnlinkTargetsParam param,
BlockSnapshotSession session) {
List<SnapshotSessionUnlinkTargetParam> targetIds = new ArrayList<SnapshotSessionUnlinkTargetParam>();
List<URI> selectedURIs = new ArrayList<URI>();
StringSet sessionTargets = session.getLinkedTargets();
if (param.getLinkedTargets() != null && !param.getLinkedTargets().isEmpty()) {
for (SnapshotSessionUnlinkTargetParam unlinkTarget : param.getLinkedTargets()) {
URI snapURI = unlinkTarget.getId();
// Snapshot session targets are represented by BlockSnapshot instances in ViPR.
ArgValidator.checkFieldUriType(snapURI, BlockSnapshot.class, "id");
BlockSnapshot snap = _dbClient.queryObject(BlockSnapshot.class, snapURI);
ArgValidator.checkEntityNotNull(snap, snapURI, isIdEmbeddedInURL(snapURI));
if (sessionTargets != null && sessionTargets.contains(snapURI.toString())) {
targetIds.add(unlinkTarget);
selectedURIs.add(snapURI);
}
}
} else {
ArgValidator.checkFieldNotEmpty(param.getTargetName(), "target_name");
for (String linkedTgtId : session.getLinkedTargets()) {
URI snapURI = URI.create(linkedTgtId);
BlockSnapshot snap = _dbClient.queryObject(BlockSnapshot.class, snapURI);
if (StringUtils.equals(param.getTargetName(), snap.getSnapsetLabel())) {
SnapshotSessionUnlinkTargetParam unlinkTarget = new SnapshotSessionUnlinkTargetParam();
unlinkTarget.setId(snap.getId());
unlinkTarget.setDeleteTarget(param.getDeleteTarget() == null ? false : param.getDeleteTarget());
targetIds.add(unlinkTarget);
selectedURIs.add(snapURI);
}
}
}
log.info(String.format("Target ids for snapshot session %s : %s",
session.getLabel(), Joiner.on(',').join(selectedURIs)));
if (targetIds.isEmpty()) {
// None of the provided target belong to this snapshot session.
throw APIException.badRequests.snapshotSessionDoesNotHaveAnyTargets(session.getId().toString());
}
return targetIds;
}
/**
* Deactivate the specified Volume Group Snapshot Sessions
* - Deactivates snapshot sessions for all the array replication groups within this Application.
* - If partial flag is specified, it deactivates snapshot session only for set of array replication groups.
* A snapshot session from each array replication group can be provided to indicate which array replication
* groups's sessions needs to be deactivated.
*
* @prereq Create volume group snapshot session.
*
* @param volumeGroupId The URI of the volume group.
* @param param VolumeGroupSnapshotSessionDeactivateParam.
*
* @brief Deactivate volume group snapshot sessions.
*
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshot-sessions/deactivate")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList deactivateVolumeGroupSnapshotSession(@PathParam("id") final URI volumeGroupId,
final VolumeGroupSnapshotSessionDeactivateParam param) {
return performVolumeGroupSnapshotSessionOperation(volumeGroupId, param,
OperationTypeEnum.DELETE_VOLUME_GROUP_SNAPSHOT_SESSION);
}
/**
* Restores the specified Volume Group Snapshot Sessions to the source object.
* - Restores snapshot sessions for all the array replication groups within this Application.
* - If partial flag is specified, it restores snapshot session only for set of array replication groups.
* A snapshot session from each array replication group can be provided to indicate which array replication
* groups's sessions needs to be restored.
*
* @prereq None
*
* @param volumeGroupId The URI of the volume group.
* @param param VolumeGroupSnapshotSessionRestoreParam.
*
* @brief Restore volume group snapshot sessions to source.
*
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshot-sessions/restore")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList restoreVolumeGroupSnapshotSession(@PathParam("id") final URI volumeGroupId,
final VolumeGroupSnapshotSessionRestoreParam param) {
return performVolumeGroupSnapshotSessionOperation(volumeGroupId, param,
OperationTypeEnum.RESTORE_VOLUME_GROUP_SNAPSHOT_SESSION);
}
/**
* The method implements the API to create and link new target volumes
* to an existing BlockSnapshotSession instances in the volume group.
* - Links target volumes to snapshot sessions for all the array replication groups within this Application.
* - If partial flag is specified, it links targets to snapshot session only for set of array replication groups.
* A snapshot session from each array replication group can be provided to indicate which array replication
* groups's sessions needs to be linked.
*
* @prereq The block snapshot session has been created and the maximum
* number of targets has not already been linked to the snapshot sessions
* for the source object.
*
* @param volumeGroupId The URI of the volume group.
* @param param VolumeGroupSnapshotSessionLinkTargetsParam.
*
* @brief Link target volumes to volume group snapshot sessions
*
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshot-sessions/link-targets")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList linkTargetVolumesForVolumeGroupSession(@PathParam("id") final URI volumeGroupId,
VolumeGroupSnapshotSessionLinkTargetsParam param) {
return performVolumeGroupSnapshotSessionOperation(volumeGroupId, param,
OperationTypeEnum.LINK_VOLUME_GROUP_SNAPSHOT_SESSION_TARGET);
}
/**
* The method implements the API to re-link a target to either its current snapshot sessions
* or to a different snapshot sessions of the same source in the volume group.
* - Re-links targets for all the array replication groups within this Application.
* - If partial flag is specified, it re-links targets only for set of array replication groups.
* A snapshot session from each array replication group can be provided to indicate which array replication
* groups's sessions needs to be linked.
*
* @prereq The target volumes are linked to a snapshot session of the same source object.
*
* @param volumeGroupId The URI of the volume group.
* @param param VolumeGroupSnapshotSessionRelinkTargetsParam.
*
* @brief Re-link target volumes to volume group snapshot sessions
*
* @return TaskList
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshot-sessions/relink-targets")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList relinkTargetVolumesForVolumeGroupSession(@PathParam("id") final URI volumeGroupId,
VolumeGroupSnapshotSessionRelinkTargetsParam param) {
return performVolumeGroupSnapshotSessionOperation(volumeGroupId, param,
OperationTypeEnum.RELINK_VOLUME_GROUP_SNAPSHOT_SESSION_TARGET);
}
/**
* The method implements the API to unlink target volumes from an existing
* BlockSnapshotSession instances and optionally delete those target volumes in the volume group.
* - Unlinks target volumes from snapshot sessions for all the array replication groups within this Application.
* - If partial flag is specified, it unlinks targets from snapshot session only for set of array replication groups.
* A snapshot session from each array replication group can be provided to indicate which array replication
* groups's sessions needs to be unlinked.
*
* @prereq A snapshot session is created and target volumes have previously
* been linked to that snapshot session.
*
* @param volumeGroupId The URI of the volume group.
* @param param VolumeGroupSnapshotSessionUnlinkTargetsParam.
*
* @brief Unlink target volumes from volume group snapshot sessions
*
* @return TaskList
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/protection/snapshot-sessions/unlink-targets")
@CheckPermission(roles = { Role.TENANT_ADMIN }, acls = { ACL.ANY })
public TaskList unlinkTargetVolumesForVolumeGroupSession(@PathParam("id") final URI volumeGroupId,
VolumeGroupSnapshotSessionUnlinkTargetsParam param) {
return performVolumeGroupSnapshotSessionOperation(volumeGroupId, param,
OperationTypeEnum.UNLINK_VOLUME_GROUP_SNAPSHOT_SESSION_TARGET);
}
/**
* Validates that the snap session belongs to the given application.
*
* @param session the snapshot session
* @param volumeGroup the volume group
*/
private void validateSnapSessionBelongsToApplication(BlockSnapshotSession session, VolumeGroup volumeGroup) {
List<BlockSnapshotSession> volumeGroupSessions = getVolumeGroupSnapshotSessions(volumeGroup);
Set<URI> sessionURIs = new HashSet<URI>();
for (BlockSnapshotSession snapSession : volumeGroupSessions) {
sessionURIs.add(snapSession.getId());
}
if (!sessionURIs.contains(session.getId())) {
throw APIException.badRequests
.replicaOperationNotAllowedSourceNotInVolumeGroup(ReplicaTypeEnum.SNAPSHOT_SESSION.toString(),
session.getLabel());
}
}
private List<VolumeGroupUtils> getVolumeGroupUtils(VolumeGroup volumeGroup) {
List<VolumeGroupUtils> utilsList = new ArrayList<VolumeGroupUtils>();
if (volumeGroup.getRoles().contains(VolumeGroup.VolumeGroupRole.COPY.toString())) {
utilsList.add(new CopyVolumeGroupUtils());
}
if (volumeGroup.getRoles().contains(VolumeGroup.VolumeGroupRole.MOBILITY.toString())) {
utilsList.add(new MobilityVolumeGroupUtils());
}
if (volumeGroup.getRoles().contains(VolumeGroup.VolumeGroupRole.DR.toString())) {
utilsList.add(new DRVolumeGroupUtils());
}
return utilsList;
}
private static abstract class VolumeGroupUtils {
/**
* @param param
* @param volumeGroup
* @param taskId
* @param taskList
* @return
*/
public abstract void updateVolumesInVolumeGroup(DbClient dbClient, final VolumeGroupUpdateParam param, VolumeGroup volumeGroup,
String taskId, TaskList taskList);
/**
* @param dbClient
* @param param
* @param volumeGroup
* @param taskId
* @param taskList
*/
public abstract void validateUpdateVolumesInVolumeGroup(DbClient dbClient, final VolumeGroupUpdateParam param,
VolumeGroup volumeGroup);
protected void updateHostObjects(DbClient dbClient, List<Host> addHosts, List<Host> removeHosts, VolumeGroup volumeGroup) {
for (Host addHost : addHosts) {
addHost.getVolumeGroupIds().add(volumeGroup.getId().toString());
}
for (Host remHost : removeHosts) {
remHost.getVolumeGroupIds().remove(volumeGroup.getId().toString());
}
dbClient.updateObject(addHosts);
dbClient.updateObject(removeHosts);
}
protected void updateClusterObjects(DbClient dbClient, List<Cluster> addClusters, List<Cluster> removeClusters,
VolumeGroup volumeGroup) {
for (Cluster addCluster : addClusters) {
addCluster.getVolumeGroupIds().add(volumeGroup.getId().toString());
}
for (Cluster remCluster : removeClusters) {
remCluster.getVolumeGroupIds().remove(volumeGroup.getId().toString());
}
dbClient.updateObject(addClusters);
dbClient.updateObject(removeClusters);
}
protected void updateVolumeObjects(DbClient dbClient, List<Volume> addVols, List<Volume> removeVols, VolumeGroup volumeGroup) {
for (Volume addVol : addVols) {
addVol.getVolumeGroupIds().add(volumeGroup.getId().toString());
}
for (Volume remVol : removeVols) {
remVol.getVolumeGroupIds().remove(volumeGroup.getId().toString());
}
dbClient.updateObject(addVols);
dbClient.updateObject(removeVols);
}
/**
* Add task for volumes and consistency groups
*
* @param addVols
* @param removeVols
* @param removeVolumeCGs
* @param taskId
* @param taskList
*/
protected void addTasksForVolumesAndCGs(DbClient dbClient, List<Volume> addVols, List<Volume> removeVols, Set<URI> removeVolumeCGs,
String taskId, TaskList taskList) {
if (addVols != null && !addVols.isEmpty()) {
for (Volume vol : addVols) {
addVolumeTask(dbClient, vol, taskList, taskId, ResourceOperationTypeEnum.UPDATE_VOLUME_GROUP);
}
}
if (removeVols != null && !removeVols.isEmpty()) {
for (Volume vol : removeVols) {
addVolumeTask(dbClient, vol, taskList, taskId, ResourceOperationTypeEnum.UPDATE_VOLUME_GROUP);
}
}
if (removeVolumeCGs != null && !removeVolumeCGs.isEmpty()) {
for (URI cg : removeVolumeCGs) {
addConsistencyGroupTask(dbClient, cg, taskList, taskId, ResourceOperationTypeEnum.UPDATE_VOLUME_GROUP);
}
}
}
/**
* @param dbClient
* @param uriList
* @param taskId
* @param e
*/
protected void updateFailedVolumeTasks(DbClient dbClient, List<URI> uriList, String taskId, ServiceCoded e) {
for (URI uri : uriList) {
Volume vol = dbClient.queryObject(Volume.class, uri);
Operation op = vol.getOpStatus().get(taskId);
if (op != null) {
op.error(e);
vol.getOpStatus().updateTaskStatus(taskId, op);
dbClient.updateObject(vol);
}
}
}
/**
* Creates tasks against consistency group associated with a request and adds them to the given task list.
*
* @param group
* @param taskList
* @param taskId
* @param operationTypeEnum
*/
private void addConsistencyGroupTask(DbClient dbClient, URI groupUri, TaskList taskList,
String taskId,
ResourceOperationTypeEnum operationTypeEnum) {
BlockConsistencyGroup group = dbClient.queryObject(BlockConsistencyGroup.class, groupUri);
Operation op = dbClient.createTaskOpStatus(BlockConsistencyGroup.class, group.getId(), taskId,
operationTypeEnum);
taskList.getTaskList().add(TaskMapper.toTask(group, taskId, op));
}
/**
* Creates tasks against volume associated with a request and adds them to the given task list.
*
* @param volume
* @param taskList
* @param taskId
* @param operationTypeEnum
*/
private void addVolumeTask(DbClient dbClient, Volume volume, TaskList taskList,
String taskId,
ResourceOperationTypeEnum operationTypeEnum) {
Operation op = dbClient.createTaskOpStatus(Volume.class, volume.getId(), taskId,
operationTypeEnum);
taskList.getTaskList().add(TaskMapper.toTask(volume, taskId, op));
}
}
private static class MobilityVolumeGroupUtils extends VolumeGroupUtils {
/**
* Validate if all volumes within a CG are part of the specified volume list.
* If a CG doesn't contain all its volumes, the order will fail.
*
* @param volumeGroup being update
* @param volumes being added or removed
*/
protected void validateSameCG(DbClient dbClient, VolumeGroup volumeGroup, List<Volume> volumes) {
Set<URI> consistencyGroups = Sets.newHashSet();
List<URI> volumeIds = new ArrayList<URI>();
// Get list of all consistency groups for these volumes
if (volumes != null && !volumes.isEmpty()) {
for (Volume volume : volumes) {
volumeIds.add(volume.getId());
if (!NullColumnValueGetter.isNullURI(volume.getConsistencyGroup())) {
consistencyGroups.add(volume.getConsistencyGroup());
}
}
Volume firstVol = volumes.get(0);
BlockServiceApi blockService = CopyVolumeGroupUtils.getBlockService(dbClient, firstVol);
for (URI consistencyGroupId : consistencyGroups) {
BlockConsistencyGroup consistencyGroup = dbClient.queryObject(BlockConsistencyGroup.class, consistencyGroupId);
List<Volume> cgVolumes = null;
if (consistencyGroup.getRequestedTypes().contains(Types.RP.toString())) {
cgVolumes = RPHelper.getCgSourceVolumes(consistencyGroup.getId(), dbClient);
} else {
cgVolumes = blockService.getActiveCGVolumes(consistencyGroup);
}
// make sure all volumes in 'cgVolumes' are also in 'volumes'
for (Volume cgVolume : cgVolumes) {
if (!volumeIds.contains(cgVolume.getId())) {
throw APIException.badRequests.volumeGroupCantBeUpdated(volumeGroup.getLabel(),
String.format("a consistency group %s does not contain all of its volumes",
consistencyGroup.getLabel()));
}
}
}
}
}
/*
* (non-Javadoc)
*
* @see
* com.emc.storageos.api.service.impl.resource.VolumeGroupService.VolumeGroupUtils#updateVolumesInVolumeGroup(com.emc.storageos.
* db.client.DbClient, java.net.URI, com.emc.storageos.model.application.VolumeGroupUpdateParam,
* com.emc.storageos.db.client.model.VolumeGroup, java.lang.String, com.emc.storageos.model.TaskList)
*/
@Override
public void updateVolumesInVolumeGroup(DbClient dbClient, VolumeGroupUpdateParam param, VolumeGroup volumeGroup, String taskId,
TaskList taskList) {
List<Volume> removeVols = new ArrayList<Volume>();
List<Volume> addVols = new ArrayList<Volume>();
if (param.hasVolumesToAdd()) {
Iterator<Volume> addVolItr = dbClient.queryIterativeObjects(Volume.class, param.getAddVolumesList().getVolumes());
while (addVolItr.hasNext()) {
addVols.add(addVolItr.next());
}
validateSameCG(dbClient, volumeGroup, addVols);
}
if (param.hasVolumesToRemove()) {
Iterator<Volume> remVolItr = dbClient.queryIterativeObjects(Volume.class, param.getRemoveVolumesList().getVolumes());
while (remVolItr.hasNext()) {
removeVols.add(remVolItr.next());
}
validateSameCG(dbClient, volumeGroup, removeVols);
}
List<Host> removeHosts = new ArrayList<Host>();
List<Host> addHosts = new ArrayList<Host>();
if (param.hasHostsToAdd()) {
Iterator<Host> addHostItr = dbClient.queryIterativeObjects(Host.class, param.getAddHostsList());
while (addHostItr.hasNext()) {
addHosts.add(addHostItr.next());
}
}
if (param.hasHostsToRemove()) {
Iterator<Host> remHostItr = dbClient.queryIterativeObjects(Host.class, param.getRemoveHostsList());
while (remHostItr.hasNext()) {
removeHosts.add(remHostItr.next());
}
}
List<Cluster> removeClusters = new ArrayList<Cluster>();
List<Cluster> addClusters = new ArrayList<Cluster>();
if (param.hasClustersToAdd()) {
Iterator<Cluster> addClusterItr = dbClient.queryIterativeObjects(Cluster.class, param.getAddClustersList());
while (addClusterItr.hasNext()) {
addClusters.add(addClusterItr.next());
}
}
if (param.hasClustersToRemove()) {
Iterator<Cluster> remClusterItr = dbClient.queryIterativeObjects(Cluster.class, param.getRemoveClustersList());
while (remClusterItr.hasNext()) {
removeClusters.add(remClusterItr.next());
}
}
Operation op = dbClient.createTaskOpStatus(VolumeGroup.class, volumeGroup.getId(),
taskId, ResourceOperationTypeEnum.UPDATE_VOLUME_GROUP);
taskList.getTaskList().add(toTask(volumeGroup, taskId, op));
addTasksForVolumesAndCGs(dbClient, addVols, removeVols, null, taskId, taskList);
try {
updateVolumeObjects(dbClient, addVols, removeVols, volumeGroup);
updateHostObjects(dbClient, addHosts, removeHosts, volumeGroup);
updateClusterObjects(dbClient, addClusters, removeClusters, volumeGroup);
} catch (InternalException | APIException e) {
VolumeGroup app = dbClient.queryObject(VolumeGroup.class, volumeGroup.getId());
op = app.getOpStatus().get(taskId);
op.error(e);
app.getOpStatus().updateTaskStatus(taskId, op);
dbClient.updateObject(app);
if (param.hasVolumesToAdd()) {
List<URI> addURIs = param.getAddVolumesList().getVolumes();
updateFailedVolumeTasks(dbClient, addURIs, taskId, e);
}
if (param.hasVolumesToRemove()) {
List<URI> removeURIs = param.getRemoveVolumesList().getVolumes();
updateFailedVolumeTasks(dbClient, removeURIs, taskId, e);
}
throw e;
}
updateVolumeAndGroupTasks(dbClient, addVols, removeVols, volumeGroup.getId(), taskId);
}
/*
* (non-Javadoc)
*
* @see
* com.emc.storageos.api.service.impl.resource.VolumeGroupService.VolumeGroupUtils#validateUpdateVolumesInVolumeGroup(com.emc.storageos
* .db.client.DbClient, com.emc.storageos.model.application.VolumeGroupUpdateParam, com.emc.storageos.db.client.model.VolumeGroup)
*/
@Override
public void validateUpdateVolumesInVolumeGroup(DbClient dbClient, VolumeGroupUpdateParam param, VolumeGroup volumeGroup) {
validateParameters(Cluster.class, param.getAddClustersList(), ADD_CLUSTERS);
validateParameters(Cluster.class, param.getRemoveClustersList(), REMOVE_CLUSTERS);
validateParameters(Host.class, param.getAddHostsList(), ADD_HOSTS);
validateParameters(Host.class, param.getRemoveHostsList(), REMOVE_HOSTS);
if (param.getAddVolumesList() != null) {
validateParameters(Volume.class, param.getAddVolumesList().getVolumes(), ADD_VOLUMES);
}
if (param.getRemoveVolumesList() != null) {
validateParameters(Volume.class, param.getRemoveVolumesList().getVolumes(), REMOVE_VOLUMES);
}
}
private void validateParameters(Class<? extends DataObject> clazz, List<URI> ids, String field) {
if (ids != null) {
for (URI id : ids) {
ArgValidator.checkFieldUriType(id, clazz, field);
}
}
}
protected void updateVolumeAndGroupTasks(DbClient dbClient, List<Volume> addVols, List<Volume> removeVols, URI volumeGroupId,
String taskId) {
if (addVols != null && !addVols.isEmpty()) {
updateVolumeTasks(dbClient, addVols, taskId);
}
if (removeVols != null && !removeVols.isEmpty()) {
updateVolumeTasks(dbClient, removeVols, taskId);
}
VolumeGroup volumeGroup = dbClient.queryObject(VolumeGroup.class, volumeGroupId);
Operation op = volumeGroup.getOpStatus().get(taskId);
op.ready();
volumeGroup.getOpStatus().updateTaskStatus(taskId, op);
dbClient.updateObject(volumeGroup);
}
protected void updateVolumeTasks(DbClient dbClient, List<Volume> vols, String taskId) {
for (Volume vol : vols) {
vol = dbClient.queryObject(Volume.class, vol.getId());
Operation op = vol.getOpStatus().get(taskId);
if (op != null) {
op.ready();
vol.getOpStatus().updateTaskStatus(taskId, op);
dbClient.updateObject(vol);
}
}
}
}
private static class DRVolumeGroupUtils extends VolumeGroupUtils {
/*
* (non-Javadoc)
*
* @see
* com.emc.storageos.api.service.impl.resource.VolumeGroupService.VolumeGroupUtils#updateVolumesInVolumeGroup(com.emc.storageos.
* db.client.DbClient, java.net.URI, com.emc.storageos.model.application.VolumeGroupUpdateParam,
* com.emc.storageos.db.client.model.VolumeGroup, java.lang.String, com.emc.storageos.model.TaskList)
*/
@Override
public void updateVolumesInVolumeGroup(DbClient dbClient, VolumeGroupUpdateParam param, VolumeGroup volumeGroup, String taskId,
TaskList taskList) {
// TODO Auto-generated method stub
}
/*
* (non-Javadoc)
*
* @see
* com.emc.storageos.api.service.impl.resource.VolumeGroupService.VolumeGroupUtils#validateUpdateVolumesInVolumeGroup(com.emc.storageos
* .db.client.DbClient, com.emc.storageos.model.application.VolumeGroupUpdateParam, com.emc.storageos.db.client.model.VolumeGroup)
*/
@Override
public void validateUpdateVolumesInVolumeGroup(DbClient dbClient, VolumeGroupUpdateParam param, VolumeGroup volumeGroup) {
// TODO Auto-generated method stub
}
}
private static class CopyVolumeGroupUtils extends VolumeGroupUtils {
private List<Volume> removeVols;
private List<Volume> addVols;
private Set<URI> impactedCGs = new HashSet<URI>();
private Volume firstVol;
private boolean validated;
/*
* (non-Javadoc)
*
* @see
* com.emc.storageos.api.service.impl.resource.VolumeGroupService.VolumeGroupUtils#validateUpdateVolumesInVolumeGroup(com.emc.storageos
* .db.client.DbClient, com.emc.storageos.model.application.VolumeGroupUpdateParam, com.emc.storageos.db.client.model.VolumeGroup,
* java.lang.String, com.emc.storageos.model.TaskList)
*/
@Override
public void validateUpdateVolumesInVolumeGroup(DbClient dbClient, VolumeGroupUpdateParam param, VolumeGroup volumeGroup) {
impactedCGs = new HashSet<URI>();
if (param.hasVolumesToAdd()) {
// if the volume is RP or VPlex, replicationGroupName is required input; otherwise it's not
Iterator<Volume> volumes = dbClient.queryIterativeObjects(Volume.class, param.getAddVolumesList().getVolumes());
if (volumes.hasNext()) {
Volume vol = volumes.next();
if (!NullColumnValueGetter.isNullURI(vol.getProtectionController())
|| vol.getAssociatedVolumes() != null && !vol.getAssociatedVolumes().isEmpty()) {
ArgValidator.checkFieldNotEmpty(param.getAddVolumesList().getReplicationGroupName(), RG_NAME_FIELD);
} else {
if (param.getAddVolumesList().getReplicationGroupName() != null) {
throw APIException.badRequests.volumeCantBeAddedToVolumeGroup(vol.getLabel(),
String.format("because %s is specified for volumes already in a replication group", RG_NAME_FIELD));
}
// for non-RP and non-VPlex, ignore any incoming replication group name and use the one that's already there on the
// volume
param.getAddVolumesList().setReplicationGroupName(vol.getReplicationGroupInstance());
}
}
addVols = validateAddVolumes(dbClient, param, volumeGroup, impactedCGs);
firstVol = addVols.get(0);
}
if (param.hasVolumesToRemove()) {
List<URI> removeVolList = param.getRemoveVolumesList().getVolumes();
removeVols = validateRemoveVolumes(dbClient, removeVolList, volumeGroup, impactedCGs);
if (!removeVols.isEmpty() && firstVol == null) {
firstVol = removeVols.get(0);
}
}
validated = true;
}
/*
* (non-Javadoc)
*
* @see com.emc.storageos.api.service.impl.resource.VolumeGroupService.VolumeGroupUtils#updateVolumesInVolumeGroup(java.net.URI,
* com.emc.storageos.model.application.VolumeGroupUpdateParam, com.emc.storageos.db.client.model.VolumeGroup, java.lang.String,
* com.emc.storageos.model.TaskList)
*/
@Override
public void updateVolumesInVolumeGroup(DbClient dbClient, VolumeGroupUpdateParam param, VolumeGroup volumeGroup, String taskId,
TaskList taskList) {
if (!validated) {
validateUpdateVolumesInVolumeGroup(dbClient, param, volumeGroup);
}
if (removeVols != null && !removeVols.isEmpty()) {
// if any of the remove volumes are not in a CG, just update the database
// this shouldn't happen but it will add robustness if anything goes wrong
List<Volume> checkVols = new ArrayList<Volume>(removeVols);
removeVols.clear();
for (Volume removeVol : checkVols) {
if (NullColumnValueGetter.isNullURI(removeVol.getConsistencyGroup())) {
removeVol.getVolumeGroupIds().remove(volumeGroup.getId().toString());
dbClient.updateObject(removeVol);
} else {
removeVols.add(removeVol);
}
}
}
if ((addVols == null || addVols.isEmpty()) && (removeVols == null || removeVols.isEmpty())) {
// no volumes to add or remove
return;
}
BlockServiceApi serviceAPI = getBlockService(dbClient, firstVol);
Operation op = dbClient.createTaskOpStatus(VolumeGroup.class, volumeGroup.getId(),
taskId, ResourceOperationTypeEnum.UPDATE_VOLUME_GROUP);
try {
taskList.getTaskList().add(toTask(volumeGroup, taskId, op));
addTasksForVolumesAndCGs(dbClient, addVols, removeVols, impactedCGs, taskId, taskList);
serviceAPI.updateVolumesInVolumeGroup(param.getAddVolumesList(), removeVols, volumeGroup.getId(), taskId);
} catch (InternalException | APIException e) {
VolumeGroup app = dbClient.queryObject(VolumeGroup.class, volumeGroup.getId());
op = app.getOpStatus().get(taskId);
op.error(e);
app.getOpStatus().updateTaskStatus(taskId, op);
dbClient.updateObject(app);
if (param.hasVolumesToAdd()) {
List<URI> addURIs = param.getAddVolumesList().getVolumes();
updateFailedVolumeTasks(dbClient, addURIs, taskId, e);
}
if (param.hasVolumesToRemove()) {
List<URI> removeURIs = param.getRemoveVolumesList().getVolumes();
updateFailedVolumeTasks(dbClient, removeURIs, taskId, e);
}
if (!impactedCGs.isEmpty()) {
updateFailedCGTasks(dbClient, impactedCGs, taskId, e);
}
throw e;
}
}
/**
* gets the list of replication group names associated with this COPY type volume group
*
* @return list of replication group names or empty list if the volume group is not COPY or no volumes exist in
* the volume group
*/
public static Set<String> getReplicationGroupNames(VolumeGroup group, DbClient dbClient) {
Set<String> groupNames = new HashSet<String>();
if (group.getRoles().contains(VolumeGroup.VolumeGroupRole.COPY.toString())) {
List<Volume> volumes = ControllerUtils.getVolumeGroupVolumes(dbClient, group);
if (volumes != null && !volumes.isEmpty()) {
BlockServiceApi serviceAPI = getBlockService(dbClient, volumes.iterator().next());
groupNames.addAll(serviceAPI.getReplicationGroupNames(group));
}
}
return groupNames;
}
/**
* return the list of virtual arrays for a volume group
*
* @param group
* @param dbClient
* @return
*/
public static Set<NamedRelatedResourceRep> getVirtualArrays(VolumeGroup group, DbClient dbClient) {
Set<URI> varrayIds = new HashSet<URI>();
if (group.getRoles().contains(VolumeGroup.VolumeGroupRole.COPY.toString())) {
List<Volume> volumes = ControllerUtils.getVolumeGroupVolumes(dbClient, group);
if (volumes != null && !volumes.isEmpty()) {
for (Volume volume : volumes) {
varrayIds.add(volume.getVirtualArray());
}
}
}
Set<NamedRelatedResourceRep> virtualArrays = new HashSet<NamedRelatedResourceRep>();
for (URI varrayId : varrayIds) {
VirtualArray varray = dbClient.queryObject(VirtualArray.class, varrayId);
if (varray != null && !varray.getInactive()) {
virtualArrays.add(DbObjectMapper.toNamedRelatedResource(varray));
}
}
return virtualArrays;
}
/**
* Validate the volumes to be added to the volume group.
* For role COPY:
* All volumes should be the same type (block, or RP, or VPLEX, or SRDF), and should be in consistency groups
*
* @param volumes
* @return The validated volumes
*/
private List<Volume> validateAddVolumes(DbClient dbClient, VolumeGroupUpdateParam param, VolumeGroup volumeGroup,
Set<URI> impactedCGs) {
String addedVolType = null;
String firstVolLabel = null;
URI consistencyGroupURI = null;
URI tenantId = null;
List<URI> addVolList = param.getAddVolumesList().getVolumes();
List<Volume> volumes = new ArrayList<Volume>();
Volume firstAddedVolume = null;
for (URI volUri : addVolList) {
ArgValidator.checkFieldUriType(volUri, Volume.class, "id");
Volume volume = dbClient.queryObject(Volume.class, volUri);
if (volume == null || volume.getInactive()) {
throw APIException.badRequests.volumeCantBeAddedToVolumeGroup(volUri.toString(), "the volume has been deleted");
}
if (firstAddedVolume == null) {
firstAddedVolume = volume;
}
URI cgUri = volume.getConsistencyGroup();
if (NullColumnValueGetter.isNullURI(cgUri)) {
throw APIException.badRequests.volumeCantBeAddedToVolumeGroup(volume.getLabel(),
"Volume is not in a consistency group");
}
if (consistencyGroupURI == null) { // first volume
consistencyGroupURI = cgUri;
} else if (!consistencyGroupURI.equals(cgUri)) {
// volume is not from the same CG
throw APIException.badRequests.volumeCantBeAddedToVolumeGroup(volume.getLabel(),
"Volume is not in the same consistency group as others in the same request");
}
if (tenantId == null) {
tenantId = volume.getTenant().getURI();
} else if (!tenantId.equals(volume.getTenant().getURI())) {
// volume is not from the same tenant
throw APIException.badRequests.volumeCantBeAddedToVolumeGroup(volume.getLabel(),
"the volume does not have the same tenant as others in the same request");
}
BlockConsistencyGroup cg = dbClient.queryObject(BlockConsistencyGroup.class, cgUri);
if (cg == null || cg.getInactive()) {
throw APIException.badRequests.volumeGroupCantBeUpdated(volumeGroup.getLabel(),
String.format("Consistency group %s does not exist", cgUri));
}
BlockServiceUtils.validateVolumeNoReplica(volume, volumeGroup, dbClient);
URI systemUri = volume.getStorageController();
StorageSystem system = dbClient.queryObject(StorageSystem.class, systemUri);
String type = system.getSystemType();
if (!ALLOWED_SYSTEM_TYPES.contains(type)) {
throw APIException.badRequests.volumeCantBeAddedToVolumeGroup(volume.getLabel(),
"Storage system type that the volume created in is not allowed");
}
String volType = getVolumeType(volume, dbClient);
/*
* Adding SRDF Volume into Application is not supported.
*/
if (DiscoveredDataObject.Type.srdf.name().equals(volType)) {
throw APIException.badRequests.volumeCantBeAddedToVolumeGroup(volume.getLabel(),
"Adding SRDF Volume into application is not supported");
}
if (addedVolType == null) {
addedVolType = volType;
firstVolLabel = volume.getLabel();
}
if (!volType.equals(addedVolType)) {
throw APIException.badRequests.volumeCantBeAddedToVolumeGroup(volume.getLabel(),
"Volume type is not same as others");
}
// check to make sure this volume is not part of another application
StringSet volumeGroups = volume.getVolumeGroupIds();
List<String> badVolumeGroups = new ArrayList<String>();
if (volumeGroups != null && !volumeGroups.isEmpty()) {
for (String vgId : volumeGroups) {
VolumeGroup vg = dbClient.queryObject(VolumeGroup.class, URI.create(vgId));
if (vg == null || vg.getInactive()) {
// this means the volume points to a non-existent volume group;
// this shouldn't happen but we can clean this dangling reference up
badVolumeGroups.add(vgId);
} else {
if (vg.getRoles().contains(VolumeGroup.VolumeGroupRole.COPY.toString())) {
throw APIException.badRequests.volumeCantBeAddedToVolumeGroup(volume.getLabel(),
String.format("The volume is already a member of an application: %s", vg.getLabel()));
}
}
}
if (!badVolumeGroups.isEmpty()) {
for (String vgId : badVolumeGroups) {
volume.getVolumeGroupIds().remove(vgId);
}
dbClient.updateObject(volume);
volume = dbClient.queryObject(Volume.class, volume.getId());
}
}
volumes.add(volume);
impactedCGs.add(volume.getConsistencyGroup());
}
// Check if the to-add volumes are the same volume type as existing volumes in the application
List<Volume> existingVols = ControllerUtils.getVolumeGroupVolumes(dbClient, volumeGroup);
if (!existingVols.isEmpty()) {
Volume firstVolume = existingVols.get(0);
String existingType = getVolumeType(firstVolume, dbClient);
if (!existingType.equals(addedVolType)) {
throw APIException.badRequests.volumeCantBeAddedToVolumeGroup(firstVolLabel,
"The volume type is not same as existing volumes in the application");
}
// check to make sure the new volumes are the same tenant as existing volumes
if (!firstVolume.getTenant().getURI().equals(tenantId)) {
throw APIException.badRequests.volumeCantBeAddedToVolumeGroup(firstVolLabel,
"the volume does not have the same tenant as existing volumes in the application");
}
// if volumes are vplex, make sure local vs distributed matches
if (DiscoveredDataObject.Type.vplex.name().equals(existingType) && firstAddedVolume != null) {
boolean isExistingDistributed = firstVolume.getAssociatedVolumes() != null
&& firstVolume.getAssociatedVolumes().size() > 1;
boolean isAddVolsDistributed = firstAddedVolume.getAssociatedVolumes() != null
&& firstAddedVolume.getAssociatedVolumes().size() > 1;
if (isAddVolsDistributed && !isExistingDistributed) {
throw APIException.badRequests.volumeCantBeAddedToVolumeGroup(firstAddedVolume.getLabel(),
"the VPlex volume being added is distributed and the existing volumes in the application are not");
} else if (!isAddVolsDistributed && isExistingDistributed) {
throw APIException.badRequests.volumeCantBeAddedToVolumeGroup(firstAddedVolume.getLabel(),
"the existing volumes in the application are distributed and the VPlex volume being added is not");
}
}
}
// Check to make sure the replication group name is not used in a CG that is not part of an application
// or part of another application
// Check to make sure to be added volumes are in the same CG if the backend volumes are in the same backend array
// All volumes in the same replication group should belong to the same CG.
if (param.getAddVolumesList().getReplicationGroupName() != null) {
String replicationGroupName = param.getAddVolumesList().getReplicationGroupName();
List<Volume> volumesInReplicationGroup = CustomQueryUtility.queryActiveResourcesByConstraint(
dbClient, Volume.class, AlternateIdConstraint.Factory.getVolumeByReplicationGroupInstance(replicationGroupName));
List<URI> toAddVolumes = param.getAddVolumesList().getVolumes();
// Get the backend volumes not in replication group and sort them in storage system and CG
Map<URI, URI> backendVolSystemCGMap = new HashMap<URI, URI>();
for (URI volUri : toAddVolumes) {
Volume volToAdd = dbClient.queryObject(Volume.class, volUri);
URI cgURI = volToAdd.getConsistencyGroup();
StringSet backendVols = volToAdd.getAssociatedVolumes();
if (backendVols != null && !backendVols.isEmpty()) {
for (String backendUri : backendVols) {
Volume backendVol = dbClient.queryObject(Volume.class, URI.create(backendUri));
if (backendVol != null && NullColumnValueGetter.isNullValue(backendVol.getReplicationGroupInstance())) {
URI storage = backendVol.getStorageController();
URI sortCG = backendVolSystemCGMap.get(storage);
if (sortCG != null && !cgURI.equals(sortCG)) {
// there are at least two volumes backend volumes are from the same storage system,
// but their CGs are different, throw error
throw APIException.badRequests
.volumeCantBeAddedToVolumeGroup(volToAdd.getLabel(),
"the volumes in the request are from different consistency group, they could not be added into the same replication group.");
} else if (sortCG == null) {
backendVolSystemCGMap.put(storage, cgURI);
}
}
}
} else {
URI storage = volToAdd.getStorageController();
backendVolSystemCGMap.put(storage, cgURI);
}
}
if (volumesInReplicationGroup != null && !volumesInReplicationGroup.isEmpty()) {
for (Volume volumeInRepGrp : volumesInReplicationGroup) {
URI storage = volumeInRepGrp.getStorageController();
URI addingCG = backendVolSystemCGMap.get(storage);
if (addingCG != null) {
URI existingVolCG = volumeInRepGrp.getConsistencyGroup();
if (!addingCG.equals(existingVolCG)) {
throw APIException.badRequests
.volumeCantBeAddedToVolumeGroup(
firstVolLabel,
String.format(
"the replication group %s is existing, but the volumes in the request are from different consistency group",
replicationGroupName));
}
Volume volToCheck = volumeInRepGrp;
// if this is a vplex backing volume, get the parent virtual voume
if (VPlexUtil.isVplexBackendVolume(volumeInRepGrp, dbClient)) {
List<Volume> vplexVolumes = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, Volume.class,
getVolumesByAssociatedId(volumeInRepGrp.getId().toString()));
if (vplexVolumes != null && !vplexVolumes.isEmpty()) {
// we expect just one parent virtual volume for each backing volume
volToCheck = vplexVolumes.get(0);
}
}
// check to see if the volume is part of another application or not part of an application
VolumeGroup grp = volToCheck.getApplication(dbClient);
if (grp == null) {
if (!replicationGroupName.equals(volumeInRepGrp.getReplicationGroupInstance())) {
throw APIException.badRequests.volumeGroupCantBeUpdated(volumeGroup.getLabel(),
String.format("a volume, %s is part of the volume group %s but is not part of any application",
volToCheck.getLabel(), replicationGroupName));
}
} else if (!grp.getId().equals(volumeGroup.getId())) {
throw APIException.badRequests.volumeGroupCantBeUpdated(volumeGroup.getLabel(),
String.format("a volume, %s is part of the volume group %s and is part of another application: %s",
volToCheck.getLabel(), replicationGroupName, grp.getLabel()));
}
}
}
}
}
return volumes;
}
/**
* Valid the volumes to be removed from the volume group. Called by updateVolumeGroup()
*
* @param volumes the volumes to be removed from volume group
* @param volumeGroup The volume group
* @return The validated volumes
*/
private List<Volume> validateRemoveVolumes(DbClient dbClient, List<URI> volumes, VolumeGroup volumeGroup, Set<URI> removeVolumeCGs) {
List<Volume> removeVolumes = new ArrayList<Volume>();
for (URI voluri : volumes) {
ArgValidator.checkFieldUriType(voluri, Volume.class, "id");
Volume vol = dbClient.queryObject(Volume.class, voluri);
if (vol == null || vol.getInactive()) {
log.warn(String.format(
"The volume [%s] will not be removed from application %s because it does not exist or has been deleted",
voluri.toString(), volumeGroup.getLabel()));
continue;
}
StringSet volumeGroups = vol.getVolumeGroupIds();
if (volumeGroups == null || !volumeGroups.contains(volumeGroup.getId().toString())) {
log.warn(String.format(
"The volume %s will not be removed from application %s because it is not assigned to the application",
vol.getLabel(), volumeGroup.getLabel()));
continue;
}
if (vol.isInCG() && !vol.isVPlexVolume(dbClient)) {
removeVolumeCGs.add(vol.getConsistencyGroup());
}
removeVolumes.add(vol);
}
return removeVolumes;
}
/**
* Get Volume type, either block, rp, vplex or srdf
*
* @param type The system type
* @return
*/
private static String getVolumeType(Volume volume, DbClient dbClient) {
if (!isNullURI(volume.getProtectionController())
&& volume.checkForRp()) {
return DiscoveredDataObject.Type.rp.name();
}
if (Volume.checkForSRDF(dbClient, volume.getId())) {
return DiscoveredDataObject.Type.srdf.name();
}
if (volume.isVPlexVolume(dbClient)) {
return DiscoveredDataObject.Type.vplex.name();
}
return BLOCK;
}
private void updateFailedCGTasks(DbClient dbClient, Set<URI> uriList, String taskId, ServiceCoded e) {
for (URI uri : uriList) {
BlockConsistencyGroup cg = dbClient.queryObject(BlockConsistencyGroup.class, uri);
Operation op = cg.getOpStatus().get(taskId);
if (op != null) {
op.error(e);
cg.getOpStatus().updateTaskStatus(taskId, op);
dbClient.updateObject(cg);
}
}
}
private static BlockServiceApi getBlockService(DbClient dbClient, final Volume volume) {
if (!isNullURI(volume.getProtectionController())
&& volume.checkForRp()) {
return getBlockServiceImpl(DiscoveredDataObject.Type.rp.name());
}
if (Volume.checkForSRDF(dbClient, volume.getId())) {
return getBlockServiceImpl(DiscoveredDataObject.Type.srdf.name());
}
String volType = getVolumeType(volume, dbClient);
return getBlockServiceImpl(volType);
}
}
/**
* Get volume group hosts
*
* @param volumeGroup
* @return The list of hosts in volume group
*/
private static List<Host> getVolumeGroupHosts(DbClient dbClient, VolumeGroup volumeGroup) {
final List<Host> hosts = CustomQueryUtility
.queryActiveResourcesByConstraint(dbClient, Host.class,
AlternateIdConstraint.Factory.getHostsByVolumeGroupId(volumeGroup.getId().toString()));
return hosts;
}
/**
* get the children for this volume group
*
* @param dbClient
* db client for db queries
* @param volumeGroup
* volume group to get children for
* @return a list of volume groups
*/
private static List<VolumeGroup> getVolumeGroupChildren(DbClient dbClient, VolumeGroup volumeGroup) {
List<VolumeGroup> result = new ArrayList<VolumeGroup>();
final List<VolumeGroup> volumeGroups = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, VolumeGroup.class,
ContainmentConstraint.Factory.getVolumesGroupsByVolumeGroupId(volumeGroup.getId()));
for (VolumeGroup volGroup : volumeGroups) {
result.add(volGroup);
}
return result;
}
/**
* Get volume group clusters
*
* @param volumeGroup
* @return The list of clusters in volume group
*/
private static List<Cluster> getVolumeGroupClusters(DbClient dbClient, VolumeGroup volumeGroup) {
final List<Cluster> clusters = CustomQueryUtility
.queryActiveResourcesByConstraint(dbClient, Cluster.class,
AlternateIdConstraint.Factory.getClustersByVolumeGroupId(volumeGroup.getId().toString()));
return clusters;
}
private String setParent(VolumeGroup volumeGroup, String parent) {
String errorMsg = null;
// add parent if specified
if (parent != null && !parent.isEmpty()) {
if (URIUtil.isValid(parent)) {
URI parentId = URI.create(parent);
ArgValidator.checkFieldUriType(parentId, VolumeGroup.class, "parent");
VolumeGroup parentVG = _dbClient.queryObject(VolumeGroup.class, parentId);
if (parentVG == null || parentVG.getInactive()) {
errorMsg = "The parent volume group does not exist";
} else {
volumeGroup.setParent(parentId);
}
} else if (NullColumnValueGetter.isNullValue(parent)) {
volumeGroup.setParent(NullColumnValueGetter.getNullURI());
} else {
List<VolumeGroup> parentVg = CustomQueryUtility
.queryActiveResourcesByConstraint(_dbClient, VolumeGroup.class,
PrefixConstraint.Factory.getLabelPrefixConstraint(VolumeGroup.class, parent));
if (parentVg == null || parentVg.isEmpty()) {
errorMsg = "The parent volume group does not exist";
} else {
volumeGroup.setParent(parentVg.iterator().next().getId());
}
}
}
return errorMsg;
}
/**
* Gets all clones for the given set name and volume group.
*
* @param cloneSetName
* @param volumeGroupId
* @param dbClient
* @return
*/
private List<Volume> getClonesBySetName(String cloneSetName, URI volumeGroupId) {
List<Volume> setClones = new ArrayList<Volume>();
if (cloneSetName != null) {
URIQueryResultList list = new URIQueryResultList();
_dbClient.queryByConstraint(AlternateIdConstraint.Factory.getFullCopiesBySetName(cloneSetName), list);
Iterator<Volume> iter = _dbClient.queryIterativeObjects(Volume.class, list);
while (iter.hasNext()) {
Volume vol = iter.next();
URI sourceId = getSourceIdForFullCopy(vol);
if (!NullColumnValueGetter.isNullURI(sourceId)) {
Volume sourceVol = _dbClient.queryObject(Volume.class, sourceId);
if (sourceVol != null && !sourceVol.getInactive() && sourceVol.getVolumeGroupIds() != null
&& sourceVol.getVolumeGroupIds().contains(volumeGroupId.toString())) {
setClones.add(vol);
}
}
}
}
return setClones;
}
/**
* gets the source URI for a replica
*
* @param replica
* @return
*/
private URI getSourceIdForFullCopy(BlockObject replica) {
URI sourceURI = null;
if (replica instanceof BlockSnapshot) {
sourceURI = ((BlockSnapshot) replica).getParent().getURI();
} else if (replica instanceof BlockMirror) {
sourceURI = ((BlockMirror) replica).getSource().getURI();
} else if (replica instanceof Volume) {
sourceURI = ((Volume) replica).getAssociatedSourceVolume();
}
return sourceURI;
}
/**
* gets the replica type for a replica
*
* @param replica
* @return
*/
private String getReplicaType(BlockObject replica) {
String replicaType = null;
if (replica instanceof BlockSnapshot) {
replicaType = ReplicaTypeEnum.SNAPSHOT.toString();
} else if (replica instanceof BlockMirror) {
replicaType = ReplicaTypeEnum.MIRROR.toString();
} else if (replica instanceof Volume) {
replicaType = ReplicaTypeEnum.FULL_COPY.toString();
}
return replicaType;
}
/**
* Check if the application and its CGs/volumes/snapshots/snapshotSessions have any pending tasks
*
* @param volumeGroup The volume group
* @param dbClient
* @param preventAnyPendingTask If throw error when there is any pending task
*/
private void checkForApplicationPendingTasks(VolumeGroup volumeGroup, DbClient dbClient, boolean preventAnyPendingTask) {
checkForPendingTask(volumeGroup.getId(), dbClient, preventAnyPendingTask);
Set<URI> cgs = new HashSet<URI>();
List<Volume> allVolumes = ControllerUtils.getVolumeGroupVolumes(dbClient, volumeGroup);
for (Volume vol : allVolumes) {
checkForPendingTask(vol.getId(), dbClient, preventAnyPendingTask);
URI cg = vol.getConsistencyGroup();
if (!NullColumnValueGetter.isNullURI(cg)) {
cgs.add(vol.getConsistencyGroup());
}
}
for (URI cg : cgs) {
checkForPendingTask(cg, dbClient, preventAnyPendingTask);
}
// Get the snapshots for each volume in the group
// we don't need the similar logic for clone, since if there is any clone operation in the application,
// it has a corresponding task in CG. so the above checking on CG should cover the case for clone.
for (Volume volume : allVolumes) {
Volume theVol = volume;
if (volume.isVPlexVolume(dbClient)) {
theVol = VPlexUtil.getVPLEXBackendVolume(volume, true, dbClient);
if (theVol == null || theVol.getInactive()) {
log.warn("Cannot find backend volume for VPLEX volume {}", volume.getLabel());
continue;
}
}
URIQueryResultList snapshotURIs = new URIQueryResultList();
dbClient.queryByConstraint(ContainmentConstraint.Factory.getVolumeSnapshotConstraint(
theVol.getId()), snapshotURIs);
Iterator<URI> it = snapshotURIs.iterator();
while (it.hasNext()) {
URI snapURI = it.next();
checkForPendingTask(snapURI, dbClient, preventAnyPendingTask);
}
}
// Get snapshotSessions
List<BlockSnapshotSession> sessions = getVolumeGroupSnapshotSessions(volumeGroup);
for (BlockSnapshotSession session : sessions) {
checkForPendingTask(session.getId(), dbClient, preventAnyPendingTask);
}
}
/**
* Check pending tasks. if preventAnyPendingTask is true, it will throw exception if there is any pending task
* if preventAnyPendingTask is false, it will only throw exception if the pending task is in the PENDING_TASK_NAMES
* (restore, delete snapshot and snapshotSession, detach full copy and delete volume)
*
* @param id the resource URI
* @param dbClient
* @param preventAnyPendingTask If throw error when there is any pending task
*/
private void checkForPendingTask(URI id, DbClient dbClient, boolean preventAnyPendingTask) {
List<Task> newTasks = TaskUtils.findResourceTasks(dbClient, id);
if (newTasks != null) {
for (Task task : newTasks) {
if (task != null && !task.getInactive() && task.isPending()) {
if (preventAnyPendingTask) {
throw APIException.badRequests.cannotExecuteOperationWhilePendingTask(id.toString());
} else {
String taskName = task.getLabel();
log.info(String.format("The pending task is %s", taskName));
if (taskName != null && PENDING_TASK_NAMES.contains(taskName)) {
throw APIException.badRequests.cannotExecuteOperationWhilePendingTask(id.toString());
}
}
}
}
}
}
}