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