/** * Validate the SourceVolume VArray details with ingested target volumes VArray. * * @param unManagedVolume * @param VirtualPool * @return */ private void validateSourceVolumeVarrayWithTargetVPool( UnManagedVolume unManagedVolume, VirtualPool sourceVPool) { StringSetMap unManagedVolumeInformation = unManagedVolume.getVolumeInformation(); // find whether all targets are ingested StringSet targetUnManagedVolumeGuids = unManagedVolumeInformation.get(SupportedVolumeInformation.REMOTE_MIRRORS.toString()); if (null != targetUnManagedVolumeGuids && !targetUnManagedVolumeGuids.isEmpty()) { StringSet targetVolumeNativeGuids = VolumeIngestionUtil.getListofVolumeIds(targetUnManagedVolumeGuids); // check whether target exists List<URI> targetUris = VolumeIngestionUtil.getVolumeUris(targetVolumeNativeGuids, _dbClient); if (null == targetUris || targetUris.isEmpty()) { _logger.info( "None of the targets ingested for source volume: {}", unManagedVolume.getNativeGuid()); } else { List<Volume> targetVolumes = _dbClient.queryObject(Volume.class, targetUris); for (Volume targetVolume : targetVolumes) { Map<URI, VpoolRemoteCopyProtectionSettings> settings = sourceVPool.getRemoteProtectionSettings(sourceVPool, _dbClient); if (null == settings || settings.size() == 0 || !settings.containsKey(targetVolume.getVirtualArray())) { _logger.info( "Target Volume's VArray {} is not matching already ingested source volume virtual pool's remote VArray {}", targetVolume.getVirtualArray(), Joiner.on(",").join(settings.keySet())); throw IngestionException.exceptions.unmanagedSRDFSourceVolumeVArrayMismatch( unManagedVolume.getLabel(), targetVolume.getVirtualArray().toString()); } } } } }
/** * Validate the Target Volume VirtualArray with the Source Volume VPool VirtualArray. * * @param type * @param unManagedVolume * @param virtualArray */ private void validateTargetVolumeVpoolWithSourceVolume( UnManagedVolume unManagedVolume, VirtualArray virtualArray) { String sourceUnManagedVolumeId = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.REMOTE_MIRROR_SOURCE_VOLUME.toString(), unManagedVolume.getVolumeInformation()); String sourceVolumeId = sourceUnManagedVolumeId.replace( VolumeIngestionUtil.UNMANAGEDVOLUME, VolumeIngestionUtil.VOLUME); List<URI> sourceUris = _dbClient.queryByConstraint( AlternateIdConstraint.Factory.getVolumeNativeGuidConstraint(sourceVolumeId)); if (sourceUris.isEmpty()) { _logger.info( "Source {} Not found for target {}", sourceVolumeId, unManagedVolume.getNativeGuid()); } else { // if source volume is ingested, then Volume sourceVolume = _dbClient.queryObject(Volume.class, sourceUris.get(0)); // check whether the source Volume's VPool is actually having this // target Volume's varray // specified as remote VirtualPool sourceVPool = _dbClient.queryObject(VirtualPool.class, sourceVolume.getVirtualPool()); Map<URI, VpoolRemoteCopyProtectionSettings> settings = sourceVPool.getRemoteProtectionSettings(sourceVPool, _dbClient); if (null == settings || settings.isEmpty() || !settings.containsKey(virtualArray.getId())) { _logger.info( "Target Volume's VArray {} is not matching already ingested source volume virtual pool's remote VArray ", virtualArray.getId()); throw IngestionException.exceptions.unmanagedSRDFTargetVolumeVArrayMismatch( unManagedVolume.getLabel(), sourceVolume.getVirtualArray().toString()); } } }
/** * 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 srcPrimaryVolume The primary volume of the VPLEX volume being copied. * @param srcCapabilities The capabilities of the primary volume. * @param volumeDescriptors The list of descriptors. * @return A list of the prepared primary volumes for the VPLEX volume copy. */ private List<Volume> prepareFullCopyPrimaryVolumes( String name, int copyCount, Volume srcPrimaryVolume, VirtualPoolCapabilityValuesWrapper srcCapabilities, List<VolumeDescriptor> volumeDescriptors) { List<Volume> copyPrimaryVolumes = new ArrayList<Volume>(); // 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, srcPrimaryVolume.getVirtualArray()); VirtualPool vPool = _dbClient.queryObject(VirtualPool.class, srcPrimaryVolume.getVirtualPool()); List<VolumeRecommendation> recommendations = ((VPlexScheduler) _scheduler) .getBlockScheduler() .getRecommendationsForVolumeClones(vArray, vPool, srcPrimaryVolume, 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(), srcPrimaryVolume, 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; }
/** * Updates the initiator to target list map in the export mask * * @throws Exception */ private void updateTargetsInExportMask( StorageSystem storage, Volume volume, Map<Volume, Map<String, List<String>>> volumeToInitiatorTargetMapFromAttachResponse, List<Initiator> fcInitiatorList, ExportMask exportMask) throws Exception { log.debug("START - updateTargetsInExportMask"); // ITLS for initiator URIs vs Target port URIs - This will be the final // filtered list to send for the zoning map update Map<URI, List<URI>> mapFilteredInitiatorURIVsTargetURIList = new HashMap<URI, List<URI>>(); // From the initiators list, construct the map of initiator WWNs to // their URIs Map<String, URI> initiatorsWWNVsURI = getWWNvsURIFCInitiatorsMap(fcInitiatorList); URI varrayURI = volume.getVirtualArray(); /* * Get the list of storage ports from the storage system which are * associated with the varray This will be map of storage port WWNs with * their URIs */ Map<String, URI> mapVarrayTaggedPortWWNVsURI = getVarrayTaggedStoragePortWWNs(storage, varrayURI); // List of WWN entries, used below for filtering the target port list // from attach response Set<String> varrayTaggedPortWWNs = mapVarrayTaggedPortWWNVsURI.keySet(); URI vpoolURI = volume.getVirtualPool(); VirtualPool vpool = dbClient.queryObject(VirtualPool.class, vpoolURI); int pathsPerInitiator = vpool.getPathsPerInitiator(); // Process the attach response output Set<Volume> volumeKeysSet = volumeToInitiatorTargetMapFromAttachResponse.keySet(); for (Volume volumeRes : volumeKeysSet) { log.info( String.format( "Processing attach response for the volume with URI %s and name %s", volumeRes.getId(), volumeRes.getLabel())); Map<String, List<String>> initiatorTargetMap = volumeToInitiatorTargetMapFromAttachResponse.get(volumeRes); Set<String> initiatorKeysSet = initiatorTargetMap.keySet(); for (String initiatorKey : initiatorKeysSet) { // The list of filtered target ports ( which are varray tagged ) // from the attach response List<String> filteredTargetList = filterTargetsFromResponse(varrayTaggedPortWWNs, initiatorTargetMap, initiatorKey); log.info( String.format( "For initiator %s accessible storage ports are %s ", initiatorKey, filteredTargetList.toString())); List<String> tmpTargetList = null; if (!isVplex(volumeRes)) { // For VPLEX - no path validations // Path validations are required only for the Host Exports tmpTargetList = checkPathsPerInitiator(pathsPerInitiator, filteredTargetList); if (null == tmpTargetList) { // Rollback case - throw the exception throw new Exception( String.format( "Paths per initiator criteria is not met for the initiator : %s " + " Target counts is: %s Expected paths per initiator is: %s", initiatorKey, String.valueOf(filteredTargetList.size()), String.valueOf(pathsPerInitiator))); } } else { tmpTargetList = filteredTargetList; } // Now populate URIs for the map to be returned - convert WWNs // to URIs populateInitiatorTargetURIMap( mapFilteredInitiatorURIVsTargetURIList, initiatorsWWNVsURI, mapVarrayTaggedPortWWNVsURI, initiatorKey, tmpTargetList); } // End initiator iteration } // End volume iteration // Clean all existing targets in the export mask and add new targets List<URI> storagePortListFromMask = StringSetUtil.stringSetToUriList(exportMask.getStoragePorts()); for (URI removeUri : storagePortListFromMask) { exportMask.removeTarget(removeUri); } exportMask.setStoragePorts(null); // Now add new target ports and populate the zoning map Set<URI> initiatorURIKeys = mapFilteredInitiatorURIVsTargetURIList.keySet(); for (URI initiatorURI : initiatorURIKeys) { List<URI> storagePortURIList = mapFilteredInitiatorURIVsTargetURIList.get(initiatorURI); for (URI portURI : storagePortURIList) { exportMask.addTarget(portURI); } } log.debug("END - updateTargetsInExportMask"); }
/** * 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<Volume>(); // Get the storage placement recommendations for the volumes. // Placement must occur on the same VPLEX system Set<URI> vplexSystemURIS = new HashSet<URI>(); 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.getSourceDevice(), haRecommendation.getSourcePool(), 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; }
/** {@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; TaskList taskList = new TaskList(); List<Volume> vplexCopyVolumes = new ArrayList<Volume>(); List<VolumeDescriptor> volumeDescriptors = new ArrayList<VolumeDescriptor>(); List<BlockObject> sortedSourceObjectList = sortFullCopySourceList(fcSourceObjList); for (BlockObject fcSourceObj : sortedSourceObjectList) { URI fcSourceURI = fcSourceObj.getId(); if (URIUtil.isType(fcSourceURI, BlockSnapshot.class)) { // Full copy of snapshots is not supported for VPLEX. return super.create(sortedSourceObjectList, varray, name, createInactive, count, taskId); } Volume vplexSrcVolume = (Volume) fcSourceObj; String copyName = name + (sortedSourceObjectList.size() > 1 ? "-" + ++sourceCounter : ""); // 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. vplexSrcSystemId = fcSourceObj.getStorageController(); VolumeDescriptor vplexSrcVolumeDescr = new VolumeDescriptor( VolumeDescriptor.Type.VPLEX_VIRT_VOLUME, vplexSrcSystemId, fcSourceURI, null, null); Map<String, Object> descrParams = new HashMap<String, Object>(); descrParams.put(VolumeDescriptor.PARAM_IS_COPY_SOURCE_ID, Boolean.TRUE); vplexSrcVolumeDescr.setParameters(descrParams); volumeDescriptors.add(vplexSrcVolumeDescr); // 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); // 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. Volume vplexSrcPrimaryVolume = null; Volume vplexSrcHAVolume = null; StringSet assocVolumeURIs = vplexSrcVolume.getAssociatedVolumes(); 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 = 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. List<Volume> vplexCopyPrimaryVolumes = prepareFullCopyPrimaryVolumes( copyName, count, vplexSrcPrimaryVolume, capabilities, volumeDescriptors); // 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<Volume>(); if (vplexSrcHAVolume != null) { vplexCopyHAVolumes.addAll( prepareFullCopyHAVolumes( copyName, count, size, vplexSrcSystem, vplexSystemProject, varray, vplexSrcHAVolume, taskId, volumeDescriptors)); } // 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.size() != 0) { vplexCopyHAVolume = vplexCopyHAVolumes.get(i); } Volume vplexCopyVolume = prepareFullCopyVPlexVolume( copyName, count, i, size, vplexSrcVolume, vplexSrcProject, varray, vpool, vplexSrcSystemId, vplexCopyPrimaryVolume, vplexCopyHAVolume, taskId, volumeDescriptors); vplexCopyVolumes.add(vplexCopyVolume); // Create task for each copy. Operation op = vplexCopyVolume.getOpStatus().get(taskId); TaskResourceRep task = toTask(vplexCopyVolume, taskId, op); taskList.getTaskList().add(task); } } // Invoke the VPLEX controller to create the copies. try { s_logger.info("Getting VPlex controller {}.", taskId); VPlexController controller = getController(VPlexController.class, DiscoveredDataObject.Type.vplex.toString()); // TBD controller needs to be updated to handle CGs. controller.createFullCopy(vplexSrcSystemId, volumeDescriptors, taskId); s_logger.info("Successfully invoked controller."); } catch (InternalException e) { s_logger.error("Controller error", e); // Update the status for the VPLEX volume copies and their // corresponding tasks. for (Volume vplexCopyVolume : vplexCopyVolumes) { Operation op = vplexCopyVolume.getOpStatus().get(taskId); if (op != null) { op.error(e); vplexCopyVolume.getOpStatus().updateTaskStatus(taskId, op); _dbClient.persistObject(vplexCopyVolume); for (TaskResourceRep task : taskList.getTaskList()) { if (task.getResource().getId().equals(vplexCopyVolume.getId())) { task.setState(op.getStatus()); task.setMessage(op.getMessage()); break; } } } } // 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.persistObject(volume); } } } return taskList; }