/* * Copyright (c) 2014 EMC Corporation * All Rights Reserved */ package com.emc.storageos.volumecontroller.impl.block; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.emc.storageos.db.client.model.ExportGroup; import com.emc.storageos.db.client.model.ExportMask; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.util.StringSetUtil; import com.emc.storageos.exceptions.DeviceControllerException; import com.emc.storageos.svcs.errorhandling.model.ServiceError; import com.emc.storageos.volumecontroller.BlockStorageDevice; import com.emc.storageos.volumecontroller.impl.block.taskcompleter.ExportOrchestrationTask; import com.emc.storageos.volumecontroller.impl.block.taskcompleter.ExportTaskCompleter; import com.emc.storageos.workflow.Workflow; import com.emc.storageos.workflow.WorkflowException; import com.google.common.base.Joiner; /** * This class will have common code used by the MaskingOrchestrator * implementations. It also provides a simple, default implementation of the * export operations, which assumes that the ExportMasks on the array will only * be created by the system. Any existing exports maybe clobber or the operation * may fail in such scenarios. * * This class is added from 2.2 release to reverse the zoning and masking * operations. Extend from this class if the requirement is to do masking first * and then zoning. For E.g : Cinder, VNX and HDS systems require masking to be * done first and then zoning */ abstract public class AbstractMaskingFirstOrchestrator extends AbstractBasicMaskingOrchestrator { /** * Create storage level masking components to support the requested * ExportGroup object. This operation will be flexible enough to take into * account initiators that are in some already existent in some * StorageGroup. In such a case, the underlying masking component will be * "adopted" by the ExportGroup. Further operations against the "adopted" * mask will only allow for addition and removal of those initiators/volumes * that were added by a Bourne request. Existing initiators/volumes will be * maintained. * * * @param storageURI - URI referencing underlying storage array * @param exportGroupURI - URI referencing Bourne-level masking, ExportGroup * @param initiatorURIs - List of Initiator URIs * @param volumeMap - Map of Volume URIs to requested Integer URI * @param token - Identifier for operation * @throws Exception */ @Override public void exportGroupCreate(URI storageURI, URI exportGroupURI, List<URI> initiatorURIs, Map<URI, Integer> volumeMap, String token) throws Exception { ExportOrchestrationTask taskCompleter = null; try { BlockStorageDevice device = getDevice(); ExportGroup exportGroup = _dbClient.queryObject(ExportGroup.class, exportGroupURI); StorageSystem storage = _dbClient.queryObject(StorageSystem.class, storageURI); taskCompleter = new ExportOrchestrationTask(exportGroupURI, token); if (initiatorURIs != null && !initiatorURIs.isEmpty()) { _log.info("export_create: initiator list non-empty"); createWorkFlowAndSubmitForExportGroupCreate(initiatorURIs, volumeMap, token, taskCompleter, device, exportGroup, storage); } else { _log.info("export_create: initiator list is empty"); taskCompleter.ready(_dbClient); } } catch (DeviceControllerException dex) { if (taskCompleter != null) { taskCompleter.error(_dbClient, DeviceControllerException.errors .vmaxExportGroupCreateError(dex.getMessage())); } } catch (Exception ex) { _log.error("ExportGroup Orchestration failed.", ex); // TODO add service code here if (taskCompleter != null) { ServiceError serviceError = DeviceControllerException.errors .jobFailedMsg(ex.getMessage(), ex); taskCompleter.error(_dbClient, serviceError); } } } /** * Create device specific steps for work flow execution * * @param initiatorURIs * @param volumeMap * @param token * @param taskCompleter * @param device * @param exportGroup * @param storage * @throws Exception */ public abstract void createWorkFlowAndSubmitForExportGroupCreate( List<URI> initiatorURIs, Map<URI, Integer> volumeMap, String token, ExportOrchestrationTask taskCompleter, BlockStorageDevice device, ExportGroup exportGroup, StorageSystem storage) throws Exception; @Override public void exportGroupAddVolumes(URI storageURI, URI exportGroupURI, Map<URI, Integer> volumeMap, String token) throws Exception { ExportTaskCompleter taskCompleter = null; try { _log.info(String .format("exportAddVolume START - Array: %s ExportMask: %s Volume: %s", storageURI.toString(), exportGroupURI.toString(), Joiner.on(',').join(volumeMap.entrySet()))); ExportGroup exportGroup = _dbClient.queryObject(ExportGroup.class, exportGroupURI); StorageSystem storage = _dbClient.queryObject(StorageSystem.class, storageURI); taskCompleter = new ExportOrchestrationTask(exportGroupURI, token); createWorkFlowAndSubmitForAddVolumes(storageURI, exportGroupURI, volumeMap, token, taskCompleter, exportGroup, storage); _log.info(String .format("exportAddVolume END - Array: %s ExportMask: %s Volume: %s", storageURI.toString(), exportGroupURI.toString(), volumeMap.toString())); } catch (Exception e) { if (taskCompleter != null) { ServiceError serviceError = DeviceControllerException.errors .jobFailedMsg(e.getMessage(), e); taskCompleter.error(_dbClient, serviceError); } else { throw DeviceControllerException.exceptions.exportGroupAddVolumesFailed(e); } } } /** * Creates device specific work flow steps for the add volumes to export * group operation. * * @param storageURI * @param exportGroupURI * @param volumeMap * @param token * @param taskCompleter * @param exportGroup * @param storage * @throws Exception */ public abstract void createWorkFlowAndSubmitForAddVolumes(URI storageURI, URI exportGroupURI, Map<URI, Integer> volumeMap, String token, ExportTaskCompleter taskCompleter, ExportGroup exportGroup, StorageSystem storage) throws Exception; @Override public void increaseMaxPaths(Workflow workflow, StorageSystem storageSystem, ExportGroup exportGroup, ExportMask exportMask, List<URI> newInitiators, String token) throws Exception { /* * Increases the MaxPaths for a given ExportMask if it has Initiators * that are not currently zoned to ports. The method * generateExportMaskAddInitiatorsWorkflow will allocate additional * ports for the newInitiators to be processed. These will be zoned and * then subsequently added to the MaskingView / ExportMask. */ Map<URI, List<URI>> zoneMasksToInitiatorsURIs = new HashMap<URI, List<URI>>(); zoneMasksToInitiatorsURIs.put(exportMask.getId(), newInitiators); Set<URI> volumeURIs = new HashSet<URI>(StringSetUtil.stringSetToUriList(exportMask.getUserAddedVolumes().values())); String maskinStep = generateExportMaskAddInitiatorsWorkflow(workflow, null, storageSystem, exportGroup, exportMask, newInitiators, volumeURIs, token); generateZoningAddInitiatorsWorkflow(workflow, maskinStep, exportGroup, zoneMasksToInitiatorsURIs); } /** * Generates the sequence of work flow based on the device type for * addInitiators operation in export mask. This is default implementation. * If there is any device specific implementation, we should override this * method and implement device specific logic. * * @param workflow * @param previousStep * @param storage * @param exportGroup * @param mask * @param initiatorsURIs * @param maskToInitiatorsMap * @param token * @throws */ @Override public String generateDeviceSpecificAddInitiatorWorkFlow(Workflow workflow, String previousStep, StorageSystem storage, ExportGroup exportGroup, ExportMask mask, List<URI> volumeURIs, List<URI> initiatorsURIs, Map<URI, List<URI>> maskToInitiatorsMap, String token) throws Exception { // First masking step is created Set<URI> volumes = new HashSet<URI>(volumeURIs); String maskingStep = generateExportMaskAddInitiatorsWorkflow(workflow, previousStep, storage, exportGroup, mask, initiatorsURIs, volumes, token); // Zoning step is second - it waits till the completion of masking step return generateZoningAddInitiatorsWorkflow(workflow, maskingStep, exportGroup, maskToInitiatorsMap); } /** * Generates device specific sequence of work flow to addVolumes in export * mask. * * @param workflow * @param attachGroupSnapshot * @param storage * @param exportGroup * @param mask * @param volumesToAdd * @param volumeURIs * @return workflow stepId: export mask stepId. * @throws Exception */ @Override public String generateDeviceSpecificAddVolumeWorkFlow(Workflow workflow, String previousStep, StorageSystem storage, ExportGroup exportGroup, ExportMask mask, Map<URI, Integer> volumesToAdd, List<URI> volumeURIs, List<URI> initiatorURIs) throws Exception { List<ExportMask> masks = new ArrayList<ExportMask>(); masks.add(mask); // First create the masking step String maskingStepId = generateExportMaskAddVolumesWorkflow(workflow, previousStep, storage, exportGroup, mask, volumesToAdd, initiatorURIs); // Second create the zoning step - this will wait for the masking // completion generateZoningAddVolumesWorkflow(workflow, maskingStepId, exportGroup, masks, volumeURIs); return maskingStepId; } /** * Generates Device specific workflow step to create exportmask. * * @param workflow * @param previousStepId * @param storage * @param exportGroup * @param hostInitiators * @param volumeMap * @param token * @return * @throws Exception */ @Override public GenExportMaskCreateWorkflowResult generateDeviceSpecificExportMaskCreateWorkFlow( Workflow workflow, String zoningGroupId, StorageSystem storage, ExportGroup exportGroup, List<URI> hostInitiators, Map<URI, Integer> volumeMap, String token) throws Exception { // Removed the dependency for zoning, hence masking will be performed first return generateExportMaskCreateWorkflow(workflow, null, storage, exportGroup, hostInitiators, volumeMap, token); } /** * Generates Device specific workflow step to create zoning in exportmask. * * @param workflow * @param previousStepId * @param exportGroup * @param exportMaskList * @param overallVolumeMap */ @Override public String generateDeviceSpecificZoningCreateWorkflow(Workflow workflow, String previousStepId, ExportGroup exportGroup, List<URI> exportMaskList, Map<URI, Integer> overallVolumeMap) { return generateZoningCreateWorkflow(workflow, previousStepId, exportGroup, exportMaskList, overallVolumeMap); } /** * Generates Device specific workflow step to addInitiators in exportMask. * * @param workflow * @param zoningGroupId * @param storage * @param exportGroup * @param mask * @param newInitiators * @param token * @throws Exception */ @Override public String generateDeviceSpecificExportMaskAddInitiatorsWorkflow(Workflow workflow, String zoningGroupId, StorageSystem storage, ExportGroup exportGroup, ExportMask mask, List<URI> volumes, List<URI> newInitiators, String token) throws Exception { // Removed the dependency for zoning, hence masking will be performed first Set<URI> volumeURIs = new HashSet<URI>(); if (volumes != null) { volumeURIs.addAll(volumes); } return generateExportMaskAddInitiatorsWorkflow(workflow, null, storage, exportGroup, mask, newInitiators, volumeURIs, token); } /** * Generates device specific workflow step to do zoning for addInitiators in * exportmask. * * @param workflow * @param object * @param exportGroup * @param zoneMasksToInitiatorsURIs */ @Override public String generateDeviceSpecificZoningAddInitiatorsWorkflow(Workflow workflow, String previousStep, ExportGroup exportGroup, Map<URI, List<URI>> zoneMasksToInitiatorsURIs) { return generateZoningAddInitiatorsWorkflow(workflow, previousStep, exportGroup, zoneMasksToInitiatorsURIs); } /** * Generates work flow step for zoning map update in export mask. * * @param workflow * @param previousStep * @param exportMask * @param storage * @return * @throws WorkflowException */ public String generateZoningMapUpdateWorkflow(Workflow workflow, String previousStep, ExportGroup exportGroup, StorageSystem storage) throws WorkflowException { String updateZoningMapStep = workflow.createStepId(); Workflow.Method maskingExecuteMethod = new Workflow.Method( "doExportMaskZoningMapUpdate", exportGroup.getId(), storage.getId()); Workflow.Method maskingRollbackMethod = new Workflow.Method( "rollbackExportMaskZoningMapUpdate", exportGroup.getId(), storage.getId()); updateZoningMapStep = workflow.createStep( EXPORT_GROUP_UPDATE_ZONING_MAP, String.format("Updating zoning map in export mask "), previousStep, storage.getId(), storage.getSystemType(), MaskingWorkflowEntryPoints.class, maskingExecuteMethod, maskingRollbackMethod, updateZoningMapStep); return updateZoningMapStep; } }