/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.storageos.api.service.impl.resource.fullcopy; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.api.mapper.BlockMapper; import com.emc.storageos.api.mapper.DbObjectMapper; import com.emc.storageos.api.mapper.TaskMapper; import com.emc.storageos.api.service.authorization.PermissionsHelper; import com.emc.storageos.api.service.impl.placement.PlacementManager; import com.emc.storageos.api.service.impl.placement.Scheduler; 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.ResourceService; import com.emc.storageos.api.service.impl.resource.TenantsService; 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.VolumeIngestionUtil; import com.emc.storageos.blockorchestrationcontroller.VolumeDescriptor; import com.emc.storageos.coordinator.client.service.CoordinatorClient; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.URIUtil; import com.emc.storageos.db.client.model.BlockConsistencyGroup; import com.emc.storageos.db.client.model.BlockObject; import com.emc.storageos.db.client.model.BlockSnapshot; import com.emc.storageos.db.client.model.DataObject.Flag; 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.StorageSystem; import com.emc.storageos.db.client.model.StringSet; import com.emc.storageos.db.client.model.TenantOrg; import com.emc.storageos.db.client.model.VirtualArray; import com.emc.storageos.db.client.model.VirtualPool; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.db.client.model.VolumeGroup; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.hds.HDSConstants; import com.emc.storageos.model.ResourceOperationTypeEnum; import com.emc.storageos.model.TaskList; import com.emc.storageos.model.TaskResourceRep; import com.emc.storageos.model.block.NamedVolumesList; import com.emc.storageos.model.block.VolumeFullCopyCreateParam; import com.emc.storageos.model.block.VolumeRestRep; import com.emc.storageos.protectioncontroller.impl.recoverpoint.RPHelper; import com.emc.storageos.security.audit.AuditLogManager; import com.emc.storageos.security.authentication.InterNodeHMACAuthFilter; import com.emc.storageos.security.authentication.StorageOSUser; import com.emc.storageos.services.OperationTypeEnum; import com.emc.storageos.services.util.TimeUtils; import com.emc.storageos.svcs.errorhandling.resources.APIException; import com.emc.storageos.svcs.errorhandling.resources.InternalException; import com.emc.storageos.util.VPlexUtil; import com.emc.storageos.volumecontroller.impl.ControllerUtils; /** * Class that manages all aspects of full copies, also known as clones, for * block volumes. */ public class BlockFullCopyManager { // Enumeration specifying the valid keys for the full copy implementations map. private enum FullCopyImpl { dflt, vmax, vmax3, vnx, vnxe, hds, openstack, scaleio, xtremio, xiv, rp, vplex, ceph } private static final int VMAX_MAX_FULLCOPY_COUNT = 8; // Applies for VMAX3 also private static final int VNX_MAX_FULLCOPY_COUNT = 8; private static final int SCALEIO_MAX_FULLCOPY_COUNT = 31; private static final int XIV_MAX_FULLCOPY_COUNT = Integer.MAX_VALUE; // No known limit private static final int OPENSTACK_MAX_FULLCOPY_COUNT = Integer.MAX_VALUE; // No known limit private static final int CEPH_MAX_FULLCOPY_COUNT = Integer.MAX_VALUE; // No known limit // Map of the values for maximum active full copy sessions for each block storage platform. public static Map<String, Integer> s_maxFullCopyMap = new HashMap<String, Integer>(); static { s_maxFullCopyMap.put(DiscoveredDataObject.Type.vmax.name(), VMAX_MAX_FULLCOPY_COUNT); s_maxFullCopyMap.put(DiscoveredDataObject.Type.vnxblock.name(), VNX_MAX_FULLCOPY_COUNT); s_maxFullCopyMap.put(DiscoveredDataObject.Type.vnxe.name(), 0); // not supported s_maxFullCopyMap.put(DiscoveredDataObject.Type.hds.name(), HDSConstants.MAX_SHADOWIMAGE_PAIR_COUNT); s_maxFullCopyMap.put(DiscoveredDataObject.Type.openstack.name(), OPENSTACK_MAX_FULLCOPY_COUNT); s_maxFullCopyMap.put(DiscoveredDataObject.Type.scaleio.name(), SCALEIO_MAX_FULLCOPY_COUNT); s_maxFullCopyMap.put(DiscoveredDataObject.Type.xtremio.name(), 0); // not supported s_maxFullCopyMap.put(DiscoveredDataObject.Type.ibmxiv.name(), XIV_MAX_FULLCOPY_COUNT); s_maxFullCopyMap.put(DiscoveredDataObject.Type.ceph.name(), CEPH_MAX_FULLCOPY_COUNT); } // A reference to a database client. private final DbClient _dbClient; // A reference to a permissions helper. private PermissionsHelper _permissionsHelper = null; // A reference to the audit log manager. private AuditLogManager _auditLogManager = null; // A reference to the placement manager private PlacementManager _placementManager = null; // A reference to the full copy request. protected HttpServletRequest _request; // A reference to the security context private final SecurityContext _securityContext; // A reference to the URI information. private final UriInfo _uriInfo; // The supported block full copy API implementations private final Map<String, BlockFullCopyApi> _fullCopyImpls = new HashMap<String, BlockFullCopyApi>(); // A reference to a logger. private static final Logger s_logger = LoggerFactory.getLogger(BlockFullCopyManager.class); /** * Constructor * * @param dbClient A reference to a database client. * @param permissionsHelper A reference to a permission helper. * @param auditLogManager A reference to an audit log manager. * @param coordinator A reference to the coordinator. * @param placementManager A reference to the placement manager. * @param securityContext A reference to the security context. * @param uriInfo A reference to the URI info. * @param request A reference to the full copy request. * @param tenantsService A reference to the tenants service or null. */ public BlockFullCopyManager(DbClient dbClient, PermissionsHelper permissionsHelper, AuditLogManager auditLogManager, CoordinatorClient coordinator, PlacementManager placementManager, SecurityContext securityContext, UriInfo uriInfo, HttpServletRequest request, TenantsService tenantsService) { _dbClient = dbClient; _permissionsHelper = permissionsHelper; _auditLogManager = auditLogManager; _placementManager = placementManager; _securityContext = securityContext; _uriInfo = uriInfo; _request = request; // Create full copy implementations. createPlatformSpecificFullCopyImpls(coordinator, tenantsService); } /** * Create all platform specific full copy implementations. * * @param coordinator A reference to the coordinator. * @param tenantsService A reference to the tenants service or null. */ private void createPlatformSpecificFullCopyImpls(CoordinatorClient coordinator, TenantsService tenantsService) { Scheduler blockScheduler = _placementManager.getStorageScheduler("block"); Scheduler vplexScheduler = _placementManager.getStorageScheduler("vplex"); Scheduler rpScheduler = _placementManager.getStorageScheduler("rp"); _fullCopyImpls.put(FullCopyImpl.dflt.name(), new DefaultBlockFullCopyApiImpl(_dbClient, coordinator, blockScheduler, this)); _fullCopyImpls.put(FullCopyImpl.vmax.name(), new VMAXBlockFullCopyApiImpl(_dbClient, coordinator, blockScheduler, this)); _fullCopyImpls.put(FullCopyImpl.vmax3.name(), new VMAX3BlockFullCopyApiImpl(_dbClient, coordinator, blockScheduler, this)); _fullCopyImpls.put(FullCopyImpl.vnx.name(), new VNXBlockFullCopyApiImpl(_dbClient, coordinator, blockScheduler, this)); _fullCopyImpls.put(FullCopyImpl.vnxe.name(), new VNXEBlockFullCopyApiImpl(_dbClient, coordinator, blockScheduler, this)); _fullCopyImpls.put(FullCopyImpl.hds.name(), new HDSBlockFullCopyApiImpl(_dbClient, coordinator, blockScheduler, this)); _fullCopyImpls.put(FullCopyImpl.openstack.name(), new OpenstackBlockFullCopyApiImpl(_dbClient, coordinator, blockScheduler, this)); _fullCopyImpls.put(FullCopyImpl.scaleio.name(), new ScaleIOBlockFullCopyApiImpl(_dbClient, coordinator, blockScheduler, this)); _fullCopyImpls.put(FullCopyImpl.xtremio.name(), new XtremIOBlockFullCopyApiImpl(_dbClient, coordinator, blockScheduler, this)); _fullCopyImpls.put(FullCopyImpl.xiv.name(), new XIVBlockFullCopyApiImpl(_dbClient, coordinator, blockScheduler, this)); _fullCopyImpls.put(FullCopyImpl.ceph.name(), new CephBlockFullCopyApiImpl(_dbClient, coordinator, blockScheduler, this)); _fullCopyImpls.put(FullCopyImpl.vplex.name(), new VPlexBlockFullCopyApiImpl(_dbClient, coordinator, vplexScheduler, tenantsService, this)); _fullCopyImpls.put(FullCopyImpl.rp.name(), new RPBlockFullCopyApiImpl(_dbClient, coordinator, rpScheduler, this)); } /** * Determines and returns the platform specific full copy implementation * for the passed system. * * @param system A reference to storage system * * @return The platform specific full copy implementation */ public BlockFullCopyApi getPlatformSpecificFullCopyImplForSystem(StorageSystem system) { BlockFullCopyApi fullCopyApi = null; String systemType = system.getSystemType(); if (DiscoveredDataObject.Type.vmax.name().equals(systemType)) { fullCopyApi = _fullCopyImpls.get(FullCopyImpl.vmax.name()); if (system.checkIfVmax3()) { fullCopyApi = _fullCopyImpls.get(FullCopyImpl.vmax3.name()); } } else if (DiscoveredDataObject.Type.vnxblock.name().equals(systemType)) { fullCopyApi = _fullCopyImpls.get(FullCopyImpl.vnx.name()); } else if (DiscoveredDataObject.Type.vnxe.name().equals(systemType)) { fullCopyApi = _fullCopyImpls.get(FullCopyImpl.vnxe.name()); } else if (DiscoveredDataObject.Type.hds.name().equals(systemType)) { fullCopyApi = _fullCopyImpls.get(FullCopyImpl.hds.name()); } else if (DiscoveredDataObject.Type.openstack.name().equals(systemType)) { fullCopyApi = _fullCopyImpls.get(FullCopyImpl.openstack.name()); } else if (DiscoveredDataObject.Type.scaleio.name().equals(systemType)) { fullCopyApi = _fullCopyImpls.get(FullCopyImpl.scaleio.name()); } else if (DiscoveredDataObject.Type.xtremio.name().equals(systemType)) { fullCopyApi = _fullCopyImpls.get(FullCopyImpl.xtremio.name()); } else if (DiscoveredDataObject.Type.ibmxiv.name().equals(systemType)) { fullCopyApi = _fullCopyImpls.get(FullCopyImpl.xiv.name()); } else if (DiscoveredDataObject.Type.ceph.name().equals(systemType)) { fullCopyApi = _fullCopyImpls.get(FullCopyImpl.ceph.name()); } else { fullCopyApi = _fullCopyImpls.get(FullCopyImpl.dflt.name()); } return fullCopyApi; } /** * Manages a request to create a full copy from the block object with the * passed URI. The URI must reference a volume or snapshot. For supported * platforms, if the source is a volume and it is in a consistency group, * full copies will be created for all volumes in the consistency group. * * @param sourceURI The URI of the source volume or snapshot. * @param param The request data specifying the parameters for the request. * * @return TaskList * * @throws InternalException */ public TaskList createFullCopy(URI sourceURI, VolumeFullCopyCreateParam param) throws InternalException { s_logger.info("START create full copy for source {}", sourceURI); // Create a unique task identifier. String taskId = UUID.randomUUID().toString(); List<BlockObject> fcSourceObjList = null; BlockFullCopyApi fullCopyApiImpl = null; // Get the volume/snapshot. BlockObject fcSourceObj = BlockFullCopyUtils.queryFullCopyResource(sourceURI, _uriInfo, true, _dbClient); // Get the requested full copy count. int count = param.getCount() == null ? 1 : param.getCount(); // Get the full copy name. String name = TimeUtils.formatDateForCurrent(param.getName()); // Get whether or not the full copy should be created inactive. // Check if the request calls for activation of the full copy boolean createInactive = param.getCreateInactive() == null ? Boolean.FALSE : param.getCreateInactive(); // if Volume is part of Application (COPY type VolumeGroup) VolumeGroup volumeGroup = (fcSourceObj instanceof Volume) ? ((Volume) fcSourceObj).getApplication(_dbClient) : null; boolean partialRequest = fcSourceObj.checkInternalFlags(Flag.VOLUME_GROUP_PARTIAL_REQUEST); VirtualArray varray = null; if (volumeGroup != null && !partialRequest) { s_logger.info("Volume {} is part of Application, Creating full copy for all volumes in the Application.", sourceURI); // get all volumes List<Volume> volumes = ControllerUtils.getVolumeGroupVolumes(_dbClient, volumeGroup); // if RP get source or target volumes if (volumes != null && !volumes.isEmpty() && Volume.checkForRP(_dbClient, volumes.iterator().next().getId())) { List<Volume> rpVolumes = RPHelper.getVolumesForSite(param.getVarrayId(), param.getVpoolId(), volumes); volumes.clear(); volumes.addAll(rpVolumes); } // group volumes by Array Group Map<String, List<Volume>> arrayGroupToVolumesMap = ControllerUtils.groupVolumesByArrayGroup(volumes, _dbClient); fcSourceObjList = new ArrayList<BlockObject>(); for (String arrayGroupName : arrayGroupToVolumesMap.keySet()) { List<Volume> volumeList = arrayGroupToVolumesMap.get(arrayGroupName); s_logger.debug("Processing Array Replication Group {}, volumes: {}", arrayGroupName, volumeList.size()); fcSourceObj = volumeList.iterator().next(); s_logger.debug("volume selected :{}", fcSourceObj.getNativeGuid()); // Get the project for the full copy source object. Project project = BlockFullCopyUtils.queryFullCopySourceProject(fcSourceObj, _dbClient); // verify the virtual array. varray = BlockServiceUtils.verifyVirtualArrayForRequest(project, fcSourceObj.getVirtualArray(), _uriInfo, _permissionsHelper, _dbClient); // Get the platform specific block full copy implementation. fullCopyApiImpl = getPlatformSpecificFullCopyImpl(fcSourceObj); // Get the list of all block objects for which we need to // create full copies. For example, when creating a full copy // for a volume in a consistency group, we may create full // copies for all volumes in the consistency group. List<BlockObject> fcSourceObjListPerArrayGroup = fullCopyApiImpl.getAllSourceObjectsForFullCopyRequest(fcSourceObj); // Validate the full copy request. validateFullCopyCreateRequest(fcSourceObjListPerArrayGroup, project, name, count, createInactive, fullCopyApiImpl); fcSourceObjList.addAll(fcSourceObjListPerArrayGroup); } } else { // Get the project for the full copy source object. Project project = BlockFullCopyUtils.queryFullCopySourceProject(fcSourceObj, _dbClient); // verify the virtual array. varray = BlockServiceUtils.verifyVirtualArrayForRequest(project, fcSourceObj.getVirtualArray(), _uriInfo, _permissionsHelper, _dbClient); // Get the platform specific block full copy implementation. fullCopyApiImpl = getPlatformSpecificFullCopyImpl(fcSourceObj); // Get the list of all block objects for which we need to // create full copies. For example, when creating a full copy // for a volume in a consistency group, we may create full // copies for all volumes in the consistency group. fcSourceObjList = fullCopyApiImpl.getAllSourceObjectsForFullCopyRequest(fcSourceObj); // Validate the full copy request. validateFullCopyCreateRequest(fcSourceObjList, project, name, count, createInactive, fullCopyApiImpl); } // Create the full copies TaskList taskList = fullCopyApiImpl.create(fcSourceObjList, varray, name, createInactive, count, taskId); s_logger.info("FINISH create full copy for source {}", sourceURI); return taskList; } /** * Validates the full copy creation request. * * @param fcSourceObjList A list of full copy sources. * @param project A reference to the project. * @param name The desired name for the full copy volume. * @param count The number of full copies requested. * @param createInactive true if the full copy is to activated when created, * false otherwise. * @param fullCopyApiImpl A reference to the platform specific full copy * implementation. */ private void validateFullCopyCreateRequest(List<BlockObject> fcSourceObjList, Project project, String name, int count, boolean createInactive, BlockFullCopyApi fullCopyApiImpl) { // Verify the requested full copy count. if (count <= 0) { throw APIException.badRequests.parameterMustBeGreaterThan("count", 0); } // Verify the requested name. ArgValidator.checkFieldNotEmpty(name, "name"); // Validate the project tenant. TenantOrg tenant = _dbClient.queryObject(TenantOrg.class, project.getTenantOrg().getURI()); ArgValidator.checkEntity(tenant, project.getTenantOrg().getURI(), false); // Verify the user is authorized. BlockServiceUtils.verifyUserIsAuthorizedForRequest(project, BlockServiceUtils.getUserFromContext(_securityContext), _permissionsHelper); // Verify the source objects. Map<URI, VirtualPool> vpoolMap = new HashMap<URI, VirtualPool>(); Map<URI, List<BlockObject>> vpoolSourceObjMap = new HashMap<URI, List<BlockObject>>(); for (BlockObject fcSourceObj : fcSourceObjList) { // The full copy source object id. URI fcSourceURI = fcSourceObj.getId(); if (URIUtil.isType(fcSourceURI, Volume.class)) { // Verify the operation is supported for ingested volumes. VolumeIngestionUtil.checkOperationSupportedOnIngestedVolume((Volume) fcSourceObj, ResourceOperationTypeEnum.CREATE_VOLUME_FULL_COPY, _dbClient); } // Verify the source is not an internal object. BlockServiceUtils.validateNotAnInternalBlockObject(fcSourceObj, false); // Update virtual pool map. VirtualPool vpool = BlockFullCopyUtils.queryFullCopySourceVPool(fcSourceObj, _dbClient); URI vpoolURI = vpool.getId(); if (!vpoolMap.containsKey(vpoolURI)) { vpoolMap.put(vpoolURI, vpool); } // Update the full copy source objects for this vpool. if (!vpoolSourceObjMap.containsKey(vpoolURI)) { List<BlockObject> vpoolSourceObjs = new ArrayList<BlockObject>(); vpoolSourceObjs.add(fcSourceObj); vpoolSourceObjMap.put(vpoolURI, vpoolSourceObjs); } else { List<BlockObject> vpoolSourceObjs = vpoolSourceObjMap.get(vpoolURI); vpoolSourceObjs.add(fcSourceObj); } } // Verify capacity quotas. for (URI vpoolURI : vpoolSourceObjMap.keySet()) { long totalRequiredSize = 0; List<BlockObject> vpoolSourceObjs = vpoolSourceObjMap.get(vpoolURI); for (BlockObject vpoolSourcObj : vpoolSourceObjs) { totalRequiredSize += count * BlockFullCopyUtils.getCapacityForFullCopySource(vpoolSourcObj, _dbClient); } CapacityUtils.validateQuotasForProvisioning(_dbClient, vpoolMap.get(vpoolURI), project, tenant, totalRequiredSize, "volume"); } // Platform specific checks fullCopyApiImpl.validateFullCopyCreateRequest(fcSourceObjList, count); } /** * Manages the activation of the full copy with the passed URI for the * source with the passed URI. For supported platforms, if the source is a * volume and the volume is part of a consistency group, this method will * also activate the corresponding full copies for all other volumes in the * consistency group. * * @param sourceURI The URI of the source. * @param fullCopyURI The URI of the full copy volume. * * @return TaskList * * @throws InternalException */ public TaskList activateFullCopy(URI sourceURI, URI fullCopyURI) throws InternalException { s_logger.info("START activate full copy {}", fullCopyURI); // Verify passed URIs for the full copy request. Map<URI, BlockObject> resourceMap = BlockFullCopyUtils.verifySourceAndFullCopy( sourceURI, fullCopyURI, _uriInfo, _dbClient); // Get the source and full copy volume. BlockObject fcSourceObj = resourceMap.get(sourceURI); Volume fullCopyVolume = (Volume) resourceMap.get(fullCopyURI); // If the full copy is detached, it cannot be activated. if (BlockFullCopyUtils.isFullCopyDetached(fullCopyVolume, _dbClient)) { throw APIException.badRequests.detachedFullCopyCannotBeActivated(fullCopyURI .toString()); } // Otherwise, check if the full copy is in the inactive state. // If it is in any other state, it was already activated. // In this case, the code simply returns a successful task. boolean alreadyActive = !BlockFullCopyUtils.isFullCopyInactive(fullCopyVolume, _dbClient); // Now activate. BlockFullCopyApi fullCopyApiImpl = getPlatformSpecificFullCopyImpl(fullCopyVolume); TaskList taskList = fullCopyApiImpl.activate(fcSourceObj, fullCopyVolume); // Log an audit message if we actually need to active the full copy. if (!alreadyActive) { auditOp(OperationTypeEnum.ACTIVATE_VOLUME_FULL_COPY, true, AuditLogManager.AUDITOP_BEGIN, fullCopyURI); } s_logger.info("FINISH activate full copy {}", fullCopyURI); return taskList; } /** * Detaches the full copy with the passed URI from the source with the * passed URI. For supported platforms, if the source is a volume and the * volume is part of a consistency group, this method will also detach the * corresponding full copies for all other volumes in the consistency group. * * @param sourceURI The URI of the source. * @param fullCopyURI The URI of the full copy volume. * * @return TaskList * * @throws InternalException */ public TaskList detachFullCopy(URI sourceURI, URI fullCopyURI) throws InternalException { s_logger.info("START detach full copy {}", fullCopyURI); // Check if full copy has been detached. // It will return success if it has been detached Volume fullCopy = (Volume) BlockFullCopyUtils.queryFullCopyResource(fullCopyURI, _uriInfo, false, _dbClient); if (BlockFullCopyUtils.isFullCopyDetached(fullCopy, _dbClient)) { s_logger.info("The full copy {} has been detached", fullCopyURI); TaskList taskList = new TaskList(); String taskId = UUID.randomUUID().toString(); Operation op = new Operation(); op.setResourceType(ResourceOperationTypeEnum.DETACH_VOLUME_FULL_COPY); op.ready("Full copy is already detached"); _dbClient.createTaskOpStatus(Volume.class, fullCopyURI, taskId, op); TaskResourceRep task = TaskMapper.toTask(fullCopy, taskId, op); taskList.addTask(task); return taskList; } // Verify passed URIs for the full copy request. Map<URI, BlockObject> resourceMap = BlockFullCopyUtils.verifySourceAndFullCopy( sourceURI, fullCopyURI, _uriInfo, _dbClient); // Get the source and full copy volume. BlockObject fcSourceObj = resourceMap.get(sourceURI); Volume fullCopyVolume = (Volume) resourceMap.get(fullCopyURI); // Determine if the copy is activated. It is activated if it is // not detached and not in the inactive state. boolean wasActive = (!BlockFullCopyUtils.isFullCopyDetached(fullCopyVolume, _dbClient) && !BlockFullCopyUtils.isFullCopyInactive(fullCopyVolume, _dbClient)); // Get the platform specific full copy implementation. BlockFullCopyApi fullCopyApiImpl = getPlatformSpecificFullCopyImpl(fullCopyVolume); // Now detach. TaskList taskList = fullCopyApiImpl.detach(fcSourceObj, fullCopyVolume); // Log an audit message if we actually need to active the full copy. if (wasActive) { auditOp(OperationTypeEnum.ACTIVATE_VOLUME_FULL_COPY, true, AuditLogManager.AUDITOP_BEGIN, fullCopyURI); } s_logger.info("FINISH detach full copy {}", fullCopyURI); return taskList; } /** * Restores the source with the passed URI from the full copy with the * passed URI. For supported platforms, if the source is a volume and the * volume is part of a consistency group, this method will also restore all * other volumes in the consistency group with their corresponding full * copies. * * @param sourceURI The URI of the source. * @param fullCopyURI The URI of the full copy volume. * * @return TaskList * * @throws InternalException */ public TaskList restoreFullCopy(URI sourceURI, URI fullCopyURI) throws InternalException { s_logger.info("START restore source {} from full copy {}", sourceURI, fullCopyURI); // Verify passed URIs for the full copy request. Map<URI, BlockObject> resourceMap = BlockFullCopyUtils.verifySourceAndFullCopy( sourceURI, fullCopyURI, _uriInfo, _dbClient); // We don't currently support restore when the source // is a snapshot. if (URIUtil.isType(sourceURI, BlockSnapshot.class)) { throw APIException.badRequests.fullCopyRestoreNotSupportedForSnapshot(); } // Get the source object and full copy volume Volume sourceVolume = (Volume) resourceMap.get(sourceURI); Volume fullCopyVolume = (Volume) resourceMap.get(fullCopyURI); // Check if the full copy is detached. if (BlockFullCopyUtils.isFullCopyDetached(fullCopyVolume, _dbClient)) { throw APIException.badRequests .detachedFullCopyCannotBeRestored(fullCopyURI.toString()); } // Check if the full copy was not activated. if (BlockFullCopyUtils.isFullCopyInactive(fullCopyVolume, _dbClient)) { throw APIException.badRequests .inactiveFullCopyCannotBeRestored(fullCopyURI.toString()); } // Verify that the full copy is restorable otherwise. if (!BlockFullCopyUtils.isFullCopyRestorable(fullCopyVolume, _dbClient)) { throw APIException.badRequests.fullCopyCannotBeRestored(fullCopyURI .toString(), fullCopyVolume.getReplicaState()); } // Get the platform specific full copy implementation. BlockFullCopyApi fullCopyApiImpl = getPlatformSpecificFullCopyImpl(fullCopyVolume); // Now restore the source. TaskList taskList = fullCopyApiImpl.restoreSource(sourceVolume, fullCopyVolume); // Log an audit message auditOp(OperationTypeEnum.RESTORE_VOLUME_FULL_COPY, true, AuditLogManager.AUDITOP_BEGIN, fullCopyURI); s_logger.info("FINISH restore source {} from full copy {}", sourceURI, fullCopyURI); return taskList; } /** * Resynchronizes the full copy with the passed URI with the latest data on * the source volume with the passed URI. For supported platforms, if the * source is a volume and the volume is part of a consistency group, this * method will also resynchronize the corresponding full copies for all * other volumes in the consistency group. * * @param sourceURI The URI of the source. * @param fullCopyURI The URI of the full copy volume. * * @return TaskList * * @throws InternalException */ public TaskList resynchronizeFullCopy(URI sourceURI, URI fullCopyURI) throws InternalException { s_logger.info("START resynchronize full copy {} from source {}", fullCopyURI, sourceURI); // Verify passed URIs for the full copy request. Map<URI, BlockObject> resourceMap = BlockFullCopyUtils.verifySourceAndFullCopy( sourceURI, fullCopyURI, _uriInfo, _dbClient); // We don't currently support resynchronize when the source // is a snapshot. if (URIUtil.isType(sourceURI, BlockSnapshot.class)) { throw APIException.badRequests.fullCopyResyncNotSupportedForSnapshot(); } // Get the source and full copy volumes Volume sourceVolume = (Volume) resourceMap.get(sourceURI); Volume fullCopyVolume = (Volume) resourceMap.get(fullCopyURI); // Check if the full copy is detached. if (BlockFullCopyUtils.isFullCopyDetached(fullCopyVolume, _dbClient)) { throw APIException.badRequests .detachedFullCopyCannotBeResynchronized(fullCopyURI.toString()); } // Check if the full copy was not activated. if (BlockFullCopyUtils.isFullCopyInactive(fullCopyVolume, _dbClient)) { throw APIException.badRequests .inactiveFullCopyCannotBeResynchronized(fullCopyURI.toString()); } // Verify that the full copy is resynchronizable otherwise. if (!BlockFullCopyUtils.isFullCopyResynchronizable(fullCopyVolume, _dbClient)) { throw APIException.badRequests.fullCopyCannotBeResynchronized(fullCopyURI .toString(), fullCopyVolume.getReplicaState()); } // Get the platform specific full copy implementation. BlockFullCopyApi fullCopyApiImpl = getPlatformSpecificFullCopyImpl(fullCopyVolume); // Now restore the source volume. TaskList taskList = fullCopyApiImpl.resynchronizeCopy(sourceVolume, fullCopyVolume); // Log an audit message auditOp(OperationTypeEnum.RESYNCHRONIZE_VOLUME_FULL_COPY, true, AuditLogManager.AUDITOP_BEGIN, fullCopyURI); s_logger.info("FINISH resynchronize full copy {} from source {}", fullCopyURI, sourceURI); return taskList; } /** * Generates a group synchronized between volume Replication group * and clone Replication group. * * @param sourceURI The URI of the source. * @param fullCopyURI The URI of the full copy volume. * * @return TaskList * * @throws InternalException */ public TaskList startFullCopy(URI sourceURI, URI fullCopyURI) throws InternalException { s_logger.info( "START establish group relation between Volume group and Full copy group." + " Source: {}, Full copy: {}", sourceURI, fullCopyURI); // Verify passed URIs for the full copy request. Map<URI, BlockObject> resourceMap = BlockFullCopyUtils.verifySourceAndFullCopy( sourceURI, fullCopyURI, _uriInfo, _dbClient); // Get the source and full copy volumes Volume sourceVolume = (Volume) resourceMap.get(sourceURI); Volume fullCopyVolume = (Volume) resourceMap.get(fullCopyURI); if (!sourceVolume.hasConsistencyGroup() || NullColumnValueGetter.isNullValue(fullCopyVolume.getReplicationGroupInstance())) { // check if this is vplex if (!VPlexUtil.isBackendFullCopyInReplicationGroup(fullCopyVolume, _dbClient)) { throw APIException.badRequests.blockObjectHasNoConsistencyGroup(); } } // Check if the full copy is detached. if (BlockFullCopyUtils.isFullCopyDetached(fullCopyVolume, _dbClient)) { throw APIException.badRequests .cannotEstablishGroupRelationForDetachedFullCopy(fullCopyURI.toString()); } // Check if the full copy was not activated. if (BlockFullCopyUtils.isFullCopyInactive(fullCopyVolume, _dbClient)) { throw APIException.badRequests .cannotEstablishGroupRelationForInactiveFullCopy(fullCopyURI.toString()); } // Get the platform specific full copy implementation. BlockFullCopyApi fullCopyApiImpl = getPlatformSpecificFullCopyImpl(fullCopyVolume); // Now restore the source volume. TaskList taskList = fullCopyApiImpl.establishVolumeAndFullCopyGroupRelation(sourceVolume, fullCopyVolume); // Log an audit message auditOp(OperationTypeEnum.ESTABLISH_VOLUME_FULL_COPY, true, AuditLogManager.AUDITOP_BEGIN, fullCopyURI); s_logger.info("FINISH establish group relation between Volume group and FullCopy group"); return taskList; } /** * Checks the progress of the data copy from the source with the * passed URI to the full copy with the passed URI. * * TBD Maybe vice versa for restore? * * @param sourceURI The URI of the source. * @param fullCopyURI The URI of the full copy volume. * * @return VolumeRestRep * * @throws InternalException */ public VolumeRestRep checkFullCopyProgress(URI sourceURI, URI fullCopyURI) throws InternalException { s_logger.info("START full copy progress check for {}", fullCopyURI); // Verify passed URIs for the full copy request. Map<URI, BlockObject> resourceMap = BlockFullCopyUtils.verifySourceAndFullCopy( sourceURI, fullCopyURI, _uriInfo, _dbClient); // Get the full copy volume. Volume fullCopyVolume = (Volume) resourceMap.get(fullCopyURI); // Check if the full copy is detached. if (BlockFullCopyUtils.isFullCopyDetached(fullCopyVolume, _dbClient)) { throw APIException.badRequests.cannotCheckProgressFullCopyDetached(fullCopyURI .toString()); } // Get the platform specific full copy implementation. BlockFullCopyApi fullCopyApiImpl = getPlatformSpecificFullCopyImpl(fullCopyVolume); // Now check the progress. s_logger.info("FINISH full copy progress check for {}", fullCopyURI); VolumeRestRep volumeRestRep = fullCopyApiImpl.checkProgress(sourceURI, fullCopyVolume); return volumeRestRep; } /** * Returns the full copies for the source volume with the passed URI. * * @param sourceVolumeURI The URI of the full copy source volume. * * @return NamedVolumesList */ public NamedVolumesList getFullCopiesForSource(URI sourceVolumeURI) { NamedVolumesList fullCopyList = new NamedVolumesList(); ArgValidator.checkFieldUriType(sourceVolumeURI, Volume.class, "id"); Volume sourceVolume = _dbClient.queryObject(Volume.class, sourceVolumeURI); StringSet fullCopyIds = sourceVolume.getFullCopies(); if (fullCopyIds == null || fullCopyIds.isEmpty()) { return fullCopyList; } for (String fullCopyId : fullCopyIds) { Volume fullCopyVolume = _dbClient.queryObject(Volume.class, URI.create(fullCopyId)); if (fullCopyVolume == null || fullCopyVolume.getInactive()) { s_logger.warn("Stale full copy {} found for volume {}", fullCopyId, sourceVolumeURI); continue; } fullCopyList.getVolumes().add(DbObjectMapper.toNamedRelatedResource(fullCopyVolume)); } return fullCopyList; } /** * Returns the full copy with the passed URI. * * @param fullCopyURI The URI of the full copy volume. * * @return VolumeRestRep */ public VolumeRestRep getFullCopy(URI fullCopyURI) { Volume fullCopyVolume = (Volume) BlockFullCopyUtils.queryFullCopyResource( fullCopyURI, _uriInfo, false, _dbClient); return BlockMapper.map(_dbClient, fullCopyVolume); } /** * For ViPR-Only delete, cleans up the full copy associations between * source and full copy volumes. * * @param volumeDescriptors The descriptors for volumes being * deleted from ViPR. * @param dbClient A reference to a database client. */ public static void cleanUpFullCopyAssociations( List<VolumeDescriptor> volumeDescriptors, DbClient dbClient) { List<URI> volumeURIs = VolumeDescriptor.getVolumeURIs(volumeDescriptors); for (URI volumeURI : volumeURIs) { Volume volume = dbClient.queryObject(Volume.class, volumeURI); URI sourceVolumeURI = volume.getAssociatedSourceVolume(); if (!NullColumnValueGetter.isNullURI(sourceVolumeURI)) { // The volume being removed is a full copy. Make sure the copies // list of the source no longer references this volume. Note // that it is possible that the source was already deleted but // we left the source URI set in the copy, so one could always // know the source of the copy. So, check for a null source // volume. Volume sourceVolume = dbClient.queryObject(Volume.class, sourceVolumeURI); if (sourceVolume != null) { StringSet fullCopyIds = sourceVolume.getFullCopies(); if (fullCopyIds.contains(volumeURI.toString())) { fullCopyIds.remove(volumeURI.toString()); dbClient.updateObject(sourceVolume); } } } } } /** * Verify that the passed volume can be deleted. * * @param volume A reference to a volume. * * @return true if the volume can be deleted, false otherwise. */ public boolean volumeCanBeDeleted(Volume volume) { if ((BlockFullCopyUtils.isVolumeFullCopy(volume, _dbClient)) || (BlockFullCopyUtils.isVolumeFullCopySource(volume, _dbClient))) { // Delegate to the platform specific full copy implementation // for the passed volume. BlockFullCopyApi fullCopyApiImpl = getPlatformSpecificFullCopyImpl(volume); return fullCopyApiImpl.volumeCanBeDeleted(volume); } return true; } /** * Verify that the passed volume can be expanded. * * @param volume A reference to a volume. * * @return true if the volume can be expanded, false otherwise. */ public boolean volumeCanBeExpanded(Volume volume) { if ((BlockFullCopyUtils.isVolumeFullCopy(volume, _dbClient)) || (BlockFullCopyUtils.isVolumeFullCopySource(volume, _dbClient))) { // Delegate to the platform specific full copy implementation // for the passed volume. BlockFullCopyApi fullCopyApiImpl = getPlatformSpecificFullCopyImpl(volume); return fullCopyApiImpl.volumeCanBeExpanded(volume); } return true; } /** * Verify that new volumes can be created in the passed consistency group. * * @param consistencyGroup A reference to the consistency group. * @param cgVolumes The volumes in the consistency group. */ public void verifyNewVolumesCanBeCreatedInConsistencyGroup( BlockConsistencyGroup consistencyGroup, List<Volume> cgVolumes) { if (!canConsistencyGroupBeModified(consistencyGroup, cgVolumes)) { throw APIException.badRequests.cantCreateNewVolumesInCGActiveFullCopies( consistencyGroup.getLabel()); } } /** * Verify the passed consistency group can be updated. * * @param consistencyGroup A reference to the consistency group. * @param cgVolumes The volumes in the consistency group. */ public void verifyConsistencyGroupCanBeUpdated( BlockConsistencyGroup consistencyGroup, List<Volume> cgVolumes) { if (!canConsistencyGroupBeModified(consistencyGroup, cgVolumes)) { throw APIException.badRequests.cantUpdateCGActiveFullCopies( consistencyGroup.getLabel()); } } /** * Determines if the passed consistency group can have new volumes added or * existing volumes removed. * * @param consistencyGroup A reference to the consistency group. * @param cgVolumes The volumes in the consistency group. * * @return true if the group an be modified, false otherwise. */ private boolean canConsistencyGroupBeModified(BlockConsistencyGroup consistencyGroup, List<Volume> cgVolumes) { Iterator<Volume> cgVolumesIter = cgVolumes.iterator(); while (cgVolumesIter.hasNext()) { Volume cgVolume = cgVolumesIter.next(); // Volumes that are full copies must be detached. if ((BlockFullCopyUtils.isVolumeFullCopy(cgVolume, _dbClient)) && (!BlockFullCopyUtils.isFullCopyDetached(cgVolume, _dbClient))) { return false; } // Volumes with full copies must be detached from // those copies. if ((BlockFullCopyUtils.isVolumeCGFullCopySource(cgVolume, _dbClient)) && (!BlockFullCopyUtils.volumeDetachedFromFullCopies(cgVolume, _dbClient))) { return false; } } return true; } /** * Given the passed full copy volume, return all volumes in the full copy * set. * * @param volume A Reference to a volume that is a full copy. * * @return The full copy volumes in the full copy set. */ public Collection<Volume> getFullCopySet(Volume fullCopyVolume) { if (!BlockFullCopyUtils.isVolumeFullCopy(fullCopyVolume, _dbClient)) { return new HashSet<Volume>(); } BlockObject fcSourceObj = BlockObject.fetch(_dbClient, fullCopyVolume.getAssociatedSourceVolume()); BlockFullCopyApi fullCopyApi = getPlatformSpecificFullCopyImpl(fullCopyVolume); return fullCopyApi.getFullCopySetMap(fcSourceObj, fullCopyVolume).values(); } /** * Verifies that the volume's status with respect to full copies allows * snapshots to be created. * * @param requestedVolume A reference to the volume for which a snapshot was requested. * @param volumesToSnap The list of volumes that would be snapped. */ public void validateSnapshotCreateRequest(Volume requestedVolume, List<Volume> volumesToSnap) { BlockFullCopyApi fullCopyApiImpl = getPlatformSpecificFullCopyImpl(requestedVolume); fullCopyApiImpl.validateSnapshotCreateRequest(requestedVolume, volumesToSnap); } /** * Determines and returns the platform specific full copy implementation. * * @param fcSourceObj A reference to the full copy source. * * @return The platform specific full copy implementation */ private BlockFullCopyApi getPlatformSpecificFullCopyImpl(BlockObject fcSourceObj) { BlockFullCopyApi fullCopyApi = null; if (BlockObject.checkForRP(_dbClient, fcSourceObj.getId())) { fullCopyApi = _fullCopyImpls.get(FullCopyImpl.rp.name()); } else { VirtualPool vpool = BlockFullCopyUtils.queryFullCopySourceVPool(fcSourceObj, _dbClient); if (VirtualPool.vPoolSpecifiesHighAvailability(vpool)) { fullCopyApi = _fullCopyImpls.get(FullCopyImpl.vplex.name()); } else { URI systemURI = fcSourceObj.getStorageController(); StorageSystem system = _dbClient.queryObject(StorageSystem.class, systemURI); return getPlatformSpecificFullCopyImplForSystem(system); } } return fullCopyApi; } /** * returns the vplex specific full copy implementation * * @return vplex specific full copy implementation */ public BlockFullCopyApi getVplexFullCopyImpl() { return _fullCopyImpls.get(FullCopyImpl.vplex.name()); } /** * Record audit log for services. * * @param opType audit event type (e.g. CREATE_VPOOL|TENANT etc.) * @param operationalStatus Status of operation (true|false) * @param operationStage Stage of operation. For sync operation, it should * be null; For async operation, it should be "BEGIN" or "END"; * @param descparams Description parameters */ private void auditOp(OperationTypeEnum opType, boolean operationalStatus, String operationStage, Object... descparams) { URI tenantId; URI username; if (!BlockServiceUtils.hasValidUserInContext(_securityContext) && InterNodeHMACAuthFilter.isInternalRequest(_request)) { // Use default values for internal datasvc requests that lack a user // context tenantId = _permissionsHelper.getRootTenant().getId(); username = ResourceService.INTERNAL_DATASVC_USER; } else { StorageOSUser user = BlockServiceUtils.getUserFromContext(_securityContext); tenantId = URI.create(user.getTenantId()); username = URI.create(user.getName()); } _auditLogManager.recordAuditLog(tenantId, username, BlockService.EVENT_SERVICE_TYPE, opType, System.currentTimeMillis(), operationalStatus ? AuditLogManager.AUDITLOG_SUCCESS : AuditLogManager.AUDITLOG_FAILURE, operationStage, descparams); } /** * Return the maximum number of active full copies for storage * systems of the passed type. * * @param systemType The system type. * * @return The maximum number of active full copies. */ public static int getMaxFullCopiesForSystemType(String systemType) { if (s_maxFullCopyMap.containsKey(systemType)) { return s_maxFullCopyMap.get(systemType); } return Integer.MAX_VALUE; } }