/** * Copyright (c) 2015 EMC Corporation * All Rights Reserved * * This software contains the intellectual property of EMC Corporation * or is licensed to EMC Corporation from third parties. Use of this * software and the intellectual property contained therein is expressly * limited to the terms and conditions of the License Agreement under which * it is provided by or on behalf of EMC. */ package com.emc.storageos.api.service.impl.resource.cinder; import static com.emc.storageos.api.mapper.TaskMapper.toTask; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.UUID; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.api.service.impl.placement.PlacementManager; import com.emc.storageos.api.service.impl.resource.BlockService; import com.emc.storageos.api.service.impl.resource.BlockServiceApi; import com.emc.storageos.api.service.impl.resource.fullcopy.BlockFullCopyManager; import com.emc.storageos.api.service.impl.resource.utils.CinderApiUtils; import com.emc.storageos.cinder.CinderConstants; import com.emc.storageos.cinder.model.ConsistencyGroupSnapshotCreateRequest; import com.emc.storageos.cinder.model.ConsistencyGroupSnapshotCreateResponse; import com.emc.storageos.cinder.model.ConsistencyGroupSnapshotDetail; import com.emc.storageos.cinder.model.ConsistencyGroupSnapshotListDetail; 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.BlockSnapshot; import com.emc.storageos.db.client.model.DiscoveredDataObject; import com.emc.storageos.db.client.model.Operation; import com.emc.storageos.db.client.model.Project; import com.emc.storageos.db.client.model.ScopedLabel; import com.emc.storageos.db.client.model.ScopedLabelSet; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.model.StringMap; import com.emc.storageos.db.client.model.Task; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.db.client.model.util.TaskUtils; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.model.ResourceOperationTypeEnum; import com.emc.storageos.model.TaskList; import com.emc.storageos.model.TaskResourceRep; import com.emc.storageos.model.block.VolumeDeleteTypeEnum; import com.emc.storageos.security.audit.AuditLogManager; 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; /** * This class provides CRUD operations on consistency group snapshot * * @author singhc1 * */ @Path("/v2/{tenant_id}/cgsnapshots") @DefaultPermissions(readRoles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, readAcls = { ACL.OWN, ACL.ALL }, writeRoles = { Role.TENANT_ADMIN }, writeAcls = { ACL.OWN, ACL.ALL }) public class ConsistencyGroupSnapshotService extends AbstractConsistencyGroupService { private static final Logger _log = LoggerFactory.getLogger(ConsistencyGroupSnapshotService.class); // Block service implementations private Map<String, BlockServiceApi> _blockServiceApis; public void setBlockServiceApis(final Map<String, BlockServiceApi> serviceInterfaces) { _blockServiceApis = serviceInterfaces; } // A reference to the placement manager. private PlacementManager _placementManager; /** * Setter for the placement manager. * * @param placementManager * A reference to the placement manager. */ public void setPlacementManager(PlacementManager placementManager) { _placementManager = placementManager; } /** * Create consistency group snapshot * * @param openstackTenantId * openstack tenant Id * @param param * Pojo class to bind request * @param isV1Call * cinder V1 api * @param header * HTTP header * @brief Create Consistency Group Snapshot * @return Response */ @POST @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response createConsistencyGroupSnapshot(@PathParam("tenant_id") String openstackTenantId, final ConsistencyGroupSnapshotCreateRequest param, @HeaderParam("X-Cinder-V1-Call") String isV1Call, @Context HttpHeaders header) { // Query Consistency Group final String consistencyGroupId = param.cgsnapshot.consistencygroup_id; final BlockConsistencyGroup consistencyGroup = findConsistencyGroup(consistencyGroupId, openstackTenantId); if (consistencyGroup == null) { _log.error("Not Found : No Such Consistency Group Found {}", consistencyGroupId); return CinderApiUtils.createErrorResponse(404, "Not Found : No Such Consistency Group Found"); } else if (!consistencyGroupId.equals(CinderApiUtils.splitString(consistencyGroup.getId().toString(), ":", 3))) { _log.error("Bad Request : Invalid Snapshot Id {} : Please enter valid or full Id", consistencyGroupId); return CinderApiUtils.createErrorResponse(400, "Bad Request : No such consistency id exist, Please enter valid or full Id"); } if (!isSnapshotCreationpermissible(consistencyGroup)) { _log.error("Bad Request : vpool not being configured for the snapshots creation"); return CinderApiUtils.createErrorResponse(400, "Bad Request : vpool not being configured for the snapshots creation"); } // Ensure that the Consistency Group has been created on all of its defined // system types. if (!consistencyGroup.created()) { CinderApiUtils.createErrorResponse(400, "No such consistency group created"); } Project project = getCinderHelper().getProject(openstackTenantId, getUserFromContext()); URI cgStorageControllerURI = consistencyGroup.getStorageController(); if (!NullColumnValueGetter.isNullURI(cgStorageControllerURI)) { // No snapshots for VPLEX consistency groups. StorageSystem cgStorageController = _dbClient.queryObject( StorageSystem.class, cgStorageControllerURI); if ((DiscoveredDataObject.Type.vplex.name().equals(cgStorageController .getSystemType())) && (!consistencyGroup.checkForType(Types.LOCAL))) { CinderApiUtils.createErrorResponse(400, "can't create snapshot for VPLEX"); } } // Get the block service implementation BlockServiceApi blockServiceApiImpl = getBlockServiceImpl(consistencyGroup); // Get the volumes in the consistency group. List<Volume> volumeList = blockServiceApiImpl.getActiveCGVolumes(consistencyGroup); _log.info("Active CG volume list : " + volumeList); // Generate task id String taskId = UUID.randomUUID().toString(); // Set snapshot type. String snapshotType = BlockSnapshot.TechnologyType.NATIVE.toString(); if (consistencyGroup.checkForType(BlockConsistencyGroup.Types.RP)) { snapshotType = BlockSnapshot.TechnologyType.RP.toString(); } else if ((!volumeList.isEmpty()) && (volumeList.get(0).checkForSRDF())) { snapshotType = BlockSnapshot.TechnologyType.SRDF.toString(); } // Determine the snapshot volume for RP. Volume snapVolume = null; if (consistencyGroup.checkForType(BlockConsistencyGroup.Types.RP)) { for (Volume volumeToSnap : volumeList) { // Get the RP source volume. if (volumeToSnap.getPersonality() != null && volumeToSnap.getPersonality().equals(Volume.PersonalityTypes.SOURCE.toString())) { snapVolume = volumeToSnap; break; } } } else if (!volumeList.isEmpty()) { // Any volume. snapVolume = volumeList.get(0); } // Set the create inactive flag. Boolean createInactive = Boolean.FALSE; Boolean readOnly = Boolean.FALSE; // Validate the snapshot request. String snapshotName = param.cgsnapshot.name; blockServiceApiImpl.validateCreateSnapshot(snapVolume, volumeList, snapshotType, snapshotName, readOnly, getFullCopyManager()); // Prepare and create the snapshots for the group. List<URI> snapIdList = new ArrayList<URI>(); List<BlockSnapshot> snapshotList = new ArrayList<BlockSnapshot>(); TaskList response = new TaskList(); snapshotList.addAll(blockServiceApiImpl.prepareSnapshots( volumeList, snapshotType, snapshotName, snapIdList, taskId)); for (BlockSnapshot snapshot : snapshotList) { response.getTaskList().add(toTask(snapshot, taskId)); } blockServiceApiImpl.createSnapshot(snapVolume, snapIdList, snapshotType, createInactive, readOnly, taskId); auditBlockConsistencyGroup(OperationTypeEnum.CREATE_CONSISTENCY_GROUP_SNAPSHOT, AuditLogManager.AUDITLOG_SUCCESS, AuditLogManager.AUDITOP_BEGIN, param.cgsnapshot.name, consistencyGroup.getId().toString()); ConsistencyGroupSnapshotCreateResponse cgSnapshotCreateRes = new ConsistencyGroupSnapshotCreateResponse(); for (TaskResourceRep rep : response.getTaskList()) { URI snapshotUri = rep.getResource().getId(); BlockSnapshot snap = _dbClient.queryObject(BlockSnapshot.class, snapshotUri); snap.setId(snapshotUri); snap.setConsistencyGroup(consistencyGroup.getId()); snap.setLabel(snapshotName); if (snap != null) { StringMap extensions = snap.getExtensions(); if (extensions == null) { extensions = new StringMap(); } extensions.put("status", CinderConstants.ComponentStatus.CREATING.getStatus().toLowerCase()); extensions.put("taskid", rep.getId().toString()); snap.setExtensions(extensions); ScopedLabelSet tagSet = new ScopedLabelSet(); snap.setTag(tagSet); tagSet.add(new ScopedLabel(project.getTenantOrg().getURI().toString(), CinderApiUtils.splitString(snapshotUri.toString(), ":", 3))); } _dbClient.updateObject(snap); cgSnapshotCreateRes.id = CinderApiUtils.splitString(snapshotUri.toString(), ":", 3); cgSnapshotCreateRes.name = param.cgsnapshot.name; } return CinderApiUtils.getCinderResponse(cgSnapshotCreateRes, header, true,CinderConstants.STATUS_OK); } /** * Get Consistency Group Snapshot info * * @param openstackTenantId * openstack tenant Id * @param consistencyGroupSnapshotId * Consistency Group Snapshot Id * @param isV1Call * Cinder V1 api * @param header * HTTP Header * @brief * Get Consistency Group Snapshot info * @return Response */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{consistencyGroupSnapshot_id}") @CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY }) public Response getConsistencyGroupSnapshotDetail(@PathParam("tenant_id") String openstackTenantId, @PathParam("consistencyGroupSnapshot_id") String consistencyGroupSnapshotId, @HeaderParam("X-Cinder-V1-Call") String isV1Call, @Context HttpHeaders header) { Project project = getCinderHelper().getProject(openstackTenantId, getUserFromContext()); if (project == null) { String message = "Bad Request: Project with the OpenStack Tenant Id : " + openstackTenantId + " does not exist"; _log.error(message); return CinderApiUtils.createErrorResponse(400, message); } final BlockSnapshot snapshot = findSnapshot(consistencyGroupSnapshotId, openstackTenantId); if (null == snapshot) { _log.error("Bad Request : Invalid Snapshot Id {}", consistencyGroupSnapshotId); return CinderApiUtils.createErrorResponse(400, "Bad Request: No such snapshot id exist"); } else if (!consistencyGroupSnapshotId.equals(CinderApiUtils.splitString(snapshot.getId().toString(), ":", 3))) { _log.error("Bad Request : Invalid Snapshot Id {} : Please enter valid or full Id", consistencyGroupSnapshotId); return CinderApiUtils.createErrorResponse(400, "Bad Request: No such snapshot id exist, Please enter valid or full Id"); } ConsistencyGroupSnapshotDetail cgSnapshotDetail = new ConsistencyGroupSnapshotDetail(); cgSnapshotDetail.id = consistencyGroupSnapshotId; cgSnapshotDetail.name = snapshot.getLabel(); cgSnapshotDetail.created_at = CinderApiUtils.timeFormat(snapshot.getCreationTime()); cgSnapshotDetail.consistencygroup_id = CinderApiUtils.splitString(snapshot.getConsistencyGroup().toString(), ":", 3); StringMap extensions = snapshot.getExtensions(); String description = null; if (extensions != null) { description = extensions.get("display_description"); _log.debug("Retreiving the tasks for snapshot id {}", snapshot.getId()); List<Task> taskLst = TaskUtils.findResourceTasks(_dbClient, snapshot.getId()); _log.debug("Retreived the tasks for snapshot id {}", snapshot.getId()); String taskInProgressId = null; if (snapshot.getExtensions().containsKey("taskid")) { taskInProgressId = snapshot.getExtensions().get("taskid"); Task acttask = TaskUtils.findTaskForRequestId(_dbClient, snapshot.getId(), taskInProgressId); for (Task tsk : taskLst) { if (tsk.getId().toString().equals(taskInProgressId)) { if (tsk.getStatus().equals("ready")) { cgSnapshotDetail.status = CinderConstants.ComponentStatus.AVAILABLE.getStatus().toLowerCase(); snapshot.getExtensions().put("status", CinderConstants.ComponentStatus.AVAILABLE.getStatus().toLowerCase()); snapshot.getExtensions().remove("taskid"); } else if (tsk.getStatus().equals("pending")) { if (tsk.getDescription().equals(ResourceOperationTypeEnum.CREATE_VOLUME_SNAPSHOT.getDescription())) { cgSnapshotDetail.status = CinderConstants.ComponentStatus.CREATING.getStatus().toLowerCase(); } else if (tsk.getDescription().equals(ResourceOperationTypeEnum.DELETE_VOLUME_SNAPSHOT.getDescription())) { cgSnapshotDetail.status = CinderConstants.ComponentStatus.DELETING.getStatus().toLowerCase(); } } else if (tsk.getStatus().equals("error")) { cgSnapshotDetail.status = CinderConstants.ComponentStatus.ERROR.getStatus().toLowerCase(); snapshot.getExtensions().put("status", CinderConstants.ComponentStatus.ERROR.getStatus().toLowerCase()); snapshot.getExtensions().remove("taskid"); } _dbClient.updateObject(snapshot); break; } } } else if (snapshot.getExtensions().containsKey("status") && !snapshot.getExtensions().get("status").toString().toLowerCase().equals("")) { cgSnapshotDetail.status = snapshot.getExtensions().get("status").toString().toLowerCase(); } else { // status is available cgSnapshotDetail.status = CinderConstants.ComponentStatus.AVAILABLE.getStatus().toLowerCase(); } } cgSnapshotDetail.description = (description == null) ? "" : description; return CinderApiUtils.getCinderResponse(cgSnapshotDetail, header, true,CinderConstants.STATUS_OK); } /** * Detail Info for Consistency group snapshot * * @param openstackTenantId * Openstack tenant id * @param isV1Call * openstack V1 call * @param header * Http Headers * @brief Get Detail Info for Consistency group snapshots * @return Response */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/detail") @CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY }) public Response getConsistencyGroupSnapshotDetailList( @PathParam("tenant_id") String openstackTenantId, @HeaderParam("X-Cinder-V1-Call") String isV1Call, @Context HttpHeaders header) { ConsistencyGroupSnapshotListDetail cgSnapshotDetailListResponse = new ConsistencyGroupSnapshotListDetail(); URIQueryResultList cgUris = getCinderHelper().getConsistencyGroupsUris(openstackTenantId, getUserFromContext()); if (null != cgUris) { for (URI cgUri : cgUris) { URIQueryResultList uris = getCinderHelper().getConsistencyGroupSnapshotUris(cgUri); if (null != uris) { for (URI cgSnapshotURI : uris) { BlockSnapshot blockSnapshot = _dbClient.queryObject(BlockSnapshot.class, cgSnapshotURI); if (null != blockSnapshot && !(blockSnapshot.getInactive())) { cgSnapshotDetailListResponse .addConsistencyGroupSnapshotDetail(getConsistencyGroupSnapshotDetail(blockSnapshot)); } } } } } return CinderApiUtils.getCinderResponse(cgSnapshotDetailListResponse, header, false,CinderConstants.STATUS_OK); } /** * Delete a consistency group snapshot * * @param openstackTenantId * openstack tenant id * @param consistencyGroupSnapshot_id * consistency group snapshot id * @brief Delete a consistency group snapshot * @param isV1Call * Cinder V1 call * @param header * Http Header * @return Response */ @DELETE @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{consistencyGroupSnapshot_id}") @CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY }) public Response deleteConsistencyGroupSnapshot( @PathParam("tenant_id") String openstackTenantId, @PathParam("consistencyGroupSnapshot_id") String consistencyGroupSnapshot_id, @HeaderParam("X-Cinder-V1-Call") String isV1Call, @Context HttpHeaders header) { final BlockSnapshot snapshot = findSnapshot(consistencyGroupSnapshot_id, openstackTenantId); final URI snapshotCgURI = snapshot.getConsistencyGroup(); URIQueryResultList uris = getCinderHelper().getConsistencyGroupsUris(openstackTenantId, getUserFromContext()); boolean isConsistencyGroupHasSnapshotId = false; if (uris != null && snapshotCgURI != null) { for (URI blockCGUri : uris) { BlockConsistencyGroup blockCG = _dbClient.queryObject( BlockConsistencyGroup.class, blockCGUri); if (blockCG != null && !blockCG.getInactive()) { if (snapshotCgURI.equals(blockCG.getId())) { isConsistencyGroupHasSnapshotId = true; } } } } if (isConsistencyGroupHasSnapshotId) { // Generate task id final String task = UUID.randomUUID().toString(); TaskList response = new TaskList(); // Not an error if the snapshot we try to delete is already deleted if (snapshot.getInactive()) { Operation op = new Operation(); op.ready("The consistency group snapshot has already been deactivated"); op.setResourceType(ResourceOperationTypeEnum.DELETE_CONSISTENCY_GROUP_SNAPSHOT); _dbClient.createTaskOpStatus(BlockSnapshot.class, snapshot.getId(), task, op); TaskResourceRep taskResponse = toTask(snapshot, task, op); if (taskResponse.getState().equals("ready")) { return Response.status(202).build(); } } Volume volume = _permissionsHelper.getObjectById(snapshot.getParent(), Volume.class); BlockServiceApi blockServiceApiImpl = BlockService.getBlockServiceImpl(volume, _dbClient); blockServiceApiImpl.deleteSnapshot(snapshot, Arrays.asList(snapshot), task, VolumeDeleteTypeEnum.FULL.name()); auditBlockConsistencyGroup(OperationTypeEnum.DELETE_CONSISTENCY_GROUP_SNAPSHOT, AuditLogManager.AUDITLOG_SUCCESS, AuditLogManager.AUDITOP_BEGIN, snapshot.getId() .toString(), snapshot.getLabel()); return Response.status(202).build(); } else { return CinderApiUtils.createErrorResponse(400, "Snapshot not attached to any active consistencygroup"); } } // internal function private BlockServiceApi getBlockServiceImpl(final BlockConsistencyGroup cg) { BlockServiceApi blockServiceApiImpl = null; if (cg.checkForType(Types.RP)) { blockServiceApiImpl = getBlockServiceImpl(BlockConsistencyGroup.Types.RP.toString().toLowerCase()); } else if (cg.checkForType(Types.VPLEX)) { blockServiceApiImpl = getBlockServiceImpl(BlockConsistencyGroup.Types.VPLEX.toString().toLowerCase()); } else { blockServiceApiImpl = getBlockServiceImpl("group"); } return blockServiceApiImpl; } private BlockServiceApi getBlockServiceImpl(final String type) { return _blockServiceApis.get(type); } /** * Creates and returns an instance of the block full copy manager to handle * a full copy request. * * @return BlockFullCopyManager */ private BlockFullCopyManager getFullCopyManager() { BlockFullCopyManager fcManager = new BlockFullCopyManager(_dbClient, _permissionsHelper, _auditMgr, _coordinator, _placementManager, sc, uriInfo, _request, null); return fcManager; } /** * Record audit log for Block service * * @param auditType * Type of AuditLog * @param operationalStatus * Status of operation * @param operationStage * Stage of operation. For sync operation, it should be null; For async operation, it * should be "BEGIN" or "END"; * @param descparams * Description paramters */ public void auditBlockConsistencyGroup(final OperationTypeEnum auditType, final String operationalStatus, final String operationStage, final Object... descparams) { _auditMgr.recordAuditLog(URI.create(getUserFromContext().getTenantId()), URI.create(getUserFromContext().getName()), "block", auditType, System.currentTimeMillis(), operationalStatus, operationStage, descparams); } /** * Find Snapshot based on snapshot id and tenant id * * @param snapshotId * Snapshot id * @param openstackTenantId * tenant Id * @return BlockSnapshot */ private BlockSnapshot findSnapshot(String snapshotId, String openstackTenantId) { BlockSnapshot snapshot = (BlockSnapshot) getCinderHelper().queryByTag( URI.create(snapshotId), getUserFromContext(),BlockSnapshot.class ); return snapshot; } // internal function private ConsistencyGroupSnapshotDetail getConsistencyGroupSnapshotDetail( BlockSnapshot blockSnapshot) { ConsistencyGroupSnapshotDetail response = new ConsistencyGroupSnapshotDetail(); if (null != blockSnapshot) { response.id = CinderApiUtils.splitString(blockSnapshot.getId().toString(), ":", 3); response.name = blockSnapshot.getLabel(); response.created_at = CinderApiUtils.timeFormat(blockSnapshot.getCreationTime()); response.status = blockSnapshot.getExtensions().get("status"); response.consistencygroup_id = CinderApiUtils.splitString(blockSnapshot.getConsistencyGroup().toString(), ":", 3); } return response; } }