/* * 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.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.api.mapper.BlockMapper; import com.emc.storageos.api.mapper.TaskMapper; import com.emc.storageos.api.service.impl.placement.Scheduler; import com.emc.storageos.api.service.impl.placement.StorageScheduler; import com.emc.storageos.api.service.impl.placement.VPlexScheduler; import com.emc.storageos.api.service.impl.placement.VolumeRecommendation; import com.emc.storageos.api.service.impl.resource.TenantsService; import com.emc.storageos.api.service.impl.resource.VPlexBlockServiceApiImpl; import com.emc.storageos.api.service.impl.resource.utils.BlockServiceUtils; import com.emc.storageos.blockorchestrationcontroller.BlockOrchestrationController; 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.constraint.AlternateIdConstraint; 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.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.StoragePool; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.model.StringSet; 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.BlockConsistencyGroupUtils; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.model.ResourceOperationTypeEnum; import com.emc.storageos.model.TaskList; import com.emc.storageos.model.TaskResourceRep; import com.emc.storageos.model.block.VolumeRestRep; import com.emc.storageos.svcs.errorhandling.resources.APIException; import com.emc.storageos.svcs.errorhandling.resources.InternalException; import com.emc.storageos.svcs.errorhandling.resources.InternalServerErrorException; import com.emc.storageos.util.VPlexUtil; import com.emc.storageos.volumecontroller.Recommendation; import com.emc.storageos.volumecontroller.VPlexRecommendation; import com.emc.storageos.volumecontroller.impl.ControllerUtils; import com.emc.storageos.volumecontroller.impl.utils.VirtualPoolCapabilityValuesWrapper; import com.emc.storageos.vplexcontroller.VPlexController; /** * The VPLEX storage system implementation for the block full copy API. */ public class VPlexBlockFullCopyApiImpl extends AbstractBlockFullCopyApiImpl { // A reference to the tenants service or null. private TenantsService _tenantsService = null; // A reference to a logger. private static final Logger s_logger = LoggerFactory.getLogger(VPlexBlockFullCopyApiImpl.class); /** * Constructor * * @param dbClient A reference to a database client. * @param coordinator A reference to the coordinator client. * @param scheduler A reference to a scheduler. * @param fullCopyMgr A reference to the full copy manager. */ public VPlexBlockFullCopyApiImpl(DbClient dbClient, CoordinatorClient coordinator, Scheduler scheduler, TenantsService tenantsService, BlockFullCopyManager fullCopyMgr) { super(dbClient, coordinator, scheduler, fullCopyMgr); _tenantsService = tenantsService; } /** * {@inheritDoc} */ @Override public List<BlockObject> getAllSourceObjectsForFullCopyRequest(BlockObject fcSourceObj) { // Treats full copies of snapshots as is done in base class. if (URIUtil.isType(fcSourceObj.getId(), BlockSnapshot.class)) { return super.getAllSourceObjectsForFullCopyRequest(fcSourceObj); } // By default, if the passed volume is in a consistency group // all volumes in the consistency group should be copied. List<BlockObject> fcSourceObjList = new ArrayList<>(); Volume fcSourceVolume = (Volume) fcSourceObj; URI cgURI = fcSourceObj.getConsistencyGroup(); if (!NullColumnValueGetter.isNullURI(cgURI)) { // if volume is part of Application, get only the Array Group volumes if (fcSourceVolume.getApplication(_dbClient) != null) { // Get VPLEX volumes based on the backed volume replication group String replicationGroupName = null; Volume srcBackendVolume = VPlexUtil.getVPLEXBackendVolume(fcSourceVolume, true, _dbClient); if (srcBackendVolume != null) { replicationGroupName = srcBackendVolume.getReplicationGroupInstance(); } s_logger.info("Source Volume: {}, Backend Volume: {}, Replication Group name: {}", fcSourceVolume.getLabel(), srcBackendVolume != null ? srcBackendVolume.getLabel() : null, replicationGroupName); if (replicationGroupName != null) { List<Volume> backendVolumes = ControllerUtils.getVolumesPartOfRG(srcBackendVolume.getStorageController(), replicationGroupName, _dbClient); for (Volume backendVolume : backendVolumes) { Volume vplexVolume = Volume.fetchVplexVolume(_dbClient, backendVolume); s_logger.debug("other VPLEX Volume in the group: {}", vplexVolume.getLabel()); fcSourceObjList.add(vplexVolume); } } else { fcSourceObjList.add(fcSourceObj); } } else { BlockConsistencyGroup cg = _dbClient.queryObject(BlockConsistencyGroup.class, cgURI); // If there is no corresponding native CG for the VPLEX // CG, then this is a CG created prior to 2.2 and in this // case we want full copies treated like snapshots, which // is only create a copy of the passed object. if (cg.checkForType(Types.LOCAL) || cg.checkForType(Types.SRDF)) { fcSourceObjList.addAll(getActiveCGVolumes(cg)); } else { fcSourceObjList.add(fcSourceObj); } } } else { fcSourceObjList.add(fcSourceObj); } return fcSourceObjList; } /** * {@inheritDoc} */ @Override public Map<URI, Volume> getFullCopySetMap(BlockObject fcSourceObj, Volume fullCopyVolume) { Map<URI, Volume> fullCopyMap = new HashMap<>(); // Get the source side backend volume of the VPLEX source Volume. Volume sourceVolume = (Volume) fcSourceObj; Volume srcBackendSrcVolume = VPlexUtil.getVPLEXBackendVolume( sourceVolume, true, _dbClient, true); // Get the source side backend volume of the VPLEX volume copy. // This is the backend volume full copy. Volume fcBackendSrcVolume = VPlexUtil.getVPLEXBackendVolume( fullCopyVolume, true, _dbClient, true); // Get the backend full copy set. Map<URI, Volume> backendFullCopyMap = super.getFullCopySetMap( srcBackendSrcVolume, fcBackendSrcVolume); // Now we need to get the VPLEX volumes for these backend volumes. // These will be the VPLEX full copy volumes in the set. Iterator<URI> backendCopyIter = backendFullCopyMap.keySet().iterator(); while (backendCopyIter.hasNext()) { URIQueryResultList queryResults = new URIQueryResultList(); _dbClient.queryByConstraint( AlternateIdConstraint.Factory .getVolumeByAssociatedVolumesConstraint(backendCopyIter.next() .toString()), queryResults); if (queryResults.iterator().hasNext()) { URI vplexCopyVolumeURI = queryResults.iterator().next(); fullCopyMap.put(vplexCopyVolumeURI, _dbClient.queryObject(Volume.class, vplexCopyVolumeURI)); } } return fullCopyMap; } /** * {@inheritDoc} */ @Override protected List<Volume> getActiveCGVolumes(BlockConsistencyGroup cg) { return BlockConsistencyGroupUtils.getActiveVplexVolumesInCG(cg, _dbClient, null); } /** * {@inheritDoc} */ @Override public void validateFullCopyCreateRequest(List<BlockObject> fcSourceObjList, int count) { if (!fcSourceObjList.isEmpty()) { BlockObject fcsourceObj = fcSourceObjList.get(0); URI fcSourceObjURI = fcsourceObj.getId(); if (URIUtil.isType(fcSourceObjURI, BlockSnapshot.class) && !BlockServiceUtils.isSnapshotFullCopySupported(fcSourceObjURI, _dbClient)) { // Snapshot full copy is supported only for OpenStack, VNXBlock, VMAX and IBMXIV throw APIException.badRequests.cantCreateFullCopyForVPlexSnapshot(); } // Group clone for IBM XIV storage system type is not supported if (null != fcsourceObj.getConsistencyGroup() && VPlexUtil.isIBMXIVBackend(fcsourceObj, _dbClient)) { throw APIException.methodNotAllowed.notSupportedWithReason( "Consistency Group Full Copy is not supported on backend IBM XIV storage systems"); } // Call super first. super.validateFullCopyCreateRequest(fcSourceObjList, count); // Platform specific checks. // all the volumes in vplex cg should be having association with back end cg/Volume Group. if (VPlexUtil.isBackendVolumesNotHavingBackendCG(fcSourceObjList, _dbClient)) { throw APIException.badRequests.fullcopyNotAllowedWhenBackendVolumeDoestHavingCG(); } for (BlockObject fcSourceObj : fcSourceObjList) { if (fcSourceObj instanceof Volume) { Volume fcSourceVolume = (Volume) fcSourceObj; // If the volume is a VPLEX volume created on a block snapshot, // we don't support creation of a full copy. if (VPlexUtil.isVolumeBuiltOnBlockSnapshot(_dbClient, fcSourceVolume)) { throw APIException.badRequests.fullCopyNotAllowedVolumeIsExposedSnapshot(fcSourceVolume.getId().toString()); } StorageSystem system = _dbClient.queryObject(StorageSystem.class, fcSourceObj.getStorageController()); if (DiscoveredDataObject.Type.vplex.name().equals(system.getSystemType())) { // If the volume is a VPLEX volume, then we need to be sure that // storage pool of the source backend volume of the VPLEX volume, // which is volume used to create the native full copy, supports // full copy. Volume srcBackendVolume = VPlexUtil.getVPLEXBackendVolume( fcSourceVolume, true, _dbClient, true); StoragePool storagePool = _dbClient.queryObject(StoragePool.class, srcBackendVolume.getPool()); verifyFullCopySupportedForStoragePool(storagePool); // If the full copy source is itself a full copy, it is not // detached, and the native full copy i.e., the source side // backend volume, is VNX, then creating a full copy of the // volume will fail. As such, we prevent it. if ((BlockFullCopyUtils.isVolumeFullCopy(fcSourceVolume, _dbClient)) && (!BlockFullCopyUtils.isFullCopyDetached(fcSourceVolume, _dbClient))) { URI backendSystemURI = srcBackendVolume.getStorageController(); StorageSystem backendSystem = _dbClient.queryObject( StorageSystem.class, backendSystemURI); if (DiscoveredDataObject.Type.vnxblock.name().equals(backendSystem.getSystemType())) { throw APIException.badRequests.cantCreateFullCopyOfVPlexFullCopyUsingVNX(); } } } } } } } /* (non-Javadoc) * @see com.emc.storageos.api.service.impl.resource.fullcopy.AbstractBlockFullCopyApiImpl#handlePlacementFailure(java.util.List) */ @Override public void handlePlacementFailure(List<Volume> volumesList) { // one of the volumes on the list might have an associated source volume that needs to be cleared out List<Volume> updateVolumes = new ArrayList<Volume>(); for (Volume volume : volumesList) { if (!NullColumnValueGetter.isNullURI(volume.getId()) && !NullColumnValueGetter.isNullURI(volume.getAssociatedSourceVolume())) { Volume srcVolume = _dbClient.queryObject(Volume.class, volume.getAssociatedSourceVolume()); if (srcVolume != null && !srcVolume.getInactive()) { for (String fullCopyUri : srcVolume.getFullCopies()) { if (fullCopyUri.equalsIgnoreCase(volume.getId().toString())) { srcVolume.getFullCopies().remove(volume.getId().toString()); updateVolumes.add(srcVolume); volume.setAssociatedSourceVolume(NullColumnValueGetter.getNullURI()); updateVolumes.add(volume); break; } } } } } _dbClient.updateObject(updateVolumes); // super method sets volumes to inactive and corrects the available pool space super.handlePlacementFailure(volumesList); } /** * {@inheritDoc} */ @Override public TaskList create(List<BlockObject> fcSourceObjList, VirtualArray varray, String name, boolean createInactive, int count, String taskId) { // Populate the descriptors list with all volumes required // to create the VPLEX volume copies. int sourceCounter = 0; URI vplexSrcSystemId = null; List<Volume> allNewVolumes = new ArrayList<>(); List<Volume> vplexCopyVolumes = new ArrayList<>(); List<VolumeDescriptor> volumeDescriptors = new ArrayList<>(); List<BlockObject> sortedSourceObjectList = sortFullCopySourceList(fcSourceObjList); Map<URI, VirtualArray> vArrayCache = new HashMap<>(); BlockObject aFCSource = null; try { for (BlockObject fcSourceObj : sortedSourceObjectList) { if (aFCSource == null) { aFCSource = fcSourceObj; } URI fcSourceURI = fcSourceObj.getId(); // volumes in VolumeGroup can be from different vArrays varray = getVarrayFromCache(vArrayCache, fcSourceObj.getVirtualArray()); String copyName = null; if (fcSourceObj instanceof Volume && ((Volume) fcSourceObj).getApplication(_dbClient) != null) { Volume backendVolume = VPlexUtil.getVPLEXBackendVolume((Volume) fcSourceObj, true, _dbClient); if (NullColumnValueGetter.isNotNullValue(backendVolume.getReplicationGroupInstance())) { copyName = name + "-" + backendVolume.getReplicationGroupInstance() + (sortedSourceObjectList.size() > 1 ? "-" + ++sourceCounter : ""); } } if (copyName == null) { copyName = name + (sortedSourceObjectList.size() > 1 ? "-" + ++sourceCounter : ""); } vplexSrcSystemId = fcSourceObj.getStorageController(); if (fcSourceObj instanceof Volume) { // DO IT ONLY FOR VOLUME CLONE - In case of snapshot new VPLEX volume needs to be created // Create a volume descriptor for the source VPLEX volume being copied. // and add it to the descriptors list. Be sure to identify this VPLEX // volume as the source volume being copied. VolumeDescriptor vplexSrcVolumeDescr = new VolumeDescriptor(VolumeDescriptor.Type.VPLEX_VIRT_VOLUME, vplexSrcSystemId, fcSourceURI, null, null); Map<String, Object> descrParams = new HashMap<>(); descrParams.put(VolumeDescriptor.PARAM_IS_COPY_SOURCE_ID, Boolean.TRUE); vplexSrcVolumeDescr.setParameters(descrParams); volumeDescriptors.add(vplexSrcVolumeDescr); } else { BlockSnapshot sourceSnapshot = (BlockSnapshot) fcSourceObj; URIQueryResultList queryResults = new URIQueryResultList(); _dbClient.queryByConstraint(AlternateIdConstraint.Factory.getVolumeByAssociatedVolumesConstraint( sourceSnapshot.getParent().getURI().toString()), queryResults); URI vplexVolumeURI = queryResults.iterator().next(); if (null != vplexVolumeURI) { Volume vplexVolume = _dbClient.queryObject(Volume.class, vplexVolumeURI); vplexSrcSystemId = vplexVolume.getStorageController(); } } // Get some info about the VPLEX volume being copied and its storage system. Project vplexSrcProject = BlockFullCopyUtils.queryFullCopySourceProject(fcSourceObj, _dbClient); StorageSystem vplexSrcSystem = _dbClient.queryObject(StorageSystem.class, vplexSrcSystemId); Project vplexSystemProject = VPlexBlockServiceApiImpl.getVplexProject(vplexSrcSystem, _dbClient, _tenantsService); Volume vplexSrcPrimaryVolume = null; Volume vplexSrcHAVolume = null; Volume vplexSrcVolume = null; if (fcSourceObj instanceof Volume) { // For the VPLEX volume being copied, determine which of the associated // backend volumes is the primary and, for distributed volumes, which // is the HA volume. The primary volume will be natively copied and we // we need to place and prepare a volume to hold the copy. This copy // will be the primary backend volume for the VPLEX volume copy. For // a distributed virtual volume, we will need to place and prepare // a volume to hold the HA volume of the VPLEX volume copy. vplexSrcVolume = (Volume) fcSourceObj; StringSet assocVolumeURIs = vplexSrcVolume.getAssociatedVolumes(); if (null == assocVolumeURIs || assocVolumeURIs.isEmpty()) { s_logger.error("VPLEX volume {} has no backend volumes.", vplexSrcVolume.forDisplay()); throw InternalServerErrorException.internalServerErrors .noAssociatedVolumesForVPLEXVolume(vplexSrcVolume.forDisplay()); } Iterator<String> assocVolumeURIsIter = assocVolumeURIs.iterator(); while (assocVolumeURIsIter.hasNext()) { URI assocVolumeURI = URI.create(assocVolumeURIsIter.next()); Volume assocVolume = _dbClient.queryObject(Volume.class, assocVolumeURI); if (assocVolume.getVirtualArray().toString().equals(varray.getId().toString())) { vplexSrcPrimaryVolume = assocVolume; } else { vplexSrcHAVolume = assocVolume; } } } // Get the capabilities VirtualPool vpool = BlockFullCopyUtils.queryFullCopySourceVPool(fcSourceObj, _dbClient); VirtualPoolCapabilityValuesWrapper capabilities = getCapabilitiesForFullCopyCreate(fcSourceObj, vpool, count); // Get the number of copies to create and the size of the volumes. // Note that for the size, we must use the actual provisioned size // of the source side backend volume. The size passed in the // capabilities will be the size of the VPLEX volume. When the // source side backend volume for the copy is provisioned, you // might not get that actual size. On VMAX, the size will be slightly // larger while for VNX the size will be exactly what is requested. // So, if the source side is a VMAX, the source side for the copy // will be slightly larger than the size in the capabilities. If the HA // side is VNX and we use the size in the capabilities, then you will // get exactly that size for the HA backend volume. As a result, source // side backend volume for the copy will be slightly larger than the // HA side. Now the way a VPLEX copy is made is it uses native full // copy to create a native full copy of the source side backend // volume. It then provisions the HA side volume. The new source side // backend copy is then imported into VPLEX in the same way as is done // for a vpool change that imports a volume to VPLEX. This code in the // VPLEX controller creates a local VPLEX volume using the source side // copy and for a distributed volume it then attaches as a remote // mirror the HA backend volume that is provisioned. If the HA volume // is slightly smaller, then this will fail on the VPLEX. So, we must // ensure that HA side volume is big enough by using the provisioned // capacity of the source side backend volume of the VPLEX volume being // copied. long size = 0L; List<Volume> vplexCopyPrimaryVolumes = null; if (null != vplexSrcPrimaryVolume) { size = vplexSrcPrimaryVolume.getProvisionedCapacity(); // Place and prepare a volume for each copy to serve as a native // copy of a VPLEX backend volume. The VPLEX backend volume that // is copied is the backend volume in the same virtual array as the // VPLEX volume i.e, the primary backend volume. Create // descriptors for these prepared volumes and add them to the list. vplexCopyPrimaryVolumes = prepareFullCopyPrimaryVolumes(copyName, count, vplexSrcPrimaryVolume, capabilities, volumeDescriptors, vpool); } else { // Get the provisioned capacity of the snapshot size = ((BlockSnapshot) fcSourceObj).getProvisionedCapacity(); // Place and prepare a back-end volume for each block snapshot vplexCopyPrimaryVolumes = prepareFullCopyPrimaryVolumes(copyName, count, fcSourceObj, capabilities, volumeDescriptors, vpool); } allNewVolumes.addAll(vplexCopyPrimaryVolumes); // If the VPLEX volume being copied is distributed, then the VPLEX // HA volume should be non-null. We use the VPLEX scheduler to place // and then prepare volumes for the HA volumes of the VPLEX volume // copies. This should be done in the same manner as is done for the // import volume routine. This is because to form the VPLEX volume // copy we import the copy of the primary backend volume. List<Volume> vplexCopyHAVolumes = new ArrayList<>(); if (vplexSrcHAVolume != null) { vplexCopyHAVolumes.addAll(prepareFullCopyHAVolumes(copyName, count, size, vplexSrcSystem, vplexSystemProject, varray, vplexSrcHAVolume, taskId, volumeDescriptors)); } allNewVolumes.addAll(vplexCopyHAVolumes); // For each copy to be created, place and prepare a volume for the // primary backend volume copy. When copying a distributed VPLEX // volume, we also must place and prepare a volume for the HA // backend volume copy. Lastly, we must prepare a volume for the // VPLEX volume copy. Create descriptors for these prepared volumes // and add them to the volume descriptors list. for (int i = 0; i < count; i++) { // Prepare a new VPLEX volume for each copy. Volume vplexCopyPrimaryVolume = vplexCopyPrimaryVolumes.get(i); Volume vplexCopyHAVolume = null; if (!vplexCopyHAVolumes.isEmpty()) { vplexCopyHAVolume = vplexCopyHAVolumes.get(i); } Volume vplexCopyVolume = prepareFullCopyVPlexVolume(copyName, name, count, i, size, fcSourceObj, vplexSrcProject, varray, vpool, vplexSrcSystemId, vplexCopyPrimaryVolume, vplexCopyHAVolume, taskId, volumeDescriptors); vplexCopyVolumes.add(vplexCopyVolume); allNewVolumes.addAll(vplexCopyHAVolumes); } } } catch (Exception e) { handlePlacementFailure(allNewVolumes); throw e; } // get all tasks TaskList taskList = getTasksForCreateFullCopy(aFCSource, vplexCopyVolumes, taskId); // Invoke the VPLEX controller to create the copies. try { s_logger.info("Getting Orchestration controller {}.", taskId); BlockOrchestrationController controller = getController(BlockOrchestrationController.class, BlockOrchestrationController.BLOCK_ORCHESTRATION_DEVICE); controller.createFullCopy(volumeDescriptors, taskId); s_logger.info("Successfully invoked controller."); } catch (InternalException e) { s_logger.error("Controller error", e); // Update the status for the VPLEX copy volume and CG tasks. handleFailedRequest(taskId, taskList, new ArrayList<Volume>(), e, false); // Mark all volumes inactive, except for the VPLEX volume // we were trying to copy. for (VolumeDescriptor descriptor : volumeDescriptors) { if (descriptor.getParameters().get( VolumeDescriptor.PARAM_IS_COPY_SOURCE_ID) == null) { Volume volume = _dbClient.queryObject(Volume.class, descriptor.getVolumeURI()); volume.setInactive(true); _dbClient.updateObject(volume); } } } return taskList; } /** * Places and prepares the HA volumes when copying a distributed VPLEX * volume. * * @param name The base name for the volume. * @param copyCount The number of copies to be made. * @param size The size for the HA volume. * @param vplexSystem A reference to the VPLEX storage system. * @param vplexSystemProject A reference to the VPLEX system project. * @param srcVarray The virtual array for the VPLEX volume being copied. * @param srcHAVolume The HA volume of the VPLEX volume being copied. * @param taskId The task identifier. * @param volumeDescriptors The list of descriptors. * * @return A list of the prepared HA volumes for the VPLEX volume copy. */ private List<Volume> prepareFullCopyHAVolumes(String name, int copyCount, Long size, StorageSystem vplexSystem, Project vplexSystemProject, VirtualArray srcVarray, Volume srcHAVolume, String taskId, List<VolumeDescriptor> volumeDescriptors) { List<Volume> copyHAVolumes = new ArrayList<>(); // Get the storage placement recommendations for the volumes. // Placement must occur on the same VPLEX system Set<URI> vplexSystemURIS = new HashSet<>(); vplexSystemURIS.add(vplexSystem.getId()); VirtualArray haVarray = _dbClient.queryObject(VirtualArray.class, srcHAVolume.getVirtualArray()); VirtualPool haVpool = _dbClient.queryObject(VirtualPool.class, srcHAVolume.getVirtualPool()); VirtualPoolCapabilityValuesWrapper haCapabilities = new VirtualPoolCapabilityValuesWrapper(); haCapabilities.put(VirtualPoolCapabilityValuesWrapper.SIZE, size); haCapabilities.put(VirtualPoolCapabilityValuesWrapper.RESOURCE_COUNT, copyCount); VirtualPool vpool = BlockFullCopyUtils.queryFullCopySourceVPool(srcHAVolume, _dbClient); if (VirtualPool.ProvisioningType.Thin.toString().equalsIgnoreCase( vpool.getSupportedProvisioningType())) { haCapabilities.put(VirtualPoolCapabilityValuesWrapper.THIN_PROVISIONING, Boolean.TRUE); // To guarantee that storage pool for a copy has enough physical // space to contain current allocated capacity of thin source volume haCapabilities.put(VirtualPoolCapabilityValuesWrapper.THIN_VOLUME_PRE_ALLOCATE_SIZE, BlockFullCopyUtils.getAllocatedCapacityForFullCopySource(srcHAVolume, _dbClient)); } List<Recommendation> recommendations = ((VPlexScheduler) _scheduler) .scheduleStorageForImport(srcVarray, vplexSystemURIS, haVarray, haVpool, haCapabilities); if (recommendations.isEmpty()) { throw APIException.badRequests.noStorageForHaVolumesForVplexVolumeCopies(); } // Prepare the HA volumes for the VPLEX volume copy. int copyIndex = 1; for (Recommendation recommendation : recommendations) { VPlexRecommendation haRecommendation = (VPlexRecommendation) recommendation; for (int i = 0; i < haRecommendation.getResourceCount(); i++) { // Determine the name for the HA volume copy. StringBuilder nameBuilder = new StringBuilder(name); nameBuilder.append("-1"); if (copyCount > 1) { nameBuilder.append("-"); nameBuilder.append(copyIndex++); } // Prepare the volume. Volume volume = VPlexBlockServiceApiImpl.prepareVolumeForRequest(size, vplexSystemProject, haVarray, haVpool, haRecommendation.getSourceStorageSystem(), haRecommendation.getSourceStoragePool(), nameBuilder.toString(), null, taskId, _dbClient); volume.addInternalFlags(Flag.INTERNAL_OBJECT); _dbClient.persistObject(volume); copyHAVolumes.add(volume); // Create the volume descriptor and add it to the passed list. VolumeDescriptor volumeDescriptor = new VolumeDescriptor( VolumeDescriptor.Type.BLOCK_DATA, volume.getStorageController(), volume.getId(), volume.getPool(), haCapabilities); volumeDescriptors.add(volumeDescriptor); } } return copyHAVolumes; } /** * Prepares the VPLEX volume copies. * * @param name The base name for the volume. * @param fullCopySetName * @param copyCount The total number of copies. * @param copyIndex The index for this copy. * @param size The size for the HA volume. * @param fcSourceObject The VPLEX volume or the snapshot being copied. * @param srcProject The project for the VPLEX volume being copied. * @param srcVarray The virtual array for the VPLEX volume being copied. * @param srcVpool The virtual pool for the VPLEX volume being copied. * @param srcSystemURI The VPLEX system URI. * @param primaryVolume The primary volume for the copy. * @param haVolume The HA volume for the copy, or null. * @param taskId The task identifier. * @param volumeDescriptors The list of descriptors. * * @return A reference to the prepared VPLEX volume copy. */ private Volume prepareFullCopyVPlexVolume(String name, String fullCopySetName, int copyCount, int copyIndex, long size, BlockObject fcSourceObject, Project srcProject, VirtualArray srcVarray, VirtualPool srcVpool, URI srcSystemURI, Volume primaryVolume, Volume haVolume, String taskId, List<VolumeDescriptor> volumeDescriptors) { // Determine the VPLEX volume copy name. StringBuilder nameBuilder = new StringBuilder(name); if (copyCount > 1) { nameBuilder.append("-"); nameBuilder.append(copyIndex + 1); } // Prepare the VPLEX volume copy. Volume vplexCopyVolume = VPlexBlockServiceApiImpl.prepareVolumeForRequest(size, srcProject, srcVarray, srcVpool, srcSystemURI, NullColumnValueGetter.getNullURI(), nameBuilder.toString(), ResourceOperationTypeEnum.CREATE_VOLUME_FULL_COPY, taskId, _dbClient); // Create a volume descriptor and add it to the passed list. VolumeDescriptor vplexCopyVolumeDescr = new VolumeDescriptor( VolumeDescriptor.Type.VPLEX_VIRT_VOLUME, srcSystemURI, vplexCopyVolume.getId(), null, null); volumeDescriptors.add(vplexCopyVolumeDescr); // Set the associated volumes for this new VPLEX volume copy to // the copy of the backend primary and the newly prepared HA // volume if the VPLEX volume being copied is distributed. vplexCopyVolume.setAssociatedVolumes(new StringSet()); StringSet assocVolumes = vplexCopyVolume.getAssociatedVolumes(); assocVolumes.add(primaryVolume.getId().toString()); if (haVolume != null) { assocVolumes.add(haVolume.getId().toString()); } // Set the VPLEX source volume or the snapshot for the copy. vplexCopyVolume.setAssociatedSourceVolume(fcSourceObject.getId()); // Except for the Openstack, all Copies always created active. if (VPlexUtil.isOpenStackBackend(fcSourceObject, _dbClient)) { vplexCopyVolume.setSyncActive(Boolean.FALSE); } else { vplexCopyVolume.setSyncActive(Boolean.TRUE); } // For Application, set the user provided clone name on all the clones to identify clone set if (fcSourceObject instanceof Volume && ((Volume) fcSourceObject).getApplication(_dbClient) != null) { vplexCopyVolume.setFullCopySetName(fullCopySetName); } // Persist the copy. _dbClient.persistObject(vplexCopyVolume); return vplexCopyVolume; } /** * Places and prepares the primary copy volumes when copying a VPLEX virtual * volume. * * @param name The base name for the volume. * @param copyCount The number of copies to be made. * @param srcBlockObject The primary volume of the VPLEX volume or snapshot being copied. * @param srcCapabilities The capabilities of the primary volume. * @param volumeDescriptors The list of descriptors. * @param vPool The vPool to which the source object belongs to. * * @return A list of the prepared primary volumes for the VPLEX volume copy. */ private List<Volume> prepareFullCopyPrimaryVolumes(String name, int copyCount, BlockObject srcBlockObject, VirtualPoolCapabilityValuesWrapper srcCapabilities, List<VolumeDescriptor> volumeDescriptors, VirtualPool vPool) { List<Volume> copyPrimaryVolumes = new ArrayList<>(); // Get the placement recommendations for the primary volume copies. // Use the same method as is done for native volume copy. VirtualArray vArray = _dbClient.queryObject(VirtualArray.class, srcBlockObject.getVirtualArray()); List<VolumeRecommendation> recommendations = ((VPlexScheduler) _scheduler).getBlockScheduler() .getRecommendationsForVolumeClones(vArray, vPool, srcBlockObject, srcCapabilities); if (recommendations.isEmpty()) { throw APIException.badRequests.noStorageForPrimaryVolumesForVplexVolumeCopies(); } // Prepare the copy volumes for each recommendation. Again, // use the same manner as is done for native volume copy. StringBuilder nameBuilder = new StringBuilder(name); nameBuilder.append("-0"); int copyIndex = (copyCount > 1) ? 1 : 0; for (VolumeRecommendation recommendation : recommendations) { Volume volume = StorageScheduler.prepareFullCopyVolume(_dbClient, nameBuilder.toString(), srcBlockObject, recommendation, copyIndex++, srcCapabilities); volume.addInternalFlags(Flag.INTERNAL_OBJECT); _dbClient.persistObject(volume); copyPrimaryVolumes.add(volume); // Create the volume descriptor and add it to the passed list. VolumeDescriptor volumeDescriptor = new VolumeDescriptor( VolumeDescriptor.Type.VPLEX_IMPORT_VOLUME, volume.getStorageController(), volume.getId(), volume.getPool(), srcCapabilities); volumeDescriptors.add(volumeDescriptor); } return copyPrimaryVolumes; } /** * {@inheritDoc} */ @Override public TaskList activate(BlockObject fcSourceObj, Volume fullCopyVolume) { // VPLEX volumes are created active, so we can just call super // and take the is already activated path. return super.activate(fcSourceObj, fullCopyVolume); } /** * {@inheritDoc} */ @Override public TaskList detach(BlockObject fcSourceObj, Volume fullCopyVolume) { // If full copy volume is already detached or was never // activated, return detach action is completed successfully // as done in base class. Otherwise, send detach full copy // request to controller. TaskList taskList = new TaskList(); String taskId = UUID.randomUUID().toString(); if ((BlockFullCopyUtils.isFullCopyDetached(fullCopyVolume, _dbClient)) || (BlockFullCopyUtils.isFullCopyInactive(fullCopyVolume, _dbClient))) { super.detach(fcSourceObj, fullCopyVolume); } else { // You cannot create a full copy of a VPLEX snapshot, so // the source will be a volume. Volume sourceVolume = (Volume) fcSourceObj; // If the source is in a CG, then we will detach the corresponding // full copies for all the volumes in the CG. Since we did not allow // full copies for volumes or snaps in CGs prior to Jedi, there should // be a full copy for all volumes in the CG. Map<URI, Volume> fullCopyMap = getFullCopySetMap(sourceVolume, fullCopyVolume); Set<URI> fullCopyURIs = fullCopyMap.keySet(); // Get the storage system for the source volume. StorageSystem sourceSystem = _dbClient.queryObject(StorageSystem.class, sourceVolume.getStorageController()); URI sourceSystemURI = sourceSystem.getId(); // Create the detach task on the full copy volumes. for (URI fullCopyURI : fullCopyURIs) { Operation op = _dbClient.createTaskOpStatus(Volume.class, fullCopyURI, taskId, ResourceOperationTypeEnum.DETACH_VOLUME_FULL_COPY); fullCopyMap.get(fullCopyURI).getOpStatus().put(taskId, op); TaskResourceRep fullCopyVolumeTask = TaskMapper.toTask( fullCopyMap.get(fullCopyURI), taskId, op); taskList.getTaskList().add(fullCopyVolumeTask); } addConsistencyGroupTasks(Arrays.asList(sourceVolume), taskList, taskId, ResourceOperationTypeEnum.DETACH_CONSISTENCY_GROUP_FULL_COPY); // Invoke the controller. try { VPlexController controller = getController(VPlexController.class, DiscoveredDataObject.Type.vplex.toString()); controller.detachFullCopy(sourceSystemURI, new ArrayList<>( fullCopyURIs), taskId); } catch (InternalException ie) { s_logger.error("Controller error: Failed to detach volume full copy {}", fullCopyVolume.getId(), ie); handleFailedRequest(taskId, taskList, new ArrayList<>(fullCopyMap.values()), ie, false); } } return taskList; } /** * {@inheritDoc} */ @Override public TaskList restoreSource(Volume sourceVolume, Volume fullCopyVolume) { // Create the task list. TaskList taskList = new TaskList(); // Create a unique task id. String taskId = UUID.randomUUID().toString(); // If the source is in a CG, then we will restore the corresponding // full copies for all the volumes in the CG. Since we did not allow // full copies for volumes or snaps in CGs prior to Jedi, there should // be a full copy for all volumes in the CG. Map<URI, Volume> fullCopyMap = getFullCopySetMap(sourceVolume, fullCopyVolume); Set<URI> fullCopyURIs = fullCopyMap.keySet(); // Get the storage system for the source volume. StorageSystem sourceSystem = _dbClient.queryObject(StorageSystem.class, sourceVolume.getStorageController()); URI sourceSystemURI = sourceSystem.getId(); // Create the restore task on the full copy volumes. // The controller expects the task to be on the full // copy even though the source is being restored. // Not really sure why. for (URI fullCopyURI : fullCopyURIs) { Operation op = _dbClient.createTaskOpStatus(Volume.class, fullCopyURI, taskId, ResourceOperationTypeEnum.RESTORE_VOLUME_FULL_COPY); fullCopyMap.get(fullCopyURI).getOpStatus().put(taskId, op); TaskResourceRep fullCopyVolumeTask = TaskMapper.toTask( fullCopyMap.get(fullCopyURI), taskId, op); taskList.getTaskList().add(fullCopyVolumeTask); } addConsistencyGroupTasks(Arrays.asList(sourceVolume), taskList, taskId, ResourceOperationTypeEnum.RESTORE_CONSISTENCY_GROUP_FULL_COPY); // Invoke the controller. try { BlockOrchestrationController controller = getController(BlockOrchestrationController.class, BlockOrchestrationController.BLOCK_ORCHESTRATION_DEVICE); controller.restoreFromFullCopy(sourceSystemURI, new ArrayList<>( fullCopyURIs), taskId); } catch (InternalException ie) { s_logger.error("Controller error: Failed to restore volume full copy {}", fullCopyVolume.getId(), ie); handleFailedRequest(taskId, taskList, new ArrayList<>(fullCopyMap.values()), ie, false); } return taskList; } /** * {@inheritDoc} */ @Override public TaskList resynchronizeCopy(Volume sourceVolume, Volume fullCopyVolume) { // Create the task list. TaskList taskList = new TaskList(); // Create a unique task id. String taskId = UUID.randomUUID().toString(); // If the source is in a CG, then we will resynchronize the corresponding // full copies for all the volumes in the CG. Since we did not allow // full copies for volumes or snaps in CGs prior to Jedi, there should // be a full copy for all volumes in the CG. Map<URI, Volume> fullCopyMap = getFullCopySetMap(sourceVolume, fullCopyVolume); Set<URI> fullCopyURIs = fullCopyMap.keySet(); // Get the storage system for the source volume. StorageSystem sourceSystem = _dbClient.queryObject(StorageSystem.class, sourceVolume.getStorageController()); URI sourceSystemURI = sourceSystem.getId(); // Create the resynchronize task on the full copy volumes. for (URI fullCopyURI : fullCopyURIs) { Operation op = _dbClient.createTaskOpStatus(Volume.class, fullCopyURI, taskId, ResourceOperationTypeEnum.RESYNCHRONIZE_VOLUME_FULL_COPY); fullCopyMap.get(fullCopyURI).getOpStatus().put(taskId, op); TaskResourceRep fullCopyVolumeTask = TaskMapper.toTask( fullCopyMap.get(fullCopyURI), taskId, op); taskList.getTaskList().add(fullCopyVolumeTask); } addConsistencyGroupTasks(Arrays.asList(sourceVolume), taskList, taskId, ResourceOperationTypeEnum.RESYNCHRONIZE_CONSISTENCY_GROUP_FULL_COPY); // Invoke the controller. try { VPlexController controller = getController(VPlexController.class, DiscoveredDataObject.Type.vplex.toString()); controller.resyncFullCopy(sourceSystemURI, new ArrayList<>( fullCopyURIs), taskId); } catch (InternalException ie) { s_logger.error("Controller error: Failed to resync volume full copy {}", fullCopyVolume.getId(), ie); handleFailedRequest(taskId, taskList, new ArrayList<>(fullCopyMap.values()), ie, false); } return taskList; } /** * {@inheritDoc} */ @Override public VolumeRestRep checkProgress(URI sourceURI, Volume fullCopyVolume) { // Get the native backend full copy volume for this VPLEX // full copy volume. Volume nativeFullCopyVolume = VPlexUtil.getVPLEXBackendVolume(fullCopyVolume, true, _dbClient); // Call super to check the progress of the backend full // copy volume. Integer percentSynced = getSyncPercentage( nativeFullCopyVolume.getAssociatedSourceVolume(), nativeFullCopyVolume); // The synchronization progress of the VPLEX full copy is that // of the backend full copy. VolumeRestRep volumeRestRep = BlockMapper.map(_dbClient, fullCopyVolume); volumeRestRep.getProtection().getFullCopyRep().setPercentSynced(percentSynced); return volumeRestRep; } /** * {@inheritDoc} */ @Override protected void verifyFullCopyRequestCount(BlockObject fcSourceObj, int count) { // Verify the requested copy count. You can only // have as many as is allowed by the source backend volume. if (fcSourceObj instanceof Volume) { Volume fcSourceVolume = (Volume) fcSourceObj; fcSourceObj = VPlexUtil.getVPLEXBackendVolume(fcSourceVolume, true, _dbClient, true); } // Verify if the source backend volume supports full copy URI systemURI = fcSourceObj.getStorageController(); StorageSystem system = _dbClient.queryObject(StorageSystem.class, systemURI); int maxCount = Integer.MAX_VALUE; if (system != null) { maxCount = BlockFullCopyManager.getMaxFullCopiesForSystemType(system.getSystemType()); } // If max count is 0, then the operation is not supported if (maxCount == 0) { throw APIException.badRequests.fullCopyNotSupportedByBackendSystem(fcSourceObj.getId()); } BlockFullCopyUtils.validateActiveFullCopyCount(fcSourceObj, count, _dbClient); } /** * {@inheritDoc} */ @Override public TaskList establishVolumeAndFullCopyGroupRelation(Volume sourceVolume, Volume fullCopyVolume) { // Create the task list. TaskList taskList = new TaskList(); // Create a unique task id. String taskId = UUID.randomUUID().toString(); // Get the id of the source volume. URI sourceVolumeURI = sourceVolume.getId(); // Get the id of the full copy volume. URI fullCopyURI = fullCopyVolume.getId(); // Get the storage system for the source volume. StorageSystem sourceSystem = _dbClient.queryObject(StorageSystem.class, sourceVolume.getStorageController()); URI sourceSystemURI = sourceSystem.getId(); // Create the task on the full copy volume. Operation op = _dbClient.createTaskOpStatus(Volume.class, fullCopyURI, taskId, ResourceOperationTypeEnum.ESTABLISH_VOLUME_FULL_COPY); fullCopyVolume.getOpStatus().put(taskId, op); TaskResourceRep fullCopyVolumeTask = TaskMapper.toTask( fullCopyVolume, taskId, op); taskList.getTaskList().add(fullCopyVolumeTask); // Invoke the controller. try { VPlexController controller = getController(VPlexController.class, DiscoveredDataObject.Type.vplex.toString()); controller.establishVolumeAndFullCopyGroupRelation(sourceSystemURI, sourceVolumeURI, fullCopyURI, taskId); } catch (InternalException ie) { s_logger.error("Controller error", ie); // Update the status for the VPLEX volume copies and their // corresponding tasks. if (op != null) { op.error(ie); fullCopyVolume.getOpStatus().updateTaskStatus(taskId, op); _dbClient.persistObject(fullCopyVolume); fullCopyVolumeTask.setState(op.getStatus()); fullCopyVolumeTask.setMessage(op.getMessage()); } } return taskList; } /** * {@inheritDoc} */ @Override public boolean volumeCanBeDeleted(Volume volume) { // For OpenStack, not required to do the detach checks if (VPlexUtil.isOpenStackBackend(volume, _dbClient)) { return true; } return super.volumeCanBeDeleted(volume); } /** * {@inheritDoc} */ @Override public boolean volumeCanBeExpanded(Volume volume) { // For OpenStack, not required to do the detach checks if (VPlexUtil.isOpenStackBackend(volume, _dbClient)) { return true; } return super.volumeCanBeExpanded(volume); } /** * {@inheritDoc} */ @Override public void validateSnapshotCreateRequest(Volume requestedVolume, List<Volume> volumesToSnap) { for (Volume volumeToSnap : volumesToSnap) { Volume volumeToValidate = volumeToSnap; StorageSystem system = _dbClient.queryObject(StorageSystem.class, volumeToValidate.getStorageController()); if (DiscoveredDataObject.Type.vplex.name().equals(system.getSystemType())) { // VPLEX volumes were passed rather than the backend volumes themselves // so get the system for the source side backend volume. We only need the // source side backend volume as that is the only volume that is snapped // in the case of distributed VPLEX volumes. volumeToValidate = VPlexUtil.getVPLEXBackendVolume(volumeToSnap, true, _dbClient); system = _dbClient.queryObject(StorageSystem.class, volumeToValidate.getStorageController()); } // Call the platform specific validation for the backend volume. BlockFullCopyApi fullCopyApiImpl = _fullCopyMgr.getPlatformSpecificFullCopyImplForSystem(system); fullCopyApiImpl.validateSnapshotCreateRequest(volumeToValidate, Arrays.asList(volumeToValidate)); } } }