@Override
  public void createOrAddVolumesToExportMask(
      URI arrayURI,
      URI exportGroupURI,
      URI exportMaskURI,
      Map<URI, Integer> volumeMap,
      List<URI> initiatorURIs2,
      TaskCompleter completer,
      String stepId) {
    try {
      StorageSystem array = _dbClient.queryObject(StorageSystem.class, arrayURI);
      ExportMask exportMask = _dbClient.queryObject(ExportMask.class, exportMaskURI);
      WorkflowStepCompleter.stepExecuting(stepId);

      // If the exportMask isn't found, or has been deleted, fail, ask user to retry.
      if (exportMask == null || exportMask.getInactive()) {
        _log.info(String.format("ExportMask %s deleted or inactive, failing", exportMaskURI));
        ServiceError svcerr =
            VPlexApiException.errors.createBackendExportMaskDeleted(
                exportMaskURI.toString(), arrayURI.toString());
        WorkflowStepCompleter.stepFailed(stepId, svcerr);
        return;
      }

      // Protect concurrent operations by locking {host, array} dupple.
      // Lock will be released when workflow step completes.
      List<String> lockKeys =
          ControllerLockingUtil.getHostStorageLockKeys(
              _dbClient,
              ExportGroupType.Host,
              StringSetUtil.stringSetToUriList(exportMask.getInitiators()),
              arrayURI);
      getWorkflowService()
          .acquireWorkflowStepLocks(
              stepId, lockKeys, LockTimeoutValue.get(LockType.VPLEX_BACKEND_EXPORT));

      // Fetch the Initiators
      List<URI> initiatorURIs = new ArrayList<URI>();
      List<Initiator> initiators = new ArrayList<Initiator>();
      for (String initiatorId : exportMask.getInitiators()) {
        Initiator initiator = _dbClient.queryObject(Initiator.class, URI.create(initiatorId));
        if (initiator != null) {
          initiators.add(initiator);
          initiatorURIs.add(initiator.getId());
        }
      }

      // We do not refresh here, as the VNXExportOperations code will throw an exception
      // if the StorageGroup was not found.
      BlockStorageDevice device = _blockController.getDevice(array.getSystemType());
      if (!exportMask.hasAnyVolumes() && exportMask.getCreatedBySystem()) {
        // We are creating this ExportMask on the hardware! (Maybe not the first time though...)

        // Fetch the targets
        List<URI> targets = new ArrayList<URI>();
        for (String targetId : exportMask.getStoragePorts()) {
          targets.add(URI.create(targetId));
        }

        // Clear the export_mask nativeId; otherwise the VnxExportOps will attempt to look it
        // up and fail. An empty String will suffice as having no nativeId.
        if (exportMask.getNativeId() != null) {
          exportMask.setNativeId("");
          _dbClient.updateAndReindexObject(exportMask);
        }
        // The default completer passed in is for add volume, create correct one
        completer =
            new ExportMaskCreateCompleter(
                exportGroupURI, exportMaskURI, initiatorURIs, volumeMap, stepId);
        device.doExportCreate(array, exportMask, volumeMap, initiators, targets, completer);
      } else {
        device.doExportAddVolumes(array, exportMask, initiators, volumeMap, completer);
      }
    } catch (Exception ex) {
      _log.error("Failed to create or add volumes to export mask for vnx: ", ex);
      VPlexApiException vplexex =
          DeviceControllerExceptions.vplex.addStepsForCreateVolumesFailed(ex);
      WorkflowStepCompleter.stepFailed(stepId, vplexex);
    }
  }
  /**
   * Update the zoning map entries from the updated target list in the mask
   *
   * <p>1. Clean existing zoning map entries 2. From the target storage ports in the mask, generate
   * a map of networkURI string vs list of target storage ports 3. From the initiator ports in the
   * mask, generate a map of its URI vs InitiatorWWN 4. From the initiatorPortMap, generate map of
   * its WWN vs networkURI string 5. Based on the networkURI matching, generate zoning map entries
   * adhering to vplex best practices 6. Persist the updated mask.
   *
   * @param initiatorPortMap
   * @param exportMask
   */
  private void updateZoningMap(
      Map<URI, List<StoragePort>> initiatorPortMap,
      Map<String, Set<String>> directorToInitiatorIds,
      ExportMask exportMask) {

    // STEP 1 - Clean the existing zoning map
    for (String initiatorURIStr : exportMask.getZoningMap().keySet()) {
      exportMask.removeZoningMapEntry(initiatorURIStr);
    }
    exportMask.setZoningMap(null);

    // STEP 2- From Back-end storage system ports, which are used as target storage ports for VPLEX
    // generate a map of networkURI string vs list of target storage ports.
    Map<String, List<StoragePort>> nwUriVsTargetPortsFromMask = new HashMap<>();
    StringSet targetPorts = exportMask.getStoragePorts();
    for (String targetPortUri : targetPorts) {
      StoragePort targetPort = _dbClient.queryObject(StoragePort.class, URI.create(targetPortUri));
      String networkUri = targetPort.getNetwork().toString();
      if (nwUriVsTargetPortsFromMask.containsKey(networkUri)) {
        nwUriVsTargetPortsFromMask.get(networkUri).add(targetPort);
      } else {
        nwUriVsTargetPortsFromMask.put(networkUri, new ArrayList<StoragePort>());
        nwUriVsTargetPortsFromMask.get(networkUri).add(targetPort);
      }
    }

    // STEP 3 - From the initiator ports in the mask, generate a map of its URI vs InitiatorWWN
    // Map<String, URI> initiatorWWNvsUriFromMask = new HashMap<>();
    Map<String, String> initiatorUrivsWWNFromMask = new HashMap<>();
    StringSet initiatorPorts = exportMask.getInitiators();
    for (String initiatorUri : initiatorPorts) {
      Initiator initiator = _dbClient.queryObject(Initiator.class, URI.create(initiatorUri));
      String initiatorWWN = initiator.getInitiatorPort();
      initiatorUrivsWWNFromMask.put(initiator.getId().toString(), initiatorWWN);
    }

    // STEP 4 - Convert networkURIvsStoragePort to Initiator Port WWN vs NetworkURI
    Map<String, String> initiatorWWNvsNetworkURI = new HashMap<>();
    Set<URI> networkURIs = initiatorPortMap.keySet();
    for (URI networkURI : networkURIs) {
      List<StoragePort> initiatorPortList = initiatorPortMap.get(networkURI);
      List<String> initiatorWWNList = new ArrayList<>(initiatorPortList.size());
      for (StoragePort initPort : initiatorPortList) {
        initiatorWWNList.add(initPort.getPortNetworkId());
        initiatorWWNvsNetworkURI.put(initPort.getPortNetworkId(), networkURI.toString());
      }
    }

    // STEP 5 - Consider directors to restrict paths not more than 4 for each director
    // And add the zoning map entries to adhere to the VPLEX best practices.
    Map<StoragePort, Integer> portUsage = new HashMap<>();
    Set<String> directorKeySet = directorToInitiatorIds.keySet();
    for (String director : directorKeySet) {
      Set<String> initiatorIds = directorToInitiatorIds.get(director);
      int directorPaths = 0;
      for (String initiatorId : initiatorIds) {
        if (4 == directorPaths) {
          break;
        }

        String initWWN = initiatorUrivsWWNFromMask.get(initiatorId);
        String initiatorNetworkURI = initiatorWWNvsNetworkURI.get(initWWN);
        List<StoragePort> matchingTargetPorts = nwUriVsTargetPortsFromMask.get(initiatorNetworkURI);

        if (null != matchingTargetPorts && !matchingTargetPorts.isEmpty()) {
          StoragePort assignedPort = assignPortBasedOnUsage(matchingTargetPorts, portUsage);
          StringSet targetPortURIs = new StringSet();
          targetPortURIs.add(assignedPort.getId().toString());
          _log.info(
              String.format(
                  "Adding zoning map entry - Initiator is %s and its targetPorts %s",
                  initiatorId, targetPortURIs.toString()));
          exportMask.addZoningMapEntry(initiatorId, targetPortURIs);
          directorPaths++;
        }
      }
    }

    // STEP 6 - persist the mask
    _dbClient.updateAndReindexObject(exportMask);
  }
  @Override
  public void createOrAddVolumesToExportMask(
      URI arrayURI,
      URI exportGroupURI,
      URI exportMaskURI,
      Map<URI, Integer> volumeMap,
      TaskCompleter completer,
      String stepId) {
    _log.debug("START - createOrAddVolumesToExportMask");
    try {
      WorkflowStepCompleter.stepExecuting(stepId);
      StorageSystem array = _dbClient.queryObject(StorageSystem.class, arrayURI);
      ExportMask exportMask = _dbClient.queryObject(ExportMask.class, exportMaskURI);

      // If the exportMask isn't found, or has been deleted, fail, ask user to retry.
      if (exportMask == null || exportMask.getInactive()) {
        _log.info(String.format("ExportMask %s deleted or inactive, failing", exportMaskURI));
        ServiceError svcerr =
            VPlexApiException.errors.createBackendExportMaskDeleted(
                exportMaskURI.toString(), arrayURI.toString());
        WorkflowStepCompleter.stepFailed(stepId, svcerr);
        return;
      }

      // Protect concurrent operations by locking {host, array} dupple.
      // Lock will be released when work flow step completes.
      List<String> lockKeys =
          ControllerLockingUtil.getHostStorageLockKeys(
              _dbClient,
              ExportGroupType.Host,
              StringSetUtil.stringSetToUriList(exportMask.getInitiators()),
              arrayURI);
      getWorkflowService()
          .acquireWorkflowStepLocks(
              stepId, lockKeys, LockTimeoutValue.get(LockType.VPLEX_BACKEND_EXPORT));

      // Refresh the ExportMask
      BlockStorageDevice device = _blockController.getDevice(array.getSystemType());

      if (!exportMask.hasAnyVolumes()) {
        /*
         * We are creating this ExportMask on the hardware! (Maybe not
         * the first time though...)
         *
         * Fetch the Initiators
         */
        List<Initiator> initiators = new ArrayList<Initiator>();
        for (String initiatorId : exportMask.getInitiators()) {
          Initiator initiator = _dbClient.queryObject(Initiator.class, URI.create(initiatorId));
          if (initiator != null) {
            initiators.add(initiator);
          }
        }

        // Fetch the targets
        List<URI> targets = new ArrayList<URI>();
        for (String targetId : exportMask.getStoragePorts()) {
          targets.add(URI.create(targetId));
        }

        if (volumeMap != null) {
          for (URI volume : volumeMap.keySet()) {
            exportMask.addVolume(volume, volumeMap.get(volume));
          }
        }

        _dbClient.persistObject(exportMask);

        _log.debug(
            String.format(
                "Calling doExportGroupCreate on the device %s", array.getId().toString()));
        device.doExportGroupCreate(array, exportMask, volumeMap, initiators, targets, completer);
      } else {
        _log.debug(
            String.format("Calling doExportAddVolumes on the device %s", array.getId().toString()));
        device.doExportAddVolumes(array, exportMask, volumeMap, completer);
      }

    } catch (Exception ex) {
      _log.error("Failed to create or add volumes to export mask for cinder: ", ex);
      VPlexApiException vplexex =
          DeviceControllerExceptions.vplex.addStepsForCreateVolumesFailed(ex);
      WorkflowStepCompleter.stepFailed(stepId, vplexex);
    }

    _log.debug("END - createOrAddVolumesToExportMask");
  }
  /**
   * Updates the initiator to target list map in the export mask
   *
   * @throws Exception
   */
  private void updateTargetsInExportMask(
      StorageSystem storage,
      Volume volume,
      Map<Volume, Map<String, List<String>>> volumeToInitiatorTargetMapFromAttachResponse,
      List<Initiator> fcInitiatorList,
      ExportMask exportMask)
      throws Exception {
    log.debug("START - updateTargetsInExportMask");
    // ITLS for initiator URIs vs Target port URIs - This will be the final
    // filtered list to send for the zoning map update
    Map<URI, List<URI>> mapFilteredInitiatorURIVsTargetURIList = new HashMap<URI, List<URI>>();

    // From the initiators list, construct the map of initiator WWNs to
    // their URIs
    Map<String, URI> initiatorsWWNVsURI = getWWNvsURIFCInitiatorsMap(fcInitiatorList);

    URI varrayURI = volume.getVirtualArray();

    /*
     * Get the list of storage ports from the storage system which are
     * associated with the varray This will be map of storage port WWNs with
     * their URIs
     */
    Map<String, URI> mapVarrayTaggedPortWWNVsURI =
        getVarrayTaggedStoragePortWWNs(storage, varrayURI);

    // List of WWN entries, used below for filtering the target port list
    // from attach response
    Set<String> varrayTaggedPortWWNs = mapVarrayTaggedPortWWNVsURI.keySet();

    URI vpoolURI = volume.getVirtualPool();
    VirtualPool vpool = dbClient.queryObject(VirtualPool.class, vpoolURI);
    int pathsPerInitiator = vpool.getPathsPerInitiator();

    // Process the attach response output
    Set<Volume> volumeKeysSet = volumeToInitiatorTargetMapFromAttachResponse.keySet();
    for (Volume volumeRes : volumeKeysSet) {
      log.info(
          String.format(
              "Processing attach response for the volume with URI %s and name %s",
              volumeRes.getId(), volumeRes.getLabel()));
      Map<String, List<String>> initiatorTargetMap =
          volumeToInitiatorTargetMapFromAttachResponse.get(volumeRes);
      Set<String> initiatorKeysSet = initiatorTargetMap.keySet();
      for (String initiatorKey : initiatorKeysSet) {
        // The list of filtered target ports ( which are varray tagged )
        // from the attach response
        List<String> filteredTargetList =
            filterTargetsFromResponse(varrayTaggedPortWWNs, initiatorTargetMap, initiatorKey);
        log.info(
            String.format(
                "For initiator %s accessible storage ports are %s ",
                initiatorKey, filteredTargetList.toString()));

        List<String> tmpTargetList = null;
        if (!isVplex(volumeRes)) {
          // For VPLEX - no path validations
          // Path validations are required only for the Host Exports
          tmpTargetList = checkPathsPerInitiator(pathsPerInitiator, filteredTargetList);

          if (null == tmpTargetList) {
            // Rollback case - throw the exception
            throw new Exception(
                String.format(
                    "Paths per initiator criteria is not met for the initiator : %s "
                        + " Target counts is: %s Expected paths per initiator is: %s",
                    initiatorKey,
                    String.valueOf(filteredTargetList.size()),
                    String.valueOf(pathsPerInitiator)));
          }

        } else {
          tmpTargetList = filteredTargetList;
        }

        // Now populate URIs for the map to be returned - convert WWNs
        // to URIs
        populateInitiatorTargetURIMap(
            mapFilteredInitiatorURIVsTargetURIList,
            initiatorsWWNVsURI,
            mapVarrayTaggedPortWWNVsURI,
            initiatorKey,
            tmpTargetList);
      } // End initiator iteration
    } // End volume iteration

    // Clean all existing targets in the export mask and add new targets
    List<URI> storagePortListFromMask =
        StringSetUtil.stringSetToUriList(exportMask.getStoragePorts());
    for (URI removeUri : storagePortListFromMask) {
      exportMask.removeTarget(removeUri);
    }
    exportMask.setStoragePorts(null);

    // Now add new target ports and populate the zoning map
    Set<URI> initiatorURIKeys = mapFilteredInitiatorURIVsTargetURIList.keySet();
    for (URI initiatorURI : initiatorURIKeys) {
      List<URI> storagePortURIList = mapFilteredInitiatorURIVsTargetURIList.get(initiatorURI);
      for (URI portURI : storagePortURIList) {
        exportMask.addTarget(portURI);
      }
    }

    log.debug("END - updateTargetsInExportMask");
  }