/*
   * (non-Javadoc)
   *
   * @see com.emc.storageos.volumecontroller.BlockStorageDevice#doExpandVolume(com.emc.storageos.db.client.model.StorageSystem,
   * com.emc.storageos.db.client.model.StoragePool, com.emc.storageos.db.client.model.Volume, java.lang.Long,
   * com.emc.storageos.volumecontroller.TaskCompleter)
   */
  @Override
  public void doExpandVolume(
      StorageSystem storageSystem,
      StoragePool storagePool,
      Volume volume,
      Long size,
      TaskCompleter taskCompleter)
      throws DeviceControllerException {

    log.info(
        String.format(
            "Expand Volume Start - Array: %s, Pool: %s, Volume: %s, New size: %d",
            storageSystem.getSerialNumber(), storagePool.getNativeGuid(), volume.getLabel(), size));
    try {
      HDSApiClient hdsApiClient =
          hdsApiFactory.getClient(
              HDSUtils.getHDSServerManagementServerInfo(storageSystem),
              storageSystem.getSmisUserName(),
              storageSystem.getSmisPassword());
      String systemObjectID = HDSUtils.getSystemObjectID(storageSystem);
      String asyncTaskMessageId = null;

      if (volume.getThinlyProvisioned()) {
        asyncTaskMessageId =
            hdsApiClient.modifyThinVolume(
                systemObjectID,
                HDSUtils.getLogicalUnitObjectId(volume.getNativeId(), storageSystem),
                size);
      }

      if (null != asyncTaskMessageId) {
        HDSJob expandVolumeJob =
            new HDSVolumeExpandJob(
                asyncTaskMessageId,
                storageSystem.getId(),
                storagePool.getId(),
                taskCompleter,
                "ExpandVolume");
        ControllerServiceImpl.enqueueJob(new QueueJob(expandVolumeJob));
      }
    } catch (final InternalException e) {
      log.error("Problem in doExpandVolume: ", e);
      taskCompleter.error(dbClient, e);
    } catch (final Exception e) {
      log.error("Problem in doExpandVolume: ", e);
      ServiceError serviceError =
          DeviceControllerErrors.hds.methodFailed("doExpandVolume", e.getMessage());
      taskCompleter.error(dbClient, serviceError);
    }
    log.info(
        String.format(
            "Expand Volume End - Array: %s, Pool: %s, Volume: %s",
            storageSystem.getSerialNumber(), storagePool.getNativeGuid(), volume.getLabel()));
  }
  public void vcenterClusterCreateDatastoreOperation(
      URI vcenterId,
      URI vcenterDataCenterId,
      URI clusterId,
      URI volumeId,
      String selectHostStepId,
      String stepId) {
    VcenterApiClient vcenterApiClient = null;
    try {
      WorkflowStepCompleter.stepExecuting(stepId);

      URI hostId = (URI) _workflowService.loadStepData(selectHostStepId);
      if (hostId == null) {
        _log.error("Workflow loadStepData on " + selectHostStepId + " is null");
        throw new Exception("Workflow loadStepData on " + selectHostStepId + " is null");
      }

      VcenterDataCenter vcenterDataCenter =
          _dbClient.queryObject(VcenterDataCenter.class, vcenterDataCenterId);
      Cluster cluster = _dbClient.queryObject(Cluster.class, clusterId);
      Vcenter vcenter = _dbClient.queryObject(Vcenter.class, vcenterId);
      Host host = _dbClient.queryObject(Host.class, hostId);
      Volume volume = _dbClient.queryObject(Volume.class, volumeId);

      vcenterApiClient = new VcenterApiClient(_coordinator.getPropertyInfo());
      vcenterApiClient.setup(vcenter.getIpAddress(), vcenter.getUsername(), vcenter.getPassword());
      String key =
          vcenterApiClient.createDatastore(
              vcenterDataCenter.getLabel(),
              cluster.getExternalId(),
              host.getHostName(),
              volume.getWWN(),
              volume.getLabel());
      _log.info("Successfully created or located datastore " + volume.getLabel() + " " + key);

      host.setVcenterDataCenter(vcenterDataCenter.getId());
      _dbClient.updateAndReindexObject(host);

      WorkflowStepCompleter.stepSucceded(stepId);
    } catch (Exception e) {
      _log.error("vcenterClusterCreateDatastoreOperation exception " + e);
      WorkflowStepCompleter.stepFailed(
          stepId, VcenterControllerException.exceptions.hostException(e.getLocalizedMessage(), e));
    } finally {
      if (vcenterApiClient != null) vcenterApiClient.destroy();
    }
  }
  private void handleBlockVolumeErrors(DbClient dbClient) {
    for (VolumeDescriptor volumeDescriptor :
        VolumeDescriptor.getDescriptors(_volumeDescriptors, VolumeDescriptor.Type.BLOCK_DATA)) {

      Volume volume = dbClient.queryObject(Volume.class, volumeDescriptor.getVolumeURI());

      if (volume != null && (volume.getNativeId() == null || volume.getNativeId().equals(""))) {
        _log.info("No native id was present on volume {}, marking inactive", volume.getLabel());
        dbClient.markForDeletion(volume);
      }
    }
  }
  /*
   * (non-Javadoc)
   *
   * @see com.emc.storageos.volumecontroller.BlockStorageDevice#doExpandAsMetaVolume(com.emc.storageos.db.client.model.StorageSystem,
   * com.emc.storageos.db.client.model.StoragePool, com.emc.storageos.db.client.model.Volume, long,
   * com.emc.storageos.volumecontroller.impl.smis.MetaVolumeRecommendation, com.emc.storageos.volumecontroller.TaskCompleter)
   */
  @Override
  public void doExpandAsMetaVolume(
      StorageSystem storageSystem,
      StoragePool storagePool,
      Volume metaHead,
      long size,
      MetaVolumeRecommendation recommendation,
      VolumeExpandCompleter volumeCompleter)
      throws DeviceControllerException {
    StringBuilder logMsgBuilder =
        new StringBuilder(
            String.format(
                "Expand Meta Volume Start - Array:%s, Pool:%s %n Volume: %s, id: %s",
                storageSystem.getSerialNumber(),
                storagePool.getNativeId(),
                metaHead.getLabel(),
                metaHead.getId()));
    log.info(logMsgBuilder.toString());
    long metaMemberCapacity = recommendation.getMetaMemberSize();
    int metaMemberCount = (int) recommendation.getMetaMemberCount();
    MetaVolumeTaskCompleter metaVolumeTaskCompleter = new MetaVolumeTaskCompleter(volumeCompleter);
    try {
      // Step 1: create meta members.
      List<String> newMetaMembers =
          metaVolumeOperations.createMetaVolumeMembers(
              storageSystem,
              storagePool,
              metaHead,
              metaMemberCount,
              metaMemberCapacity,
              metaVolumeTaskCompleter);
      log.info("ldevMetaMembers created successfully: {}", newMetaMembers);

      if (metaVolumeTaskCompleter.getLastStepStatus() == Job.JobStatus.SUCCESS) {
        metaVolumeOperations.expandMetaVolume(
            storageSystem, storagePool, metaHead, newMetaMembers, metaVolumeTaskCompleter);
      } else {
        ServiceError serviceError =
            DeviceControllerErrors.hds.jobFailed("LDEV Meta Member creation failed");
        volumeCompleter.error(dbClient, serviceError);
      }

    } catch (final InternalException e) {
      log.error("Problem in doExpandAsMetaVolume: ", e);
      volumeCompleter.error(dbClient, e);
    } catch (final Exception e) {
      log.error("Problem in doExpandAsMetaVolume: ", e);
      ServiceError serviceError =
          DeviceControllerErrors.hds.methodFailed("doExpandAsMetaVolume", e.getMessage());
      volumeCompleter.error(dbClient, serviceError);
    }
  }
  private void handleVplexVolumeErrors(DbClient dbClient) {

    List<String> finalMessages = new ArrayList<String>();

    for (VolumeDescriptor volumeDescriptor :
        VolumeDescriptor.getDescriptors(
            _volumeDescriptors, VolumeDescriptor.Type.VPLEX_VIRT_VOLUME)) {

      Volume volume = dbClient.queryObject(Volume.class, volumeDescriptor.getVolumeURI());

      _log.info("Looking at VPLEX virtual volume {}", volume.getLabel());

      boolean deactivateVirtualVolume = true;
      List<String> livingVolumeNames = new ArrayList<String>();

      _log.info("Its associated volumes are: " + volume.getAssociatedVolumes());
      for (String associatedVolumeUri : volume.getAssociatedVolumes()) {
        Volume associatedVolume =
            dbClient.queryObject(Volume.class, URI.create(associatedVolumeUri));
        if (associatedVolume != null && !associatedVolume.getInactive()) {
          _log.warn(
              "VPLEX virtual volume {} has active associated volume {}",
              volume.getLabel(),
              associatedVolume.getLabel());
          livingVolumeNames.add(associatedVolume.getLabel());
          deactivateVirtualVolume = false;
        }
      }

      if (deactivateVirtualVolume) {
        _log.info(
            "VPLEX virtual volume {} has no active associated volumes, marking for deletion",
            volume.getLabel());
        dbClient.markForDeletion(volume);
      } else {
        String message =
            "VPLEX virtual volume "
                + volume.getLabel()
                + " will not be marked for deletion "
                + "because it still has active associated volumes (";
        message += Joiner.on(",").join(livingVolumeNames) + ")";
        finalMessages.add(message);
        _log.warn(message);
      }
    }

    if (!finalMessages.isEmpty()) {
      String finalMessage = Joiner.on("; ").join(finalMessages) + ".";
      _log.error(finalMessage);
    }
  }
  /*
   * (non-Javadoc)
   *
   * @see com.emc.storageos.volumecontroller.BlockStorageDevice#doCleanupMetaMembers(com.emc.storageos.db.client.model.StorageSystem,
   * com.emc.storageos.db.client.model.Volume,
   * com.emc.storageos.volumecontroller.impl.block.taskcompleter.CleanupMetaVolumeMembersCompleter)
   */
  @Override
  public void doCleanupMetaMembers(
      StorageSystem storageSystem,
      Volume volume,
      CleanupMetaVolumeMembersCompleter cleanupCompleter)
      throws DeviceControllerException {
    // Remove meta member volumes from storage device
    try {
      log.info(
          String.format(
              "doCleanupMetaMembers  Start - Array: %s, Volume: %s",
              storageSystem.getSerialNumber(), volume.getLabel()));
      // Load meta volume members from WF data
      String sourceStepId = cleanupCompleter.getSourceStepId();
      HDSApiClient hdsApiClient =
          hdsApiFactory.getClient(
              HDSUtils.getHDSServerManagementServerInfo(storageSystem),
              storageSystem.getUsername(),
              storageSystem.getSmisPassword());
      List<String> metaMembers =
          (ArrayList<String>) WorkflowService.getInstance().loadStepData(sourceStepId);
      if (metaMembers != null && !metaMembers.isEmpty()) {
        log.info(
            String.format(
                "doCleanupMetaMembers: Members stored for meta volume: %n %s", metaMembers));
        // Check if volumes still exist in array and if it is not composite member (already
        // added to the meta volume)
        Set<String> volumeIds = new HashSet<String>();
        for (String logicalUnitObjectId : metaMembers) {
          LogicalUnit logicalUnit =
              hdsApiClient.getLogicalUnitInfo(
                  HDSUtils.getSystemObjectID(storageSystem), logicalUnitObjectId);
          if (logicalUnit != null) {
            log.debug(
                "doCleanupMetaMembers: Volume: "
                    + logicalUnitObjectId
                    + ", Usage of volume: "
                    + logicalUnit.getComposite());
            if (logicalUnit.getComposite() != HDSConstants.COMPOSITE_ELEMENT_MEMBER) {
              volumeIds.add(logicalUnitObjectId);
            }
          }
        }
        if (volumeIds.isEmpty()) {
          cleanupCompleter.setSuccess(true);
          log.info("doCleanupMetaMembers: No meta members to cleanup in array.");
        } else {
          log.info(
              String.format(
                  "doCleanupMetaMembers: Members to cleanup in array: %n   %s", volumeIds));
          // Prepare parameters and call method to delete meta members from array

          HDSCleanupMetaVolumeMembersJob hdsJobCompleter = null;
          // When "cleanup" is separate workflow step, call async (for example rollback
          // step in volume expand)
          // Otherwise, call synchronously (for example when cleanup is part of meta
          // volume create rollback)
          String asyncMessageId =
              hdsApiClient.deleteThickLogicalUnits(
                  HDSUtils.getSystemObjectID(storageSystem), volumeIds);

          if (cleanupCompleter.isWFStep()) {
            if (asyncMessageId != null) {
              ControllerServiceImpl.enqueueJob(
                  new QueueJob(
                      new HDSCleanupMetaVolumeMembersJob(
                          asyncMessageId,
                          storageSystem.getId(),
                          volume.getId(),
                          cleanupCompleter)));
            }
          } else {
            // invoke synchronously
            hdsJobCompleter =
                new HDSCleanupMetaVolumeMembersJob(
                    asyncMessageId, storageSystem.getId(), volume.getId(), cleanupCompleter);
            ((HDSMetaVolumeOperations) metaVolumeOperations)
                .invokeMethodSynchronously(hdsApiFactory, asyncMessageId, hdsJobCompleter);
          }
        }
      } else {
        log.info(
            "doCleanupMetaMembers: No meta members stored for meta volume. Nothing to cleanup in array.");
        cleanupCompleter.setSuccess(true);
      }
    } catch (Exception e) {
      log.error("Problem in doCleanupMetaMembers: ", e);
      ServiceError error =
          DeviceControllerErrors.smis.methodFailed("doCleanupMetaMembers", e.getMessage());
      cleanupCompleter.setError(error);
      cleanupCompleter.setSuccess(false);
    }
    log.info(
        String.format(
            "doCleanupMetaMembers End - Array: %s,  Volume: %s",
            storageSystem.getSerialNumber(), volume.getLabel()));
  }
  /*
   * (non-Javadoc)
   *
   * @see com.emc.storageos.volumecontroller.BlockStorageDevice#doDeleteVolumes(com.emc.storageos.db.client.model.StorageSystem,
   * java.lang.String, java.util.List, com.emc.storageos.volumecontroller.TaskCompleter)
   */
  @Override
  public void doDeleteVolumes(
      StorageSystem storageSystem, String opId, List<Volume> volumes, TaskCompleter taskCompleter)
      throws DeviceControllerException {

    try {
      StringBuilder logMsgBuilder =
          new StringBuilder(
              String.format("Delete Volume Start - Array:%s", storageSystem.getSerialNumber()));
      MultiVolumeTaskCompleter multiVolumeTaskCompleter = (MultiVolumeTaskCompleter) taskCompleter;
      Set<String> thickLogicalUnitIdList = new HashSet<String>();
      Set<String> thinLogicalUnitIdList = new HashSet<String>();
      HDSApiClient hdsApiClient =
          hdsApiFactory.getClient(
              HDSUtils.getHDSServerManagementServerInfo(storageSystem),
              storageSystem.getSmisUserName(),
              storageSystem.getSmisPassword());
      String systemObjectId = HDSUtils.getSystemObjectID(storageSystem);
      log.info("volumes size: {}", volumes.size());
      for (Volume volume : volumes) {
        logMsgBuilder.append(String.format("%nVolume:%s", volume.getLabel()));
        String logicalUnitObjectId =
            HDSUtils.getLogicalUnitObjectId(volume.getNativeId(), storageSystem);
        LogicalUnit logicalUnit =
            hdsApiClient.getLogicalUnitInfo(systemObjectId, logicalUnitObjectId);
        if (logicalUnit == null) {
          // related volume state (if any) has been deleted. skip
          // processing, if already deleted from array.
          log.info(String.format("Volume %s already deleted: ", volume.getNativeId()));
          volume.setInactive(true);
          dbClient.persistObject(volume);
          VolumeTaskCompleter deleteTaskCompleter =
              multiVolumeTaskCompleter.skipTaskCompleter(volume.getId());
          deleteTaskCompleter.ready(dbClient);
          continue;
        }
        if (volume.getThinlyProvisioned()) {
          thinLogicalUnitIdList.add(logicalUnitObjectId);
        } else {
          thickLogicalUnitIdList.add(logicalUnitObjectId);
        }
      }
      log.info(logMsgBuilder.toString());
      if (!multiVolumeTaskCompleter.isVolumeTaskCompletersEmpty()) {
        if (null != thickLogicalUnitIdList && !thickLogicalUnitIdList.isEmpty()) {
          String asyncThickLUsJobId =
              hdsApiClient.deleteThickLogicalUnits(systemObjectId, thickLogicalUnitIdList);
          if (null != asyncThickLUsJobId) {
            ControllerServiceImpl.enqueueJob(
                new QueueJob(
                    new HDSDeleteVolumeJob(
                        asyncThickLUsJobId, volumes.get(0).getStorageController(), taskCompleter)));
          }
        }

        if (null != thinLogicalUnitIdList && !thinLogicalUnitIdList.isEmpty()) {
          String asyncThinHDSJobId =
              hdsApiClient.deleteThinLogicalUnits(systemObjectId, thinLogicalUnitIdList);

          // Not sure whether this really works as tracking two jobs
          // in single operation.
          if (null != asyncThinHDSJobId) {
            ControllerServiceImpl.enqueueJob(
                new QueueJob(
                    new HDSDeleteVolumeJob(
                        asyncThinHDSJobId, volumes.get(0).getStorageController(), taskCompleter)));
          }
        }
      } else {
        // If we are here, there are no volumes to delete, we have
        // invoked ready() for the VolumeDeleteCompleter, and told
        // the multiVolumeTaskCompleter to skip these completers.
        // In this case, the multiVolumeTaskCompleter complete()
        // method will not be invoked and the result is that the
        // workflow that initiated this delete request will never
        // be updated. So, here we just call complete() on the
        // multiVolumeTaskCompleter to ensure the workflow status is
        // updated.
        multiVolumeTaskCompleter.ready(dbClient);
      }
    } catch (Exception e) {
      log.error("Problem in doDeleteVolume: ", e);
      ServiceError error =
          DeviceControllerErrors.hds.methodFailed("doDeleteVolume", e.getMessage());
      taskCompleter.error(dbClient, error);
    }
    StringBuilder logMsgBuilder =
        new StringBuilder(
            String.format("Delete Volume End - Array: %s", storageSystem.getSerialNumber()));
    for (Volume volume : volumes) {
      logMsgBuilder.append(String.format("%nVolume:%s", volume.getLabel()));
    }
    log.info(logMsgBuilder.toString());
  }
  /*
   * (non-Javadoc)
   *
   * @see com.emc.storageos.volumecontroller.BlockStorageDevice#doCreateVolumes(com.emc.storageos.db.client.model.StorageSystem,
   * com.emc.storageos.db.client.model.StoragePool, java.lang.String, java.util.List,
   * com.emc.storageos.volumecontroller.impl.utils.VirtualPoolCapabilityValuesWrapper, com.emc.storageos.volumecontroller.TaskCompleter)
   */
  @Override
  public void doCreateVolumes(
      StorageSystem storageSystem,
      StoragePool storagePool,
      String opId,
      List<Volume> volumes,
      VirtualPoolCapabilityValuesWrapper capabilities,
      TaskCompleter taskCompleter)
      throws DeviceControllerException {
    String label = null;
    Long capacity = null;
    boolean isThinVolume = false;
    boolean opCreationFailed = false;
    StringBuilder logMsgBuilder =
        new StringBuilder(
            String.format(
                "Create Volume Start - Array:%s, Pool:%s",
                storageSystem.getSerialNumber(), storagePool.getNativeGuid()));
    for (Volume volume : volumes) {
      logMsgBuilder.append(
          String.format(
              "%nVolume:%s , IsThinlyProvisioned: %s",
              volume.getLabel(), volume.getThinlyProvisioned()));

      if ((label == null) && (volumes.size() == 1)) {
        String tenantName = "";
        try {
          TenantOrg tenant = dbClient.queryObject(TenantOrg.class, volume.getTenant().getURI());
          tenantName = tenant.getLabel();
        } catch (DatabaseException e) {
          log.error("Error lookup TenantOrb object", e);
        }
        label =
            nameGenerator.generate(
                tenantName,
                volume.getLabel(),
                volume.getId().toString(),
                '-',
                HDSConstants.MAX_VOLUME_NAME_LENGTH);
      }

      if (capacity == null) {
        capacity = volume.getCapacity();
      }
      isThinVolume = volume.getThinlyProvisioned();
    }
    log.info(logMsgBuilder.toString());
    try {
      multiVolumeCheckForHitachiModel(volumes, storageSystem);

      HDSApiClient hdsApiClient =
          hdsApiFactory.getClient(
              HDSUtils.getHDSServerManagementServerInfo(storageSystem),
              storageSystem.getSmisUserName(),
              storageSystem.getSmisPassword());
      String systemObjectID = HDSUtils.getSystemObjectID(storageSystem);
      String poolObjectID = HDSUtils.getPoolObjectID(storagePool);
      String asyncTaskMessageId = null;

      // isThinVolume = true, creates VirtualVolumes
      // isThinVolume = false, creates LogicalUnits
      if (isThinVolume) {
        asyncTaskMessageId =
            hdsApiClient.createThinVolumes(
                systemObjectID,
                storagePool.getNativeId(),
                capacity,
                volumes.size(),
                label,
                QUICK_FORMAT_TYPE,
                storageSystem.getModel());
      } else if (!isThinVolume) {
        asyncTaskMessageId =
            hdsApiClient.createThickVolumes(
                systemObjectID,
                poolObjectID,
                capacity,
                volumes.size(),
                label,
                null,
                storageSystem.getModel(),
                null);
      }

      if (asyncTaskMessageId != null) {
        HDSJob createHDSJob =
            (volumes.size() > 1)
                ? new HDSCreateMultiVolumeJob(
                    asyncTaskMessageId,
                    volumes.get(0).getStorageController(),
                    storagePool.getId(),
                    volumes.size(),
                    taskCompleter)
                : new HDSCreateVolumeJob(
                    asyncTaskMessageId,
                    volumes.get(0).getStorageController(),
                    storagePool.getId(),
                    taskCompleter);
        ControllerServiceImpl.enqueueJob(new QueueJob(createHDSJob));
      }
    } catch (final InternalException e) {
      log.error("Problem in doCreateVolumes: ", e);
      opCreationFailed = true;
      taskCompleter.error(dbClient, e);
    } catch (final Exception e) {
      log.error("Problem in doCreateVolumes: ", e);
      opCreationFailed = true;
      ServiceError serviceError =
          DeviceControllerErrors.hds.methodFailed("doCreateVolumes", e.getMessage());
      taskCompleter.error(dbClient, serviceError);
    }
    if (opCreationFailed) {
      for (Volume vol : volumes) {
        vol.setInactive(true);
        dbClient.persistObject(vol);
      }
    }

    logMsgBuilder =
        new StringBuilder(
            String.format(
                "Create Volumes End - Array:%s, Pool:%s",
                storageSystem.getSerialNumber(), storagePool.getNativeGuid()));
    for (Volume volume : volumes) {
      logMsgBuilder.append(String.format("%nVolume:%s", volume.getLabel()));
    }
    log.info(logMsgBuilder.toString());
  }
  @Override
  public void doModifyVolumes(
      StorageSystem storage,
      StoragePool storagePool,
      String opId,
      List<Volume> volumes,
      TaskCompleter taskCompleter)
      throws DeviceControllerException {
    StringBuilder logMsgBuilder =
        new StringBuilder(
            String.format(
                "Modify Volume Start - Array:%s, Pool:%s",
                storage.getSerialNumber(), storagePool.getNativeGuid()));

    String systemObjectID = HDSUtils.getSystemObjectID(storage);
    for (Volume volume : volumes) {
      try {
        HDSApiClient hdsApiClient =
            hdsApiFactory.getClient(
                HDSUtils.getHDSServerManagementServerInfo(storage),
                storage.getSmisUserName(),
                storage.getSmisPassword());
        logMsgBuilder.append(
            String.format(
                "%nVolume:%s , IsThinlyProvisioned: %s, tieringPolicy: %s",
                volume.getLabel(),
                volume.getThinlyProvisioned(),
                volume.getAutoTieringPolicyUri()));
        LogicalUnit logicalUnit =
            hdsApiClient.getLogicalUnitInfo(
                systemObjectID, HDSUtils.getLogicalUnitObjectId(volume.getNativeId(), storage));
        String policyName = ControllerUtils.getAutoTieringPolicyName(volume.getId(), dbClient);
        String autoTierPolicyName = null;
        if (policyName.equals(Constants.NONE)) {
          autoTierPolicyName = null;
        } else {
          autoTierPolicyName =
              HitachiTieringPolicy.getPolicy(
                      policyName.replaceAll(
                          HDSConstants.SLASH_OPERATOR, HDSConstants.UNDERSCORE_OPERATOR))
                  .getKey();
        }
        if (null != logicalUnit
            && null != logicalUnit.getLdevList()
            && !logicalUnit.getLdevList().isEmpty()) {
          Iterator<LDEV> ldevItr = logicalUnit.getLdevList().iterator();
          if (ldevItr.hasNext()) {
            LDEV ldev = ldevItr.next();
            String asyncMessageId =
                hdsApiClient.modifyThinVolumeTieringPolicy(
                    systemObjectID,
                    logicalUnit.getObjectID(),
                    ldev.getObjectID(),
                    autoTierPolicyName);
            if (null != asyncMessageId) {
              HDSJob modifyHDSJob =
                  new HDSModifyVolumeJob(
                      asyncMessageId,
                      volume.getStorageController(),
                      taskCompleter,
                      HDSModifyVolumeJob.VOLUME_MODIFY_JOB);
              ControllerServiceImpl.enqueueJob(new QueueJob(modifyHDSJob));
            }
          }
        } else {
          String errorMsg = String.format("No LDEV's found for volume: %s", volume.getId());
          log.info(errorMsg);
          ServiceError serviceError =
              DeviceControllerErrors.hds.methodFailed("doModifyVolumes", errorMsg);
          taskCompleter.error(dbClient, serviceError);
        }
      } catch (final InternalException e) {
        log.error("Problem in doModifyVolumes: ", e);
        taskCompleter.error(dbClient, e);
      } catch (final Exception e) {
        log.error("Problem in doModifyVolumes: ", e);
        ServiceError serviceError =
            DeviceControllerErrors.hds.methodFailed("doModifyVolumes", e.getMessage());
        taskCompleter.error(dbClient, serviceError);
      }
    }
  }
  /**
   * Creates the RP source volume/journal and the specified number of target/journal volumes.
   *
   * @param volumeName
   * @param numTargets
   */
  private List<Volume> createRpVolumes(
      String volumeName, int numTargets, ProtectionSet protectionSet, boolean isRpVPlex) {
    List<Volume> volumes = new ArrayList<Volume>();

    StringSet associatedVolumes = new StringSet();
    associatedVolumes.add("associatedVol1");

    StorageSystem storageSystem = null;
    if (isRpVPlex) {
      storageSystem = createStorageSystem(true);
    } else {
      storageSystem = createStorageSystem(false);
    }

    String rsetName = "RSet-" + volumeName;

    Volume sourceVolume = new Volume();
    URI sourceVolumeURI = URIUtil.createId(Volume.class);
    volumes.add(sourceVolume);
    sourceVolume.setId(sourceVolumeURI);
    sourceVolume.setLabel(volumeName);
    sourceVolume.setPersonality(Volume.PersonalityTypes.SOURCE.toString());
    sourceVolume.setRSetName(rsetName);
    sourceVolume.setProtectionSet(new NamedURI(protectionSet.getId(), sourceVolume.getLabel()));
    sourceVolume.setStorageController(storageSystem.getId());
    if (isRpVPlex) {
      sourceVolume.setAssociatedVolumes(associatedVolumes);
      sourceVolume.setNativeId(
          "/clusters/cluster-1/virtual-volumes/device_V000195701573-01E7A_vol");
      // Create a VPLEX ViPR BlockConsistencyGroup for the source volume
      BlockConsistencyGroup sourceVolumeCg =
          createBlockConsistencyGroup(
              sourceVolume.getLabel() + "-CG", storageSystem.getId(), Types.VPLEX.name(), true);
      addVolumeToBlockConsistencyGroup(sourceVolumeCg.getId(), sourceVolume);
      rpVplexVolumeToCgMapping.put(sourceVolumeURI, sourceVolumeCg.getId());
    } else {
      rpVolumeURIs.add(sourceVolumeURI);
    }
    _dbClient.createObject(sourceVolume);

    Volume sourceVolumeJournal = new Volume();
    URI sourceVolumeJournalURI = URIUtil.createId(Volume.class);
    volumes.add(sourceVolumeJournal);
    sourceVolumeJournal.setId(sourceVolumeJournalURI);
    sourceVolumeJournal.setLabel(volumeName + RP_SRC_JOURNAL_APPEND);
    sourceVolumeJournal.setPersonality(Volume.PersonalityTypes.METADATA.toString());
    sourceVolumeJournal.setProtectionSet(
        new NamedURI(protectionSet.getId(), sourceVolumeJournal.getLabel()));
    sourceVolumeJournal.setStorageController(storageSystem.getId());
    if (isRpVPlex) {
      sourceVolumeJournal.setAssociatedVolumes(associatedVolumes);
      sourceVolumeJournal.setNativeId(
          "/clusters/cluster-1/virtual-volumes/device_V000195701573-01E7B_vol");
      // Create a VPLEX ViPR BlockConsistencyGroup for the source journal volume
      BlockConsistencyGroup sourceVolumeJournalCg =
          createBlockConsistencyGroup(
              sourceVolumeJournal.getLabel() + "-CG",
              storageSystem.getId(),
              Types.VPLEX.name(),
              true);
      addVolumeToBlockConsistencyGroup(sourceVolumeJournalCg.getId(), sourceVolumeJournal);
      rpVplexVolumeToCgMapping.put(sourceVolumeJournalURI, sourceVolumeJournalCg.getId());
    } else {
      rpVolumeURIs.add(sourceVolumeJournalURI);
    }
    _dbClient.createObject(sourceVolumeJournal);

    for (int i = 1; i <= numTargets; i++) {
      Volume sourceVolumeTarget = new Volume();
      URI sourceVolumeTargetURI = URIUtil.createId(Volume.class);
      volumes.add(sourceVolumeTarget);
      sourceVolumeTarget.setId(sourceVolumeTargetURI);
      sourceVolumeTarget.setLabel(volumeName + RP_TGT_APPEND + "vArray" + i);
      sourceVolumeTarget.setPersonality(Volume.PersonalityTypes.TARGET.toString());
      sourceVolumeTarget.setRSetName(rsetName);
      sourceVolumeTarget.setProtectionSet(
          new NamedURI(protectionSet.getId(), sourceVolumeTarget.getLabel()));
      sourceVolumeTarget.setStorageController(storageSystem.getId());
      if (isRpVPlex) {
        sourceVolumeTarget.setAssociatedVolumes(associatedVolumes);
        sourceVolumeTarget.setNativeId(
            "/clusters/cluster-2/virtual-volumes/device_V000195701573-01E7C_vol" + i);
        // Create a VPLEX ViPR BlockConsistencyGroup for the target volume
        BlockConsistencyGroup sourceVolumeTargetCg =
            createBlockConsistencyGroup(
                sourceVolumeTarget.getLabel() + "-CG",
                storageSystem.getId(),
                Types.VPLEX.name(),
                true);
        addVolumeToBlockConsistencyGroup(sourceVolumeTargetCg.getId(), sourceVolumeTarget);
        rpVplexVolumeToCgMapping.put(sourceVolumeTargetURI, sourceVolumeTargetCg.getId());
      } else {
        rpVolumeURIs.add(sourceVolumeTargetURI);
      }

      _dbClient.createObject(sourceVolumeTarget);

      Volume sourceVolumeTargetJournal = new Volume();
      URI sourceVolumeTargetJournalURI = URIUtil.createId(Volume.class);
      volumes.add(sourceVolumeTargetJournal);
      sourceVolumeTargetJournal.setId(sourceVolumeTargetJournalURI);
      sourceVolumeTargetJournal.setLabel(volumeName + RP_TGT_JOURNAL_APPEND + "vArray" + i);
      sourceVolumeTargetJournal.setPersonality(Volume.PersonalityTypes.METADATA.toString());
      sourceVolumeTargetJournal.setProtectionSet(
          new NamedURI(protectionSet.getId(), sourceVolumeTargetJournal.getLabel()));
      sourceVolumeTargetJournal.setStorageController(storageSystem.getId());
      if (isRpVPlex) {
        sourceVolumeTargetJournal.setAssociatedVolumes(associatedVolumes);
        sourceVolumeTargetJournal.setNativeId(
            "/clusters/cluster-2/virtual-volumes/device_V000195701573-01ED_vol" + i);
        // Create a VPLEX ViPR BlockConsistencyGroup for the source target journal volume
        BlockConsistencyGroup sourceVolumeTargetJournalCg =
            createBlockConsistencyGroup(
                sourceVolumeTargetJournal.getLabel() + "-CG",
                storageSystem.getId(),
                Types.VPLEX.name(),
                true);
        addVolumeToBlockConsistencyGroup(
            sourceVolumeTargetJournalCg.getId(), sourceVolumeTargetJournal);
        rpVplexVolumeToCgMapping.put(
            sourceVolumeTargetJournalURI, sourceVolumeTargetJournalCg.getId());
      } else {
        rpVolumeURIs.add(sourceVolumeTargetJournalURI);
      }
      _dbClient.createObject(sourceVolumeTargetJournal);
    }

    return volumes;
  }
  private void processVolumes(
      CloseableIterator<CIMInstance> volumeInstances, Map<String, Object> keyMap)
      throws IOException {

    List<CIMObjectPath> metaVolumes = new ArrayList<>();
    while (volumeInstances.hasNext()) {
      CIMInstance volumeViewInstance = volumeInstances.next();
      String nativeGuid = getVolumeViewNativeGuid(volumeViewInstance.getObjectPath(), keyMap);

      if (isSnapShot(volumeViewInstance)) {
        BlockSnapshot snapShot = checkSnapShotExistsInDB(nativeGuid, _dbClient);
        if (null == snapShot || snapShot.getInactive()) {
          _logger.debug("Skipping Snapshot, as its not being managed in Bourne");
          continue;
        }
        updateBlockSnapShot(volumeViewInstance, snapShot, keyMap);
        if (_updateSnapShots.size() > BATCH_SIZE) {
          _partitionManager.updateInBatches(
              _updateSnapShots, getPartitionSize(keyMap), _dbClient, BLOCK_SNAPSHOT);
          _updateSnapShots.clear();
        }
      } else if (isMirror(volumeViewInstance)) {
        BlockMirror mirror = checkBlockMirrorExistsInDB(nativeGuid, _dbClient);
        if (null == mirror || mirror.getInactive()) {
          _logger.debug("Skipping Mirror, as its not being managed in Bourne");
          continue;
        }
        updateBlockMirror(volumeViewInstance, mirror, keyMap);
        if (_updateMirrors.size() > BATCH_SIZE) {
          _partitionManager.updateInBatches(
              _updateMirrors, getPartitionSize(keyMap), _dbClient, BLOCK_MIRROR);
          _updateMirrors.clear();
        }
      } else {
        Volume storageVolume = checkStorageVolumeExistsInDB(nativeGuid, _dbClient);
        if (null == storageVolume || storageVolume.getInactive()) {
          continue;
        }
        _logger.debug("Volume managed by Bourne :" + storageVolume.getNativeGuid());
        updateStorageVolume(volumeViewInstance, storageVolume, keyMap);

        // Check if this is a meta volume and if we need to set missing meta volume related
        // properties.
        // This is applicable for meta volumes discovered as unmanaged volumes and ingested prior to
        // vipr controller 2.2 .
        if (storageVolume.getIsComposite()
            && (storageVolume.getCompositionType() == null
                || storageVolume.getCompositionType().isEmpty())) {
          // meta volume is missing meta related data. Need to discover this data and set in the
          // volume.
          metaVolumes.add(volumeViewInstance.getObjectPath());
          _logger.info(
              "Found meta volume in vipr with missing data: {}, name: {}",
              volumeViewInstance.getObjectPath(),
              storageVolume.getLabel());
        }
      }
      if (_updateVolumes.size() > BATCH_SIZE) {
        _partitionManager.updateInBatches(
            _updateVolumes, getPartitionSize(keyMap), _dbClient, VOLUME);
        _updateVolumes.clear();
      }
    }

    // Add meta volumes to the keyMap
    try {
      if (metaVolumes != null && !metaVolumes.isEmpty()) {
        _metaVolumeViewPaths.addAll(metaVolumes);
        _logger.info("Added  {} meta volumes.", metaVolumes.size());
      }
    } catch (Exception ex) {
      _logger.error("Processing meta volumes.", ex);
    }
  }
  /*
   * (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);
    }
  }
  /**
   * 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");
  }