@Override
  public void deleteSingleVolumeMirror(
      StorageSystem storage, URI mirror, TaskCompleter taskCompleter)
      throws DeviceControllerException {
    _log.info("deleteSingleVolumeMirror operation START");
    try {
      BlockMirror mirrorObj = _dbClient.queryObject(BlockMirror.class, mirror);
      if (storage.checkIfVmax3()) {
        _helper.removeVolumeFromParkingSLOStorageGroup(storage, mirrorObj.getNativeId(), false);
        _log.info(
            "Done invoking remove volume {} from parking SLO storage group",
            mirrorObj.getNativeId());
      }

      CIMObjectPath mirrorPath = _cimPath.getBlockObjectPath(storage, mirrorObj);
      CIMObjectPath configSvcPath = _cimPath.getConfigSvcPath(storage);
      CIMArgument[] inArgs = _helper.getDeleteMirrorInputArguments(storage, mirrorPath);
      CIMArgument[] outArgs = new CIMArgument[5];
      _helper.invokeMethod(
          storage, configSvcPath, SmisConstants.RETURN_TO_STORAGE_POOL, inArgs, outArgs);
      CIMObjectPath job = _cimPath.getCimObjectPathFromOutputArgs(outArgs, SmisConstants.JOB);
      if (job != null) {
        ControllerServiceImpl.enqueueJob(
            new QueueJob(new SmisBlockDeleteMirrorJob(job, storage.getId(), taskCompleter)));
      }
    } catch (Exception e) {
      _log.info("Problem making SMI-S call: ", e);
      ServiceError serviceError =
          DeviceControllerErrors.smis.unableToCallStorageProvider(e.getMessage());
      taskCompleter.error(_dbClient, serviceError);
    }
  }
  @Override
  public void deleteSingleVolumeSnapshot(
      StorageSystem storage, URI snapshot, TaskCompleter taskCompleter)
      throws DeviceControllerException {

    try {
      BlockSnapshot snap = _dbClient.queryObject(BlockSnapshot.class, snapshot);
      VNXeApiClient apiClient = getVnxeClient(storage);
      VNXeLunSnap lunSnap = apiClient.getLunSnapshot(snap.getNativeId());
      if (lunSnap != null) {
        VNXeCommandJob job = apiClient.deleteLunSnap(lunSnap.getId());
        if (job != null) {
          ControllerServiceImpl.enqueueJob(
              new QueueJob(
                  new VNXeBlockDeleteSnapshotJob(job.getId(), storage.getId(), taskCompleter)));
        }
      } else {
        // Perhaps, it's already been deleted or was deleted on the array.
        // In that case, we'll just say all is well, so that this operation
        // is idempotent.
        snap.setInactive(true);
        snap.setIsSyncActive(false);
        _dbClient.updateObject(snap);
        taskCompleter.ready(_dbClient);
      }
    } catch (VNXeException e) {
      _log.error("Delete volume snapshot got the exception", e);
      taskCompleter.error(_dbClient, e);
    } catch (Exception ex) {
      _log.error("Delete volume snapshot got the exception", ex);
      ServiceError error = DeviceControllerErrors.vnxe.jobFailed("DeleteSnapshot", ex.getMessage());
      taskCompleter.error(_dbClient, error);
    }
  }
  /*
   * (non-Javadoc)
   *
   * @see
   * com.emc.storageos.volumecontroller.BlockStorageDevice#doWaitForSynchronized
   * (java.lang.Class, com.emc.storageos.db.client.model.StorageSystem,
   * java.net.URI, com.emc.storageos.volumecontroller.TaskCompleter)
   */
  @Override
  public void doWaitForSynchronized(
      Class<? extends BlockObject> clazz,
      StorageSystem storageObj,
      URI target,
      TaskCompleter completer) {
    log.info("START waitForSynchronized for {}", target);

    try {
      Volume targetObj = dbClient.queryObject(Volume.class, target);
      // Source could be either Volume or BlockSnapshot
      BlockObject sourceObj = BlockObject.fetch(dbClient, targetObj.getAssociatedSourceVolume());

      // We split the pair which causes the data to be synchronized.
      // When the split is complete that data is synchronized.
      HDSApiClient hdsApiClient =
          hdsApiFactory.getClient(
              HDSUtils.getHDSServerManagementServerInfo(storageObj),
              storageObj.getSmisUserName(),
              storageObj.getSmisPassword());
      HDSApiProtectionManager hdsApiProtectionManager = hdsApiClient.getHdsApiProtectionManager();
      String replicationGroupObjectID = hdsApiProtectionManager.getReplicationGroupObjectId();
      ReplicationInfo replicationInfo =
          hdsApiProtectionManager.getReplicationInfoFromSystem(
                  sourceObj.getNativeId(), targetObj.getNativeId())
              .first;
      hdsApiProtectionManager.modifyShadowImagePair(
          replicationGroupObjectID,
          replicationInfo.getObjectID(),
          HDSApiProtectionManager.ShadowImageOperationType.split);

      // Update state in case we are waiting for synchronization
      // after creation of a new full copy that was not created
      // inactive.
      String state = targetObj.getReplicaState();
      if (!ReplicationState.SYNCHRONIZED.name().equals(state)) {
        targetObj.setSyncActive(true);
        targetObj.setReplicaState(ReplicationState.SYNCHRONIZED.name());
        dbClient.persistObject(targetObj);
      }

      // Queue job to wait for replication status to move to split.
      ControllerServiceImpl.enqueueJob(
          new QueueJob(
              new HDSReplicationSyncJob(
                  storageObj.getId(),
                  sourceObj.getNativeId(),
                  targetObj.getNativeId(),
                  ReplicationStatus.SPLIT,
                  completer)));
    } catch (Exception e) {
      log.error("Exception occurred while waiting for synchronization", e);
      ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
      completer.error(dbClient, serviceError);
    }
    log.info("completed doWaitForSynchronized");
  }
  /*
   * (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()));
  }
 @Override
 public BlockStorageDevice getDevice() {
   BlockStorageDevice device = HDS_BLOCK_DEVICE.get();
   synchronized (HDS_BLOCK_DEVICE) {
     if (device == null) {
       device = (BlockStorageDevice) ControllerServiceImpl.getBean(HDS_STORAGE_DEVICE);
       HDS_BLOCK_DEVICE.compareAndSet(null, device);
     }
   }
   return device;
 }
  @Override
  public void deleteGroupSnapshots(StorageSystem storage, URI snapshot, TaskCompleter taskCompleter)
      throws DeviceControllerException {
    try {
      List<BlockSnapshot> snapshots =
          _dbClient.queryObject(BlockSnapshot.class, Arrays.asList(snapshot));
      BlockSnapshot snapshotObj = snapshots.get(0);

      VNXeApiClient apiClient = getVnxeClient(storage);
      VNXeLunGroupSnap lunGroupSnap =
          apiClient.getLunGroupSnapshot(snapshotObj.getReplicationGroupInstance());
      if (lunGroupSnap != null) {
        VNXeCommandJob job = apiClient.deleteLunGroupSnap(lunGroupSnap.getId());
        if (job != null) {
          ControllerServiceImpl.enqueueJob(
              new QueueJob(
                  new VNXeBlockDeleteSnapshotJob(job.getId(), storage.getId(), taskCompleter)));
        } else {
          // Should not take this path, but treat as an error if we do
          // happen to get a null job due to some error in the client.
          _log.error("Unexpected null job from VNXe client call to delete group snapshot.");
          ServiceCoded sc =
              DeviceControllerExceptions.vnxe.nullJobForDeleteGroupSnapshot(
                  snapshotObj.forDisplay(), snapshotObj.getReplicationGroupInstance());
          taskCompleter.error(_dbClient, sc);
        }
      } else {
        // Treat as in the single volume snapshot case and presume
        // the group snapshot has already been deleted.
        List<BlockSnapshot> grpSnapshots =
            ControllerUtils.getSnapshotsPartOfReplicationGroup(snapshotObj, _dbClient);
        for (BlockSnapshot grpSnapshot : grpSnapshots) {
          grpSnapshot.setInactive(true);
          grpSnapshot.setIsSyncActive(false);
        }
        _dbClient.updateObject(grpSnapshots);
        taskCompleter.ready(_dbClient);
      }
    } catch (VNXeException e) {
      _log.error("Delete group snapshot got the exception", e);
      taskCompleter.error(_dbClient, e);
    } catch (Exception ex) {
      _log.error("Delete group snapshot got the exception", ex);
      ServiceError error =
          DeviceControllerErrors.vnxe.jobFailed("DeletGroupSnapshot", ex.getMessage());
      taskCompleter.error(_dbClient, error);
    }
  }
  @Override
  public void createGroupSnapshots(
      StorageSystem storage,
      List<URI> snapshotList,
      Boolean createInactive,
      Boolean readOnly,
      TaskCompleter taskCompleter)
      throws DeviceControllerException {
    try {
      URI snapshot = snapshotList.get(0);
      BlockSnapshot snapshotObj = _dbClient.queryObject(BlockSnapshot.class, snapshot);

      Volume volume = _dbClient.queryObject(Volume.class, snapshotObj.getParent());
      TenantOrg tenant = _dbClient.queryObject(TenantOrg.class, volume.getTenant().getURI());
      String tenantName = tenant.getLabel();
      String snapLabelToUse =
          _nameGenerator.generate(
              tenantName,
              snapshotObj.getLabel(),
              snapshot.toString(),
              '-',
              SmisConstants.MAX_SNAPSHOT_NAME_LENGTH);
      String groupName = getConsistencyGroupName(snapshotObj);
      VNXeApiClient apiClient = getVnxeClient(storage);
      VNXeCommandJob job = apiClient.createLunGroupSnap(groupName, snapLabelToUse);
      if (job != null) {
        ControllerServiceImpl.enqueueJob(
            new QueueJob(
                new VNXeBlockCreateCGSnapshotJob(
                    job.getId(), storage.getId(), !createInactive, taskCompleter)));
      }

    } catch (VNXeException e) {
      _log.error("Create volume snapshot got the exception", e);
      taskCompleter.error(_dbClient, e);

    } catch (Exception ex) {
      _log.error("Create volume snapshot got the exception", ex);
      ServiceError error =
          DeviceControllerErrors.vnxe.jobFailed("CreateCGSnapshot", ex.getMessage());
      taskCompleter.error(_dbClient, error);
    }
  }
  @Override
  public void restoreGroupSnapshots(
      StorageSystem storage, URI volume, URI snapshot, TaskCompleter taskCompleter)
      throws DeviceControllerException {

    try {
      BlockSnapshot snapshotObj = _dbClient.queryObject(BlockSnapshot.class, snapshot);

      VNXeApiClient apiClient = getVnxeClient(storage);
      VNXeLunGroupSnap lunGroupSnap =
          apiClient.getLunGroupSnapshot(snapshotObj.getReplicationGroupInstance());
      // Error out if the snapshot is attached
      if (lunGroupSnap.getIsAttached()) {
        _log.error(
            "Snapshot {})is attached and cannot be used for restore", snapshotObj.getLabel());
        ServiceError error =
            DeviceControllerErrors.vnxe.cannotRestoreAttachedSnapshot(snapshot.toString());
        taskCompleter.error(_dbClient, error);
      }
      VNXeCommandJob job = apiClient.restoreLunGroupSnap(lunGroupSnap.getId());
      if (job != null) {
        ControllerServiceImpl.enqueueJob(
            new QueueJob(
                new VNXeBlockRestoreSnapshotJob(job.getId(), storage.getId(), taskCompleter)));
      }

    } catch (VNXeException e) {
      _log.error("Restore group snapshot got the exception", e);
      taskCompleter.error(_dbClient, e);

    } catch (Exception ex) {
      _log.error("Restore group snapshot got the exception", ex);
      ServiceError error =
          DeviceControllerErrors.vnxe.jobFailed("RestoreSnapshotJob", ex.getMessage());
      taskCompleter.error(_dbClient, error);
    }
  }
  @Override
  public void createSingleVolumeMirror(
      StorageSystem storage, URI mirror, Boolean createInactive, TaskCompleter taskCompleter)
      throws DeviceControllerException {
    _log.info("createSingleVolumeMirror operation START");
    try {
      BlockMirror mirrorObj = _dbClient.queryObject(BlockMirror.class, mirror);
      StoragePool targetPool = _dbClient.queryObject(StoragePool.class, mirrorObj.getPool());
      Volume source = _dbClient.queryObject(Volume.class, mirrorObj.getSource());
      TenantOrg tenant = _dbClient.queryObject(TenantOrg.class, source.getTenant().getURI());
      String tenantName = tenant.getLabel();
      String targetLabelToUse =
          _nameGenerator.generate(
              tenantName,
              mirrorObj.getLabel(),
              mirror.toString(),
              '-',
              SmisConstants.MAX_VOLUME_NAME_LENGTH);
      CIMObjectPath replicationSvcPath = _cimPath.getControllerReplicationSvcPath(storage);
      CIMArgument[] inArgs = null;
      if (storage.checkIfVmax3()) {
        CIMObjectPath volumeGroupPath = _helper.getVolumeGroupPath(storage, source, targetPool);
        CIMInstance replicaSettingData = getDefaultReplicationSettingData(storage);
        inArgs =
            _helper.getCreateElementReplicaMirrorInputArguments(
                storage,
                source,
                targetPool,
                createInactive,
                targetLabelToUse,
                volumeGroupPath,
                replicaSettingData);
      } else {
        inArgs =
            _helper.getCreateElementReplicaMirrorInputArguments(
                storage, source, targetPool, createInactive, targetLabelToUse);
      }

      CIMArgument[] outArgs = new CIMArgument[5];
      _helper.invokeMethod(
          storage, replicationSvcPath, SmisConstants.CREATE_ELEMENT_REPLICA, inArgs, outArgs);
      CIMObjectPath job = _cimPath.getCimObjectPathFromOutputArgs(outArgs, SmisConstants.JOB);
      if (job != null) {
        ControllerServiceImpl.enqueueJob(
            new QueueJob(
                new SmisBlockCreateMirrorJob(
                    job, storage.getId(), !createInactive, taskCompleter)));
        // Resynchronizing state applies to the initial copy as well as future
        // re-synchronization's.
        mirrorObj.setSyncState(SynchronizationState.RESYNCHRONIZING.toString());
        _dbClient.persistObject(mirrorObj);
      }
    } catch (final InternalException e) {
      _log.info("Problem making SMI-S call: ", e);
      taskCompleter.error(_dbClient, e);
    } catch (Exception e) {
      _log.info("Problem making SMI-S call: ", e);
      ServiceError serviceError =
          DeviceControllerErrors.smis.unableToCallStorageProvider(e.getMessage());
      taskCompleter.error(_dbClient, serviceError);
    }
  }
  @Override
  public void deleteGroupMirrors(
      StorageSystem storage, List<URI> mirrorList, TaskCompleter taskCompleter)
      throws DeviceControllerException {
    _log.info("deleteGroupMirrors operation START");
    if (!((storage.getUsingSmis80() && storage.deviceIsType(Type.vmax))
        || storage.deviceIsType(Type.vnxblock))) {
      throw DeviceControllerException.exceptions.blockDeviceOperationNotSupported();
    }

    try {
      String[] deviceIds = null;
      BlockMirror firstMirror = _dbClient.queryObject(BlockMirror.class, mirrorList.get(0));
      String repGroupName = firstMirror.getReplicationGroupInstance();
      if (NullColumnValueGetter.isNotNullValue(repGroupName)) {
        CIMObjectPath repGroupPath = _cimPath.getReplicationGroupPath(storage, repGroupName);
        Set<String> deviceIdsSet =
            _helper.getVolumeDeviceIdsFromStorageGroup(storage, repGroupPath);
        deviceIds = deviceIdsSet.toArray(new String[deviceIdsSet.size()]);

        // Delete replication group
        ReplicationUtils.deleteReplicationGroup(
            storage, repGroupName, _dbClient, _helper, _cimPath);
        // Set mirrors replication group to null
        List<BlockMirror> mirrors = _dbClient.queryObject(BlockMirror.class, mirrorList);
        for (BlockMirror mirror : mirrors) {
          mirror.setConsistencyGroup(NullColumnValueGetter.getNullURI());
          mirror.setReplicationGroupInstance(NullColumnValueGetter.getNullStr());
        }

        _dbClient.persistObject(mirrors);
      } else {
        deviceIds = _helper.getBlockObjectNativeIds(mirrorList);
      }

      if (storage.checkIfVmax3()) {
        for (String deviceId : deviceIds) {
          _helper.removeVolumeFromParkingSLOStorageGroup(storage, deviceId, false);
          _log.info("Done invoking remove volume {} from parking SLO storage group", deviceId);
        }
      }

      CIMObjectPath[] mirrorPaths = _cimPath.getVolumePaths(storage, deviceIds);
      CIMObjectPath configSvcPath = _cimPath.getConfigSvcPath(storage);
      CIMArgument[] inArgs = null;
      if (storage.deviceIsType(Type.vnxblock)) {
        inArgs = _helper.getReturnElementsToStoragePoolArguments(mirrorPaths);
      } else {
        inArgs =
            _helper.getReturnElementsToStoragePoolArguments(
                mirrorPaths, SmisConstants.CONTINUE_ON_NONEXISTENT_ELEMENT);
      }
      CIMArgument[] outArgs = new CIMArgument[5];
      _helper.invokeMethod(
          storage, configSvcPath, SmisConstants.RETURN_ELEMENTS_TO_STORAGE_POOL, inArgs, outArgs);
      CIMObjectPath job = _cimPath.getCimObjectPathFromOutputArgs(outArgs, SmisConstants.JOB);
      ControllerServiceImpl.enqueueJob(
          new QueueJob(new SmisBlockDeleteCGMirrorJob(job, storage.getId(), taskCompleter)));
    } catch (Exception e) {
      _log.error("Problem making SMI-S call: ", e);
      ServiceError serviceError =
          DeviceControllerErrors.smis.unableToCallStorageProvider(e.getMessage());
      taskCompleter.error(_dbClient, serviceError);
    }
  }
 @Override
 public void resumeSingleVolumeMirror(
     StorageSystem storage, URI mirror, TaskCompleter taskCompleter)
     throws DeviceControllerException {
   _log.info("resumeSingleVolumeMirror operation START");
   CloseableIterator<CIMObjectPath> storageSyncRefs = null;
   try {
     BlockMirror mirrorObj = _dbClient.queryObject(BlockMirror.class, mirror);
     CIMObjectPath mirrorPath = _cimPath.getBlockObjectPath(storage, mirrorObj);
     // Get reference to the CIM_StorageSynchronized instance
     storageSyncRefs =
         _helper.getReference(storage, mirrorPath, SmisConstants.CIM_STORAGE_SYNCHRONIZED, null);
     if (!storageSyncRefs.hasNext()) {
       _log.error("No synchronization instance found for {}", mirror);
       taskCompleter.error(
           _dbClient, DeviceControllerException.exceptions.resumeVolumeMirrorFailed(mirror));
       return;
     }
     boolean isVmax3 = storage.checkIfVmax3();
     while (storageSyncRefs.hasNext()) {
       CIMObjectPath storageSync = storageSyncRefs.next();
       _log.debug(storageSync.toString());
       /**
        * JIRA CTRL-11855 User created mirror and did pause operation using SMI 4.6.2. Then He
        * upgraded to SMI 8.0.3. While doing mirror resume getting exception from SMI because of
        * the existing mirrorObj.getSynchronizedInstance() contains
        * SystemName=\"SYMMETRIX+000195701573\"" This is wrong with 8.0.3 as
        * SystemName=\"SYMMETRIX-+-000195701573\"". To resolve this issue setting new value
        * collected from current smis provider here.
        */
       mirrorObj.setSynchronizedInstance(storageSync.toString());
       _dbClient.persistObject(mirrorObj);
       CIMArgument[] inArgs =
           isVmax3
               ? _helper.getResumeSynchronizationInputArgumentsWithCopyState(storageSync)
               : _helper.getResumeSynchronizationInputArguments(storageSync);
       CIMArgument[] outArgs = new CIMArgument[5];
       _helper.callModifyReplica(storage, inArgs, outArgs);
       CIMObjectPath job = _cimPath.getCimObjectPathFromOutputArgs(outArgs, SmisConstants.JOB);
       if (job != null) {
         ControllerServiceImpl.enqueueJob(
             new QueueJob(new SmisBlockResumeMirrorJob(job, storage.getId(), taskCompleter)));
       } else {
         CIMInstance syncObject =
             _helper.getInstance(
                 storage, storageSync, false, false, new String[] {SmisConstants.CP_SYNC_STATE});
         mirrorObj.setSyncState(
             CIMPropertyFactory.getPropertyValue(syncObject, SmisConstants.CP_SYNC_STATE));
         _dbClient.persistObject(mirrorObj);
         taskCompleter.ready(_dbClient);
       }
     }
   } catch (Exception e) {
     _log.error("Failed to resume single volume mirror: {}", mirror);
     ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
     taskCompleter.error(_dbClient, serviceError);
   } finally {
     if (storageSyncRefs != null) {
       storageSyncRefs.close();
     }
   }
 }
  /*
   * (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);
      }
    }
  }
  /*
   * (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);
    }
  }