/**
  * Validate the SourceVolume VArray details with ingested target volumes VArray.
  *
  * @param unManagedVolume
  * @param VirtualPool
  * @return
  */
 private void validateSourceVolumeVarrayWithTargetVPool(
     UnManagedVolume unManagedVolume, VirtualPool sourceVPool) {
   StringSetMap unManagedVolumeInformation = unManagedVolume.getVolumeInformation();
   // find whether all targets are ingested
   StringSet targetUnManagedVolumeGuids =
       unManagedVolumeInformation.get(SupportedVolumeInformation.REMOTE_MIRRORS.toString());
   if (null != targetUnManagedVolumeGuids && !targetUnManagedVolumeGuids.isEmpty()) {
     StringSet targetVolumeNativeGuids =
         VolumeIngestionUtil.getListofVolumeIds(targetUnManagedVolumeGuids);
     // check whether target exists
     List<URI> targetUris = VolumeIngestionUtil.getVolumeUris(targetVolumeNativeGuids, _dbClient);
     if (null == targetUris || targetUris.isEmpty()) {
       _logger.info(
           "None of the targets ingested for source volume: {}", unManagedVolume.getNativeGuid());
     } else {
       List<Volume> targetVolumes = _dbClient.queryObject(Volume.class, targetUris);
       for (Volume targetVolume : targetVolumes) {
         Map<URI, VpoolRemoteCopyProtectionSettings> settings =
             sourceVPool.getRemoteProtectionSettings(sourceVPool, _dbClient);
         if (null == settings
             || settings.size() == 0
             || !settings.containsKey(targetVolume.getVirtualArray())) {
           _logger.info(
               "Target Volume's VArray {} is not matching already ingested source volume virtual pool's remote VArray {}",
               targetVolume.getVirtualArray(),
               Joiner.on(",").join(settings.keySet()));
           throw IngestionException.exceptions.unmanagedSRDFSourceVolumeVArrayMismatch(
               unManagedVolume.getLabel(), targetVolume.getVirtualArray().toString());
         }
       }
     }
   }
 }
 /**
  * Validate the Target Volume VirtualArray with the Source Volume VPool VirtualArray.
  *
  * @param type
  * @param unManagedVolume
  * @param virtualArray
  */
 private void validateTargetVolumeVpoolWithSourceVolume(
     UnManagedVolume unManagedVolume, VirtualArray virtualArray) {
   String sourceUnManagedVolumeId =
       PropertySetterUtil.extractValueFromStringSet(
           SupportedVolumeInformation.REMOTE_MIRROR_SOURCE_VOLUME.toString(),
           unManagedVolume.getVolumeInformation());
   String sourceVolumeId =
       sourceUnManagedVolumeId.replace(
           VolumeIngestionUtil.UNMANAGEDVOLUME, VolumeIngestionUtil.VOLUME);
   List<URI> sourceUris =
       _dbClient.queryByConstraint(
           AlternateIdConstraint.Factory.getVolumeNativeGuidConstraint(sourceVolumeId));
   if (sourceUris.isEmpty()) {
     _logger.info(
         "Source {} Not found for target {}", sourceVolumeId, unManagedVolume.getNativeGuid());
   } else {
     // if source volume is ingested, then
     Volume sourceVolume = _dbClient.queryObject(Volume.class, sourceUris.get(0));
     // check whether the source Volume's VPool is actually having this
     // target Volume's varray
     // specified as remote
     VirtualPool sourceVPool =
         _dbClient.queryObject(VirtualPool.class, sourceVolume.getVirtualPool());
     Map<URI, VpoolRemoteCopyProtectionSettings> settings =
         sourceVPool.getRemoteProtectionSettings(sourceVPool, _dbClient);
     if (null == settings || settings.isEmpty() || !settings.containsKey(virtualArray.getId())) {
       _logger.info(
           "Target Volume's VArray {} is not matching already ingested source volume virtual pool's remote VArray ",
           virtualArray.getId());
       throw IngestionException.exceptions.unmanagedSRDFTargetVolumeVArrayMismatch(
           unManagedVolume.getLabel(), sourceVolume.getVirtualArray().toString());
     }
   }
 }
  /**
   * Places and prepares the primary copy volumes when copying a VPLEX virtual volume.
   *
   * @param name The base name for the volume.
   * @param copyCount The number of copies to be made.
   * @param srcPrimaryVolume The primary volume of the VPLEX volume being copied.
   * @param srcCapabilities The capabilities of the primary volume.
   * @param volumeDescriptors The list of descriptors.
   * @return A list of the prepared primary volumes for the VPLEX volume copy.
   */
  private List<Volume> prepareFullCopyPrimaryVolumes(
      String name,
      int copyCount,
      Volume srcPrimaryVolume,
      VirtualPoolCapabilityValuesWrapper srcCapabilities,
      List<VolumeDescriptor> volumeDescriptors) {

    List<Volume> copyPrimaryVolumes = new ArrayList<Volume>();

    // Get the placement recommendations for the primary volume copies.
    // Use the same method as is done for native volume copy.
    VirtualArray vArray =
        _dbClient.queryObject(VirtualArray.class, srcPrimaryVolume.getVirtualArray());
    VirtualPool vPool = _dbClient.queryObject(VirtualPool.class, srcPrimaryVolume.getVirtualPool());
    List<VolumeRecommendation> recommendations =
        ((VPlexScheduler) _scheduler)
            .getBlockScheduler()
            .getRecommendationsForVolumeClones(vArray, vPool, srcPrimaryVolume, srcCapabilities);
    if (recommendations.isEmpty()) {
      throw APIException.badRequests.noStorageForPrimaryVolumesForVplexVolumeCopies();
    }

    // Prepare the copy volumes for each recommendation. Again,
    // use the same manner as is done for native volume copy.
    StringBuilder nameBuilder = new StringBuilder(name);
    nameBuilder.append("-0");
    int copyIndex = (copyCount > 1) ? 1 : 0;
    for (VolumeRecommendation recommendation : recommendations) {
      Volume volume =
          StorageScheduler.prepareFullCopyVolume(
              _dbClient,
              nameBuilder.toString(),
              srcPrimaryVolume,
              recommendation,
              copyIndex++,
              srcCapabilities);
      volume.addInternalFlags(Flag.INTERNAL_OBJECT);
      _dbClient.persistObject(volume);
      copyPrimaryVolumes.add(volume);

      // Create the volume descriptor and add it to the passed list.
      VolumeDescriptor volumeDescriptor =
          new VolumeDescriptor(
              VolumeDescriptor.Type.VPLEX_IMPORT_VOLUME,
              volume.getStorageController(),
              volume.getId(),
              volume.getPool(),
              srcCapabilities);
      volumeDescriptors.add(volumeDescriptor);
    }

    return copyPrimaryVolumes;
  }
  /**
   * 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");
  }
  /**
   * Places and prepares the HA volumes when copying a distributed VPLEX volume.
   *
   * @param name The base name for the volume.
   * @param copyCount The number of copies to be made.
   * @param size The size for the HA volume.
   * @param vplexSystem A reference to the VPLEX storage system.
   * @param vplexSystemProject A reference to the VPLEX system project.
   * @param srcVarray The virtual array for the VPLEX volume being copied.
   * @param srcHAVolume The HA volume of the VPLEX volume being copied.
   * @param taskId The task identifier.
   * @param volumeDescriptors The list of descriptors.
   * @return A list of the prepared HA volumes for the VPLEX volume copy.
   */
  private List<Volume> prepareFullCopyHAVolumes(
      String name,
      int copyCount,
      Long size,
      StorageSystem vplexSystem,
      Project vplexSystemProject,
      VirtualArray srcVarray,
      Volume srcHAVolume,
      String taskId,
      List<VolumeDescriptor> volumeDescriptors) {

    List<Volume> copyHAVolumes = new ArrayList<Volume>();

    // Get the storage placement recommendations for the volumes.
    // Placement must occur on the same VPLEX system
    Set<URI> vplexSystemURIS = new HashSet<URI>();
    vplexSystemURIS.add(vplexSystem.getId());
    VirtualArray haVarray =
        _dbClient.queryObject(VirtualArray.class, srcHAVolume.getVirtualArray());
    VirtualPool haVpool = _dbClient.queryObject(VirtualPool.class, srcHAVolume.getVirtualPool());
    VirtualPoolCapabilityValuesWrapper haCapabilities = new VirtualPoolCapabilityValuesWrapper();
    haCapabilities.put(VirtualPoolCapabilityValuesWrapper.SIZE, size);
    haCapabilities.put(VirtualPoolCapabilityValuesWrapper.RESOURCE_COUNT, copyCount);
    VirtualPool vpool = BlockFullCopyUtils.queryFullCopySourceVPool(srcHAVolume, _dbClient);
    if (VirtualPool.ProvisioningType.Thin.toString()
        .equalsIgnoreCase(vpool.getSupportedProvisioningType())) {
      haCapabilities.put(VirtualPoolCapabilityValuesWrapper.THIN_PROVISIONING, Boolean.TRUE);
      // To guarantee that storage pool for a copy has enough physical
      // space to contain current allocated capacity of thin source volume
      haCapabilities.put(
          VirtualPoolCapabilityValuesWrapper.THIN_VOLUME_PRE_ALLOCATE_SIZE,
          BlockFullCopyUtils.getAllocatedCapacityForFullCopySource(srcHAVolume, _dbClient));
    }
    List<Recommendation> recommendations =
        ((VPlexScheduler) _scheduler)
            .scheduleStorageForImport(
                srcVarray, vplexSystemURIS, haVarray, haVpool, haCapabilities);
    if (recommendations.isEmpty()) {
      throw APIException.badRequests.noStorageForHaVolumesForVplexVolumeCopies();
    }

    // Prepare the HA volumes for the VPLEX volume copy.
    int copyIndex = 1;
    for (Recommendation recommendation : recommendations) {
      VPlexRecommendation haRecommendation = (VPlexRecommendation) recommendation;
      for (int i = 0; i < haRecommendation.getResourceCount(); i++) {
        // Determine the name for the HA volume copy.
        StringBuilder nameBuilder = new StringBuilder(name);
        nameBuilder.append("-1");
        if (copyCount > 1) {
          nameBuilder.append("-");
          nameBuilder.append(copyIndex++);
        }

        // Prepare the volume.
        Volume volume =
            VPlexBlockServiceApiImpl.prepareVolumeForRequest(
                size,
                vplexSystemProject,
                haVarray,
                haVpool,
                haRecommendation.getSourceDevice(),
                haRecommendation.getSourcePool(),
                nameBuilder.toString(),
                null,
                taskId,
                _dbClient);
        volume.addInternalFlags(Flag.INTERNAL_OBJECT);
        _dbClient.persistObject(volume);
        copyHAVolumes.add(volume);

        // Create the volume descriptor and add it to the passed list.
        VolumeDescriptor volumeDescriptor =
            new VolumeDescriptor(
                VolumeDescriptor.Type.BLOCK_DATA,
                volume.getStorageController(),
                volume.getId(),
                volume.getPool(),
                haCapabilities);
        volumeDescriptors.add(volumeDescriptor);
      }
    }

    return copyHAVolumes;
  }
  /** {@inheritDoc} */
  @Override
  public TaskList create(
      List<BlockObject> fcSourceObjList,
      VirtualArray varray,
      String name,
      boolean createInactive,
      int count,
      String taskId) {

    // Populate the descriptors list with all volumes required
    // to create the VPLEX volume copies.
    int sourceCounter = 0;
    URI vplexSrcSystemId = null;
    TaskList taskList = new TaskList();
    List<Volume> vplexCopyVolumes = new ArrayList<Volume>();
    List<VolumeDescriptor> volumeDescriptors = new ArrayList<VolumeDescriptor>();
    List<BlockObject> sortedSourceObjectList = sortFullCopySourceList(fcSourceObjList);
    for (BlockObject fcSourceObj : sortedSourceObjectList) {
      URI fcSourceURI = fcSourceObj.getId();
      if (URIUtil.isType(fcSourceURI, BlockSnapshot.class)) {
        // Full copy of snapshots is not supported for VPLEX.
        return super.create(sortedSourceObjectList, varray, name, createInactive, count, taskId);
      }

      Volume vplexSrcVolume = (Volume) fcSourceObj;
      String copyName = name + (sortedSourceObjectList.size() > 1 ? "-" + ++sourceCounter : "");

      // Create a volume descriptor for the source VPLEX volume being copied.
      // and add it to the descriptors list. Be sure to identify this VPLEX
      // volume as the source volume being copied.
      vplexSrcSystemId = fcSourceObj.getStorageController();
      VolumeDescriptor vplexSrcVolumeDescr =
          new VolumeDescriptor(
              VolumeDescriptor.Type.VPLEX_VIRT_VOLUME, vplexSrcSystemId, fcSourceURI, null, null);
      Map<String, Object> descrParams = new HashMap<String, Object>();
      descrParams.put(VolumeDescriptor.PARAM_IS_COPY_SOURCE_ID, Boolean.TRUE);
      vplexSrcVolumeDescr.setParameters(descrParams);
      volumeDescriptors.add(vplexSrcVolumeDescr);

      // Get some info about the VPLEX volume being copied and its storage
      // system.
      Project vplexSrcProject =
          BlockFullCopyUtils.queryFullCopySourceProject(fcSourceObj, _dbClient);
      StorageSystem vplexSrcSystem = _dbClient.queryObject(StorageSystem.class, vplexSrcSystemId);
      Project vplexSystemProject =
          VPlexBlockServiceApiImpl.getVplexProject(vplexSrcSystem, _dbClient, _tenantsService);

      // For the VPLEX volume being copied, determine which of the associated
      // backend volumes is the primary and, for distributed volumes, which
      // is the HA volume. The primary volume will be natively copied and we
      // we need to place and prepare a volume to hold the copy. This copy
      // will be the primary backend volume for the VPLEX volume copy. For
      // a distributed virtual volume, we will need to place and prepare
      // a volume to hold the HA volume of the VPLEX volume copy.
      Volume vplexSrcPrimaryVolume = null;
      Volume vplexSrcHAVolume = null;
      StringSet assocVolumeURIs = vplexSrcVolume.getAssociatedVolumes();
      Iterator<String> assocVolumeURIsIter = assocVolumeURIs.iterator();
      while (assocVolumeURIsIter.hasNext()) {
        URI assocVolumeURI = URI.create(assocVolumeURIsIter.next());
        Volume assocVolume = _dbClient.queryObject(Volume.class, assocVolumeURI);
        if (assocVolume.getVirtualArray().toString().equals(varray.getId().toString())) {
          vplexSrcPrimaryVolume = assocVolume;
        } else {
          vplexSrcHAVolume = assocVolume;
        }
      }

      // Get the capabilities
      VirtualPool vpool = BlockFullCopyUtils.queryFullCopySourceVPool(fcSourceObj, _dbClient);
      VirtualPoolCapabilityValuesWrapper capabilities =
          getCapabilitiesForFullCopyCreate(fcSourceObj, vpool, count);

      // Get the number of copies to create and the size of the volumes.
      // Note that for the size, we must use the actual provisioned size
      // of the source side backend volume. The size passed in the
      // capabilities will be the size of the VPLEX volume. When the
      // source side backend volume for the copy is provisioned, you
      // might not get that actual size. On VMAX, the size will be slightly
      // larger while for VNX the size will be exactly what is requested.
      // So, if the source side is a VMAX, the source side for the copy
      // will be slightly larger than the size in the capabilities. If the HA
      // side is VNX and we use the size in the capabilities, then you will
      // get exactly that size for the HA backend volume. As a result, source
      // side backend volume for the copy will be slightly larger than the
      // HA side. Now the way a VPLEX copy is made is it uses native full
      // copy to create a native full copy of the source side backend
      // volume. It then provisions the HA side volume. The new source side
      // backend copy is then imported into VPLEX in the same way as is done
      // for a vpool change that imports a volume to VPLEX. This code in the
      // VPLEX controller creates a local VPLEX volume using the source side
      // copy and for a distributed volume it then attaches as a remote
      // mirror the HA backend volume that is provisioned. If the HA volume
      // is slightly smaller, then this will fail on the VPLEX. So, we must
      // ensure that HA side volume is big enough by using the provisioned
      // capacity of the source side backend volume of the VPLEX volume being
      // copied.
      long size = vplexSrcPrimaryVolume.getProvisionedCapacity();

      // Place and prepare a volume for each copy to serve as a native
      // copy of a VPLEX backend volume. The VPLEX backend volume that
      // is copied is the backend volume in the same virtual array as the
      // VPLEX volume i.e, the primary backend volume. Create
      // descriptors for these prepared volumes and add them to the list.
      List<Volume> vplexCopyPrimaryVolumes =
          prepareFullCopyPrimaryVolumes(
              copyName, count, vplexSrcPrimaryVolume, capabilities, volumeDescriptors);

      // If the VPLEX volume being copied is distributed, then the VPLEX
      // HA volume should be non-null. We use the VPLEX scheduler to place
      // and then prepare volumes for the HA volumes of the VPLEX volume
      // copies. This should be done in the same manner as is done for the
      // import volume routine. This is because to form the VPLEX volume
      // copy we import the copy of the primary backend volume.
      List<Volume> vplexCopyHAVolumes = new ArrayList<Volume>();
      if (vplexSrcHAVolume != null) {
        vplexCopyHAVolumes.addAll(
            prepareFullCopyHAVolumes(
                copyName,
                count,
                size,
                vplexSrcSystem,
                vplexSystemProject,
                varray,
                vplexSrcHAVolume,
                taskId,
                volumeDescriptors));
      }

      // For each copy to be created, place and prepare a volume for the
      // primary backend volume copy. When copying a distributed VPLEX
      // volume, we also must place and prepare a volume for the HA
      // backend volume copy. Lastly, we must prepare a volume for the
      // VPLEX volume copy. Create descriptors for these prepared volumes
      // and add them to the volume descriptors list.
      for (int i = 0; i < count; i++) {
        // Prepare a new VPLEX volume for each copy.
        Volume vplexCopyPrimaryVolume = vplexCopyPrimaryVolumes.get(i);
        Volume vplexCopyHAVolume = null;
        if (vplexCopyHAVolumes.size() != 0) {
          vplexCopyHAVolume = vplexCopyHAVolumes.get(i);
        }
        Volume vplexCopyVolume =
            prepareFullCopyVPlexVolume(
                copyName,
                count,
                i,
                size,
                vplexSrcVolume,
                vplexSrcProject,
                varray,
                vpool,
                vplexSrcSystemId,
                vplexCopyPrimaryVolume,
                vplexCopyHAVolume,
                taskId,
                volumeDescriptors);
        vplexCopyVolumes.add(vplexCopyVolume);

        // Create task for each copy.
        Operation op = vplexCopyVolume.getOpStatus().get(taskId);
        TaskResourceRep task = toTask(vplexCopyVolume, taskId, op);
        taskList.getTaskList().add(task);
      }
    }

    // Invoke the VPLEX controller to create the copies.
    try {
      s_logger.info("Getting VPlex controller {}.", taskId);
      VPlexController controller =
          getController(VPlexController.class, DiscoveredDataObject.Type.vplex.toString());
      // TBD controller needs to be updated to handle CGs.
      controller.createFullCopy(vplexSrcSystemId, volumeDescriptors, taskId);
      s_logger.info("Successfully invoked controller.");
    } catch (InternalException e) {
      s_logger.error("Controller error", e);

      // Update the status for the VPLEX volume copies and their
      // corresponding tasks.
      for (Volume vplexCopyVolume : vplexCopyVolumes) {
        Operation op = vplexCopyVolume.getOpStatus().get(taskId);
        if (op != null) {
          op.error(e);
          vplexCopyVolume.getOpStatus().updateTaskStatus(taskId, op);
          _dbClient.persistObject(vplexCopyVolume);
          for (TaskResourceRep task : taskList.getTaskList()) {
            if (task.getResource().getId().equals(vplexCopyVolume.getId())) {
              task.setState(op.getStatus());
              task.setMessage(op.getMessage());
              break;
            }
          }
        }
      }

      // Mark all volumes inactive, except for the VPLEX volume
      // we were trying to copy.
      for (VolumeDescriptor descriptor : volumeDescriptors) {
        if (descriptor.getParameters().get(VolumeDescriptor.PARAM_IS_COPY_SOURCE_ID) == null) {
          Volume volume = _dbClient.queryObject(Volume.class, descriptor.getVolumeURI());
          volume.setInactive(true);
          _dbClient.persistObject(volume);
        }
      }
    }

    return taskList;
  }