/* 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 static com.emc.storageos.db.client.constraint.ContainmentConstraint.Factory.getBlockSnapshotByConsistencyGroup;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
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.apache.commons.lang.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.api.mapper.DbObjectMapper;
import com.emc.storageos.api.service.authorization.PermissionsHelper;
import com.emc.storageos.api.service.impl.placement.PlacementManager;
import com.emc.storageos.api.service.impl.placement.StorageScheduler;
import com.emc.storageos.api.service.impl.placement.VpoolUse;
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.fullcopy.BlockFullCopyManager;
import com.emc.storageos.api.service.impl.resource.utils.BlockServiceUtils;
import com.emc.storageos.api.service.impl.resource.utils.CapacityUtils;
import com.emc.storageos.api.service.impl.resource.utils.CinderApiUtils;
import com.emc.storageos.api.service.impl.response.ProjOwnedResRepFilter;
import com.emc.storageos.api.service.impl.response.ResRepFilter;
import com.emc.storageos.api.service.impl.response.SearchedResRepList;
import com.emc.storageos.cinder.CinderConstants;
import com.emc.storageos.cinder.model.Attachment;
import com.emc.storageos.cinder.model.CinderVolume;
import com.emc.storageos.cinder.model.UsageStats;
import com.emc.storageos.cinder.model.VolumeCreateRequestGen;
import com.emc.storageos.cinder.model.VolumeDetail;
import com.emc.storageos.cinder.model.VolumeDetails;
import com.emc.storageos.cinder.model.VolumeUpdateRequestGen;
import com.emc.storageos.cinder.model.VolumesRestResp;
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.BlockConsistencyGroup;
import com.emc.storageos.db.client.model.BlockConsistencyGroup.Types;
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.DataObject.Flag;
import com.emc.storageos.db.client.model.DiscoveredDataObject;
import com.emc.storageos.db.client.model.ExportGroup;
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.StoragePort;
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.TenantOrg;
import com.emc.storageos.db.client.model.VirtualArray;
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.VolumeCreate;
import com.emc.storageos.model.block.VolumeFullCopyCreateParam;
import com.emc.storageos.model.search.SearchResultResourceRep;
import com.emc.storageos.model.vpool.ProtectionType;
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 com.emc.storageos.volumecontroller.Recommendation;
import com.emc.storageos.volumecontroller.impl.utils.VirtualPoolCapabilityValuesWrapper;
@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 VolumeService extends TaskResourceService {
private static final Logger _log = LoggerFactory.getLogger(VolumeService.class);
private static final String EVENT_SERVICE_TYPE = "block";
private static final String DELETE_TASK_ID = "delete_taskid";
private static final long halfGB = 512 * 1024 * 1024;
private static final long GB = 1024 * 1024 * 1024;
private static final long ZERO_BYTES = 0;
private static final String VOLUME_FROM_SNAPSHOT = "Volume created from snapshot";
private static final String VOLUME_FROM_CLONE = "Volume created from clone";
private static final int MAX_VMAX_COPY_SESSIONS = 8;
private static final int CLONE_COUNT = 1;
private static final int SNAP_COUNT = 1;
private static final String PROJECT_TENANTID_NULL = "Both Project and Tenant Id are null";
private static final String TRUE = "true";
private static final int STATUS_OK = 200;
private PlacementManager _placementManager;
private CinderHelpers helper;// = new CinderHelpers(_dbClient , _permissionsHelper);
public void setPlacementManager(PlacementManager placementManager) {
_placementManager = placementManager;
}
@Override
public Class<Volume> getResourceClass() {
return Volume.class;
}
protected CinderHelpers getCinderHelper() {
return CinderHelpers.getInstance(_dbClient, _permissionsHelper);
}
private QuotaHelper getQuotaHelper() {
return QuotaHelper.getInstance(_dbClient, _permissionsHelper);
}
/**
* Get the summary list of all volumes for the given tenant
*
*
* @prereq none
*
* @param tenant_id the URN of the tenant
*
* @brief List volumes
* @return Volume list
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public Response getVolumeList(@PathParam("tenant_id") String openstackTenantId, @HeaderParam("X-Cinder-V1-Call") String isV1Call,
@Context HttpHeaders header) {
VolumesRestResp volumes = new VolumesRestResp();
URIQueryResultList uris = getVolumeUris(openstackTenantId);
if (uris != null) {
while (uris.iterator().hasNext()) {
URI volumeUri = uris.iterator().next();
Volume volume = _dbClient.queryObject(Volume.class, volumeUri);
if (volume != null && !volume.getInactive()) {
CinderVolume cinder_volume = new CinderVolume();
cinder_volume.id = getCinderHelper().trimId(volume.getId().toString());
cinder_volume.setLink(DbObjectMapper.toLink(volume));
cinder_volume.name = volume.getLabel();
volumes.getVolumes().add(cinder_volume);
}
}
}
return CinderApiUtils.getCinderResponse(volumes, header, false, STATUS_OK);
}
/**
* Get the detailed list of all volumes for the given tenant
*
*
* @prereq none
*
* @param tenant_id the URN of the tenant
*
* @brief List volumes in detail
* @return Volume detailed list
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/detail")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public Response getDetailedVolumeList(@PathParam("tenant_id") String openstackTenantId,
@HeaderParam("X-Cinder-V1-Call") String isV1Call, @Context HttpHeaders header) {
_log.debug("START get detailed volume list");
URIQueryResultList uris = getVolumeUris(openstackTenantId);
// convert to detailed format
VolumeDetails volumeDetails = new VolumeDetails();
if (uris != null) {
for (URI volumeUri : uris) {
Volume vol = _dbClient.queryObject(Volume.class, volumeUri);
if (vol != null && !vol.getInactive()) {
VolumeDetail volumeDetail = getVolumeDetail(vol, isV1Call, openstackTenantId);
volumeDetails.getVolumes().add(volumeDetail);
}
}
}
return CinderApiUtils.getCinderResponse(volumeDetails, header, false, STATUS_OK);
}
/**
* Get the details of a specific volume
*
*
* @prereq none
*
* @param tenant_id the URN of the tenant
* @param volume_id the URN of the volume
*
* @brief Show volume
* @return Volume details
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{volume_id}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public Response getVolume(@PathParam("tenant_id") String openstackTenantId,
@PathParam("volume_id") String volumeId, @HeaderParam("X-Cinder-V1-Call") String isV1Call, @Context HttpHeaders header) {
VolumeDetail response = new VolumeDetail();
if (volumeId == null) {
_log.info("Volume id is empty ");
return CinderApiUtils.createErrorResponse(404, "Not Found : volume id is empty");
}
Volume vol = findVolume(volumeId, openstackTenantId);
if (vol != null) {
response = getVolumeDetail(vol, isV1Call, openstackTenantId);
} else {
_log.info("Invalid volume id ={} ", volumeId);
return CinderApiUtils.createErrorResponse(404, "Not Found : Invalid volume id");
}
return CinderApiUtils.getCinderResponse(response, header, true, STATUS_OK);
}
/**
* The fundamental abstraction in the Block Store is a
* volume. A volume is a unit of block storage capacity that has been
* allocated by a consumer to a project. This API allows the user to
* create one or more volumes. The volumes are created in the same
* storage pool.
*
* NOTE: This is an asynchronous operation.
*
*
* @prereq none
*
* @param param POST data containing the volume creation information.
*
* @brief Create volume
* @return Details of the newly created volume
* @throws InternalException
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Response createVolume(@PathParam("tenant_id") String openstackTenantId,
@HeaderParam("X-Cinder-V1-Call") String isV1Call, VolumeCreateRequestGen param, @Context HttpHeaders header)
throws InternalException {
// Step 1: Parameter validation
Project project = getCinderHelper().getProject(openstackTenantId, getUserFromContext());
String snapshotId = param.volume.snapshot_id;
String sourceVolId = param.volume.source_volid;
String imageId = param.volume.imageRef;
String consistencygroup_id = param.volume.consistencygroup_id;
String volume_type = param.volume.volume_type;
boolean hasConsistencyGroup = false;
if (project == null) {
if (openstackTenantId != null) {
throw APIException.badRequests.projectWithTagNonexistent(openstackTenantId);
} else {
throw APIException.badRequests.parameterIsNullOrEmpty(PROJECT_TENANTID_NULL);
}
}
URI tenantUri = project.getTenantOrg().getURI();
TenantOrg tenant = _dbClient.queryObject(TenantOrg.class, tenantUri);
if (tenant == null)
throw APIException.notFound.unableToFindUserScopeOfSystem();
_log.debug("Create volume: project = {}, tenant = {}", project.getLabel(), tenant.getLabel());
if (param.volume.size <= 0)
{
_log.error("volume size should not be zero or negative ={} ", param.volume.size);
return CinderApiUtils.createErrorResponse(400, "Bad Request : Invalid Volume size");
}
long requestedSize = param.volume.size * GB;
// convert volume type from name to vpool
VirtualPool vpool = getVpool(param.volume.volume_type);
Volume sourceVolume = null;
if (vpool == null) {
if (sourceVolId != null) {
sourceVolume = findVolume(sourceVolId, openstackTenantId);
if (sourceVolume == null) {
_log.error("Invalid Source Volume ID ={} ", sourceVolId);
return CinderApiUtils.createErrorResponse(404, "Not Found : Invalid Source Volume ID " + sourceVolId);
}
vpool = _dbClient.queryObject(VirtualPool.class, sourceVolume.getVirtualPool());
}
else {
_log.error("Invalid Volume Type ={} ", volume_type);
return CinderApiUtils.createErrorResponse(404, "Not Found : Invalid Volume Type " + volume_type);
}
}
if (!validateVolumeCreate(openstackTenantId, null, requestedSize)) {
_log.info("The volume can not be created because of insufficient project quota.");
throw APIException.badRequests.insufficientQuotaForProject(project.getLabel(), "volume");
}
else if (!validateVolumeCreate(openstackTenantId, vpool, requestedSize)) {
_log.info("The volume can not be created because of insufficient quota for virtual pool.");
throw APIException.badRequests.insufficientQuotaForVirtualPool(vpool.getLabel(), "virtual pool");
}
_log.debug("Create volume: vpool = {}", vpool.getLabel());
VirtualArray varray = getCinderHelper().getVarray(param.volume.availability_zone, getUserFromContext());
if ((snapshotId == null) && (sourceVolId == null) && (varray == null)) {
// snapshotId and sourceVolId is optional can be null
// when snapshotId and sourceVolId values are absent varry values has to be provided
// otherwise availability_zone exception will be thrown
throw APIException.badRequests.parameterIsNotValid(param.volume.availability_zone);
}
// Validating consistency group
URI blockConsistencyGroupId = null;
BlockConsistencyGroup blockConsistencyGroup = null;
if (consistencygroup_id != null) {
_log.info("Verifying for consistency group : " + consistencygroup_id);
blockConsistencyGroup = (BlockConsistencyGroup) getCinderHelper().queryByTag(URI.create(consistencygroup_id),
getUserFromContext(), BlockConsistencyGroup.class);
if (getCinderHelper().verifyConsistencyGroupHasSnapshot(blockConsistencyGroup)) {
_log.error("Bad Request : Consistency Group has Snapshot ");
return CinderApiUtils.createErrorResponse(400, "Bad Request : Consistency Group has Snapshot ");
}
blockConsistencyGroupId = blockConsistencyGroup.getId();
if (blockConsistencyGroup.getTag() != null && consistencygroup_id.equals(blockConsistencyGroupId.toString().split(":")[3])) {
for (ScopedLabel tag : blockConsistencyGroup.getTag()) {
if (tag.getScope().equals("volume_types")) {
if (tag.getLabel().equals(volume_type)) {
hasConsistencyGroup = true;
} else {
return CinderApiUtils.createErrorResponse(404,
"Invalid volume: No consistency group exist for volume : "
+ param.volume.display_name);
}
}
}
} else {
return CinderApiUtils.createErrorResponse(404, "Invalid Consistency Group Id : No Such Consistency group exists");
}
}
BlockSnapshot snapshot = null;
URI snapUri = null;
if (snapshotId != null) {
snapshot = (BlockSnapshot) getCinderHelper().queryByTag(URI.create(snapshotId), getUserFromContext(), BlockSnapshot.class);
if (snapshot == null) {
_log.error("Invalid snapshot id ={} ", snapshotId);
return CinderApiUtils.createErrorResponse(404, "Not Found : Invalid snapshot id" + snapshotId);
} else {
snapUri = snapshot.getId();
URI varrayUri = snapshot.getVirtualArray();
if (varray == null) {
varray = _dbClient.queryObject(VirtualArray.class, varrayUri);
}
}
}
if (varray != null)
_log.info("Create volume: varray = {}", varray.getLabel());
String name = null;
String description = null;
_log.info("is isV1Call {}", isV1Call);
_log.info("name = {}, description = {}", name, description);
if (isV1Call != null) {
name = param.volume.display_name;
description = param.volume.display_description;
}
else {
name = param.volume.name;
description = param.volume.description;
}
if (name == null)
{
name = "volume-" + RandomStringUtils.random(10);
}
_log.info("param.volume.name = {}, param.volume.display_name = {}", param.volume.name, param.volume.display_name);
_log.info("param.volume.description = {}, param.volume.display_description = {}", param.volume.description,
param.volume.display_description);
if (name == null || (name.length() <= 2))
throw APIException.badRequests.parameterIsNotValid(name);
URI projectUri = project.getId();
checkForDuplicateName(name, Volume.class, projectUri, "project", _dbClient);
// Step 2: Check if the user has rights for volume create
verifyUserIsAuthorizedForRequest(project, vpool, varray);
// Step 3: Check capacity Quotas
_log.debug(" volume name = {}, size = {} GB", name, param.volume.size);
int volumeCount = 1;
VolumeCreate volumeCreate = new VolumeCreate(name,
Long.toString(requestedSize), volumeCount, vpool.getId(),
varray.getId(), project.getId());
BlockServiceApi api = getBlockServiceImpl(vpool, _dbClient);
CapacityUtils.validateQuotasForProvisioning(_dbClient, vpool, project,
tenant, requestedSize, "volume");
// Step 4: Call out placementManager to get the recommendation for placement.
VirtualPoolCapabilityValuesWrapper capabilities = new VirtualPoolCapabilityValuesWrapper();
capabilities.put(VirtualPoolCapabilityValuesWrapper.RESOURCE_COUNT, volumeCount);
capabilities.put(VirtualPoolCapabilityValuesWrapper.SIZE, requestedSize);
// Create a unique task id if one is not passed in the request.
String task = UUID.randomUUID().toString();
TaskList tasklist = null;
BlockFullCopyManager blkFullCpManager = new BlockFullCopyManager(_dbClient,
_permissionsHelper, _auditMgr, _coordinator, _placementManager, sc, uriInfo,
_request, null);
if (hasConsistencyGroup && blockConsistencyGroupId != null) {
try {
checkForConsistencyGroup(vpool, blockConsistencyGroup, project, api, varray, capabilities, blkFullCpManager);
volumeCreate.setConsistencyGroup(blockConsistencyGroupId);
} catch (APIException exp) {
return CinderApiUtils.createErrorResponse(400, "Bad Request : can't create volume for the consistency group : "
+ blockConsistencyGroupId);
}
}
if (sourceVolId != null)
{
_log.debug("Creating New Volume from Volume : Source volume ID ={}", sourceVolId);
if (sourceVolume != null) {
Volume vol = findVolume(sourceVolId, openstackTenantId);
if (vol == null) {
_log.debug("Creating Clone Volume failed : Invalid source volume id ");
return CinderApiUtils.createErrorResponse(404, "Not Found : Invalid source volume id" + sourceVolId);
}
tasklist = volumeClone(name, project, sourceVolId, varray, volumeCount, sourceVolume, blkFullCpManager);
} else {
_log.debug("Creating Clone Volume failed : Null Source volume ");
return CinderApiUtils.createErrorResponse(404, "Not Found : Null source volume ");
}
} else if (snapshotId != null)
{
_log.debug("Creating New Volume from Snapshot ID ={}", snapshotId);
tasklist = volumeFromSnapshot(name, project, snapshotId, varray, param, volumeCount, blkFullCpManager, snapUri, snapshot);
} else if ((snapshotId == null) && (sourceVolId == null))
{
_log.debug("Creating New Volume where snapshotId and sourceVolId are null");
tasklist = newVolume(volumeCreate, project, api, capabilities, varray, task, vpool, param, volumeCount, requestedSize, name);
}
if (imageId != null) {
_log.debug("Creating New Volume from imageid ={}", imageId);
// will be implemented
tasklist = volumeFromImage(name, project, varray, param, volumeCount, blkFullCpManager, imageId);
}
if (!(tasklist.getTaskList().isEmpty())) {
for (TaskResourceRep rep : tasklist.getTaskList()) {
URI volumeUri = rep.getResource().getId();
Volume vol = _dbClient.queryObject(Volume.class, volumeUri);
if (vol != null) {
StringMap extensions = vol.getExtensions();
if (extensions == null)
extensions = new StringMap();
extensions.put("display_description", (description == null) ? "" : description);
vol.setExtensions(extensions);
ScopedLabelSet tagSet = new ScopedLabelSet();
vol.setTag(tagSet);
String[] splits = volumeUri.toString().split(":");
String tagName = splits[3];
if (tagName == null || tagName.isEmpty() || tagName.length() < 2) {
throw APIException.badRequests.parameterTooShortOrEmpty("Tag", 2);
}
URI tenantOwner = vol.getTenant().getURI();
ScopedLabel tagLabel = new ScopedLabel(tenantOwner.toString(), tagName);
tagSet.add(tagLabel);
_dbClient.updateAndReindexObject(vol);
if (isV1Call != null) {
_log.debug("Inside V1 call");
return CinderApiUtils.getCinderResponse(getVolumeDetail(vol, isV1Call, openstackTenantId), header, true,
CinderConstants.STATUS_OK);
} else {
return CinderApiUtils.getCinderResponse(getVolumeDetail(vol, isV1Call, openstackTenantId), header, true,
CinderConstants.STATUS_ACCEPT);
}
}
else {
throw APIException.badRequests.parameterIsNullOrEmpty("Volume");
}
}
}
return CinderApiUtils.getCinderResponse(new VolumeDetail(), header, true, CinderConstants.STATUS_ACCEPT);
}
/**
* Update a specific volume
*
*
* @prereq none
*
* @param tenant_id the URN of the tenant
* @param volume_id the URN of the volume
*
* @brief Update volume
* @return Volume details
*/
@PUT
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{volume_id}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public Response updateVolume(@PathParam("tenant_id") String openstackTenantId,
@PathParam("volume_id") String volumeId, @HeaderParam("X-Cinder-V1-Call") String isV1Call,
VolumeUpdateRequestGen param, @Context HttpHeaders header) {
boolean ishex = volumeId.matches("[a-fA-F0-9\\-]+");
_log.debug(" Matcher {}", ishex);
if (!ishex) {
_log.info("Update volume is failed : Invalid source volume id ");
return CinderApiUtils.createErrorResponse(404, "Not Found : Invalid source volume id");
}
if (volumeId == null) {
_log.debug("Update volume is failed : Volume id is empty ");
return CinderApiUtils.createErrorResponse(404, "Not Found : volume id is empty");
}
Volume vol = findVolume(volumeId, openstackTenantId);
if (vol == null) {
_log.debug("Update volume is failed : Non existent source volume id ");
return CinderApiUtils.createErrorResponse(404, "Not Found : Non existent source volume id");
}
_log.debug("Update volume {}: ", vol.getLabel());
String label = null;
String description = null;
if (isV1Call != null) {
label = param.volume.display_name;
description = param.volume.display_description;
}
else {
label = param.volume.name;
description = param.volume.description;
}
_log.debug("new name = {}, description = {}", label, description);
if (label != null && (label.length() > 2)) {
if (!vol.getLabel().equals(label)) {
URI projectUri = vol.getProject().getURI();
checkForDuplicateName(label, Volume.class, projectUri, "project", _dbClient);
_log.debug("Update volume : not a duplicate name");
vol.setLabel(label);
}
}
if (description != null && (description.length() > 2)) {
StringMap extensions = vol.getExtensions();
if (extensions == null)
extensions = new StringMap();
extensions.put("display_description", description);
_log.debug("Update volume : stored description");
vol.setExtensions(extensions);
}
_dbClient.updateObject(vol);
return CinderApiUtils.getCinderResponse(getVolumeDetail(vol, isV1Call, openstackTenantId), header, true, STATUS_OK);
}
/**
* Delete a specific volume
*
*
* @prereq none
*
* @param tenant_id the URN of the tenant
* @param volume_id the URN of the volume
* @return
*
* @brief Delete volume
* @return Task result
*/
@DELETE
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{volume_id}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public Response deleteVolume(@PathParam("tenant_id") String openstackTenantId,
@PathParam("volume_id") String volumeId) {
_log.info("Delete volume: id = {} tenant: id ={}", volumeId, openstackTenantId);
if (volumeId == null) {
_log.debug("Delete volume is failed : Volume id is empty ");
return CinderApiUtils.createErrorResponse(404, "Not Found : volume id is empty");
}
Volume vol = findVolume(volumeId, openstackTenantId);
if (vol == null) {
return CinderApiUtils.createErrorResponse(404, "Not Found : Invalid volume id");
} else if (vol.hasConsistencyGroup()) {
return CinderApiUtils.createErrorResponse(400, "Invalid volume: Volume belongs to consistency group");
}
BlockServiceApi api = BlockService.getBlockServiceImpl(vol, _dbClient);
if ((api.getSnapshots(vol) != null) && (!api.getSnapshots(vol).isEmpty())) {
return CinderApiUtils.createErrorResponse(400, "Invalid volume: Volume still has one or more dependent snapshots");
}
verifyUserCanModifyVolume(vol);
// Now delete it
String task = UUID.randomUUID().toString();
URI systemUri = vol.getStorageController();
List<URI> volumeURIs = new ArrayList<URI>();
volumeURIs.add(vol.getId());
api.deleteVolumes(systemUri, volumeURIs, "FULL", task);
if (vol.getExtensions() == null) {
vol.setExtensions(new StringMap());
}
vol.getExtensions().put("status", CinderConstants.ComponentStatus.DELETING.getStatus().toLowerCase());
vol.getExtensions().put(DELETE_TASK_ID, task);
_dbClient.updateObject(vol);
return Response.status(202).build();
}
// INTERNAL FUNCTIONS
protected VolumeDetail getVolumeDetail(Volume vol, String isV1Call, String openstackTenantId) {
VolumeDetail detail = new VolumeDetail();
int sizeInGB = (int) ((vol.getCapacity() + halfGB) / GB);
detail.size = sizeInGB;
detail.id = getCinderHelper().trimId(vol.getId().toString());
detail.host_name = getCinderHelper().trimId(vol.getStorageController().toString());
detail.tenant_id = openstackTenantId;
detail.attachments = new ArrayList<Attachment>();
if (vol.getInactive()) {
detail.status = "deleted";
}
else {
if (vol.getExtensions() == null) {
vol.setExtensions(new StringMap());
}
if (vol.getProvisionedCapacity() == ZERO_BYTES) {
detail.status = CinderConstants.ComponentStatus.CREATING.getStatus().toLowerCase();
}
else if (vol.getExtensions().containsKey("status")
&& vol.getExtensions().get("status").equals(CinderConstants.ComponentStatus.DELETING.getStatus().toLowerCase())) {
Task taskObj = null;
String task = vol.getExtensions().get(DELETE_TASK_ID).toString();
taskObj = TaskUtils.findTaskForRequestId(_dbClient, vol.getId(), task);
if (taskObj != null) {
if (taskObj.getStatus().equals("error")) {
_log.info(String.format(
"Error Deleting volume %s, but moving volume to original state so that it will be usable: ", detail.name));
vol.getExtensions().put("status", CinderConstants.ComponentStatus.AVAILABLE.getStatus().toLowerCase());
vol.getExtensions().remove(DELETE_TASK_ID);
detail.status = CinderConstants.ComponentStatus.AVAILABLE.getStatus().toLowerCase();
}
else if (taskObj.getStatus().equals("pending")) {
detail.status = CinderConstants.ComponentStatus.DELETING.getStatus().toLowerCase();
}
_dbClient.updateObject(vol);
}
else {
detail.status = CinderConstants.ComponentStatus.AVAILABLE.getStatus().toLowerCase();
}
}
else if (vol.getExtensions().containsKey("status")
&& vol.getExtensions().get("status").equals(CinderConstants.ComponentStatus.EXTENDING.getStatus().toLowerCase())) {
_log.info("Extending Volume {}", vol.getId().toString());
Task taskObj = null;
String task = vol.getExtensions().get("task_id").toString();
taskObj = TaskUtils.findTaskForRequestId(_dbClient, vol.getId(), task);
_log.debug("THE TASKOBJ is {}, task_id {}", taskObj.toString(), task);
_log.debug("THE TASKOBJ STATUS is {}", taskObj.getStatus().toString());
if (taskObj != null) {
if (taskObj.getStatus().equals("ready")) {
detail.status = CinderConstants.ComponentStatus.AVAILABLE.getStatus().toLowerCase();
_log.debug(" STATUS is {}", detail.status);
vol.getExtensions().remove("task_id");
vol.getExtensions().put("status", "");
}
else if (taskObj.getStatus().equals("error")) {
detail.status = CinderConstants.ComponentStatus.AVAILABLE.getStatus().toLowerCase();
_log.info(String.format(
"Error in Extending volume %s, but moving volume to original state so that it will be usable: ",
detail.name));
vol.getExtensions().remove("task_id");
vol.getExtensions().put("status", "");
}
else {
detail.status = CinderConstants.ComponentStatus.EXTENDING.getStatus().toLowerCase();
_log.info("STATUS is {}", detail.status);
}
} else {
detail.status = CinderConstants.ComponentStatus.AVAILABLE.getStatus().toLowerCase();
_log.info(String.format(
"Error in Extending volume %s, but moving volume to original state so that it will be usable: ", detail.name));
vol.getExtensions().remove("task_id");
vol.getExtensions().put("status", "");
}
_dbClient.updateObject(vol);
}
else if (vol.getExtensions().containsKey("status") && !vol.getExtensions().get("status").equals("")) {
detail.status = vol.getExtensions().get("status").toString().toLowerCase();
}
else {
detail.status = CinderConstants.ComponentStatus.AVAILABLE.getStatus().toLowerCase();
}
}
detail.created_at = date(vol.getCreationTime().getTimeInMillis());
URI vpoolUri = vol.getVirtualPool();
VirtualPool vpool = _dbClient.queryObject(VirtualPool.class, vpoolUri);
if (vpool != null) {
detail.volume_type = vpool.getLabel();
}
URI varrayUri = vol.getVirtualArray();
VirtualArray varray = _dbClient.queryObject(VirtualArray.class, varrayUri);
if (varray != null) {
detail.availability_zone = varray.getLabel();
}
if (vol.getExtensions().containsKey("bootable") && vol.getExtensions().get("bootable").equals(TRUE)) {
detail.bootable = true;
_log.debug("Volumes Bootable Flag is TRUE");
}
else {
detail.bootable = false;
_log.debug("Volumes Bootable Flag is False");
}
detail.setLink(DbObjectMapper.toLink(vol));
detail.metadata = new HashMap<String, String>();
String description = null;
StringMap extensions = vol.getExtensions();
if (extensions != null) {
description = extensions.get("display_description");
}
if (isV1Call != null) {
detail.display_name = vol.getLabel();
detail.display_description = (description == null) ? "" : description;
detail.description = null;
detail.name = null;
}
else {
detail.name = vol.getLabel();
detail.description = (description == null) ? "" : description;
detail.display_name = null;
detail.display_description = null;
}
if (vol.getExtensions().containsKey("readonly") && vol.getExtensions().get("readonly").equals(TRUE)) {
_log.debug("Volumes Readonly Flag is TRUE");
detail.metadata.put("readonly", "true");
}
else {
_log.debug("Volumes Readonly Flag is FALSE");
detail.metadata.put("readonly", "false");
}
if (detail.status.equals("in-use")) {
if (vol.getExtensions() != null && vol.getExtensions().containsKey("OPENSTACK_NOVA_INSTANCE_ID")) {
detail.metadata.put("attached_mode", vol.getExtensions().get("OPENSTACK_ATTACH_MODE"));
detail.attachments = getVolumeAttachments(vol);
}
}
if (vol.getConsistencyGroup() != null) {
detail.consistencygroup_id = CinderApiUtils.splitString(vol.getConsistencyGroup().toString(), ":", 3);
}
return detail;
}
private boolean validateVolumeCreate(String openstackTenantId, VirtualPool pool, long requestedSize) {
QuotaOfCinder objQuota = null;
boolean isValidVolume = false;
if (pool == null)
objQuota = getQuotaHelper().getProjectQuota(openstackTenantId, getUserFromContext());
else
objQuota = getQuotaHelper().getVPoolQuota(openstackTenantId, pool, getUserFromContext());
if (objQuota == null) {
_log.info("Unable to retrive the Quota information");
return false;
}
Project proj = getCinderHelper().getProject(openstackTenantId, getUserFromContext());
long totalVolumesUsed = 0;
long totalSizeUsed = 0;
UsageStats stats = null;
if (pool != null)
stats = getQuotaHelper().getStorageStats(pool.getId(), proj.getId());
else
stats = getQuotaHelper().getStorageStats(null, proj.getId());
totalVolumesUsed = stats.volumes;
totalSizeUsed = stats.spaceUsed;
_log.info(String.format("VolumesLimit():%s ,TotalQuota:%s , TotalSizeUsed:%s, TotalVolumesUsed:%s, RequestedConsumption:%s",
objQuota.getVolumesLimit(), objQuota.getTotalQuota(), totalSizeUsed, totalVolumesUsed,
(totalSizeUsed + requestedSize / GB)));
if ((objQuota.getVolumesLimit() != QuotaService.DEFAULT_VOLUME_TYPE_VOLUMES_QUOTA)
&& (objQuota.getVolumesLimit() <= totalVolumesUsed))
{
return isValidVolume;
}
else if ((objQuota.getTotalQuota() != QuotaService.DEFAULT_VOLUME_TYPE_TOTALGB_QUOTA)
&& (objQuota.getTotalQuota() <= (totalSizeUsed + requestedSize / GB)))
{
return isValidVolume;
}
else
{
isValidVolume = true;
return isValidVolume;
}
}
protected ExportGroup findExportGroup(Volume vol) {
// Get export group for the volume
SearchedResRepList resRepList = new SearchedResRepList(ResourceTypeEnum.EXPORT_GROUP);
_dbClient.queryByConstraint(ContainmentConstraint.Factory.getVolumeExportGroupConstraint(vol.getId()),
resRepList);
if (resRepList.iterator() != null) {
for (SearchResultResourceRep res : resRepList) {
ExportGroup group = _dbClient.queryObject(ExportGroup.class, res.getId());
if ((group != null) && !(group.getInactive())) {
return group;
}
}
}
return null; // if not found
}
private List<Attachment> getVolumeAttachments(Volume vol) {
List<Attachment> attachments = new ArrayList<Attachment>();
Attachment attachment = new Attachment();
attachment.id = getCinderHelper().trimId(vol.getId().toString());
attachment.volume_id = attachment.id;
attachment.server_id = vol.getExtensions().get("OPENSTACK_NOVA_INSTANCE_ID");
attachment.id = getCinderHelper().trimId(vol.getId().toString());
attachment.volume_id = getCinderHelper().trimId(vol.getId().toString());
attachment.device = vol.getExtensions().get("OPENSTACK_NOVA_INSTANCE_MOUNTPOINT");
attachments.add(attachment);
return attachments;
}
private URIQueryResultList getVolumeUris(String openstackTenantId) {
URIQueryResultList uris = new URIQueryResultList();
Project project = getCinderHelper().getProject(openstackTenantId, getUserFromContext());
if (project == null) // return empty list
return null;
_dbClient.queryByConstraint(
ContainmentConstraint.Factory.getProjectVolumeConstraint(project.getId()),
uris);
return uris;
}
/* Get vpool from the given label */
private VirtualPool getVpool(String vpoolName) {
if ((vpoolName == null) || (vpoolName.length() == 0))
return null;
URIQueryResultList uris = new URIQueryResultList();
_dbClient.queryByConstraint(
PrefixConstraint.Factory.getLabelPrefixConstraint(
VirtualPool.class, vpoolName),
uris);
for (URI vpoolUri : uris) {
VirtualPool vpool = _dbClient.queryObject(VirtualPool.class, vpoolUri);
if (vpool != null && vpool.getType().equals(VirtualPool.Type.block.name()))
return vpool;
}
return null; // no matching vpool found
}
/**
* Verify the user is authorized for a volume creation request.
*
* @param project The reference to the Project.
* @param vpool The reference to the Virtual Pool.
* @param varray The reference to the Virtual Array.
*
* @throws APIException when the user is not authorized.
*/
private void verifyUserIsAuthorizedForRequest(Project project, VirtualPool vpool, VirtualArray varray) {
StorageOSUser user = getUserFromContext();
if (!(_permissionsHelper.userHasGivenRole(user, project.getTenantOrg().getURI(),
Role.TENANT_ADMIN) || _permissionsHelper.userHasGivenACL(user,
project.getId(), ACL.OWN, ACL.ALL))) {
throw APIException.forbidden.insufficientPermissionsForUser(user.getName());
}
URI vipr_tenantId = URI.create(user.getTenantId());
_permissionsHelper.checkTenantHasAccessToVirtualPool(vipr_tenantId, vpool);
_permissionsHelper.checkTenantHasAccessToVirtualArray(vipr_tenantId, varray);
}
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());
}
}
protected Volume findVolume(String volume_id, String openstackTenantId) {
Volume vol = (Volume) getCinderHelper().queryByTag(URI.create(volume_id), getUserFromContext(), Volume.class);
Project project = getCinderHelper().getProject(openstackTenantId, getUserFromContext());
if (project == null) {
throw APIException.badRequests.projectWithTagNonexistent(openstackTenantId);
}
if (vol != null) {
if ((project != null) &&
(vol.getProject().getURI().toString().equalsIgnoreCase(project.getId().toString()))) {
// volume is part of the project
return vol;
}
}
return null;
}
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) {
Volume volume = (Volume) queryResource(id);
return volume.getTenant().getURI();
}
/**
* Volume is not a zone level resource
*/
@Override
protected boolean isZoneLevelResource() {
return false;
}
@Override
protected ResourceTypeEnum getResourceType() {
return ResourceTypeEnum.VOLUME;
}
@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(Volume.class, id);
}
// will be implemented
protected TaskList volumeFromImage(String name, Project project, VirtualArray varray,
VolumeCreateRequestGen param, int volumeCount, BlockFullCopyManager blkFullCpManager, String imageId)
{
return null;
}
protected TaskList volumeFromSnapshot(String name, Project project, String snapshotId,
VirtualArray varray, VolumeCreateRequestGen param, int volumeCount,
BlockFullCopyManager blkFullCpManager, URI snapUri, BlockSnapshot sourceSnapshot)
{
if (sourceSnapshot != null) {
// Don't operate on VPLEX backend or RP Journal volumes.
_log.debug("Volume from Snapshot is not supported on VPLEX backend or RP Journal volumes. snapUri = {}", snapUri);
BlockServiceUtils.validateNotAnInternalBlockObject(sourceSnapshot, false);
// verify if full copy is supported on the snapshot
verifyFullCopySupportedOnSnapshot(sourceSnapshot);
if (sourceSnapshot.getProtectionSet() != null) {
throw APIException.badRequests.protectedVolumesNotSupported();
}
String volname = null;
if (param.volume.name != null)
volname = param.volume.name;
else
volname = param.volume.display_name;
auditOp(OperationTypeEnum.CREATE_BLOCK_VOLUME, true, AuditLogManager.AUDITOP_BEGIN,
volname, volumeCount, varray.getId().toString(), project.getId().toString(),
snapshotId, VOLUME_FROM_SNAPSHOT);
// Setting createInactive to true for openstack as it is not needed to
// wait for synchronization to complete and detach.
_log.debug("Block Service API call for : Create Volume from Snapshot ");
Boolean createInactive = true; // from Blocksnapshot service check
VolumeFullCopyCreateParam fullCopyParam = new VolumeFullCopyCreateParam(ProtectionType.full_copy.toString(), name,
SNAP_COUNT, createInactive);
return blkFullCpManager.createFullCopy(snapUri, fullCopyParam);
} else {
return null;
}
}
protected TaskList volumeClone(String volName, Project project, String sourceVolId,
VirtualArray varray, int volumeCount,
Volume sourceVolume, BlockFullCopyManager blkFullCpManager)
{
Volume vol = (Volume) getCinderHelper().queryByTag(URI.create(sourceVolId), getUserFromContext(), Volume.class);
URI volumeUri = vol.getId();
validateSourceVolumeHasExported(sourceVolume);
Boolean createInactive = true;
VolumeFullCopyCreateParam fullCopyParam = new VolumeFullCopyCreateParam(ProtectionType.full_copy.toString(), volName,
CLONE_COUNT, createInactive);
validateRequestedFullCopyCount(fullCopyParam, sourceVolume);
if (sourceVolume.getProtectionSet() != null)
throw APIException.badRequests.protectedVolumesNotSupported();
auditOp(OperationTypeEnum.ACTIVATE_VOLUME_FULL_COPY, true, AuditLogManager.AUDITOP_BEGIN,
volName, volumeCount, varray.getId().toString(), project.getId().toString(),
sourceVolId, VOLUME_FROM_CLONE);
_log.debug("Block Service API call for : Create Clone Volume ");
return blkFullCpManager.createFullCopy(volumeUri, fullCopyParam);
}
protected TaskList newVolume(VolumeCreate volumeCreate, Project project, BlockServiceApi api,
VirtualPoolCapabilityValuesWrapper capabilities,
VirtualArray varray, String task, VirtualPool vpool,
VolumeCreateRequestGen param, int volumeCount, long requestedSize, String name)
{
List recommendations = _placementManager.getRecommendationsForVolumeCreateRequest(
varray, project, vpool, capabilities);
Map<VpoolUse, List<Recommendation>> recommendationsMap = new HashMap<VpoolUse, List<Recommendation>>();
recommendationsMap.put(VpoolUse.ROOT, recommendations);
if (recommendations.isEmpty()) {
throw APIException.badRequests.noMatchingStoragePoolsForVpoolAndVarray(vpool.getLabel(), varray.getLabel());
}
String volname = null;
if (param.volume.name != null)
volname = param.volume.name;
else
volname = param.volume.display_name;
auditOp(OperationTypeEnum.CREATE_BLOCK_VOLUME, true, AuditLogManager.AUDITOP_BEGIN,
volname, volumeCount, varray.getId().toString(), project.getId().toString());
_log.debug("Block Service API call for : Create New Volume ");
TaskList passedTaskist = createTaskList(requestedSize, project, varray, vpool, name, task, volumeCount);
return api.createVolumes(volumeCreate, project, varray, vpool, recommendationsMap, passedTaskist, task,
capabilities);
}
/**
* Verify that the snapshot is not on vmax and hds, and not in a consistency group
* and the array has full copy enabled
*
* @param snapshot
*/
private void verifyFullCopySupportedOnSnapshot(BlockSnapshot snapshot) {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class,
snapshot.getStorageController());
_log.debug("For Vmax/HDS does not support full copy on snapshots");
if ((storageSystem != null)) {
_log.debug("Storage System Type ={}", storageSystem.getSystemType());
// vmax/HDS does not support full copy on snapshots
if ((DiscoveredDataObject.Type.vmax.name().equals(storageSystem
.getSystemType())) || (DiscoveredDataObject.Type.hds.name().equals(storageSystem
.getSystemType()))) {
throw APIException.badRequests.fullCopyNotSupportedFromSnapshot(storageSystem
.getSystemType(), snapshot.getId());
}
}
// snapshot in a consistencyGroup is not supported for full copy operation
URI cgUri = snapshot.getConsistencyGroup();
if (!NullColumnValueGetter.isNullURI(cgUri)) {
_log.debug("Snapshot in a consistencyGroup is not supported for full copy operation ");
throw APIException.badRequests.fullCopyNotSupportedForConsistencyGroup();
}
}
private TaskList createTaskList(long size, Project project, VirtualArray varray, VirtualPool vpool, String label, String task,
Integer volumeCount) {
TaskList taskList = new TaskList();
// For each volume requested, pre-create a volume object/task object
// long lsize = SizeUtil.translateSize(size);
for (int i = 0; i < volumeCount; i++) {
Volume volume = StorageScheduler.prepareEmptyVolume(_dbClient, size, project, varray, vpool, label, i, volumeCount);
Operation op = _dbClient.createTaskOpStatus(Volume.class, volume.getId(),
task, ResourceOperationTypeEnum.CREATE_BLOCK_VOLUME);
volume.getOpStatus().put(task, op);
TaskResourceRep volumeTask = toTask(volume, task, op);
taskList.getTaskList().add(volumeTask);
_log.info(String.format("Volume and Task Pre-creation Objects [Init]-- Source Volume: %s, Task: %s, Op: %s",
volume.getId(), volumeTask.getId(), task));
}
return taskList;
}
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);
}
}
}
public static void checkOperationSupportedOnIngestedVolume(Volume volume,
ResourceOperationTypeEnum operation, DbClient dbClient) {
if (volume.isIngestedVolumeWithoutBackend(dbClient)) {
switch (operation) {
case CREATE_VOLUME_FULL_COPY:
case CREATE_VOLUME_SNAPSHOT:
case EXPAND_BLOCK_VOLUME:
case CREATE_VOLUME_MIRROR:
case CHANGE_BLOCK_VOLUME_VARRAY:
case UPDATE_CONSISTENCY_GROUP:
_log.error("Operation {} is not permitted on ingested volumes.", operation.getName());
throw APIException.badRequests.operationNotPermittedOnIngestedVolume(
operation.getName(), volume.getLabel());
default:
return;
}
}
}
public static void validateNotAnInternalBlockObject(BlockObject blockObject,
boolean force) {
if (blockObject != null) {
if (blockObject.checkInternalFlags(Flag.INTERNAL_OBJECT)
&& !blockObject.checkInternalFlags(Flag.SUPPORTS_FORCE)) {
throw APIException.badRequests.notSupportedForInternalVolumes();
}
else if (blockObject.checkInternalFlags(Flag.INTERNAL_OBJECT)
&& blockObject.checkInternalFlags(Flag.SUPPORTS_FORCE)
&& !force) {
throw APIException.badRequests.notSupportedForInternalVolumes();
}
}
}
private void validateRequestedFullCopyCount(VolumeFullCopyCreateParam param, Volume source) {
StorageSystem system = _dbClient.queryObject(StorageSystem.class, source.getStorageController());
if (system != null && system.getSystemType().equalsIgnoreCase(StorageSystem.Type.vmax.toString())) {
if (param.getCount() > MAX_VMAX_COPY_SESSIONS) {
throw APIException.badRequests.maxFullCopySessionLimitExceeded(source.getId(), MAX_VMAX_COPY_SESSIONS);
}
}
}
/**
* Method to support consistency group for a volume
*
* @param vpool
* @param consistencyGroup
* @param project
* @param blockServiceImpl
* @param varray
* @param capabilities
* @param blkFullCpManager
*/
public void checkForConsistencyGroup(VirtualPool vpool,
BlockConsistencyGroup consistencyGroup, Project project,
BlockServiceApi blockServiceImpl, VirtualArray varray, VirtualPoolCapabilityValuesWrapper capabilities,
BlockFullCopyManager blkFullCpManager) {
Integer volumeCount = 1;
final Boolean isMultiVolumeConsistencyOn = vpool.getMultivolumeConsistency() == null ? Boolean.FALSE
: vpool.getMultivolumeConsistency();
_log.info("*********MultiVolumeConsistencyOn************** : " + isMultiVolumeConsistencyOn);
/*
* Validate Consistency Group:
* 1. CG should be active in the database
* 2. CG project and Volume project should match
* 3. The storage system that the CG is bonded to is associated to the
* request virtual array
*/
ArrayList<String> requestedTypes = new ArrayList<String>();
final URI actualId = project.getId();
if (consistencyGroup != null) {
// Check that the Volume project and the CG project are the same
final URI expectedId = consistencyGroup.getProject().getURI();
final boolean condition = actualId.equals(expectedId);
if (!condition) {
throw APIException.badRequests.invalidProjectConflict(expectedId);
}
// If the Consistency Group was provided, MultiVolumeConsistency
// attribute should be true
if (!isMultiVolumeConsistencyOn) {
throw APIException.badRequests.invalidParameterConsistencyGroupProvidedButVirtualPoolHasNoMultiVolumeConsistency(
consistencyGroup.getId(), URI.create(vpool.toString()));
}
// Find all volumes assigned to the group
final List<Volume> activeCGVolumes = blockServiceImpl.getActiveCGVolumes(consistencyGroup);
// Validate that the number of volumes in the group plus the number
// to be added by this request does not exceed the maximum volumes
// in a CG.
int cgMaxVolCount = blockServiceImpl.getMaxVolumesForConsistencyGroup(consistencyGroup);
if ((activeCGVolumes.size() + volumeCount.intValue()) > cgMaxVolCount) {
throw APIException.badRequests.requestedVolumeCountExceedsLimitsForCG(
volumeCount.intValue(), cgMaxVolCount, consistencyGroup.getLabel());
}
if (VirtualPool.vPoolSpecifiesProtection(vpool)) {
requestedTypes.add(Types.RP.name());
}
// Note that for ingested VPLEX CGs or CGs created in releases
// prior to 2.2, there will be no corresponding native
// consistency group. We don't necessarily want to fail
// volume creations in these CGs, so we don't require the
// LOCAL type.
if (VirtualPool.vPoolSpecifiesHighAvailability(vpool)) {
requestedTypes.add(Types.VPLEX.name());
}
if (VirtualPool.vPoolSpecifiesSRDF(vpool)) {
requestedTypes.add(Types.SRDF.name());
}
if (!VirtualPool.vPoolSpecifiesProtection(vpool)
&& !VirtualPool.vPoolSpecifiesHighAvailability(vpool)
&& !VirtualPool.vPoolSpecifiesSRDF(vpool)
&& vpool.getMultivolumeConsistency()) {
requestedTypes.add(Types.LOCAL.name());
}
// If the consistency group is not yet created, verify the name is OK.
if (!consistencyGroup.created()) {
blockServiceImpl.validateConsistencyGroupName(consistencyGroup, requestedTypes);
}
// Consistency Group is already a Target, hence cannot be used to create source volume
if (consistencyGroup.srdfTarget()) {
throw APIException.badRequests.consistencyGroupBelongsToTarget(consistencyGroup.getId());
}
if (VirtualPool.vPoolSpecifiesSRDF(vpool)
&& (consistencyGroup.getLabel().length() > 8 || !isAlphaNumeric(consistencyGroup.getLabel()))) {
throw APIException.badRequests.groupNameCannotExceedEightCharactersoronlyAlphaNumericAllowed();
}
if (!VirtualPool.vPoolSpecifiesSRDF(vpool) && consistencyGroup.checkForType(Types.SRDF)) {
throw APIException.badRequests.nonSRDFVolumeCannotbeAddedToSRDFCG();
}
// check if CG's storage system is associated to the requested virtual array
validateCGValidWithVirtualArray(consistencyGroup, varray);
// Validate the CG type. We want to make sure the volume create request is appropriate
// the CG's previously requested types.
if (consistencyGroup.creationInitiated()) {
if (!consistencyGroup.getRequestedTypes().containsAll(requestedTypes)) {
throw APIException.badRequests.consistencyGroupIsNotCompatibleWithRequest(
consistencyGroup.getId(), consistencyGroup.getTypes().toString(), requestedTypes.toString());
}
}
// RP consistency group validation
if (VirtualPool.vPoolSpecifiesProtection(vpool)) {
// If an RP protected vpool is specified, ensure that the CG selected is empty or contains only RP volumes.
if (activeCGVolumes != null && !activeCGVolumes.isEmpty()
&& !consistencyGroup.getTypes().contains(BlockConsistencyGroup.Types.RP.toString())) {
throw APIException.badRequests.consistencyGroupMustBeEmptyOrContainRpVolumes(consistencyGroup.getId());
}
if (!activeCGVolumes.isEmpty()) {
// Find the first existing source volume for source/target varray comparison.
Volume existingSourceVolume = null;
for (Volume cgVolume : activeCGVolumes) {
if (cgVolume.getPersonality() != null &&
cgVolume.getPersonality().equals(Volume.PersonalityTypes.SOURCE.toString())) {
existingSourceVolume = cgVolume;
break;
}
}
if (existingSourceVolume != null) {
VirtualPool existingVpool = _dbClient.queryObject(
VirtualPool.class, existingSourceVolume.getVirtualPool());
VirtualPool requestedVpool = _dbClient.queryObject(
VirtualPool.class, URI.create(vpool.toString()));
// The source virtual arrays must much
if (existingVpool.getVirtualArrays().size() != requestedVpool.getVirtualArrays().size()
|| !existingVpool.getVirtualArrays().containsAll(requestedVpool.getVirtualArrays())) {
// The source virtual arrays are not compatible with the CG
throw APIException.badRequests.vPoolSourceVarraysNotCompatibleForCG(consistencyGroup.getLabel());
}
// Validate that the request is not attempting to mix VPlex Metro volumes and
// MetroPoint volumes in the same CG.
if (VirtualPool.vPoolSpecifiesHighAvailability(existingVpool) &&
VirtualPool.vPoolSpecifiesHighAvailability(requestedVpool)) {
// If the requested and CG assigned virtual pools both specify VPlex, ensure
// that we are not trying to mix MetroPoint volumes with Metro volumes.
if ((!VirtualPool.vPoolSpecifiesMetroPoint(requestedVpool) &&
VirtualPool.vPoolSpecifiesMetroPoint(existingVpool)) ||
(VirtualPool.vPoolSpecifiesMetroPoint(requestedVpool) &&
!VirtualPool.vPoolSpecifiesMetroPoint(existingVpool))) {
throw APIException.badRequests.cannotMixMetroPointAndNonMetroPointVolumes(consistencyGroup.getLabel());
}
}
// Check the target virtual arrays
StringMap existingProtectionVarraySettings = existingVpool.getProtectionVarraySettings();
if (existingProtectionVarraySettings == null) {
// The existing CG source volume's protection settings are null. This can only happen when a swap
// is performed on the CG. This would have been a former target volume so its virtual pool
// references a target virtual pool, which has no protection settings defined. We do not support
// adding a volume to an existing CG whose volumes have been swapped.
// NOTE: This will be supported in the future through Jira CTRL-10129
throw APIException.badRequests.cannotAddVolumesToSwappedCG(consistencyGroup.getLabel());
}
StringMap requestedProtectionVarraySettings = requestedVpool.getProtectionVarraySettings();
if (existingProtectionVarraySettings.size() != requestedProtectionVarraySettings.size()) {
// The target virtual arrays are not compatible with the CG
throw APIException.badRequests.vPoolTargetVarraysNotCompatibleForCG(consistencyGroup.getLabel());
}
for (String targetVarray : requestedProtectionVarraySettings.keySet()) {
if (!existingProtectionVarraySettings.containsKey(targetVarray)) {
// The target virtual arrays are not compatible with the CG
throw APIException.badRequests.vPoolTargetVarraysNotCompatibleForCG(consistencyGroup.getLabel());
}
}
}
}
}
checkCGForSnapshots(consistencyGroup);
// Creating new volumes in a consistency group is
// not supported when the consistency group has
// volumes with full copies to which they are still
// attached or has volumes that are full copies that
// are still attached to their source volumes.
blkFullCpManager.verifyNewVolumesCanBeCreatedInConsistencyGroup(consistencyGroup,
activeCGVolumes);
capabilities.put(VirtualPoolCapabilityValuesWrapper.BLOCK_CONSISTENCY_GROUP,
consistencyGroup.getId());
if (consistencyGroup != null) {
consistencyGroup.addRequestedTypes(requestedTypes);
_dbClient.updateAndReindexObject(consistencyGroup);
}
} else if (VirtualPool.vPoolSpecifiesProtection(vpool)) {
// The consistency group param is null and RP protection has been specified. When RP
// protection is specified, a consistency group must be selected.
throw APIException.badRequests.consistencyGroupMissingForRpProtection();
}
}
/*
* Validate if the physical array that the consistency group bonded to is associated
* with the virtual array
*
* @param consistencyGroup
*
* @param varray virtual array
*/
private void validateCGValidWithVirtualArray(BlockConsistencyGroup consistencyGroup,
VirtualArray varray) {
URI storageSystemUri = consistencyGroup.getStorageController();
if (NullColumnValueGetter.isNullURI(storageSystemUri)) {
return;
}
URIQueryResultList storagePortURIs = new URIQueryResultList();
URIQueryResultList assignedStoragePortURIs = new URIQueryResultList();
boolean isAssociated = false;
String systemString = storageSystemUri.toString();
// get all assigned storage ports
_dbClient.queryByConstraint(AlternateIdConstraint.Factory
.getVirtualArrayStoragePortsConstraint(varray.getId().toString()),
assignedStoragePortURIs);
isAssociated = anyPortsMatchStorageDevice(assignedStoragePortURIs, systemString);
if (!isAssociated) {
// get all network storage ports in the virtual array
_dbClient.queryByConstraint(AlternateIdConstraint.Factory
.getImplicitVirtualArrayStoragePortsConstraint(varray.getId().toString()),
storagePortURIs);
isAssociated = anyPortsMatchStorageDevice(storagePortURIs, systemString);
}
if (!isAssociated) {
throw APIException.badRequests.invalidParameterConsistencyGroupStorageSystemMismatchVirtualArray(
consistencyGroup.getId(), varray.getId());
}
}
/**
* Check if any port in the list is from the storage system
*
* @param ports
* @param systemUri
* @return
*/
private boolean anyPortsMatchStorageDevice(URIQueryResultList ports, String systemUri) {
boolean isMatched = false;
for (URI spUri : ports) {
StoragePort storagePort = _dbClient.queryObject(StoragePort.class, spUri);
if (storagePort != null) {
URI system = storagePort.getStorageDevice();
if (system != null && system.toString().equals(systemUri)) {
isMatched = true;
break;
}
}
}
return isMatched;
}
// Validate consistency group name
private boolean isAlphaNumeric(String consistencyGroupName) {
String pattern = "^[a-zA-Z0-9]*$";
if (consistencyGroupName.matches(pattern)) {
return true;
}
return false;
}
/**
* Checks existing CG for non-RP snapshots. If non-RP snapshots exist,
* we cannot create/add a volume to the CG.
*
* @param consistencyGroup the consistency group to validate.
*/
private void checkCGForSnapshots(BlockConsistencyGroup consistencyGroup) {
// If the Consistency Group has Snapshot(s), then Volume can not be created.
final URIQueryResultList cgSnapshotsResults = new URIQueryResultList();
_dbClient.queryByConstraint(getBlockSnapshotByConsistencyGroup(consistencyGroup.getId()),
cgSnapshotsResults);
Iterator<BlockSnapshot> blockSnapshotIterator = _dbClient.queryIterativeObjects(BlockSnapshot.class, cgSnapshotsResults);
while (blockSnapshotIterator.hasNext()) {
BlockSnapshot next = blockSnapshotIterator.next();
// RP BlockSnapshots do not prevent other volumes from being created/added to the
// consistency group.
if (!next.getTechnologyType().equalsIgnoreCase(TechnologyType.RP.name())) {
throw APIException.badRequests.cannotCreateVolumeAsConsistencyGroupHasSnapshots(consistencyGroup.getLabel(),
consistencyGroup.getId());
}
}
}
/**
* Returns the bean responsible for servicing the request
*
* @param vpool Virtual Pool
* @return block service implementation object
*/
private static BlockServiceApi getBlockServiceImpl(VirtualPool vpool, DbClient dbClient) {
// Mutually exclusive logic that selects an implementation of the block service
if (VirtualPool.vPoolSpecifiesProtection(vpool)) {
return 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());
} else if (VirtualPool.vPoolSpecifiesMirrors(vpool, dbClient)) {
return BlockService.getBlockServiceImpl("mirror");
} else if (vpool.getMultivolumeConsistency() != null && vpool.getMultivolumeConsistency()) {
return BlockService.getBlockServiceImpl("group");
}
return BlockService.getBlockServiceImpl("default");
}
}