@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);
    }
  }