@Override public void createOrAddVolumesToExportMask( URI arrayURI, URI exportGroupURI, URI exportMaskURI, Map<URI, Integer> volumeMap, List<URI> initiatorURIs2, TaskCompleter completer, String stepId) { try { StorageSystem array = _dbClient.queryObject(StorageSystem.class, arrayURI); ExportMask exportMask = _dbClient.queryObject(ExportMask.class, exportMaskURI); WorkflowStepCompleter.stepExecuting(stepId); // If the exportMask isn't found, or has been deleted, fail, ask user to retry. if (exportMask == null || exportMask.getInactive()) { _log.info(String.format("ExportMask %s deleted or inactive, failing", exportMaskURI)); ServiceError svcerr = VPlexApiException.errors.createBackendExportMaskDeleted( exportMaskURI.toString(), arrayURI.toString()); WorkflowStepCompleter.stepFailed(stepId, svcerr); return; } // Protect concurrent operations by locking {host, array} dupple. // Lock will be released when workflow step completes. List<String> lockKeys = ControllerLockingUtil.getHostStorageLockKeys( _dbClient, ExportGroupType.Host, StringSetUtil.stringSetToUriList(exportMask.getInitiators()), arrayURI); getWorkflowService() .acquireWorkflowStepLocks( stepId, lockKeys, LockTimeoutValue.get(LockType.VPLEX_BACKEND_EXPORT)); // Fetch the Initiators List<URI> initiatorURIs = new ArrayList<URI>(); List<Initiator> initiators = new ArrayList<Initiator>(); for (String initiatorId : exportMask.getInitiators()) { Initiator initiator = _dbClient.queryObject(Initiator.class, URI.create(initiatorId)); if (initiator != null) { initiators.add(initiator); initiatorURIs.add(initiator.getId()); } } // We do not refresh here, as the VNXExportOperations code will throw an exception // if the StorageGroup was not found. BlockStorageDevice device = _blockController.getDevice(array.getSystemType()); if (!exportMask.hasAnyVolumes() && exportMask.getCreatedBySystem()) { // We are creating this ExportMask on the hardware! (Maybe not the first time though...) // Fetch the targets List<URI> targets = new ArrayList<URI>(); for (String targetId : exportMask.getStoragePorts()) { targets.add(URI.create(targetId)); } // Clear the export_mask nativeId; otherwise the VnxExportOps will attempt to look it // up and fail. An empty String will suffice as having no nativeId. if (exportMask.getNativeId() != null) { exportMask.setNativeId(""); _dbClient.updateAndReindexObject(exportMask); } // The default completer passed in is for add volume, create correct one completer = new ExportMaskCreateCompleter( exportGroupURI, exportMaskURI, initiatorURIs, volumeMap, stepId); device.doExportCreate(array, exportMask, volumeMap, initiators, targets, completer); } else { device.doExportAddVolumes(array, exportMask, initiators, volumeMap, completer); } } catch (Exception ex) { _log.error("Failed to create or add volumes to export mask for vnx: ", ex); VPlexApiException vplexex = DeviceControllerExceptions.vplex.addStepsForCreateVolumesFailed(ex); WorkflowStepCompleter.stepFailed(stepId, vplexex); } }
/** * Update the zoning map entries from the updated target list in the mask * * <p>1. Clean existing zoning map entries 2. From the target storage ports in the mask, generate * a map of networkURI string vs list of target storage ports 3. From the initiator ports in the * mask, generate a map of its URI vs InitiatorWWN 4. From the initiatorPortMap, generate map of * its WWN vs networkURI string 5. Based on the networkURI matching, generate zoning map entries * adhering to vplex best practices 6. Persist the updated mask. * * @param initiatorPortMap * @param exportMask */ private void updateZoningMap( Map<URI, List<StoragePort>> initiatorPortMap, Map<String, Set<String>> directorToInitiatorIds, ExportMask exportMask) { // STEP 1 - Clean the existing zoning map for (String initiatorURIStr : exportMask.getZoningMap().keySet()) { exportMask.removeZoningMapEntry(initiatorURIStr); } exportMask.setZoningMap(null); // STEP 2- From Back-end storage system ports, which are used as target storage ports for VPLEX // generate a map of networkURI string vs list of target storage ports. Map<String, List<StoragePort>> nwUriVsTargetPortsFromMask = new HashMap<>(); StringSet targetPorts = exportMask.getStoragePorts(); for (String targetPortUri : targetPorts) { StoragePort targetPort = _dbClient.queryObject(StoragePort.class, URI.create(targetPortUri)); String networkUri = targetPort.getNetwork().toString(); if (nwUriVsTargetPortsFromMask.containsKey(networkUri)) { nwUriVsTargetPortsFromMask.get(networkUri).add(targetPort); } else { nwUriVsTargetPortsFromMask.put(networkUri, new ArrayList<StoragePort>()); nwUriVsTargetPortsFromMask.get(networkUri).add(targetPort); } } // STEP 3 - From the initiator ports in the mask, generate a map of its URI vs InitiatorWWN // Map<String, URI> initiatorWWNvsUriFromMask = new HashMap<>(); Map<String, String> initiatorUrivsWWNFromMask = new HashMap<>(); StringSet initiatorPorts = exportMask.getInitiators(); for (String initiatorUri : initiatorPorts) { Initiator initiator = _dbClient.queryObject(Initiator.class, URI.create(initiatorUri)); String initiatorWWN = initiator.getInitiatorPort(); initiatorUrivsWWNFromMask.put(initiator.getId().toString(), initiatorWWN); } // STEP 4 - Convert networkURIvsStoragePort to Initiator Port WWN vs NetworkURI Map<String, String> initiatorWWNvsNetworkURI = new HashMap<>(); Set<URI> networkURIs = initiatorPortMap.keySet(); for (URI networkURI : networkURIs) { List<StoragePort> initiatorPortList = initiatorPortMap.get(networkURI); List<String> initiatorWWNList = new ArrayList<>(initiatorPortList.size()); for (StoragePort initPort : initiatorPortList) { initiatorWWNList.add(initPort.getPortNetworkId()); initiatorWWNvsNetworkURI.put(initPort.getPortNetworkId(), networkURI.toString()); } } // STEP 5 - Consider directors to restrict paths not more than 4 for each director // And add the zoning map entries to adhere to the VPLEX best practices. Map<StoragePort, Integer> portUsage = new HashMap<>(); Set<String> directorKeySet = directorToInitiatorIds.keySet(); for (String director : directorKeySet) { Set<String> initiatorIds = directorToInitiatorIds.get(director); int directorPaths = 0; for (String initiatorId : initiatorIds) { if (4 == directorPaths) { break; } String initWWN = initiatorUrivsWWNFromMask.get(initiatorId); String initiatorNetworkURI = initiatorWWNvsNetworkURI.get(initWWN); List<StoragePort> matchingTargetPorts = nwUriVsTargetPortsFromMask.get(initiatorNetworkURI); if (null != matchingTargetPorts && !matchingTargetPorts.isEmpty()) { StoragePort assignedPort = assignPortBasedOnUsage(matchingTargetPorts, portUsage); StringSet targetPortURIs = new StringSet(); targetPortURIs.add(assignedPort.getId().toString()); _log.info( String.format( "Adding zoning map entry - Initiator is %s and its targetPorts %s", initiatorId, targetPortURIs.toString())); exportMask.addZoningMapEntry(initiatorId, targetPortURIs); directorPaths++; } } } // STEP 6 - persist the mask _dbClient.updateAndReindexObject(exportMask); }
@Override public void createOrAddVolumesToExportMask( URI arrayURI, URI exportGroupURI, URI exportMaskURI, Map<URI, Integer> volumeMap, TaskCompleter completer, String stepId) { _log.debug("START - createOrAddVolumesToExportMask"); try { WorkflowStepCompleter.stepExecuting(stepId); StorageSystem array = _dbClient.queryObject(StorageSystem.class, arrayURI); ExportMask exportMask = _dbClient.queryObject(ExportMask.class, exportMaskURI); // If the exportMask isn't found, or has been deleted, fail, ask user to retry. if (exportMask == null || exportMask.getInactive()) { _log.info(String.format("ExportMask %s deleted or inactive, failing", exportMaskURI)); ServiceError svcerr = VPlexApiException.errors.createBackendExportMaskDeleted( exportMaskURI.toString(), arrayURI.toString()); WorkflowStepCompleter.stepFailed(stepId, svcerr); return; } // Protect concurrent operations by locking {host, array} dupple. // Lock will be released when work flow step completes. List<String> lockKeys = ControllerLockingUtil.getHostStorageLockKeys( _dbClient, ExportGroupType.Host, StringSetUtil.stringSetToUriList(exportMask.getInitiators()), arrayURI); getWorkflowService() .acquireWorkflowStepLocks( stepId, lockKeys, LockTimeoutValue.get(LockType.VPLEX_BACKEND_EXPORT)); // Refresh the ExportMask BlockStorageDevice device = _blockController.getDevice(array.getSystemType()); if (!exportMask.hasAnyVolumes()) { /* * We are creating this ExportMask on the hardware! (Maybe not * the first time though...) * * Fetch the Initiators */ List<Initiator> initiators = new ArrayList<Initiator>(); for (String initiatorId : exportMask.getInitiators()) { Initiator initiator = _dbClient.queryObject(Initiator.class, URI.create(initiatorId)); if (initiator != null) { initiators.add(initiator); } } // Fetch the targets List<URI> targets = new ArrayList<URI>(); for (String targetId : exportMask.getStoragePorts()) { targets.add(URI.create(targetId)); } if (volumeMap != null) { for (URI volume : volumeMap.keySet()) { exportMask.addVolume(volume, volumeMap.get(volume)); } } _dbClient.persistObject(exportMask); _log.debug( String.format( "Calling doExportGroupCreate on the device %s", array.getId().toString())); device.doExportGroupCreate(array, exportMask, volumeMap, initiators, targets, completer); } else { _log.debug( String.format("Calling doExportAddVolumes on the device %s", array.getId().toString())); device.doExportAddVolumes(array, exportMask, volumeMap, completer); } } catch (Exception ex) { _log.error("Failed to create or add volumes to export mask for cinder: ", ex); VPlexApiException vplexex = DeviceControllerExceptions.vplex.addStepsForCreateVolumesFailed(ex); WorkflowStepCompleter.stepFailed(stepId, vplexex); } _log.debug("END - createOrAddVolumesToExportMask"); }
/** * 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"); }