/* * Copyright (c) 2016 EMC Corporation * All Rights Reserved */ package com.emc.storageos.volumecontroller.impl.externaldevice; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.db.client.model.BlockObject; import com.emc.storageos.db.client.model.ExportGroup; import com.emc.storageos.db.client.model.ExportMask; import com.emc.storageos.db.client.model.Initiator; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.exceptions.DeviceControllerException; import com.emc.storageos.services.util.StorageDriverManager; import com.emc.storageos.volumecontroller.BlockStorageDevice; import com.emc.storageos.volumecontroller.impl.ControllerServiceImpl; import com.emc.storageos.volumecontroller.impl.block.AbstractMaskingFirstOrchestrator; import com.emc.storageos.volumecontroller.impl.block.MaskingWorkflowEntryPoints; import com.emc.storageos.volumecontroller.impl.block.taskcompleter.ExportOrchestrationTask; import com.emc.storageos.volumecontroller.impl.block.taskcompleter.ExportTaskCompleter; import com.emc.storageos.volumecontroller.impl.utils.ExportMaskUtils; import com.emc.storageos.workflow.Workflow; import com.emc.storageos.workflow.WorkflowService; import com.google.common.base.Joiner; public class ExternalDeviceMaskingOrchestrator extends AbstractMaskingFirstOrchestrator { private static final Logger _log = LoggerFactory.getLogger(ExternalDeviceMaskingOrchestrator.class); private StorageDriverManager driverManager = null; private BlockStorageDevice device = null; @Override public synchronized BlockStorageDevice getDevice() { if (device == null) { device = (BlockStorageDevice) ControllerServiceImpl.getBean(StorageDriverManager.EXTERNAL_STORAGE_DEVICE); } return device; } public synchronized StorageDriverManager getDriverManager() { if (driverManager == null) { driverManager = (StorageDriverManager) ControllerServiceImpl.getBean(StorageDriverManager.STORAGE_DRIVER_MANAGER); } return driverManager; } @Override public void createWorkFlowAndSubmitForExportGroupCreate(List<URI> initiatorURIs, Map<URI, Integer> volumeMap, String token, ExportOrchestrationTask taskCompleter, BlockStorageDevice device, ExportGroup exportGroup, StorageSystem storage) throws Exception { // Check that storage system is driver managed. StorageDriverManager storageDriverManager = getDriverManager(); if (!storageDriverManager.isDriverManaged(storage.getSystemType())) { throw DeviceControllerException.exceptions.invalidSystemType(storage.getSystemType()); } _log.info("Started export group processing."); // Get new work flow to setup steps for export group creation Workflow workflow = _workflowService.getNewWorkflow( MaskingWorkflowEntryPoints.getInstance(), "exportGroupCreate", true, token); // Create two steps, one for the ExportGroup actions and one for Zoning. List<String> maskingSteps = generateExportGroupCreateSteps(workflow, null, device, storage, exportGroup, initiatorURIs, volumeMap, false, token); // Have to store export group id to be available at device level for each masking step. for (String stepId : maskingSteps) { WorkflowService.getInstance().storeStepData( workflow.getWorkflowURI(), null, stepId, exportGroup.getId()); } /* * This step is for zoning. It is not specific to a single * NetworkSystem, as it will look at all the initiators and targets and * compute the zones required (which might be on multiple * NetworkSystems.) * * Dependency task for the zoning execution is * EXPORT_GROUP_MASKING_TASK, hence enforcing the masking to be executed * before zoning is attempted */ String zoningStep = generateDeviceSpecificZoningCreateWorkflow( workflow, EXPORT_GROUP_MASKING_TASK, exportGroup, null, volumeMap); if (!maskingSteps.isEmpty() && null != zoningStep) { // Execute the plan and allow the WorkflowExecutor to fire the // taskCompleter. workflow.executePlan(taskCompleter, String.format( "ExportGroup successfully applied for StorageArray %s", storage.getLabel())); } } /** * Generates export group create steps for a given set of initiators and volumes. * Only "greenfield" case is supported --- if export masks with one or more of given * initiators exist, we will fail the request. * * @param workflow * @param previousStep * @param device * @param storage * @param exportGroup * @param initiatorURIs * @param volumeMap * volume URI to HLU map * @param zoneStepNeeded * @param token * @return list of step Ids * @throws Exception */ private List<String> generateExportGroupCreateSteps(Workflow workflow, String previousStep, BlockStorageDevice device, StorageSystem storage, ExportGroup exportGroup, List<URI> initiatorURIs, Map<URI, Integer> volumeMap, boolean zoneStepNeeded, String token) throws Exception { Map<String, URI> portNameToInitiatorURI = new HashMap<>(); List<URI> hostURIs = new ArrayList<>(); List<String> portNames = new ArrayList<>(); // host initiator names String stepId; _log.info("Started export mask steps generation."); /* * Populate the port WWN/IQNs (portNames) and the mapping of the * WWN/IQNs to Initiator URIs */ processInitiators(exportGroup, initiatorURIs, portNames, portNameToInitiatorURI, hostURIs); _log.info("Done with initiator processing."); /* * 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); _log.info("Done with matching export masks."); List<String> newSteps; if (matchingExportMaskURIs == null || matchingExportMaskURIs.isEmpty()) { _log.info(String.format( "No existing mask found w/ initiators { %s }", Joiner.on(",").join(portNames))); newSteps = 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.keySet()), Joiner.on(",").join(portNameToInitiatorURI.entrySet()))); /* * TODO: TBD. use case not supported for External device (brown field). */ throw DeviceControllerException.exceptions.blockDeviceOperationNotSupported(); } return newSteps; } @Override public void createWorkFlowAndSubmitForAddVolumes(URI storageURI, URI exportGroupURI, Map<URI, Integer> volumeMap, String token, ExportTaskCompleter taskCompleter, ExportGroup exportGroup, StorageSystem storage) throws Exception { // Note: We support only case when add volumes to export group does not change storage ports in // existing export masks where volumes are added. // Since we only execute masking step on device for existing masks --- no zoning change is required. List<ExportMask> exportMasks = ExportMaskUtils.getExportMasks(_dbClient, exportGroup, storageURI); if (exportMasks != null && !exportMasks.isEmpty()) { // Set up work flow steps. Workflow workflow = _workflowService.getNewWorkflow( MaskingWorkflowEntryPoints.getInstance(), "exportGroupAddVolumes - Added volumes to existing mask", true, token); // For each export mask in export group, invoke add Volumes if export Mask belongs to the storage array // of added volumes. Export group may have export masks for the same array but for different compute // resources (hosts or clusters). for (ExportMask exportMask : exportMasks) { if (exportMask.getStorageDevice().equals(storageURI)) { _log.info("export_volume_add: adding volume to an existing export"); exportMask.addVolumes(volumeMap); // Have to add volumes to user created volumes set in the mask since // generateExportMaskAddVolumesWorkflow() call below does not do this. for (URI volumeUri : volumeMap.keySet()) { BlockObject volume = (BlockObject) _dbClient.queryObject(volumeUri); exportMask.addToUserCreatedVolumes(volume); } _dbClient.updateObject(exportMask); List<URI> volumeURIs = new ArrayList<>(); volumeURIs.addAll(volumeMap.keySet()); String maskingStep = generateExportMaskAddVolumesWorkflow(workflow, null, storage, exportGroup, exportMask, volumeMap, null); // We do not need zoning step, since storage ports should not change. // Have to store export group id to be available at device level. WorkflowService.getInstance().storeStepData( workflow.getWorkflowURI(), null, maskingStep, exportGroup.getId()); } } String successMessage = String.format( "Volumes successfully added to export on StorageArray %s", storage.getLabel()); workflow.executePlan(taskCompleter, successMessage); } else { // This is the case when export group does not have export mask for storage array where the volumes belongs. // In this case we will create new export masks for the storage array and each compute resource in the // export group. Essentially for every existing mask we will add a new mask for the array and initiators in // the existing mask. if (exportGroup.getInitiators() != null && !exportGroup.getInitiators().isEmpty()) { _log.info("export_volume_add: adding volume, creating a new export mask"); List<URI> initiatorURIs = new ArrayList<>(); for (String initiatorId : exportGroup.getInitiators()) { Initiator initiator = _dbClient.queryObject( Initiator.class, URI.create(initiatorId)); initiatorURIs.add(initiator.getId()); } // Get new workflow to setup steps for export masks creation Workflow workflow = _workflowService.getNewWorkflow( MaskingWorkflowEntryPoints.getInstance(), "exportGroupCreate", true, token); // This call will create steps for a new mask for each compute resource // and new volumes. For example, if there are 3 compute resources in the group, // the step will create 3 new masks for these resources and new volumes. List<String> maskingSteps = createNewExportMaskWorkflowForInitiators(initiatorURIs, exportGroup, workflow, volumeMap, storage, token, null); // Have to store export group id to be available at device level for each masking step. for (String stepId : maskingSteps) { WorkflowService.getInstance().storeStepData( workflow.getWorkflowURI(), null, stepId, exportGroup.getId()); } generateZoningCreateWorkflow(workflow, EXPORT_GROUP_MASKING_TASK, exportGroup, null, volumeMap); String successMessage = String .format("Volumes successfully added to export StorageArray %s", storage.getLabel()); workflow.executePlan(taskCompleter, successMessage); } else { _log.info("Export group doesn't have initiators."); taskCompleter.ready(_dbClient); } } } @Override public void findAndUpdateFreeHLUsForClusterExport(StorageSystem storage, ExportGroup exportGroup, List<URI> initiatorURIs, Map<URI, Integer> volumeMap) throws Exception { // TODO Auto-generated method stub } }