@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());
  }
 /*
  * (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());
 }
  @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());
  }
  /*
   * (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()));
  }
 /*
  * (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());
 }
 /*
  * (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 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());
  }
  @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());
  }
 /*
  * (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());
 }
  /*
   * (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);
    }
  }
  /*
   * (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()));
  }
  /** {@inheritDoc} */
  @SuppressWarnings("unchecked")
  @Override
  public void processResult(Operation operation, Object resultObj, Map<String, Object> keyMap)
      throws BaseCollectionException {
    final Iterator<CIMInstance> it = (Iterator<CIMInstance>) resultObj;
    profile = (AccessProfile) keyMap.get(Constants.ACCESSPROFILE);
    try {
      _newPoolList = new ArrayList<StoragePool>();
      _updatePoolList = new ArrayList<StoragePool>();
      _dbClient = (DbClient) keyMap.get(Constants.dbClient);
      _cimClient = (WBEMClient) keyMap.get(Constants._cimClient);
      _coordinator = (CoordinatorClient) keyMap.get(Constants.COORDINATOR_CLIENT);
      _eventManager = (RecordableEventManager) keyMap.get(Constants.EVENT_MANAGER);
      _logger.info("StoragePoolProcessor --- event manager: " + _eventManager);
      StorageSystem device = getStorageSystem(_dbClient, profile.getSystemId());
      if (SupportedProvisioningTypes.NONE
          .toString()
          .equalsIgnoreCase(device.getSupportedProvisioningType())) {
        _logger.info(
            "Storage System doesn't support volume creations :" + device.getSerialNumber());
        return;
      }
      Set<String> protocols = (Set<String>) keyMap.get(Constants.PROTOCOLS);
      Map<URI, StoragePool> poolsToMatchWithVpool =
          (Map<URI, StoragePool>) keyMap.get(Constants.MODIFIED_STORAGEPOOLS);
      while (it.hasNext()) {
        CIMInstance poolInstance = null;
        try {
          poolInstance = it.next();

          // Supporting both thick and thin pools
          String[] poolClassNameAndSupportedVolumeTypes =
              determinePoolClassNameAndSupportedVolumeTypes(poolInstance, device);
          if (null != poolClassNameAndSupportedVolumeTypes) {
            String instanceID = getCIMPropertyValue(poolInstance, Constants.INSTANCEID);
            addPath(keyMap, operation.getResult(), poolInstance.getObjectPath());
            StoragePool pool =
                checkStoragePoolExistsInDB(getNativeIDFromInstance(instanceID), _dbClient, device);
            createStoragePool(
                pool,
                poolInstance,
                profile,
                poolClassNameAndSupportedVolumeTypes[0],
                poolClassNameAndSupportedVolumeTypes[1],
                protocols,
                poolsToMatchWithVpool,
                device);

            if (DiscoveredDataObject.Type.vnxblock
                .toString()
                .equalsIgnoreCase(device.getSystemType())) {
              addPath(keyMap, Constants.VNXPOOLS, poolInstance.getObjectPath());
            }

            if (DiscoveredDataObject.Type.vmax
                .toString()
                .equalsIgnoreCase(device.getSystemType())) {
              addPath(keyMap, Constants.VMAXPOOLS, poolInstance.getObjectPath());
              if (!device.checkIfVmax3()) {
                addPath(keyMap, Constants.VMAX2POOLS, poolInstance.getObjectPath());
              }
            }
            // This approach deviates from the existing built plugin framework for plugin
            // Discovery
            // To follow the existing pattern, we need to have different SMI-S calls
            // 1st to get Device StoragePools alone ,and 2nd to get Thin Pools.
            // Its a tradeoff between whether to go with the current plugin design or
            // reduce the number of calls to SMI Provider.
            // I chose the 2nd option.
            if (!poolClassNameAndSupportedVolumeTypes[0].contains(DEVICE_STORAGE_POOL)) {
              addPath(keyMap, Constants.THINPOOLS, poolInstance.getObjectPath());
            }

            addPath(keyMap, Constants.DEVICEANDTHINPOOLS, poolInstance.getObjectPath());
          } else {
            _logger.debug(
                "Skipping Pools other than Unified & Virtual & Device : {}",
                poolInstance.getObjectPath().toString());
          }
        } catch (Exception e) {
          _logger.warn(
              "StoragePool Discovery failed for {}",
              getCIMPropertyValue(poolInstance, Constants.INSTANCEID),
              e);
        }
      }

      _dbClient.createObject(_newPoolList);
      _dbClient.updateAndReindexObject(_updatePoolList);

      // find the pools not visible in this discovery
      List<StoragePool> discoveredPools = new ArrayList<StoragePool>(_newPoolList);
      discoveredPools.addAll(_updatePoolList);
      List<StoragePool> notVisiblePools =
          DiscoveryUtils.checkStoragePoolsNotVisible(discoveredPools, _dbClient, device.getId());
      for (StoragePool notVisiblePool : notVisiblePools) {
        poolsToMatchWithVpool.put(notVisiblePool.getId(), notVisiblePool);
      }
      // If any storage ports on the storage system are in a transport
      // zone, there is an implicit connection to the transport zone
      // varray. We need to add these implicit varray
      // connections for the new storage pool.
      StoragePoolAssociationHelper.setStoragePoolVarrays(device.getId(), _newPoolList, _dbClient);
    } catch (Exception e) {
      _logger.error("StoragePool Discovery failed --> {}", getMessage(e));
    } finally {
      _newPoolList = null;
      _updatePoolList = null;
    }
  }
  /*
   * (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);
      }
    }
  }
  /*
   * (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());
  }