/* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*
*/
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.HashMap;
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.PUT;
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.authorization.PermissionsHelper;
import com.emc.storageos.api.service.impl.placement.PlacementManager;
import com.emc.storageos.api.service.impl.resource.ArgValidator;
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.TaskResourceService;
import com.emc.storageos.api.service.impl.resource.TenantsService;
import com.emc.storageos.api.service.impl.resource.fullcopy.BlockFullCopyManager;
import com.emc.storageos.api.service.impl.resource.utils.BlockServiceUtils;
import com.emc.storageos.api.service.impl.resource.utils.CinderApiUtils;
import com.emc.storageos.api.service.impl.resource.utils.VolumeIngestionUtil;
import com.emc.storageos.api.service.impl.response.ProjOwnedResRepFilter;
import com.emc.storageos.api.service.impl.response.ResRepFilter;
import com.emc.storageos.cinder.CinderConstants;
import com.emc.storageos.cinder.model.CinderSnapshot;
import com.emc.storageos.cinder.model.CinderSnapshotListRestResp;
import com.emc.storageos.cinder.model.CinderSnapshotMetadata;
import com.emc.storageos.cinder.model.SnapshotActionRequest;
import com.emc.storageos.cinder.model.SnapshotCreateRequestGen;
import com.emc.storageos.cinder.model.SnapshotCreateResponse;
import com.emc.storageos.cinder.model.SnapshotUpdateRequestGen;
import com.emc.storageos.cinder.model.UsageStats;
import com.emc.storageos.cinder.model.VolumeDetail;
import com.emc.storageos.db.client.DbClient;
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.BlockObject;
import com.emc.storageos.db.client.model.BlockSnapshot;
import com.emc.storageos.db.client.model.BlockSnapshot.TechnologyType;
import com.emc.storageos.db.client.model.DataObject;
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.QuotaOfCinder;
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.VirtualPool;
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.RelatedResourceRep;
import com.emc.storageos.model.ResourceOperationTypeEnum;
import com.emc.storageos.model.ResourceTypeEnum;
import com.emc.storageos.model.TaskList;
import com.emc.storageos.model.TaskResourceRep;
import com.emc.storageos.model.block.VolumeDeleteTypeEnum;
import com.emc.storageos.security.audit.AuditLogManager;
import com.emc.storageos.security.authentication.StorageOSUser;
import com.emc.storageos.security.authorization.ACL;
import com.emc.storageos.security.authorization.CheckPermission;
import com.emc.storageos.security.authorization.DefaultPermissions;
import com.emc.storageos.security.authorization.Role;
import com.emc.storageos.services.OperationTypeEnum;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import com.emc.storageos.svcs.errorhandling.resources.InternalException;
import org.apache.commons.lang.RandomStringUtils;
@Path("/v2/{tenant_id}/snapshots")
@DefaultPermissions(readRoles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, readAcls = {
ACL.OWN, ACL.ALL }, writeRoles = { Role.TENANT_ADMIN }, writeAcls = {
ACL.OWN, ACL.ALL })
@SuppressWarnings({ "unchecked", "rawtypes" })
public class SnapshotService extends TaskResourceService {
private static final Logger _log = LoggerFactory
.getLogger(SnapshotService.class);
private static final String EVENT_SERVICE_TYPE = "block";
private static final long HALF_GB = 512 * 1024 * 1024;
private static final long GB = 1024 * 1024 * 1024;
private static final String ZERO_PERCENT_COMPLETION = "0%";
private static final String HUNDRED_PERCENT_COMPLETION = "100%";
protected PlacementManager _placementManager;
protected TenantsService _tenantsService;
private CinderHelpers helper = null;
public void setPlacementManager(PlacementManager placementManager) {
_placementManager = placementManager;
}
public void setTenantsService(TenantsService tenantsService) {
_tenantsService = tenantsService;
}
@Override
public Class<Volume> getResourceClass() {
return Volume.class;
}
private CinderHelpers getCinderHelper() {
return CinderHelpers.getInstance(_dbClient, _permissionsHelper);
}
private QuotaHelper getQuotaHelper() {
return QuotaHelper.getInstance(_dbClient, _permissionsHelper);
}
/**
* The snapshot of a volume in Block Store is a point in time copy of the
* volume. This API allows the user to create snapshot of a volume
* NOTE: This is an asynchronous operation.
*
*
* @prereq none
*
* @param param
* POST data containing the snapshot creation information.
*
* @brief Create snapshot
* @return Details of the newly created snapshot
* @throws InternalException
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Response createSnapshot(
@PathParam("tenant_id") String openstack_tenant_id,
SnapshotCreateRequestGen param, @Context HttpHeaders header,
@HeaderParam("X-Cinder-V1-Call") String isV1Call)
throws InternalException {
// Step 1: Parameter validation
String snapshotName = null;
String snapshotDescription = null;
if (isV1Call != null) {
snapshotName = param.snapshot.display_name;
snapshotDescription = param.snapshot.display_description;
} else {
snapshotName = param.snapshot.name;
snapshotDescription = param.snapshot.description;
}
//if snapshot name is empty create random name
if (snapshotName == null)
{
snapshotName = "snapshot-" + RandomStringUtils.random(10);
}
if (snapshotName == null || (snapshotName.length() <= 2))
{
throw APIException.badRequests
.parameterIsNotValid(param.snapshot.name);
}
URI volumeUri = null;
Volume volume = null;
volumeUri = URI.create(param.snapshot.volume_id);
volume = queryVolumeResource(volumeUri, openstack_tenant_id);
if (volume == null) {
_log.error("Invalid source volume id to create snapshot ={} ",param.snapshot.volume_id);
return CinderApiUtils.createErrorResponse(404, "Not Found : Invalid source volume id " + param.snapshot.volume_id);
}
VirtualPool pool = _dbClient.queryObject(VirtualPool.class, volume.getVirtualPool());
if (pool == null) {
_log.info("Virtual Pool corresponding to the volume does not exist.");
throw APIException.badRequests.parameterIsNotValid(volume
.getVirtualPool().toString());
}
if (!validateSnapshotCreate(openstack_tenant_id, pool,
volume.getProvisionedCapacity())) {
_log.info("The volume can not be created because of insufficient quota for virtual pool.");
throw APIException.badRequests.insufficientQuotaForVirtualPool(
pool.getLabel(), "virtual pool");
}
if (!validateSnapshotCreate(openstack_tenant_id, null,
volume.getProvisionedCapacity())) {
_log.info("The volume can not be created because of insufficient quota for Project.");
throw APIException.badRequests.insufficientQuotaForProject(
pool.getLabel(), "project");
}
BlockFullCopyManager fcManager = new BlockFullCopyManager(_dbClient,
_permissionsHelper, _auditMgr, _coordinator, _placementManager, sc, uriInfo,
_request, _tenantsService);
VolumeIngestionUtil.checkOperationSupportedOnIngestedVolume(volume,
ResourceOperationTypeEnum.CREATE_VOLUME_SNAPSHOT, _dbClient);
// Don't operate on VPLEX backend volumes or RP journal volumes.
BlockServiceUtils.validateNotAnInternalBlockObject(volume, false);
validateSourceVolumeHasExported(volume);
String snapshotType = TechnologyType.NATIVE.toString();
Boolean createInactive = Boolean.FALSE;
Boolean readOnly = Boolean.FALSE;
BlockServiceApi api = getBlockServiceImpl(pool, _dbClient);
List<Volume> volumesToSnap = new ArrayList<Volume>();
volumesToSnap.addAll(api.getVolumesToSnap(volume, snapshotType));
api.validateCreateSnapshot(volume, volumesToSnap, snapshotType,
snapshotName, readOnly, fcManager);
String taskId = UUID.randomUUID().toString();
List<URI> snapshotURIs = new ArrayList<URI>();
List<BlockSnapshot> snapshots = api.prepareSnapshots(volumesToSnap,
snapshotType, snapshotName, snapshotURIs, taskId);
TaskList response = new TaskList();
for (BlockSnapshot snapshot : snapshots) {
response.getTaskList().add(toTask(snapshot, taskId));
}
// Update the task status for the volumes task.
_dbClient.createTaskOpStatus(Volume.class, volume.getId(), taskId,
ResourceOperationTypeEnum.CREATE_VOLUME_SNAPSHOT);
// Invoke the block service API implementation to create the snapshot
api.createSnapshot(volume, snapshotURIs, snapshotType, createInactive,
readOnly, taskId);
SnapshotCreateResponse snapCreateResp = new SnapshotCreateResponse();
for (TaskResourceRep rep : response.getTaskList()) {
URI snapshotUri = rep.getResource().getId();
BlockSnapshot snap = _dbClient.queryObject(BlockSnapshot.class,
snapshotUri);
if (snap != null) {
StringMap extensions = snap.getExtensions();
if (extensions == null)
extensions = new StringMap();
extensions.put("display_description", (snapshotDescription == null) ? "" : snapshotDescription);
extensions.put("taskid", rep.getId().toString());
_log.debug("Create snapshot : stored description");
snap.setExtensions(extensions);
ScopedLabelSet tagSet = new ScopedLabelSet();
snap.setTag(tagSet);
String[] splits = snapshotUri.toString().split(":");
String tagName = splits[3];
//this check will verify whether retrieved data is not corrupted
if (tagName == null || tagName.isEmpty()
|| tagName.length() < 2) {
throw APIException.badRequests
.parameterTooShortOrEmpty("Tag", 2);
}
Volume parentVol = _permissionsHelper.getObjectById(
snap.getParent(), Volume.class);
URI tenantOwner = parentVol.getTenant().getURI();
ScopedLabel tagLabel = new ScopedLabel(
tenantOwner.toString(), tagName);
tagSet.add(tagLabel);
_dbClient.updateObject(snap);
if (isV1Call != null) {
_log.debug("Inside V1 call");
return CinderApiUtils.getCinderResponse(getSnapshotDetail(snap, isV1Call, openstack_tenant_id), header, true,CinderConstants.STATUS_OK);
} else {
return CinderApiUtils.getCinderResponse(getSnapshotDetail(snap, isV1Call, openstack_tenant_id), header, true,CinderConstants.STATUS_ACCEPT);
}
}
}
return CinderApiUtils.getCinderResponse(new CinderSnapshot(), header, true, CinderConstants.STATUS_ACCEPT);
}
/**
* Update a specific snapshot
*
*
* @prereq none
*
* @param tenant_id
* the URN of the tenant
* @param snapshot_id
* the URN of the snapshot
*
* @brief Update snapshot
* @return snapshot details
*/
@PUT
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{snapshot_id}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public Response updateSnapshot(
@PathParam("tenant_id") String openstack_tenant_id,
@PathParam("snapshot_id") String snapshot_id,
SnapshotUpdateRequestGen param,
@HeaderParam("X-Cinder-V1-Call") String isV1Call,
@Context HttpHeaders header) {
BlockSnapshot snap = findSnapshot(snapshot_id, openstack_tenant_id);
if (snap == null) {
throw APIException.badRequests.parameterIsNotValid(snapshot_id);
}
_log.debug("Update snapshot {}: ", snap.getLabel());
String label = null;
String description = null;
if (isV1Call != null) {
label = param.snapshot.display_name;
description = param.snapshot.display_description;
} else {
label = param.snapshot.name;
description = param.snapshot.description;
}
_log.debug("new name = {}, description = {}", label, description);
if (label != null && (label.length() > 2)) {
URI volumeUri = snap.getParent().getURI();
String snapshotType = TechnologyType.NATIVE.toString();
Volume volume = queryVolumeResource(
URI.create(getCinderHelper().trimId(volumeUri.toString())),
openstack_tenant_id);
URIQueryResultList uris = new URIQueryResultList();
_dbClient.queryByConstraint(ContainmentConstraint.Factory
.getVolumeSnapshotConstraint(volume.getId()), uris);
for (URI snuri : uris) {
BlockSnapshot snapIter = _dbClient.queryObject(BlockSnapshot.class, snuri);
if (snapIter != null && !snapIter.getInactive()
&& snapIter.getLabel().equals(label)) {
_log.info("Update snapshot: duplicate name");
throw APIException.badRequests.duplicateLabel(label);
}
}
//ToDo if the backend system is vplex, rp
//we cannot use the default blockservice implemenation
//we need to use other APIs(for vplex adn RP), that need to be implemented
VirtualPool pool = _dbClient.queryObject(VirtualPool.class, volume.getVirtualPool());
if (pool == null) {
_log.info("Virtual Pool corresponding to the volume does not exist.");
throw APIException.badRequests.parameterIsNotValid(volume
.getVirtualPool().toString());
}
BlockServiceApi api = getBlockServiceImpl(pool, _dbClient);
List<Volume> volumesToSnap = new ArrayList<Volume>();
volumesToSnap.addAll(api.getVolumesToSnap(volume, snapshotType));
BlockFullCopyManager fcManager = new BlockFullCopyManager(_dbClient,
_permissionsHelper, _auditMgr, _coordinator, _placementManager, sc, uriInfo,
_request, _tenantsService);
api.validateCreateSnapshot(volume, volumesToSnap, snapshotType,
label, false, fcManager);
_log.debug("Update snapshot: not a duplicate name");
snap.setLabel(label);
}
if (description != null && (description.length() > 2)) {
StringMap extensions = snap.getExtensions();
if (extensions == null)
extensions = new StringMap();
extensions.put("display_description", description);
_log.debug("Update volume : stored description");
snap.setExtensions(extensions);
}
_dbClient.updateObject(snap);
return CinderApiUtils.getCinderResponse(
getSnapshotDetail(snap, isV1Call, openstack_tenant_id), header, true,CinderConstants.STATUS_OK);
}
/**
* Action could be snapshot status update operation
* NOTE: This is an synchronous operation.
*
* @prereq none
* @param param POST data containing the snapshot action information.
* @brief update snapshot status
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
@Path("/{snapshot_id}/action")
public Object actionOnSnapshot(@PathParam("tenant_id") String openstack_tenant_id,
@PathParam("snapshot_id") String snapshot_id,
SnapshotActionRequest actionRequest) throws InternalException, InterruptedException {
BlockSnapshot snap = findSnapshot(snapshot_id, openstack_tenant_id);
if (snap == null) {
_log.error("Invalid snpashot ID ={} ",snapshot_id);
return CinderApiUtils.createErrorResponse(404, "Not Found : Invalid snapshot ID " + snapshot_id);
}
if (snap.getExtensions() == null) {
snap.setExtensions(new StringMap());
}
snap.getExtensions().put("status", actionRequest.updateStatus.status);
_dbClient.updateObject(snap);
return Response.status(202).build();
}
/**
* Update a specific snapshot's metadata
*
*
* @prereq none
*
* @param tenant_id
* the URN of the tenant
* @param snapshot_id
* the URN of the snapshot
*
* @brief Update snapshot metadata
* @return snapshot metadata details
*/
@PUT
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{snapshot_id}/metadata")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public CinderSnapshotMetadata updateSnapshotMetadata(
@PathParam("tenant_id") String openstack_tenant_id,
@PathParam("snapshot_id") String snapshot_id,
CinderSnapshotMetadata param) {
BlockSnapshot snap = findSnapshot(snapshot_id, openstack_tenant_id);
if (snap == null) {
throw APIException.badRequests.parameterIsNotValid(snapshot_id);
}
Map<String, String> metaMap = param.metadata;
StringMap extensions = snap.getExtensions();
if (extensions == null) {
extensions = new StringMap();
}
_log.debug("Update snapshot metadata:CLEARED extensions {}",extensions);
for (String mapEntry : metaMap.keySet()) {
String value = metaMap.get(mapEntry);
extensions.put("METADATA_" + mapEntry, value);
}
snap.setExtensions(extensions);
_dbClient.updateObject(snap);
return getSnapshotMetadataDetail(snap);
}
/**
* Create a specific snapshot's metadata
*
*
* @prereq none
*
* @param tenant_id
* the URN of the tenant
* @param snapshot_id
* the URN of the snapshot
*
* @brief create snapshot metadata
* @return snapshot metadata details
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{snapshot_id}/metadata")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public CinderSnapshotMetadata creatSnapshotMetadata(
@PathParam("tenant_id") String openstackTenantId,
@PathParam("snapshot_id") String snapshotId,
CinderSnapshotMetadata param) {
BlockSnapshot snap = findSnapshot(snapshotId, openstackTenantId);
if (snap == null) {
throw APIException.badRequests.parameterIsNotValid(snapshotId);
}
_log.debug("Create metadata for snapshot {}: ", snap.getLabel());
Map<String, String> metaMap = param.metadata;
StringMap extensions = snap.getExtensions();
if (extensions == null) {
extensions = new StringMap();
}
for (String mapEntry : metaMap.keySet()) {
String value = metaMap.get(mapEntry);
extensions.put("METADATA_" + mapEntry, value);
}
snap.setExtensions(extensions);
_dbClient.updateObject(snap);
_log.debug("Create snapshot metadata: created new metadata {}", snap.getExtensions());
return getSnapshotMetadataDetail(snap);
}
/**
* Delete a specific snapshot's metadata item
*
*
* @prereq none
*
* @param tenant_id
* the URN of the tenant
* @param snapshot_id
* the URN of the snapshot
* @param key
* the URN of the key
*
* @brief Delete single snapshot metadata entry which specified by key
* @return delete status
*/
@DELETE
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{snapshot_id}/metadata/{key}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public CinderSnapshotMetadata deleteSnapshotMetadataItem(
@PathParam("tenant_id") String openstackTenantId,
@PathParam("snapshot_id") String snapshotId,
@PathParam("key") String key,
@Context HttpHeaders header,
@HeaderParam("X-Cinder-V1-Call") String isV1Call) {
BlockSnapshot snap = findSnapshot(snapshotId, openstackTenantId);
if (snap == null) {
throw APIException.badRequests.parameterIsNotValid(snapshotId);
}
StringMap extensions = snap.getExtensions();
StringMap newExtensions = new StringMap();
String internalKey = "METADATA_" + key;
if (extensions != null) {
if(extensions.containsKey(internalKey)){
extensions.remove(internalKey);
_log.debug("Removing the key {}: ", internalKey);
snap.setExtensions(extensions);
_dbClient.updateObject(snap);
BlockSnapshot snapRag = findSnapshot(snapshotId, openstackTenantId);
return getSnapshotMetadataDetail(snapRag);
} else {
_log.info ("Invalid metadata key ={} ",key);
throw APIException.badRequests.parameterIsNotValid(key);
}
}
return getSnapshotMetadataDetail(snap);
}
/**
* Get the summary list of all snapshots for the given tenant
*
*
* @prereq none
*
* @param tenant_id
* the URN of the tenant
*
* @brief List snapshots
* @return Snapshot list
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/detail")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public Response getSnapshotList(
@PathParam("tenant_id") String openstack_tenant_id,
@HeaderParam("X-Cinder-V1-Call") String isV1Call,
@Context HttpHeaders header) {
CinderSnapshotListRestResp snapshots = new CinderSnapshotListRestResp();
URIQueryResultList uris = getSnapshotUris(openstack_tenant_id);
if (uris != null) {
while (uris.iterator().hasNext()) {
URI snapshotUri = uris.iterator().next();
BlockSnapshot snap = _dbClient.queryObject(BlockSnapshot.class,
snapshotUri);
if (snap != null && !snap.getInactive()) {
CinderSnapshot cinder_snapshot = getSnapshotDetail(snap,
isV1Call, openstack_tenant_id);
snapshots.getSnapshots().add(cinder_snapshot);
}
}
}
return CinderApiUtils.getCinderResponse(snapshots, header, false,CinderConstants.STATUS_OK);
}
/**
* Delete a specific snapshot
*
*
* @prereq none
*
* @param tenant_id
* the URN of the tenant
* @param snapshot_id
* the URN of the snapshot
*
* @brief Delete Snapshot
* @return Task result
*/
@DELETE
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{snapshot_id}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public Response deleteSnapshot(
@PathParam("tenant_id") String openstack_tenant_id,
@PathParam("snapshot_id") String snapshot_id) {
_log.info("Delete Snapshot: id = {}", snapshot_id);
BlockSnapshot snap = findSnapshot(snapshot_id, openstack_tenant_id);
if (snap == null) {
_log.error("Not Found : Invalid volume snapshot id");
return CinderApiUtils.createErrorResponse(404, "Not Found : Invalid volume snapshot id");
}else if(snap.hasConsistencyGroup()){
_log.error("Not Found : Snapshot belongs to a consistency group");
return CinderApiUtils.createErrorResponse(400, "Invalid snapshot: Snapshot belongs to consistency group");
}
URI snapshotURI = snap.getId();
String task = UUID.randomUUID().toString();
TaskList response = new TaskList();
ArgValidator.checkReference(BlockSnapshot.class, snapshotURI, checkForDelete(snap));
// Not an error if the snapshot we try to delete is already deleted
if (snap.getInactive()) {
Operation op = new Operation();
op.ready("The snapshot has already been deleted");
op.setResourceType(ResourceOperationTypeEnum.DELETE_VOLUME_SNAPSHOT);
_dbClient.createTaskOpStatus(BlockSnapshot.class, snap.getId(), task, op);
response.getTaskList().add(toTask(snap, task, op));
return Response.status(202).build();
}
StorageSystem device = _dbClient.queryObject(StorageSystem.class, snap.getStorageController());
List<BlockSnapshot> snapshots = new ArrayList<BlockSnapshot>();
final URI cgId = snap.getConsistencyGroup();
if (!NullColumnValueGetter.isNullURI(cgId)) {
// Collect all the BlockSnapshots if part of a CG.
URIQueryResultList results = new URIQueryResultList();
_dbClient.queryByConstraint(AlternateIdConstraint.Factory
.getBlockSnapshotsBySnapsetLabel(snap.getSnapsetLabel()),
results);
while (results.iterator().hasNext()) {
URI uri = results.iterator().next();
_log.info("BlockSnapshot being deactivated: " + uri);
BlockSnapshot snapshot = _dbClient.queryObject(BlockSnapshot.class, uri);
if (snapshot != null) {
snapshots.add(snapshot);
}
}
} else {
// Snap is not part of a CG so only delete the snap
snapshots.add(snap);
}
for (BlockSnapshot snapshot : snapshots) {
Operation snapOp = _dbClient.createTaskOpStatus(
BlockSnapshot.class, snapshot.getId(), task,
ResourceOperationTypeEnum.DELETE_VOLUME_SNAPSHOT);
response.getTaskList().add(toTask(snapshot, task, snapOp));
}
// Note that for snapshots of VPLEX volumes, the parent volume for the
// snapshot is the source side backend volume, which will have the same
// vpool as the VPLEX volume and therefore, the correct implementation
// should be returned.
Volume volume = _permissionsHelper.getObjectById(snap.getParent(), Volume.class);
BlockServiceApi blockServiceApiImpl = BlockService.getBlockServiceImpl(volume, _dbClient);
blockServiceApiImpl.deleteSnapshot(snap, snapshots, task, VolumeDeleteTypeEnum.FULL.name());
StringMap extensions = snap.getExtensions();
if (extensions == null) {
extensions = new StringMap();
}
for (TaskResourceRep rep : response.getTaskList()) {
extensions.put("taskid", rep.getId().toString());
break;
}
snap.setExtensions(extensions);
_dbClient.updateObject(snap);
auditOp(OperationTypeEnum.DELETE_VOLUME_SNAPSHOT, true,
AuditLogManager.AUDITOP_BEGIN, snapshot_id, snap.getLabel(),
snap.getParent().getName(), device.getId().toString());
return Response.status(202).build();
}
/**
* Get the details of a specific snapshot
*
*
* @prereq none
*
* @param tenant_id
* the URN of the tenant
* @param snapshot_id
* the URN of the snapshot
*
* @brief Show snapshot
* @return snapshot details
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{snapshot_id}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public Response getSnapShot(
@PathParam("tenant_id") String openstack_tenant_id,
@PathParam("snapshot_id") String snapshot_id,
@HeaderParam("X-Cinder-V1-Call") String isV1Call,
@Context HttpHeaders header) {
CinderSnapshot response = new CinderSnapshot();
_log.info("START get snapshot with id {}", snapshot_id);
BlockSnapshot snapshot = findSnapshot(snapshot_id, openstack_tenant_id);
if(snapshot==null) {
_log.error("Invalid snapshot ID ={} ",snapshot_id);
return CinderApiUtils.createErrorResponse(400, "Bad Request : Invalid snapshot " + snapshot_id);
}
response = getSnapshotDetail(snapshot, isV1Call, openstack_tenant_id);
return CinderApiUtils.getCinderResponse(response, header, true,CinderConstants.STATUS_OK);
}
/**
* Get the meta-data of a specific snapshot
*
*
* @prereq none
*
* @param tenant_id
* the URN of the tenant
* @param snapshot_id
* the URN of the snapshot
*
* @brief Show snapshot meta-data
* @return snapshot meta-data details
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{snapshot_id}/metadata")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public CinderSnapshotMetadata getSnapShotMetadata(
@PathParam("tenant_id") String openstackTenantId,
@PathParam("snapshot_id") String snapshotId) {
BlockSnapshot snapshot = findSnapshot(snapshotId, openstackTenantId);
if(snapshot==null) {
_log.error("Invalid snapshot ID ={} ",snapshotId);
throw APIException.badRequests.parameterIsNotValid(snapshotId);
}
return getSnapshotMetadataDetail(snapshot);
}
// INTERNAL FUNCTIONS
protected CinderSnapshot getSnapshotDetail(BlockSnapshot snapshot,
String isV1Call, String openstack_tenant_id) {
CinderSnapshot detail = new CinderSnapshot();
detail.id = getCinderHelper().trimId(snapshot.getId().toString());
detail.volume_id = getCinderHelper().trimId(snapshot.getParent().getURI().toString());
detail.created_at = date(snapshot.getCreationTime().getTimeInMillis());
detail.project_id = openstack_tenant_id;
detail.size = (int) ((snapshot.getProvisionedCapacity() + HALF_GB) / GB);
StringMap extensions = snapshot.getExtensions();
String description = null;
Map<String, String> metaMap = new HashMap<String, String>();
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"))
{
detail.status = CinderConstants.ComponentStatus.AVAILABLE.getStatus().toLowerCase();
snapshot.getExtensions().put("status", CinderConstants.ComponentStatus.AVAILABLE.getStatus().toLowerCase());
snapshot.getExtensions().remove("taskid");
_dbClient.updateObject(snapshot);
}
else if (tsk.getStatus().equals("pending")) {
if (tsk.getDescription().equals(ResourceOperationTypeEnum.CREATE_VOLUME_SNAPSHOT.getDescription()))
{
detail.status = CinderConstants.ComponentStatus.CREATING.getStatus().toLowerCase();
} else if (tsk.getDescription().equals(ResourceOperationTypeEnum.DELETE_VOLUME_SNAPSHOT.getDescription()))
{
detail.status = CinderConstants.ComponentStatus.DELETING.getStatus().toLowerCase();
}
}
else if (tsk.getStatus().equals("error"))
{
detail.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("")) {
detail.status = snapshot.getExtensions().get("status").toString().toLowerCase();
}
else
{
detail.status = CinderConstants.ComponentStatus.AVAILABLE.getStatus().toLowerCase(); // "available";
}
for (String mapEntry : extensions.keySet()) {
if (mapEntry.startsWith("METADATA_"))
{
String value = extensions.get(mapEntry);
metaMap.put(mapEntry.substring("METADATA_".length()), value);
}
}
}
if (isV1Call != null)
{
detail.display_name = snapshot.getLabel();
detail.display_description = (description == null) ? ""
: description;
} else
{
detail.name = snapshot.getLabel();
detail.description = (description == null) ? "" : description;
}
detail.progress = ZERO_PERCENT_COMPLETION;// default
if ((detail.status == CinderConstants.ComponentStatus.CREATING.getStatus().toLowerCase())
|| (detail.status == CinderConstants.ComponentStatus.DELETING.getStatus().toLowerCase()) ||
(detail.status == CinderConstants.ComponentStatus.ERROR.getStatus().toLowerCase())
|| (detail.status == CinderConstants.ComponentStatus.ERROR_DELETING.getStatus().toLowerCase()))
{
detail.progress = ZERO_PERCENT_COMPLETION;
}
else if (detail.status == CinderConstants.ComponentStatus.AVAILABLE.getStatus().toLowerCase())
{
detail.progress = HUNDRED_PERCENT_COMPLETION;
}
detail.metadata = metaMap;
return detail;
}
// INTERNAL FUNCTIONS
protected CinderSnapshotMetadata getSnapshotMetadataDetail(BlockSnapshot snapshot) {
StringMap extensions = snapshot.getExtensions();
if (extensions == null) {
extensions = new StringMap();
}
Map<String, String> metaMap = new HashMap<String, String>();
for (String mapEntry : extensions.keySet()) {
if (mapEntry.startsWith("METADATA_")) {
String value = extensions.get(mapEntry);
metaMap.put(mapEntry.substring("METADATA_".length()), value);
}
}
CinderSnapshotMetadata resp = new CinderSnapshotMetadata();
resp.metadata = metaMap;
return resp;
}
private boolean validateSnapshotCreate(String openstack_tenant_id,
VirtualPool pool, long requestedSize) {
_log.info("requestedSize {}", requestedSize);
QuotaOfCinder objQuota = null;
if (pool == null) {
objQuota = getQuotaHelper().getProjectQuota(openstack_tenant_id, getUserFromContext());
} else {
objQuota = getQuotaHelper().getVPoolQuota(openstack_tenant_id, pool, getUserFromContext());
}
Project proj = getCinderHelper().getProject(openstack_tenant_id, getUserFromContext());
if (proj == null) {
throw APIException.badRequests.projectWithTagNonexistent(openstack_tenant_id);
}
long totalSnapshotsUsed = 0;
long totalSizeUsed = 0;
UsageStats stats = null;
if (pool != null) {
stats = getQuotaHelper().getStorageStats(pool.getId(), proj.getId());
} else {
stats = getQuotaHelper().getStorageStats(null, proj.getId());
}
totalSnapshotsUsed = stats.snapshots;
totalSizeUsed = stats.spaceUsed;
_log.info(String
.format("objQuota.getVolumesLimit():%s ,objQuota.getSnapshotsLimit():%s,objQuota.getTotalQuota():%s,totalSizeUsed:%s,totalSnapshotsUsed:%s,willconsume:%s",
objQuota.getVolumesLimit(), objQuota.getSnapshotsLimit(), objQuota.getTotalQuota(),
totalSizeUsed, totalSnapshotsUsed, (totalSizeUsed + (long) (requestedSize / GB))));
if ((objQuota.getSnapshotsLimit() != -1)
&& (objQuota.getSnapshotsLimit() <= totalSnapshotsUsed)) {
return false;
} else if ((objQuota.getTotalQuota() != -1)
&& (objQuota.getTotalQuota() <= (totalSizeUsed + (long) (requestedSize / GB)))) {
return false;
} else {
return true;
}
}
protected BlockSnapshot findSnapshot(String snapshot_id,
String openstack_tenant_id) {
BlockSnapshot snapshot = (BlockSnapshot) getCinderHelper().queryByTag(
URI.create(snapshot_id), getUserFromContext(),BlockSnapshot.class);
if (snapshot != null) {
Project project = getCinderHelper().getProject(openstack_tenant_id, getUserFromContext());
if ((project != null)
&& (snapshot.getProject().getURI().toString()
.equalsIgnoreCase(project.getId().toString()))) {
// snapshot is part of the project
return snapshot;
}
else {
throw APIException.badRequests.projectWithTagNonexistent(openstack_tenant_id);
}
}
return null;
}
private URIQueryResultList getSnapshotUris(String openstack_tenant_id) {
URIQueryResultList uris = new URIQueryResultList();
Project project = getCinderHelper().getProject(openstack_tenant_id, getUserFromContext());
if (project == null) // return empty list
return null;
_dbClient.queryByConstraint(ContainmentConstraint.Factory
.getProjectBlockSnapshotConstraint(project.getId()), uris);
return uris;
}
protected BlockObject querySnapshotResource(URI id) {
StorageOSUser user = getUserFromContext();
URI vipr_tenantId = URI.create(user.getTenantId());
URIQueryResultList uris = new URIQueryResultList();
_dbClient.queryByConstraint(PrefixConstraint.Factory
.getTagsPrefixConstraint(BlockSnapshot.class, id.toString(),
vipr_tenantId), uris);
for (URI snapUri : uris) {
BlockSnapshot snap = _dbClient.queryObject(BlockSnapshot.class,
snapUri);
if (snap != null && isAuthorized(snapUri))
return snap;
}
return null;
}
protected Volume queryVolumeResource(URI id, String openstack_tenant_id) {
Volume vol = (Volume) getCinderHelper().queryByTag(id, getUserFromContext(), Volume.class);
if (vol != null) {
Project project = getCinderHelper().getProject(openstack_tenant_id, getUserFromContext());
if ((project != null)
&& (vol.getProject().getURI().toString().equalsIgnoreCase(project.getId().toString()))) {
// volume is part of the project
return vol;
} else {
throw APIException.badRequests.projectWithTagNonexistent(openstack_tenant_id);
}
}
return null;
}
/**
* Source volume should be exported to any host before performing hds
* snap/clone/mirror creation
*
* @param requestedVolume
*/
private void validateSourceVolumeHasExported(Volume requestedVolume) {
URI id = requestedVolume.getId();
StorageSystem storageSystem = _dbClient.queryObject(
StorageSystem.class, requestedVolume.getStorageController());
if (storageSystem != null
&& DiscoveredDataObject.Type.hds.name().equals(
storageSystem.getSystemType())) {
if (!requestedVolume.isVolumeExported(_dbClient)) {
throw APIException.badRequests.sourceNotExported(id);
}
}
}
protected void verifyUserCanModifyVolume(Volume vol) {
StorageOSUser user = getUserFromContext();
URI projectId = vol.getProject().getURI();
if (!(_permissionsHelper.userHasGivenRole(user, vol.getTenant()
.getURI(), Role.TENANT_ADMIN) || _permissionsHelper
.userHasGivenACL(user, projectId, ACL.OWN, ACL.ALL))) {
throw APIException.forbidden.insufficientPermissionsForUser(user
.getName());
}
}
static String date(Long timeInMillis) {
return new java.text.SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z")
.format(new java.util.Date(timeInMillis));
}
@Override
protected URI getTenantOwner(URI id) {
throw new UnsupportedOperationException();
}
/**
* Snapshot is not a zone level resource
*/
@Override
protected boolean isZoneLevelResource() {
return false;
}
@Override
protected ResourceTypeEnum getResourceType() {
return ResourceTypeEnum.BLOCK_SNAPSHOT;
}
@Override
public String getServiceType() {
return EVENT_SERVICE_TYPE;
}
/**
* Get object specific permissions filter
*
*/
@Override
protected ResRepFilter<? extends RelatedResourceRep> getPermissionFilter(
StorageOSUser user, PermissionsHelper permissionsHelper) {
return new ProjOwnedResRepFilter(user, permissionsHelper, Volume.class);
}
@Override
protected DataObject queryResource(URI id) {
return _dbClient.queryObject(BlockSnapshot.class, id);
}
/**
* Returns the storagetype for block service Implementation
*
* @param vpool Virtual Pool
* @return block service implementation object
*/
private static BlockServiceApi getBlockServiceImpl(VirtualPool vpool, DbClient dbClient) {
// Mutually exclusive logic that selects an implementation of the block service
if (VirtualPool.vPoolSpecifiesProtection(vpool)) {
return BlockService.getBlockServiceImpl(DiscoveredDataObject.Type.rp.name());
} else if (VirtualPool.vPoolSpecifiesHighAvailability(vpool)) {
return BlockService.getBlockServiceImpl(DiscoveredDataObject.Type.vplex.name());
} else if (VirtualPool.vPoolSpecifiesSRDF(vpool)) {
return BlockService.getBlockServiceImpl(DiscoveredDataObject.Type.srdf.name());
}
return BlockService.getBlockServiceImpl("default");
}
}