@Override public void removeInitiator( StorageSystem storage, URI exportMaskId, List<Initiator> initiators, List<URI> targets, TaskCompleter taskCompleter) throws DeviceControllerException { log.info("{} removeInitiator START...", storage.getSerialNumber()); log.info("Export mask id: {}", exportMaskId); log.info("removeInitiator: initiators : {}", initiators); log.info("removeInitiator: targets : {}", targets); try { ExportMask exportMask = dbClient.queryObject(ExportMask.class, exportMaskId); List<Volume> volumeList = new ArrayList<Volume>(); StringMap volumes = exportMask.getUserAddedVolumes(); for (String vol : volumes.values()) { Volume volume = dbClient.queryObject(Volume.class, URI.create(vol)); volumeList.add(volume); } detachVolumesFromInitiators(storage, volumeList, initiators); taskCompleter.ready(dbClient); } catch (final Exception ex) { log.error("Problem in RemoveInitiators: ", ex); ServiceError serviceError = DeviceControllerErrors.cinder.operationFailed("doRemoveInitiators", ex.getMessage()); taskCompleter.error(dbClient, serviceError); } log.info("{} removeInitiator END...", storage.getSerialNumber()); }
@Override public void addVolume( StorageSystem storage, URI exportMaskId, VolumeURIHLU[] volumeURIHLUs, TaskCompleter taskCompleter) throws DeviceControllerException { log.info("{} addVolume START...", storage.getSerialNumber()); log.info("Export mask id: {}", exportMaskId); log.info("addVolume: volume-HLU pairs: {}", volumeURIHLUs); log.info("User assigned HLUs will be ignored as Cinder does not support it."); try { ExportMask exportMask = dbClient.queryObject(ExportMask.class, exportMaskId); List<Volume> volumes = new ArrayList<Volume>(); List<Initiator> initiatorList = new ArrayList<Initiator>(); // map to store target LUN id generated for each volume Map<URI, Integer> volumeToTargetLunMap = new HashMap<URI, Integer>(); StringMap initiators = exportMask.getUserAddedInitiators(); for (VolumeURIHLU volumeURIHLU : volumeURIHLUs) { URI volumeURI = volumeURIHLU.getVolumeURI(); Volume volume = dbClient.queryObject(Volume.class, volumeURI); volumes.add(volume); } for (String ini : initiators.values()) { Initiator initiator = dbClient.queryObject(Initiator.class, URI.create(ini)); initiatorList.add(initiator); } // Map to store volume to initiatorTargetMap Map<Volume, Map<String, List<String>>> volumeToFCInitiatorTargetMap = new HashMap<Volume, Map<String, List<String>>>(); attachVolumesToInitiators( storage, volumes, initiatorList, volumeToTargetLunMap, volumeToFCInitiatorTargetMap, exportMask); // Update targets in the export mask if (!volumeToFCInitiatorTargetMap.isEmpty()) { updateTargetsInExportMask( storage, volumes.get(0), volumeToFCInitiatorTargetMap, initiatorList, exportMask); } updateTargetLunIdInExportMask(volumeToTargetLunMap, exportMask); dbClient.updateAndReindexObject(exportMask); taskCompleter.ready(dbClient); } catch (final Exception ex) { log.error("Problem in AddVolumes: ", ex); ServiceError serviceError = DeviceControllerErrors.cinder.operationFailed("doAddVolumes", ex.getMessage()); taskCompleter.error(dbClient, serviceError); } log.info("{} addVolume END...", storage.getSerialNumber()); }
@Override public void addInitiator( StorageSystem storage, URI exportMaskId, List<Initiator> initiators, List<URI> targets, TaskCompleter taskCompleter) throws DeviceControllerException { log.info("{} addInitiator START...", storage.getSerialNumber()); log.info("Export mask id: {}", exportMaskId); log.info("addInitiator: initiators : {}", initiators); log.info("addInitiator: targets : {}", targets); try { ExportMask exportMask = dbClient.queryObject(ExportMask.class, exportMaskId); List<Volume> volumeList = new ArrayList<Volume>(); // map to store target LUN id generated for each volume Map<URI, Integer> volumeToTargetLunMap = new HashMap<URI, Integer>(); StringMap volumes = exportMask.getUserAddedVolumes(); for (String vol : volumes.values()) { Volume volume = dbClient.queryObject(Volume.class, URI.create(vol)); volumeList.add(volume); } // Map to store volume to initiatorTargetMap Map<Volume, Map<String, List<String>>> volumeToFCInitiatorTargetMap = new HashMap<Volume, Map<String, List<String>>>(); attachVolumesToInitiators( storage, volumeList, initiators, volumeToTargetLunMap, volumeToFCInitiatorTargetMap, exportMask); // Update targets in the export mask if (!volumeToFCInitiatorTargetMap.isEmpty()) { updateTargetsInExportMask( storage, volumeList.get(0), volumeToFCInitiatorTargetMap, initiators, exportMask); dbClient.updateAndReindexObject(exportMask); } // TODO : update volumeToTargetLunMap in export mask.? // Do we get different LUN ID for the new initiators from the same Host.? taskCompleter.ready(dbClient); } catch (final Exception ex) { log.error("Problem in AddInitiators: ", ex); ServiceError serviceError = DeviceControllerErrors.cinder.operationFailed("doAddInitiators", ex.getMessage()); taskCompleter.error(dbClient, serviceError); } log.info("{} addInitiator END...", storage.getSerialNumber()); }
/** * Re-validate the ExportMask * * <p>This is required to be done as the ExportMask gets updated by reading the cinder export * volume response. * * @param varrayURI * @param initiatorPortMap * @param mask * @param invalidMasks * @param directorToInitiatorIds * @param idToInitiatorMap * @param dbClient * @param portWwnToClusterMap */ public void updateZoningMapAndvalidateExportMask( URI varrayURI, Map<URI, List<StoragePort>> initiatorPortMap, URI exportMaskURI, Map<String, Set<String>> directorToInitiatorIds, Map<String, Initiator> idToInitiatorMap, Map<String, String> portWwnToClusterMap, StorageSystem vplex, StorageSystem array, String clusterId, String stepId) { try { WorkflowStepCompleter.stepExecuting(stepId); // Export Mask is updated, read it from DB ExportMask exportMask = _dbClient.queryObject(ExportMask.class, exportMaskURI); // First step would be to update the zoning map based on the connectivity updateZoningMap(initiatorPortMap, directorToInitiatorIds, exportMask); boolean passed = VPlexBackEndOrchestratorUtil.validateExportMask( varrayURI, initiatorPortMap, exportMask, null, directorToInitiatorIds, idToInitiatorMap, _dbClient, portWwnToClusterMap); if (!passed) { // Mark this mask as inactive, so that we dont pick it in the next iteration exportMask.setInactive(Boolean.TRUE); _dbClient.persistObject(exportMask); _log.error("Export Mask is not suitable for VPLEX to backend storage system"); WorkflowStepCompleter.stepFailed( stepId, VPlexApiException.exceptions.couldNotFindValidArrayExportMask( vplex.getNativeGuid(), array.getNativeGuid(), clusterId)); throw VPlexApiException.exceptions.couldNotFindValidArrayExportMask( vplex.getNativeGuid(), array.getNativeGuid(), clusterId); } WorkflowStepCompleter.stepSucceded(stepId); } catch (Exception ex) { _log.error("Failed to validate export mask for cinder: ", ex); VPlexApiException vplexex = DeviceControllerExceptions.vplex.failedToValidateExportMask(exportMaskURI.toString(), ex); WorkflowStepCompleter.stepFailed(stepId, vplexex); } }
@Override public void deleteExportMask( StorageSystem storage, URI exportMaskId, List<URI> volumeURIList, List<URI> targetURIList, List<Initiator> initiatorList, TaskCompleter taskCompleter) throws DeviceControllerException { log.info("{} deleteExportMask START...", storage.getSerialNumber()); log.info("Export mask id: {}", exportMaskId); try { // There is no masking concept on Cinder to delete the export mask. // But before marking the task completer as ready, // detach the volumes from the initiators that are there in the export mask. ExportMask exportMask = dbClient.queryObject(ExportMask.class, exportMaskId); List<Volume> volumeList = new ArrayList<Volume>(); StringMap volumes = exportMask.getUserAddedVolumes(); StringMap initiators = exportMask.getUserAddedInitiators(); if (volumes != null) { for (String vol : volumes.values()) { URI volumeURI = URI.create(vol); volumeURIList.add(volumeURI); Volume volume = dbClient.queryObject(Volume.class, volumeURI); volumeList.add(volume); } } if (initiators != null) { for (String ini : initiators.values()) { Initiator initiatorObj = dbClient.queryObject(Initiator.class, URI.create(ini)); initiatorList.add(initiatorObj); } } log.info("deleteExportMask: volumes: {}", volumeURIList); log.info("deleteExportMask: assignments: {}", targetURIList); log.info("deleteExportMask: initiators: {}", initiatorList); detachVolumesFromInitiators(storage, volumeList, initiatorList); taskCompleter.ready(dbClient); } catch (final Exception ex) { log.error("Problem in DetachVolumes: ", ex); ServiceError serviceError = DeviceControllerErrors.cinder.operationFailed("doDetachVolumes", ex.getMessage()); taskCompleter.error(dbClient, serviceError); } log.info("{} deleteExportMask END...", storage.getSerialNumber()); }
/** * Updates the target LUN ID for volumes in the export mask. * * @param volumeToTargetLunMap * @param exportMask */ private void updateTargetLunIdInExportMask( Map<URI, Integer> volumeToTargetLunMap, ExportMask exportMask) { for (URI volumeURI : volumeToTargetLunMap.keySet()) { Integer targetLunId = volumeToTargetLunMap.get(volumeURI); exportMask.getVolumes().put(volumeURI.toString(), targetLunId.toString()); } }
/* * (non-Javadoc) * * @see com.emc.storageos.volumecontroller.BlockStorageDevice#doExportRemoveVolumes(com.emc.storageos.db.client.model.StorageSystem, * com.emc.storageos.db.client.model.ExportMask, java.util.List, com.emc.storageos.volumecontroller.TaskCompleter) */ @Override public void doExportRemoveVolumes( StorageSystem storage, ExportMask exportMask, List<URI> volumes, TaskCompleter taskCompleter) throws DeviceControllerException { log.info("{} doExportRemoveVolume START ...", storage.getSerialNumber()); exportMaskOperationsHelper.removeVolume(storage, exportMask.getId(), volumes, taskCompleter); log.info("{} doExportRemoveVolume END ...", storage.getSerialNumber()); }
/** * Generates workflow step to remove volumes from ExportMask. * * @param workflow * @param previousStep * @param exportGroup * @param exportMask * @param completer * @return */ public String generateWorkflowStepToToRemoveVolumesFromExportMask( Workflow workflow, String previousStep, ExportGroup exportGroup, ExportMask exportMask, List<URI> volumesToRemove, ExportTaskCompleter completer) { URI exportGroupURI = exportGroup.getId(); String stepId = workflow.createStepId(); ExportTaskCompleter exportTaskCompleter; if (completer != null) { exportTaskCompleter = completer; exportTaskCompleter.setOpId(stepId); } else { exportTaskCompleter = new ExportMaskRemoveVolumeCompleter( exportGroupURI, exportMask.getId(), volumesToRemove, stepId); } Workflow.Method removeVolumesFromExportMaskExecuteMethod = new Workflow.Method( "doExportGroupToCleanVolumesInExportMask", exportGroupURI, exportMask.getId(), volumesToRemove, exportTaskCompleter); stepId = workflow.createStep( EXPORT_MASK_CLEANUP_TASK, String.format( "ExportMask to removeVolumes %s (%s)", exportMask.getMaskName(), exportMask.getId().toString()), previousStep, NullColumnValueGetter.getNullURI(), "storage-system", MaskingWorkflowEntryPoints.class, removeVolumesFromExportMaskExecuteMethod, null, stepId); return stepId; }
/** * Generates workflow step to Mark ExportMask inActive. * * @param workflow * @param previousStep * @param exportGroup * @param exportMask * @param completer * @return */ public String generateWorkflowStepToMarkExportMaskInActive( Workflow workflow, String previousStep, ExportGroup exportGroup, ExportMask exportMask, ExportTaskCompleter completer) { URI exportGroupURI = exportGroup.getId(); String stepId = workflow.createStepId(); ExportTaskCompleter exportTaskCompleter; if (completer != null) { exportTaskCompleter = completer; exportTaskCompleter.setOpId(stepId); } else { exportTaskCompleter = new ExportMaskDeleteCompleter(exportGroupURI, exportMask.getId(), stepId); } Workflow.Method markExportMaskInActiveExecuteMethod = new Workflow.Method( "doExportGroupToCleanExportMask", exportGroupURI, exportMask.getId(), exportTaskCompleter); stepId = workflow.createStep( EXPORT_MASK_CLEANUP_TASK, String.format( "Marking exportmasks to inactive %s (%s)", exportMask.getMaskName(), exportMask.getId().toString()), previousStep, NullColumnValueGetter.getNullURI(), "storage-system", MaskingWorkflowEntryPoints.class, markExportMaskInActiveExecuteMethod, null, stepId); return stepId; }
/* * (non-Javadoc) * * @see com.emc.storageos.volumecontroller.BlockStorageDevice#doExportAddInitiator(com.emc.storageos.db.client.model.StorageSystem, * com.emc.storageos.db.client.model.ExportMask, com.emc.storageos.db.client.model.Initiator, java.util.List, * com.emc.storageos.volumecontroller.TaskCompleter) */ @Override public void doExportAddInitiator( StorageSystem storage, ExportMask exportMask, Initiator initiator, List<URI> targets, TaskCompleter taskCompleter) throws DeviceControllerException { log.info("{} doExportAddInitiator START ...", storage.getSerialNumber()); exportMaskOperationsHelper.addInitiator( storage, exportMask.getId(), Arrays.asList(initiator), targets, taskCompleter); log.info("{} doExportAddInitiator END ...", storage.getSerialNumber()); }
/* * (non-Javadoc) * * @see com.emc.storageos.volumecontroller.BlockStorageDevice#doExportRemoveInitiators(com.emc.storageos.db.client.model.StorageSystem, * com.emc.storageos.db.client.model.ExportMask, java.util.List, java.util.List, com.emc.storageos.volumecontroller.TaskCompleter) */ @Override public void doExportRemoveInitiators( StorageSystem storage, ExportMask exportMask, List<Initiator> initiators, List<URI> targets, TaskCompleter taskCompleter) throws DeviceControllerException { log.info("{} doExportRemoveInitiator START ...", storage.getSerialNumber()); exportMaskOperationsHelper.removeInitiator( storage, exportMask.getId(), initiators, targets, taskCompleter); log.info("{} doExportRemoveInitiator END ...", storage.getSerialNumber()); }
@Override public String generateDeviceSpecificExportMaskDeleteWorkflow( Workflow workflow, String previousStep, ExportGroup exportGroup, ExportMask exportMask, StorageSystem storage) throws Exception { ExportTaskCompleter hdsExportMaskDeleteCompleter = new HDSExportMaskDeleteCompleter(exportGroup.getId(), exportMask.getId(), previousStep); // HDS ExportMask operation should not depend on any other workflow step. return generateExportMaskDeleteWorkflow( workflow, null, storage, exportGroup, exportMask, hdsExportMaskDeleteCompleter); }
/** * Create Initiator Target LUN Mapping as an extension in volume object * * @param volume - volume in which ITL to be added * @param exportMask - exportMask in which the volume is to be added * @param targetLunId - integer value of host LUN id on which volume is accessible. */ private void storeITLMappingInVolume( Map<URI, Integer> volumeToTargetLunMap, ExportMask exportMask) { log.debug("START - createITLMappingInVolume"); for (URI volumeURI : volumeToTargetLunMap.keySet()) { Integer targetLunId = volumeToTargetLunMap.get(volumeURI); Volume volume = dbClient.queryObject(Volume.class, volumeURI); StringSetMap zoningMap = exportMask.getZoningMap(); Set<String> zoningMapKeys = zoningMap.keySet(); int initiatorIndex = 0; for (String initiator : zoningMapKeys) { Initiator initiatorObj = dbClient.queryObject(Initiator.class, URI.create(initiator)); String initiatorWWPN = initiatorObj.getInitiatorPort().replaceAll(CinderConstants.COLON, ""); StringSet targetPorts = zoningMap.get(initiator); int targetIndex = 0; for (String target : targetPorts) { StoragePort targetPort = dbClient.queryObject(StoragePort.class, URI.create(target)); String targetPortWWN = targetPort.getPortNetworkId().replaceAll(CinderConstants.COLON, ""); // Format is - <InitiatorWWPN>-<TargetWWPN>-<LunId> String itl = initiatorWWPN + "-" + targetPortWWN + "-" + String.valueOf(targetLunId); // ITL keys will be formed as ITL-00, ITL-01, ITL-10, ITL-11 so on String itlKey = CinderConstants.PREFIX_ITL + String.valueOf(initiatorIndex) + String.valueOf(targetIndex); log.info(String.format("Adding ITL %s with key %s", itl, itlKey)); StringMap extensionsMap = volume.getExtensions(); if (null == extensionsMap) { extensionsMap = new StringMap(); extensionsMap.put(itlKey, itl); volume.setExtensions(extensionsMap); } else { volume.getExtensions().put(itlKey, itl); } targetIndex++; } initiatorIndex++; } dbClient.updateAndReindexObject(volume); } log.debug("END - createITLMappingInVolume"); }
/* * (non-Javadoc) * * @see com.emc.storageos.volumecontroller.BlockStorageDevice#doExportGroupDelete(com.emc.storageos.db.client.model.StorageSystem, * com.emc.storageos.db.client.model.ExportMask, com.emc.storageos.volumecontroller.TaskCompleter) */ @Override public void doExportGroupDelete( StorageSystem storage, ExportMask exportMask, TaskCompleter taskCompleter) throws DeviceControllerException { log.info("{} doExportGroupDelete START ...", storage.getSerialNumber()); exportMaskOperationsHelper.deleteExportMask( storage, exportMask.getId(), new ArrayList<URI>(), new ArrayList<URI>(), new ArrayList<Initiator>(), taskCompleter); log.info("{} doExportGroupDelete END ...", storage.getSerialNumber()); }
@Override public void doExportAddVolumes( StorageSystem storage, ExportMask exportMask, Map<URI, Integer> volumes, TaskCompleter taskCompleter) throws DeviceControllerException { log.info("{} doExportAddVolume START ...", storage.getSerialNumber()); VolumeURIHLU[] volumeLunArray = ControllerUtils.getVolumeURIHLUArray(storage.getSystemType(), volumes, dbClient); exportMaskOperationsHelper.addVolume( storage, exportMask.getId(), volumeLunArray, taskCompleter); log.info("{} doExportAddVolume END ...", storage.getSerialNumber()); }
@Override public String generateDeviceSpecificExportMaskRemoveVolumesWorkflow( Workflow workflow, String previousStep, ExportGroup exportGroup, ExportMask exportMask, StorageSystem storage, List<URI> volumesToRemove, ExportTaskCompleter completer) throws Exception { ExportTaskCompleter taskCompleter = new HDSExportMaskRemoveVolumeCompleter( exportGroup.getId(), exportMask.getId(), volumesToRemove, previousStep); return generateExportMaskRemoveVolumesWorkflow( workflow, previousStep, storage, exportGroup, exportMask, volumesToRemove, taskCompleter); }
/* * (non-Javadoc) * * @see com.emc.storageos.volumecontroller.BlockStorageDevice#doExportGroupCreate(com.emc.storageos.db.client.model.StorageSystem, * com.emc.storageos.db.client.model.ExportMask, java.util.Map, java.util.List, java.util.List, * com.emc.storageos.volumecontroller.TaskCompleter) */ @Override public void doExportGroupCreate( StorageSystem storage, ExportMask exportMask, Map<URI, Integer> volumeMap, List<Initiator> initiators, List<URI> targets, TaskCompleter taskCompleter) throws DeviceControllerException { log.info("{} doExportGroupCreate START ...", storage.getSerialNumber()); VolumeURIHLU[] volumeLunArray = ControllerUtils.getVolumeURIHLUArray(storage.getSystemType(), volumeMap, dbClient); exportMaskOperationsHelper.createExportMask( storage, exportMask.getId(), volumeLunArray, targets, initiators, taskCompleter); log.info("{} doExportGroupCreate END ...", storage.getSerialNumber()); }
@Override public String generateDeviceSpecificDeleteWorkflow( Workflow workflow, String previousStep, ExportGroup exportGroup, ExportMask mask, StorageSystem storage) throws Exception { List<ExportMask> masks = new ArrayList<ExportMask>(); masks.add(mask); ExportTaskCompleter hdsExportMaskDeleteCompleter = new HDSExportMaskDeleteCompleter(exportGroup.getId(), mask.getId(), previousStep); String exportMaskDeleteStepId = generateExportMaskDeleteWorkflow( workflow, null, storage, exportGroup, mask, hdsExportMaskDeleteCompleter); String zoningStepId = generateZoningDeleteWorkflow(workflow, exportMaskDeleteStepId, exportGroup, masks); generateWorkflowStepToMarkExportMaskInActive(workflow, zoningStepId, exportGroup, mask, null); return exportMaskDeleteStepId; }
@Override public void exportGroupDelete(URI storageURI, URI exportGroupURI, String token) throws Exception { ExportOrchestrationTask taskCompleter = new ExportOrchestrationTask(exportGroupURI, token); try { ExportGroup exportGroup = _dbClient.queryObject(ExportGroup.class, exportGroupURI); StorageSystem storage = _dbClient.queryObject(StorageSystem.class, storageURI); List<ExportMask> masks = ExportMaskUtils.getExportMasks(_dbClient, exportGroup, storageURI); if (masks != null && !masks.isEmpty()) { Workflow workflow = _workflowService.getNewWorkflow( MaskingWorkflowEntryPoints.getInstance(), "exportGroupDelete", true, token); Map<URI, Integer> volumeMap = ExportUtils.getExportGroupVolumeMap(_dbClient, storage, exportGroup); List<URI> volumeURIs = new ArrayList<>(volumeMap.keySet()); List<URI> initiatorURIs = StringSetUtil.stringSetToUriList(exportGroup.getInitiators()); Map<URI, Map<URI, Integer>> exportMaskToVolumeCount = ExportMaskUtils.mapExportMaskToVolumeShareCount(_dbClient, volumeURIs, initiatorURIs); for (ExportMask exportMask : masks) { List<URI> exportGroupURIs = new ArrayList<>(); if (!ExportUtils.isExportMaskShared(_dbClient, exportMask.getId(), exportGroupURIs)) { log.info( String.format("Adding step to delete ExportMask %s", exportMask.getMaskName())); generateExportMaskDeleteWorkflow( workflow, null, storage, exportGroup, exportMask, null); } else { Map<URI, Integer> volumeToExportGroupCount = exportMaskToVolumeCount.get(exportMask.getId()); List<URI> volumesToRemove = new ArrayList<>(); for (URI uri : volumeMap.keySet()) { if (volumeToExportGroupCount == null) { continue; } // Remove the volume only if it is not shared with // more than 1 ExportGroup Integer numExportGroupsVolumeIsIn = volumeToExportGroupCount.get(uri); if (numExportGroupsVolumeIsIn != null && numExportGroupsVolumeIsIn == 1) { volumesToRemove.add(uri); } } if (!volumesToRemove.isEmpty()) { log.info( String.format( "Adding step to remove volumes %s from ExportMask %s", Joiner.on(',').join(volumesToRemove), exportMask.getMaskName())); generateExportMaskRemoveVolumesWorkflow( workflow, null, storage, exportGroup, exportMask, volumesToRemove, null); } } } String successMessage = String.format( "ExportGroup delete successfully completed for StorageArray %s", storage.getLabel()); workflow.executePlan(taskCompleter, successMessage); } else { taskCompleter.ready(_dbClient); } } catch (DeviceControllerException dex) { taskCompleter.error( _dbClient, DeviceControllerErrors.scaleio.encounteredAnExceptionFromScaleIOOperation( "exportGroupDelete", dex.getMessage())); } catch (Exception ex) { _log.error("ExportGroup Orchestration failed.", ex); taskCompleter.error( _dbClient, DeviceControllerErrors.scaleio.encounteredAnExceptionFromScaleIOOperation( "exportGroupDelete", ex.getMessage())); } }
@Override public void deleteOrRemoveVolumesFromExportMask( URI arrayURI, URI exportGroupURI, URI exportMaskURI, List<URI> volumes, List<URI> initiatorURIs, TaskCompleter completer, String stepId) { try { StorageSystem array = _dbClient.queryObject(StorageSystem.class, arrayURI); BlockStorageDevice device = _blockController.getDevice(array.getSystemType()); ExportMask exportMask = _dbClient.queryObject(ExportMask.class, exportMaskURI); WorkflowStepCompleter.stepExecuting(stepId); // If the exportMask isn't found, or has been deleted, nothing to do. if (exportMask == null || exportMask.getInactive()) { _log.info(String.format("ExportMask %s inactive, returning success", exportMaskURI)); WorkflowStepCompleter.stepSucceded(stepId); 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)); // Make sure the completer will complete the workflow. This happens on rollback case. if (!completer.getOpId().equals(stepId)) { completer.setOpId(stepId); } // Refresh the ExportMask exportMask = refreshExportMask(array, device, exportMask); // Determine if we're deleting the last volume in the mask. StringMap maskVolumesMap = exportMask.getVolumes(); Set<String> remainingVolumes = new HashSet<String>(); List<URI> passedVolumesInMask = new ArrayList<>(volumes); if (maskVolumesMap != null) { remainingVolumes.addAll(maskVolumesMap.keySet()); } for (URI volume : volumes) { remainingVolumes.remove(volume.toString()); // Remove any volumes from the volume list that are no longer // in the export mask. When a failure occurs removing a backend // volume from a mask, the rollback method will try and remove it // again. However, in the case of a distributed volume, one side // may have succeeded, so we will try and remove it again. Previously, // this was not a problem. However, new validation exists at the // block level that checks to make sure the volume to remove is // actually in the mask, which now causes a failure when you remove // it a second time. So, we check here and remove any volumes that // are not in the mask to handle this condition. if ((maskVolumesMap != null) && (!maskVolumesMap.keySet().contains(volume.toString()))) { passedVolumesInMask.remove(volume); } } // None of the volumes is in the export mask, so we are done. if (passedVolumesInMask.isEmpty()) { _log.info( "None of these volumes {} are in export mask {}", volumes, exportMask.forDisplay()); WorkflowStepCompleter.stepSucceded(stepId); return; } // If it is last volume and there are no existing volumes, delete the ExportMask. if (remainingVolumes.isEmpty() && !exportMask.hasAnyExistingVolumes()) { device.doExportDelete(array, exportMask, passedVolumesInMask, initiatorURIs, completer); } else { List<Initiator> initiators = null; if (initiatorURIs != null && !initiatorURIs.isEmpty()) { initiators = _dbClient.queryObject(Initiator.class, initiatorURIs); } device.doExportRemoveVolumes(array, exportMask, passedVolumesInMask, initiators, completer); } } catch (Exception ex) { _log.error("Failed to delete or remove volumes to export mask for vnx: ", ex); VPlexApiException vplexex = DeviceControllerExceptions.vplex.addStepsForCreateVolumesFailed(ex); WorkflowStepCompleter.stepFailed(stepId, vplexex); } }
@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); } }
@Override public void exportGroupAddInitiators( URI storageURI, URI exportGroupURI, List<URI> initiatorURIs, String token) throws Exception { /* * foreach ExportGroup.volume * if ScaleIO volume * foreach initiator * scli map --volume volid --sdc initiator.sdcid */ ExportOrchestrationTask taskCompleter = new ExportOrchestrationTask(exportGroupURI, token); try { ExportGroup exportGroup = _dbClient.queryObject(ExportGroup.class, exportGroupURI); StorageSystem storage = _dbClient.queryObject(StorageSystem.class, storageURI); if (initiatorURIs != null && !initiatorURIs.isEmpty()) { // Set up workflow steps. Workflow workflow = _workflowService.getNewWorkflow( MaskingWorkflowEntryPoints.getInstance(), "exportGroupAddInitiators", true, token); Map<String, URI> portNameToInitiatorURI = new HashMap<>(); List<URI> hostURIs = new ArrayList<>(); List<String> portNames = new ArrayList<>(); // Populate the portNames and the mapping of the portNames to Initiator URIs processInitiators(exportGroup, initiatorURIs, portNames, portNameToInitiatorURI, hostURIs); Map<URI, Integer> volumesToAdd = ExportUtils.getExportGroupVolumeMap(_dbClient, storage, exportGroup); List<URI> initiatorURIsToPlace = new ArrayList<>(initiatorURIs); Map<String, List<URI>> computeResourceToInitiators = mapInitiatorsToComputeResource(exportGroup, initiatorURIs); Set<URI> partialMasks = new HashSet<>(); Map<String, Set<URI>> initiatorToExport = determineInitiatorToExportMaskPlacements( exportGroup, storageURI, computeResourceToInitiators, Collections.EMPTY_MAP, portNameToInitiatorURI, partialMasks); Map<URI, List<URI>> exportToInitiators = toExportMaskToInitiatorURIs(initiatorToExport, portNameToInitiatorURI); for (Map.Entry<URI, List<URI>> toAddInitiators : exportToInitiators.entrySet()) { ExportMask exportMask = _dbClient.queryObject(ExportMask.class, toAddInitiators.getKey()); if (exportMask == null || exportMask.getInactive()) { continue; } for (URI toAddInitiator : toAddInitiators.getValue()) { if (!exportMask.hasInitiator(toAddInitiator.toString())) { log.info( String.format( "Add step to add initiator %s to ExportMask %s", toAddInitiator.toString(), exportMask.getMaskName())); generateExportMaskAddInitiatorsWorkflow( workflow, null, storage, exportGroup, exportMask, toAddInitiators.getValue(), null, null); } else if (volumesToAdd != null && volumesToAdd.size() > 0) { log.info( String.format( "Add step to add volumes %s to ExportMask %s", Joiner.on(',').join(volumesToAdd.entrySet()), exportMask.getMaskName())); generateExportMaskAddVolumesWorkflow( workflow, null, storage, exportGroup, exportMask, volumesToAdd); } initiatorURIsToPlace.remove(toAddInitiator); } } // If there are any new initiators that weren't already known to the system // previously, add them now. if (!initiatorURIsToPlace.isEmpty() && volumesToAdd != null) { Map<String, List<URI>> newComputeResources = mapInitiatorsToComputeResource(exportGroup, initiatorURIsToPlace); log.info( String.format( "Need to create ExportMasks for these compute resources %s", Joiner.on(',').join(newComputeResources.entrySet()))); for (Map.Entry<String, List<URI>> toCreate : newComputeResources.entrySet()) { generateExportMaskCreateWorkflow( workflow, null, storage, exportGroup, toCreate.getValue(), volumesToAdd, null); } } String successMessage = String.format( "ExportGroup add initiators successfully applied for StorageArray %s", storage.getLabel()); workflow.executePlan(taskCompleter, successMessage); } else { taskCompleter.ready(_dbClient); } } catch (DeviceControllerException dex) { taskCompleter.error( _dbClient, DeviceControllerErrors.scaleio.encounteredAnExceptionFromScaleIOOperation( "exportGroupAddInitiators", dex.getMessage())); } catch (Exception ex) { _log.error("ExportGroup Orchestration failed.", ex); taskCompleter.error( _dbClient, DeviceControllerErrors.scaleio.encounteredAnExceptionFromScaleIOOperation( "exportGroupAddInitiators", ex.getMessage())); } }
/** * Routine contains logic to create an export mask on the array * * @param workflow - Workflow object to create steps against * @param previousStep - [optional] Identifier of workflow step to wait for * @param device - BlockStorageDevice implementation * @param storage - StorageSystem object representing the underlying array * @param exportGroup - ExportGroup object representing Bourne-level masking * @param initiatorURIs - List of Initiator URIs * @param volumeMap - Map of Volume URIs to requested Integer HLUs * @param zoningStepNeeded - Not required ofr HDS * @param token - Identifier for the operation * @throws Exception */ public boolean determineExportGroupCreateSteps( Workflow workflow, String previousStep, BlockStorageDevice device, StorageSystem storage, ExportGroup exportGroup, List<URI> initiatorURIs, Map<URI, Integer> volumeMap, boolean zoningStepNeeded, String token) throws Exception { Map<String, URI> portNameToInitiatorURI = new HashMap<String, URI>(); List<URI> volumeURIs = new ArrayList<URI>(); volumeURIs.addAll(volumeMap.keySet()); Map<URI, URI> hostToExistingExportMaskMap = new HashMap<URI, URI>(); List<URI> hostURIs = new ArrayList<URI>(); List<String> portNames = new ArrayList<String>(); List<Initiator> initiators = _dbClient.queryObject(Initiator.class, initiatorURIs); // Populate the port WWN/IQNs (portNames) and the // mapping of the WWN/IQNs to Initiator URIs processInitiators(exportGroup, initiatorURIs, portNames, portNameToInitiatorURI, hostURIs); // We always want to have the full list of initiators for the hosts involved in // this export. This will allow the export operation to always find any // existing exports for a given host. queryHostInitiatorsAndAddToList(portNames, portNameToInitiatorURI, initiatorURIs, hostURIs); // Find the export masks that are associated with any or all the ports in // portNames. We will have to do processing differently based on whether // or there is an existing ExportMasks. Map<String, Set<URI>> matchingExportMaskURIs = device.findExportMasks(storage, portNames, false); if (matchingExportMaskURIs.isEmpty()) { _log.info( String.format( "No existing mask found w/ initiators { %s }", Joiner.on(",").join(portNames))); createNewExportMaskWorkflowForInitiators( initiatorURIs, exportGroup, workflow, volumeMap, storage, token, previousStep); } else { _log.info( String.format( "Mask(s) found w/ initiators {%s}. " + "MatchingExportMaskURIs {%s}, portNameToInitiators {%s}", Joiner.on(",").join(portNames), Joiner.on(",").join(matchingExportMaskURIs.values()), Joiner.on(",").join(portNameToInitiatorURI.entrySet()))); // There are some initiators that already exist. We need to create a // workflow that create new masking containers or updates masking // containers as necessary. // These data structures will be used to track new initiators - ones // that don't already exist on the array List<URI> initiatorURIsCopy = new ArrayList<URI>(); initiatorURIsCopy.addAll(initiatorURIs); // This loop will determine a list of volumes to update per export mask Map<URI, Map<URI, Integer>> existingMasksToUpdateWithNewVolumes = new HashMap<URI, Map<URI, Integer>>(); Map<URI, Set<Initiator>> existingMasksToUpdateWithNewInitiators = new HashMap<URI, Set<Initiator>>(); for (Map.Entry<String, Set<URI>> entry : matchingExportMaskURIs.entrySet()) { URI initiatorURI = portNameToInitiatorURI.get(entry.getKey()); Initiator initiator = _dbClient.queryObject(Initiator.class, initiatorURI); // Keep track of those initiators that have been found to exist already // in some export mask on the array initiatorURIsCopy.remove(initiatorURI); // Get a list of the ExportMasks that were matched to the initiator List<URI> exportMaskURIs = new ArrayList<URI>(); exportMaskURIs.addAll(entry.getValue()); List<ExportMask> masks = _dbClient.queryObject(ExportMask.class, exportMaskURIs); _log.info( String.format( "initiator %s masks {%s}", initiator.getInitiatorPort(), Joiner.on(',').join(exportMaskURIs))); for (ExportMask mask : masks) { // ExportMask is created using non-vipr. Set the mask name if it doesn't have. if (null == mask.getMaskName()) { String maskName = ExportMaskUtils.getMaskName(_dbClient, initiators, exportGroup, storage); _log.info("Generated mask name: {}", maskName); mask.setMaskName(maskName); } // Check for NO_VIPR. If found, avoid this mask. if (mask.getMaskName() != null && mask.getMaskName().toUpperCase().contains(ExportUtils.NO_VIPR)) { _log.info( String.format( "ExportMask %s disqualified because the name contains %s (in upper or lower case) to exclude it", mask.getMaskName(), ExportUtils.NO_VIPR)); continue; } _log.info( String.format( "mask %s has initiator %s", mask.getMaskName(), initiator.getInitiatorPort())); if (mask.getCreatedBySystem()) { _log.info( String.format( "initiator %s is in persisted mask %s", initiator.getInitiatorPort(), mask.getMaskName())); // We're still OK if the mask contains ONLY initiators that can be found // in our export group, because we would simply add to them. if (mask.getInitiators() != null) { for (String existingMaskInitiatorStr : mask.getInitiators()) { // Now look at it from a different angle. Which one of our export group initiators // are NOT in the current mask? And if so, if it belongs to the same host as an // existing one, // we should add it to this mask. Iterator<URI> initiatorIter = initiatorURIsCopy.iterator(); while (initiatorIter.hasNext()) { Initiator initiatorCopy = _dbClient.queryObject(Initiator.class, initiatorIter.next()); if (initiatorCopy != null && initiatorCopy.getId() != null && !mask.hasInitiator(initiatorCopy.getId().toString())) { Initiator existingMaskInitiator = _dbClient.queryObject( Initiator.class, URI.create(existingMaskInitiatorStr)); if (existingMaskInitiator != null && initiatorCopy.getHost() != null && initiatorCopy.getHost().equals(existingMaskInitiator.getHost())) { // Add to the list of initiators we need to add to this mask Set<Initiator> existingMaskInitiators = existingMasksToUpdateWithNewInitiators.get(mask.getId()); if (existingMaskInitiators == null) { existingMaskInitiators = new HashSet<Initiator>(); existingMasksToUpdateWithNewInitiators.put( mask.getId(), existingMaskInitiators); } existingMaskInitiators.add(initiatorCopy); initiatorIter .remove(); // remove this from the list of initiators we'll make a new // mask from } } } } } } else { // Insert this initiator into the mask's list of initiators managed by the system. // This will get persisted below. mask.addInitiator(initiator); if (!NullColumnValueGetter.isNullURI(initiator.getHost())) { hostToExistingExportMaskMap.put(initiator.getHost(), mask.getId()); } } // We need to see if the volume also exists the mask, // if it doesn't then we'll add it to the list of volumes to add. for (URI boURI : volumeURIs) { BlockObject bo = BlockObject.fetch(_dbClient, boURI); if (!mask.hasExistingVolume(bo)) { _log.info( String.format( "volume %s is not in mask %s", bo.getNativeGuid(), mask.getMaskName())); // The volume doesn't exist, so we have to add it to // the masking container. Map<URI, Integer> newVolumes = existingMasksToUpdateWithNewVolumes.get(mask.getId()); if (newVolumes == null) { newVolumes = new HashMap<URI, Integer>(); existingMasksToUpdateWithNewVolumes.put(mask.getId(), newVolumes); } // Check if the requested HLU for the volume is // already taken by a pre-existing volume. Integer requestedHLU = volumeMap.get(bo.getId()); StringMap existingVolumesInMask = mask.getExistingVolumes(); if (existingVolumesInMask != null && existingVolumesInMask.containsValue(requestedHLU.toString())) { ExportOrchestrationTask completer = new ExportOrchestrationTask(exportGroup.getId(), token); ServiceError serviceError = DeviceControllerException.errors.exportHasExistingVolumeWithRequestedHLU( boURI.toString(), requestedHLU.toString()); completer.error(_dbClient, serviceError); return false; } newVolumes.put(bo.getId(), requestedHLU); mask.addToUserCreatedVolumes(bo); } } // Update the list of volumes and initiators for the mask Map<URI, Integer> volumeMapForExistingMask = existingMasksToUpdateWithNewVolumes.get(mask.getId()); if (volumeMapForExistingMask != null && !volumeMapForExistingMask.isEmpty()) { mask.addVolumes(volumeMapForExistingMask); } Set<Initiator> initiatorSetForExistingMask = existingMasksToUpdateWithNewInitiators.get(mask.getId()); if (initiatorSetForExistingMask != null && initiatorSetForExistingMask.isEmpty()) { mask.addInitiators(initiatorSetForExistingMask); } updateZoningMap(exportGroup, mask); _dbClient.updateAndReindexObject(mask); // TODO: All export group modifications should be moved to completers exportGroup.addExportMask(mask.getId()); _dbClient.updateAndReindexObject(exportGroup); } } // The initiatorURIsCopy was used in the foreach initiator loop to see // which initiators already exist in a mask. If it is non-empty, // then it means there are initiators that are new, // so let's add them to the main tracker Map<URI, List<URI>> hostInitiatorMap = new HashMap<URI, List<URI>>(); if (!initiatorURIsCopy.isEmpty()) { for (URI newExportMaskInitiator : initiatorURIsCopy) { Initiator initiator = _dbClient.queryObject(Initiator.class, newExportMaskInitiator); List<URI> initiatorSet = hostInitiatorMap.get(initiator.getHost()); if (initiatorSet == null) { initiatorSet = new ArrayList<URI>(); hostInitiatorMap.put(initiator.getHost(), initiatorSet); } initiatorSet.add(initiator.getId()); _log.info( String.format( "host = %s, " + "initiators to add: %d, " + "existingMasksToUpdateWithNewVolumes.size = %d", initiator.getHost(), hostInitiatorMap.get(initiator.getHost()).size(), existingMasksToUpdateWithNewVolumes.size())); } } _log.info( String.format( "existingMasksToUpdateWithNewVolumes.size = %d", existingMasksToUpdateWithNewVolumes.size())); // At this point we have the necessary data structures populated to // determine the workflow steps. We are going to create new masks // and/or add volumes to existing masks. if (!hostInitiatorMap.isEmpty()) { for (URI hostID : hostInitiatorMap.keySet()) { // Check if there is an existing mask (created outside of ViPR) for // the host. If there is we will need to add these intiators // associated with that host to the list if (hostToExistingExportMaskMap.containsKey(hostID)) { URI existingExportMaskURI = hostToExistingExportMaskMap.get(hostID); Set<Initiator> toAddInits = new HashSet<Initiator>(); List<URI> hostInitaitorList = hostInitiatorMap.get(hostID); for (URI initURI : hostInitaitorList) { Initiator initiator = _dbClient.queryObject(Initiator.class, initURI); if (!initiator.getInactive()) { toAddInits.add(initiator); } } _log.info( String.format( "Need to add new initiators to existing mask %s, %s", existingExportMaskURI.toString(), Joiner.on(',').join(hostInitaitorList))); existingMasksToUpdateWithNewInitiators.put(existingExportMaskURI, toAddInits); continue; } // We have some brand new initiators, let's add them to new masks _log.info( String.format( "new export masks %s", Joiner.on(",").join(hostInitiatorMap.get(hostID)))); generateExportMaskCreateWorkflow( workflow, previousStep, storage, exportGroup, hostInitiatorMap.get(hostID), volumeMap, token); } } Map<URI, String> stepMap = new HashMap<URI, String>(); for (Map.Entry<URI, Map<URI, Integer>> entry : existingMasksToUpdateWithNewVolumes.entrySet()) { ExportMask mask = _dbClient.queryObject(ExportMask.class, entry.getKey()); Map<URI, Integer> volumesToAdd = entry.getValue(); _log.info( String.format( "adding these volumes %s to mask %s", Joiner.on(",").join(volumesToAdd.keySet()), mask.getMaskName())); stepMap.put( entry.getKey(), generateExportMaskAddVolumesWorkflow( workflow, null, storage, exportGroup, mask, volumesToAdd)); } for (Entry<URI, Set<Initiator>> entry : existingMasksToUpdateWithNewInitiators.entrySet()) { ExportMask mask = _dbClient.queryObject(ExportMask.class, entry.getKey()); Set<Initiator> initiatorsToAdd = entry.getValue(); List<URI> initiatorsURIs = new ArrayList<URI>(); for (Initiator initiator : initiatorsToAdd) { initiatorsURIs.add(initiator.getId()); } _log.info( String.format( "adding these initiators %s to mask %s", Joiner.on(",").join(initiatorsURIs), mask.getMaskName())); previousStep = stepMap.get(entry.getKey()) == null ? previousStep : stepMap.get(entry.getKey()); generateExportMaskAddInitiatorsWorkflow( workflow, previousStep, storage, exportGroup, mask, initiatorsURIs, null, token); } } return true; }
@Override public void exportGroupAddVolumes( URI storageURI, URI exportGroupURI, Map<URI, Integer> volumeMap, String token) throws Exception { /* * foreach volume in list * foreach initiator in ExportGroup * scli map --volume volid --sdc initiator.sdcid */ ExportOrchestrationTask taskCompleter = new ExportOrchestrationTask(exportGroupURI, token); try { ExportGroup exportGroup = _dbClient.queryObject(ExportGroup.class, exportGroupURI); StorageSystem storage = _dbClient.queryObject(StorageSystem.class, storageURI); List<URI> initiatorURIs = StringSetUtil.stringSetToUriList(exportGroup.getInitiators()); if (initiatorURIs != null && !initiatorURIs.isEmpty()) { // Set up workflow steps. Workflow workflow = _workflowService.getNewWorkflow( MaskingWorkflowEntryPoints.getInstance(), "exportGroupAddVolumes", true, token); // Create a mapping of ExportMasks to Add Volumes to or // add to a list of new Exports to create Map<URI, Map<URI, Integer>> exportMaskToVolumesToAdd = new HashMap<>(); List<URI> initiatorsToPlace = new ArrayList<>(initiatorURIs); // Need to figure out which ExportMasks to add volumes to. for (ExportMask exportMask : ExportMaskUtils.getExportMasks(_dbClient, exportGroup, storageURI)) { if (exportMask.hasAnyInitiators()) { exportMaskToVolumesToAdd.put(exportMask.getId(), volumeMap); for (String uriString : exportMask.getInitiators()) { URI initiatorURI = URI.create(uriString); initiatorsToPlace.remove(initiatorURI); } } } Map<String, List<URI>> computeResourceToInitiators = mapInitiatorsToComputeResource(exportGroup, initiatorsToPlace); log.info( String.format( "Need to create ExportMasks for these compute resources %s", Joiner.on(',').join(computeResourceToInitiators.entrySet()))); // ExportMask that need to be newly create because we just added // volumes from 'storage' StorageSystem to this ExportGroup for (Map.Entry<String, List<URI>> toCreate : computeResourceToInitiators.entrySet()) { generateExportMaskCreateWorkflow( workflow, null, storage, exportGroup, toCreate.getValue(), volumeMap, token); } log.info( String.format( "Need to add volumes for these ExportMasks %s", exportMaskToVolumesToAdd.entrySet())); // We already know about the ExportMask, so we just add volumes to it for (Map.Entry<URI, Map<URI, Integer>> toAddVolumes : exportMaskToVolumesToAdd.entrySet()) { ExportMask exportMask = _dbClient.queryObject(ExportMask.class, toAddVolumes.getKey()); generateExportMaskAddVolumesWorkflow( workflow, null, storage, exportGroup, exportMask, toAddVolumes.getValue()); } String successMessage = String.format( "ExportGroup add volumes successfully applied for StorageArray %s", storage.getLabel()); workflow.executePlan(taskCompleter, successMessage); } else { taskCompleter.ready(_dbClient); } } catch (DeviceControllerException dex) { taskCompleter.error( _dbClient, DeviceControllerErrors.scaleio.encounteredAnExceptionFromScaleIOOperation( "exportGroupAddVolumes", dex.getMessage())); } catch (Exception ex) { _log.error("ExportGroup Orchestration failed.", ex); taskCompleter.error( _dbClient, DeviceControllerErrors.scaleio.encounteredAnExceptionFromScaleIOOperation( "exportGroupAddVolumes", ex.getMessage())); } }
@Override public void exportGroupRemoveInitiators( URI storageURI, URI exportGroupURI, List<URI> initiatorURIs, String token) throws Exception { /* * foreach ScaleOI volume in ExportGroup * foreach initiator in list * if volume not used in another ExportGroup with same initiator * scli unmap --volume volid --sdc initiator.sdcid */ ExportOrchestrationTask taskCompleter = new ExportOrchestrationTask(exportGroupURI, token); try { ExportGroup exportGroup = _dbClient.queryObject(ExportGroup.class, exportGroupURI); StorageSystem storage = _dbClient.queryObject(StorageSystem.class, storageURI); if (initiatorURIs != null && !initiatorURIs.isEmpty() && exportGroup.getExportMasks() != null) { // Set up workflow steps. Workflow workflow = _workflowService.getNewWorkflow( MaskingWorkflowEntryPoints.getInstance(), "exportGroupRemoveInitiators", true, token); // Create a mapping of ExportMask URI to initiators to remove Map<URI, List<URI>> exportToInitiatorsToRemove = new HashMap<>(); Map<URI, List<URI>> exportToVolumesToRemove = new HashMap<>(); Map<URI, Integer> volumeMap = null; for (String exportMaskURIStr : exportGroup.getExportMasks()) { URI exportMaskURI = URI.create(exportMaskURIStr); ExportMask exportMask = _dbClient.queryObject(ExportMask.class, exportMaskURI); if (exportMask == null) { continue; } for (URI initiatorURI : initiatorURIs) { Initiator initiator = _dbClient.queryObject(Initiator.class, initiatorURI); if (initiator == null || !exportMask.hasInitiator(initiatorURI.toString())) { continue; } if (ExportUtils.getInitiatorExportGroups(initiator, _dbClient).size() == 1) { List<URI> initiators = exportToInitiatorsToRemove.get(exportGroupURI); if (initiators == null) { initiators = new ArrayList<>(); exportToInitiatorsToRemove.put(exportMaskURI, initiators); } initiators.add(initiatorURI); } else { if (volumeMap == null) { volumeMap = ExportUtils.getExportGroupVolumeMap(_dbClient, storage, exportGroup); } List<URI> volumeURIs = exportToVolumesToRemove.get(exportGroupURI); if (volumeURIs == null) { volumeURIs = new ArrayList<>(); exportToVolumesToRemove.put(exportMaskURI, volumeURIs); } for (URI volumeURI : volumeMap.keySet()) { // Only add to the remove list for the ExportMask if // the EM is not being shared with another ExportGroup Integer count = ExportUtils.getNumberOfExportGroupsWithVolume(initiator, volumeURI, _dbClient); if (count == 1) { volumeURIs.add(volumeURI); } } } } } // Generate the remove initiators steps for the entries that were determined above for (Map.Entry<URI, List<URI>> toRemoveInits : exportToInitiatorsToRemove.entrySet()) { ExportMask exportMask = _dbClient.queryObject(ExportMask.class, toRemoveInits.getKey()); if (exportMask != null) { List<URI> removeInitURIs = toRemoveInits.getValue(); List<String> exportMaskInitiatorURIs = new ArrayList<>(exportMask.getInitiators()); for (URI uri : removeInitURIs) { exportMaskInitiatorURIs.remove(uri.toString()); } if (exportMaskInitiatorURIs.isEmpty()) { log.info( String.format("Adding step to delete ExportMask %s", exportMask.getMaskName())); generateExportMaskDeleteWorkflow( workflow, null, storage, exportGroup, exportMask, null); } else { log.info( String.format( "Adding step to remove initiators %s from ExportMask %s", Joiner.on(',').join(removeInitURIs), exportMask.getMaskName())); generateExportMaskRemoveInitiatorsWorkflow( workflow, null, storage, exportGroup, exportMask, removeInitURIs, true); } } } // Generate the remove volume for those cases where we remove initiators // from an ExportGroup that contains more than one host/initiator for (Map.Entry<URI, List<URI>> toRemoveVols : exportToVolumesToRemove.entrySet()) { ExportMask exportMask = _dbClient.queryObject(ExportMask.class, toRemoveVols.getKey()); List<URI> removeVolumeURIs = toRemoveVols.getValue(); if (exportMask != null && !removeVolumeURIs.isEmpty()) { List<String> exportMaskVolumeURIs = new ArrayList<>(exportMask.getVolumes().keySet()); for (URI uri : removeVolumeURIs) { exportMaskVolumeURIs.remove(uri.toString()); } if (exportMaskVolumeURIs.isEmpty()) { log.info( String.format("Adding step to delete ExportMask %s", exportMask.getMaskName())); generateExportMaskDeleteWorkflow( workflow, null, storage, exportGroup, exportMask, null); } else { log.info( String.format( "Adding step to remove volumes %s from ExportMask %s", Joiner.on(',').join(removeVolumeURIs), exportMask.getMaskName())); generateExportMaskRemoveVolumesWorkflow( workflow, null, storage, exportGroup, exportMask, removeVolumeURIs, null); } } } String successMessage = String.format( "ExportGroup remove initiators successfully applied for StorageArray %s", storage.getLabel()); workflow.executePlan(taskCompleter, successMessage); } else { taskCompleter.ready(_dbClient); } } catch (DeviceControllerException dex) { taskCompleter.error( _dbClient, DeviceControllerErrors.scaleio.encounteredAnExceptionFromScaleIOOperation( "exportGroupRemoveInitiators", dex.getMessage())); } catch (Exception ex) { _log.error("ExportGroup Orchestration failed.", ex); taskCompleter.error( _dbClient, DeviceControllerErrors.scaleio.encounteredAnExceptionFromScaleIOOperation( "exportGroupRemoveInitiators", ex.getMessage())); } }
/** * 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 exportGroupRemoveVolumes( URI storageURI, URI exportGroupURI, List<URI> volumeURIs, String token) throws Exception { /* * foreach volume in list * foreach initiator in ExportGroup * if volume not used in another ExportGroup with same initiator * scli unmap --volume volid --sdc initiator.sdcid */ ExportOrchestrationTask taskCompleter = new ExportOrchestrationTask(exportGroupURI, token); try { ExportGroup exportGroup = _dbClient.queryObject(ExportGroup.class, exportGroupURI); StorageSystem storage = _dbClient.queryObject(StorageSystem.class, storageURI); List<ExportMask> masks = ExportMaskUtils.getExportMasks(_dbClient, exportGroup, storageURI); if (masks != null && !masks.isEmpty()) { // Set up workflow steps. Workflow workflow = _workflowService.getNewWorkflow( MaskingWorkflowEntryPoints.getInstance(), "exportGroupRemoveVolumes", true, token); // Generate a list of Initiators List<URI> initiatorURIs = StringSetUtil.stringSetToUriList(exportGroup.getInitiators()); Map<URI, List<URI>> exportToRemoveVolumesList = new HashMap<>(); // Generate a mapping of volume URIs to the # of // ExportGroups that it is associated with Map<URI, Map<URI, Integer>> exportMaskToVolumeCount = ExportMaskUtils.mapExportMaskToVolumeShareCount(_dbClient, volumeURIs, initiatorURIs); // Generate a mapping of the ExportMask URI to a list volumes to // remove from that ExportMask for (ExportMask exportMask : masks) { Map<URI, Integer> volumeToCountMap = exportMaskToVolumeCount.get(exportMask.getId()); if (volumeToCountMap == null) { continue; } for (Map.Entry<URI, Integer> it : volumeToCountMap.entrySet()) { URI volumeURI = it.getKey(); Integer numberOfExportGroupsVolumesIsIn = it.getValue(); if (numberOfExportGroupsVolumesIsIn == 1) { List<URI> volumesToRemove = exportToRemoveVolumesList.get(exportMask.getId()); if (volumesToRemove == null) { volumesToRemove = new ArrayList<>(); exportToRemoveVolumesList.put(exportMask.getId(), volumesToRemove); } volumesToRemove.add(volumeURI); } } } // With the mapping of ExportMask to list of volume URIs, // generate a step to remove the volumes from the ExportMask for (Map.Entry<URI, List<URI>> entry : exportToRemoveVolumesList.entrySet()) { ExportMask exportMask = _dbClient.queryObject(ExportMask.class, entry.getKey()); log.info( String.format( "Adding step to remove volumes %s from ExportMask %s", Joiner.on(',').join(entry.getValue()), exportMask.getMaskName())); generateExportMaskRemoveVolumesWorkflow( workflow, null, storage, exportGroup, exportMask, entry.getValue(), null); } String successMessage = String.format( "ExportGroup remove volumes successfully applied for StorageArray %s", storage.getLabel()); workflow.executePlan(taskCompleter, successMessage); } else { taskCompleter.ready(_dbClient); } } catch (DeviceControllerException dex) { taskCompleter.error( _dbClient, DeviceControllerErrors.scaleio.encounteredAnExceptionFromScaleIOOperation( "exportGroupRemoveVolumes", dex.getMessage())); } catch (Exception ex) { _log.error("ExportGroup Orchestration failed.", ex); taskCompleter.error( _dbClient, DeviceControllerErrors.scaleio.encounteredAnExceptionFromScaleIOOperation( "exportGroupRemoveVolumes", ex.getMessage())); } }
@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"); }
@Override public void exportGroupCreate( URI storageURI, URI exportGroupURI, List<URI> initiatorURIs, Map<URI, Integer> volumeMap, String token) throws Exception { ExportOrchestrationTask taskCompleter = new ExportOrchestrationTask(exportGroupURI, token); try { ExportGroup exportGroup = _dbClient.queryObject(ExportGroup.class, exportGroupURI); StorageSystem storage = _dbClient.queryObject(StorageSystem.class, storageURI); if (initiatorURIs != null && !initiatorURIs.isEmpty()) { // Set up workflow steps. Workflow workflow = _workflowService.getNewWorkflow( MaskingWorkflowEntryPoints.getInstance(), "exportGroupCreate", true, token); // Create a mapping of ExportMasks to Add Volumes to or // add to a list of new Exports to create Map<URI, Map<URI, Integer>> exportMaskToVolumesToAdd = new HashMap<>(); List<URI> newInitiators = new ArrayList<>(); List<Initiator> initiators = _dbClient.queryObject(Initiator.class, initiatorURIs); for (Initiator initiator : initiators) { List<ExportMask> exportMasks = ExportUtils.getInitiatorExportMasks(initiator, _dbClient); if (exportMasks == null || exportMasks.isEmpty()) { newInitiators.add(initiator.getId()); } else { for (ExportMask exportMask : exportMasks) { exportMaskToVolumesToAdd.put(exportMask.getId(), volumeMap); } } } Map<String, List<URI>> computeResourceToInitiators = mapInitiatorsToComputeResource(exportGroup, newInitiators); log.info( String.format( "Need to create ExportMasks for these compute resources %s", Joiner.on(',').join(computeResourceToInitiators.entrySet()))); // ExportMask that need to be newly create. That is, the initiators in // this ExportGroup create do not already exist on the system, hence // there aren't any already existing ExportMask for them for (Map.Entry<String, List<URI>> toCreate : computeResourceToInitiators.entrySet()) { generateExportMaskCreateWorkflow( workflow, null, storage, exportGroup, toCreate.getValue(), volumeMap, token); } log.info( String.format( "Need to add volumes for these ExportMasks %s", exportMaskToVolumesToAdd.entrySet())); // There are some existing ExportMasks for the initiators in the request. // For these, we want to reuse the ExportMask and add volumes to them. // These ExportMasks would be created by the system. ScaleIO has no // concept ExportMasks. for (Map.Entry<URI, Map<URI, Integer>> toAddVolumes : exportMaskToVolumesToAdd.entrySet()) { ExportMask exportMask = _dbClient.queryObject(ExportMask.class, toAddVolumes.getKey()); generateExportMaskAddVolumesWorkflow( workflow, null, storage, exportGroup, exportMask, toAddVolumes.getValue()); } String successMessage = String.format( "ExportGroup successfully applied for StorageArray %s", storage.getLabel()); workflow.executePlan(taskCompleter, successMessage); } else { taskCompleter.ready(_dbClient); } } catch (DeviceControllerException dex) { taskCompleter.error( _dbClient, DeviceControllerErrors.scaleio.encounteredAnExceptionFromScaleIOOperation( "exportGroupCreate", dex.getMessage())); } catch (Exception ex) { _log.error("ExportGroup Orchestration failed.", ex); taskCompleter.error( _dbClient, DeviceControllerErrors.scaleio.encounteredAnExceptionFromScaleIOOperation( "exportGroupCreate", ex.getMessage())); } }
@Override public void deleteOrRemoveVolumesFromExportMask( URI arrayURI, URI exportGroupURI, URI exportMaskURI, List<URI> volumes, TaskCompleter completer, String stepId) { try { WorkflowStepCompleter.stepExecuting(stepId); StorageSystem array = _dbClient.queryObject(StorageSystem.class, arrayURI); BlockStorageDevice device = _blockController.getDevice(array.getSystemType()); ExportMask exportMask = _dbClient.queryObject(ExportMask.class, exportMaskURI); // If the exportMask isn't found, or has been deleted, nothing to do. if (exportMask == null || exportMask.getInactive()) { _log.info(String.format("ExportMask %s inactive, returning success", exportMaskURI)); WorkflowStepCompleter.stepSucceded(stepId); 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)); // Make sure the completer will complete the work flow. This happens on roll back case. if (!completer.getOpId().equals(stepId)) { completer.setOpId(stepId); } // Refresh the ExportMask exportMask = refreshExportMask(array, device, exportMask); // Determine if we're deleting the last volume. Set<String> remainingVolumes = new HashSet<String>(); if (exportMask.getVolumes() != null) { remainingVolumes.addAll(exportMask.getVolumes().keySet()); } for (URI volume : volumes) { remainingVolumes.remove(volume.toString()); } // If it is last volume, delete the ExportMask. if (remainingVolumes.isEmpty() && (exportMask.getExistingVolumes() == null || exportMask.getExistingVolumes().isEmpty())) { _log.debug( String.format( "Calling doExportGroupDelete on the device %s", array.getId().toString())); device.doExportGroupDelete(array, exportMask, completer); } else { _log.debug( String.format( "Calling doExportRemoveVolumes on the device %s", array.getId().toString())); device.doExportRemoveVolumes(array, exportMask, volumes, completer); } } catch (Exception ex) { _log.error("Failed to delete or remove volumes to export mask for cinder: ", ex); VPlexApiException vplexex = DeviceControllerExceptions.vplex.addStepsForDeleteVolumesFailed(ex); WorkflowStepCompleter.stepFailed(stepId, vplexex); } }