/**
   * Detaches volumes from initiators.
   *
   * @param storage the storage
   * @param volumes the volumes
   * @param initiators the initiators
   * @throws Exception the exception
   */
  private void detachVolumesFromInitiators(
      StorageSystem storage, List<Volume> volumes, List<Initiator> initiators) throws Exception {
    CinderEndPointInfo ep = CinderUtils.getCinderEndPoint(storage.getActiveProviderURI(), dbClient);
    log.debug("Getting the cinder APi for the provider with id {}", storage.getActiveProviderURI());
    CinderApi cinderApi = cinderApiFactory.getApi(storage.getActiveProviderURI(), ep);

    List<Initiator> iSCSIInitiators = new ArrayList<Initiator>();
    List<Initiator> fcInitiators = new ArrayList<Initiator>();
    splitInitiatorsByProtocol(initiators, iSCSIInitiators, fcInitiators);
    String host = getHostNameFromInitiators(initiators);

    Map<String, String[]> mapSettingVsValues = getFCInitiatorsArray(fcInitiators);
    String[] fcInitiatorsWwpns = mapSettingVsValues.get(WWPNS);
    String[] fcInitiatorsWwnns = mapSettingVsValues.get(WWNNS);

    for (Volume volume : volumes) {
      // cinder generated volume ID
      String volumeId = volume.getNativeId();

      // for iSCSI
      for (Initiator initiator : iSCSIInitiators) {
        String initiatorPort = initiator.getInitiatorPort();
        log.debug(
            String.format(
                "Detaching volume %s ( %s ) from initiator %s on Openstack cinder node",
                volumeId, volume.getId(), initiatorPort));
        cinderApi.detachVolume(volumeId, initiatorPort, null, null, host);

        // TODO : Do not use Job to poll status till we figure out how
        // to get detach status.
        /*
         * CinderJob detachJob = new CinderDetachVolumeJob(volumeId,
         * volume.getLabel(), storage.getId(),
         * CinderConstants.ComponentType.volume.name(), ep,
         * taskCompleter); ControllerServiceImpl.enqueueJob(new
         * QueueJob(detachJob));
         */
      }

      // for FC
      if (fcInitiatorsWwpns.length > 0) {
        log.debug(
            String.format(
                "Detaching volume %s ( %s ) from initiator %s on Openstack cinder node",
                volumeId, volume.getId(), fcInitiatorsWwpns));
        cinderApi.detachVolume(volumeId, null, fcInitiatorsWwpns, fcInitiatorsWwnns, host);
      }

      // If ITLs are added, remove them
      removeITLsFromVolume(volume);
    }
  }
  /*
   * (non-Javadoc)
   *
   * @see com.emc.storageos.volumecontroller.CloneOperations#createSingleClone(
   * com.emc.storageos.db.client.model.StorageSystem, java.net.URI, java.net.URI,
   * java.lang.Boolean,
   * com.emc.storageos.volumecontroller.TaskCompleter)
   */
  @Override
  public void createSingleClone(
      StorageSystem storageSystem,
      URI sourceObject,
      URI cloneVolume,
      Boolean createInactive,
      TaskCompleter taskCompleter) {
    log.info("START createSingleClone operation");
    boolean isVolumeClone = true;
    try {
      BlockObject sourceObj = BlockObject.fetch(dbClient, sourceObject);
      URI tenantUri = null;
      if (sourceObj
          instanceof BlockSnapshot) { // In case of snapshot, get the tenant from its parent volume
        NamedURI parentVolUri = ((BlockSnapshot) sourceObj).getParent();
        Volume parentVolume = dbClient.queryObject(Volume.class, parentVolUri);
        tenantUri = parentVolume.getTenant().getURI();
        isVolumeClone = false;
      } else { // This is a default flow
        tenantUri = ((Volume) sourceObj).getTenant().getURI();
        isVolumeClone = true;
      }

      Volume cloneObj = dbClient.queryObject(Volume.class, cloneVolume);
      StoragePool targetPool = dbClient.queryObject(StoragePool.class, cloneObj.getPool());
      TenantOrg tenantOrg = dbClient.queryObject(TenantOrg.class, tenantUri);
      // String cloneLabel = generateLabel(tenantOrg, cloneObj);

      CinderEndPointInfo ep =
          CinderUtils.getCinderEndPoint(storageSystem.getActiveProviderURI(), dbClient);
      log.info(
          "Getting the cinder APi for the provider with id "
              + storageSystem.getActiveProviderURI());
      CinderApi cinderApi = cinderApiFactory.getApi(storageSystem.getActiveProviderURI(), ep);

      String volumeId = "";
      if (isVolumeClone) {
        volumeId =
            cinderApi.cloneVolume(
                cloneObj.getLabel(),
                (cloneObj.getCapacity() / (1024 * 1024 * 1024)),
                targetPool.getNativeId(),
                sourceObj.getNativeId());
      } else {
        volumeId =
            cinderApi.createVolumeFromSnapshot(
                cloneObj.getLabel(),
                (cloneObj.getCapacity() / (1024 * 1024 * 1024)),
                targetPool.getNativeId(),
                sourceObj.getNativeId());
      }

      log.debug("Creating volume with the id " + volumeId + " on Openstack cinder node");
      if (volumeId != null) {
        Map<String, URI> volumeIds = new HashMap<String, URI>();
        volumeIds.put(volumeId, cloneObj.getId());
        ControllerServiceImpl.enqueueJob(
            new QueueJob(
                new CinderSingleVolumeCreateJob(
                    volumeId,
                    cloneObj.getLabel(),
                    storageSystem.getId(),
                    CinderConstants.ComponentType.volume.name(),
                    ep,
                    taskCompleter,
                    targetPool.getId(),
                    volumeIds)));
      }
    } catch (InternalException e) {
      String errorMsg = String.format(CREATE_ERROR_MSG_FORMAT, sourceObject, cloneVolume);
      log.error(errorMsg, e);
      taskCompleter.error(dbClient, e);
    } catch (Exception e) {
      String errorMsg = String.format(CREATE_ERROR_MSG_FORMAT, sourceObject, cloneVolume);
      log.error(errorMsg, e);
      ServiceError serviceError =
          DeviceControllerErrors.cinder.operationFailed("createSingleClone", e.getMessage());
      taskCompleter.error(dbClient, serviceError);
    }
  }
  /**
   * Attaches volumes to initiators.
   *
   * @param storage the storage
   * @param volumes the volumes
   * @param initiators the initiators
   * @param volumeToTargetLunMap the volume to target lun map
   * @throws Exception the exception
   */
  private void attachVolumesToInitiators(
      StorageSystem storage,
      List<Volume> volumes,
      List<Initiator> initiators,
      Map<URI, Integer> volumeToTargetLunMap,
      Map<Volume, Map<String, List<String>>> volumeToInitiatorTargetMap,
      ExportMask exportMask)
      throws Exception {
    CinderEndPointInfo ep = CinderUtils.getCinderEndPoint(storage.getActiveProviderURI(), dbClient);
    log.debug(
        "Getting the cinder APi for the provider with id  {}", storage.getActiveProviderURI());
    CinderApi cinderApi = cinderApiFactory.getApi(storage.getActiveProviderURI(), ep);

    List<Initiator> iSCSIInitiators = new ArrayList<Initiator>();
    List<Initiator> fcInitiators = new ArrayList<Initiator>();
    splitInitiatorsByProtocol(initiators, iSCSIInitiators, fcInitiators);
    String host = getHostNameFromInitiators(initiators);

    Map<String, String[]> mapSettingVsValues = getFCInitiatorsArray(fcInitiators);
    String[] fcInitiatorsWwpns = mapSettingVsValues.get(WWPNS);
    String[] fcInitiatorsWwnns = mapSettingVsValues.get(WWNNS);

    for (Volume volume : volumes) {

      // cinder generated volume ID
      String volumeId = volume.getNativeId();
      int targetLunId = -1;
      VolumeAttachResponse attachResponse = null;

      // for iSCSI
      for (Initiator initiator : iSCSIInitiators) {
        String initiatorPort = initiator.getInitiatorPort();
        log.debug(
            String.format(
                "Attaching volume %s ( %s ) to initiator %s on Openstack cinder node",
                volumeId, volume.getId(), initiatorPort));
        attachResponse = cinderApi.attachVolume(volumeId, initiatorPort, null, null, host);
        log.info("Got response : {}", attachResponse.connection_info.toString());
        targetLunId = attachResponse.connection_info.data.target_lun;
      }

      // for FC
      if (fcInitiatorsWwpns.length > 0) {
        log.debug(
            String.format(
                "Attaching volume %s ( %s ) to initiators %s on Openstack cinder node",
                volumeId, volume.getId(), fcInitiatorsWwpns));
        attachResponse =
            cinderApi.attachVolume(volumeId, null, fcInitiatorsWwpns, fcInitiatorsWwnns, host);
        log.info("Got response : {}", attachResponse.connection_info.toString());
        targetLunId = attachResponse.connection_info.data.target_lun;

        Map<String, List<String>> initTargetMap =
            attachResponse.connection_info.data.initiator_target_map;
        if (null != initTargetMap && !initTargetMap.isEmpty()) {
          volumeToInitiatorTargetMap.put(
              volume, attachResponse.connection_info.data.initiator_target_map);
        }
      }

      volumeToTargetLunMap.put(volume.getId(), targetLunId);

      // After the successful export, create or modify the storage ports
      CinderStoragePortOperations storagePortOperationsInstance =
          CinderStoragePortOperations.getInstance(storage, dbClient);
      storagePortOperationsInstance.invoke(attachResponse);
    }

    // Add ITLs to volume objects
    storeITLMappingInVolume(volumeToTargetLunMap, exportMask);
  }