/**
   * Re-validate the ExportMask
   *
   * <p>This is required to be done as the ExportMask gets updated by reading the cinder export
   * volume response.
   *
   * @param varrayURI
   * @param initiatorPortMap
   * @param mask
   * @param invalidMasks
   * @param directorToInitiatorIds
   * @param idToInitiatorMap
   * @param dbClient
   * @param portWwnToClusterMap
   */
  public void updateZoningMapAndvalidateExportMask(
      URI varrayURI,
      Map<URI, List<StoragePort>> initiatorPortMap,
      URI exportMaskURI,
      Map<String, Set<String>> directorToInitiatorIds,
      Map<String, Initiator> idToInitiatorMap,
      Map<String, String> portWwnToClusterMap,
      StorageSystem vplex,
      StorageSystem array,
      String clusterId,
      String stepId) {

    try {
      WorkflowStepCompleter.stepExecuting(stepId);
      // Export Mask is updated, read it from DB
      ExportMask exportMask = _dbClient.queryObject(ExportMask.class, exportMaskURI);

      // First step would be to update the zoning map based on the connectivity
      updateZoningMap(initiatorPortMap, directorToInitiatorIds, exportMask);

      boolean passed =
          VPlexBackEndOrchestratorUtil.validateExportMask(
              varrayURI,
              initiatorPortMap,
              exportMask,
              null,
              directorToInitiatorIds,
              idToInitiatorMap,
              _dbClient,
              portWwnToClusterMap);

      if (!passed) {
        // Mark this mask as inactive, so that we dont pick it in the next iteration
        exportMask.setInactive(Boolean.TRUE);
        _dbClient.persistObject(exportMask);

        _log.error("Export Mask is not suitable for VPLEX to backend storage system");
        WorkflowStepCompleter.stepFailed(
            stepId,
            VPlexApiException.exceptions.couldNotFindValidArrayExportMask(
                vplex.getNativeGuid(), array.getNativeGuid(), clusterId));
        throw VPlexApiException.exceptions.couldNotFindValidArrayExportMask(
            vplex.getNativeGuid(), array.getNativeGuid(), clusterId);
      }

      WorkflowStepCompleter.stepSucceded(stepId);

    } catch (Exception ex) {
      _log.error("Failed to validate export mask for cinder: ", ex);
      VPlexApiException vplexex =
          DeviceControllerExceptions.vplex.failedToValidateExportMask(exportMaskURI.toString(), ex);
      WorkflowStepCompleter.stepFailed(stepId, vplexex);
    }
  }
  /**
   * Checks the UnManaged Volume's policy with vPool's policy.
   *
   * @param vPool the vPool
   * @param autoTierPolicyId the auto tier policy id on unmanaged volume
   * @param system the system
   * @return true, if matching, false otherwise
   */
  public static boolean checkVPoolValidForUnManagedVolumeAutoTieringPolicy(
      VirtualPool vPool, String autoTierPolicyId, StorageSystem system) {

    _log.debug("Policy Id: {}, vPool: {}", autoTierPolicyId, vPool);
    boolean policyMatching = false;
    String policyIdfromVPool = vPool.getAutoTierPolicyName();
    if (autoTierPolicyId != null) {
      if (policyIdfromVPool != null) {
        if (vPool.getUniquePolicyNames()
            || DiscoveredDataObject.Type.vnxblock.name().equalsIgnoreCase(system.getSystemType())) {
          // Unique Policy names field will not be set for VNX. vPool will have policy name, not the
          // policy's nativeGuid
          policyIdfromVPool =
              NativeGUIDGenerator.generateAutoTierPolicyNativeGuid(
                  system.getNativeGuid(),
                  policyIdfromVPool,
                  NativeGUIDGenerator.getTieringPolicyKeyForSystem(system));
          _log.debug("Policy Id generated: {}", policyIdfromVPool);
        }
        if (autoTierPolicyId.equalsIgnoreCase(policyIdfromVPool)) {
          policyMatching = true;
        }
      }
    } else if ((policyIdfromVPool == null) || (policyIdfromVPool.equalsIgnoreCase("none"))) {
      // if policy is not set in both unmanaged volume and vPool. Note
      // that the value in the vpool could be set to "none".
      policyMatching = true;
    }

    // Default policy for VNX - match volume with default policy to vPool with no policy as well
    if (!policyMatching
        && DiscoveredDataObject.Type.vnxblock.name().equalsIgnoreCase(system.getSystemType())) {
      if (autoTierPolicyId != null
          && autoTierPolicyId.contains(VnxFastPolicy.DEFAULT_START_HIGH_THEN_AUTOTIER.name())
          && policyIdfromVPool == null) {
        policyMatching = true;
      }
    }

    // Default policy for HDS - match volume with default policy to vPool with no policy as well
    if (!policyMatching
        && DiscoveredDataObject.Type.hds.name().equalsIgnoreCase(system.getSystemType())) {
      if (autoTierPolicyId != null
          && autoTierPolicyId.contains(HitachiTieringPolicy.All.name())
          && policyIdfromVPool == null) {
        policyMatching = true;
      }
    }

    return policyMatching;
  }
  /**
   * Filters supported vPools in UnManaged Volume based on Auto-Tiering Policy.
   *
   * @param unManagedVolume the UnManaged volume
   * @param policyName the policy name associated with UnManaged volume
   * @param system the system
   * @param dbClient the db client
   */
  public static void filterSupportedVpoolsBasedOnTieringPolicy(
      UnManagedVolume unManagedVolume, String policyName, StorageSystem system, DbClient dbClient) {

    StringSetMap unManagedVolumeInformation = unManagedVolume.getVolumeInformation();
    StringSet supportedVpoolURIs =
        unManagedVolumeInformation.get(SupportedVolumeInformation.SUPPORTED_VPOOL_LIST.toString());
    List<String> vPoolsToRemove = new ArrayList<String>();
    if (supportedVpoolURIs != null) {
      Iterator<String> itr = supportedVpoolURIs.iterator();
      while (itr.hasNext()) {
        String uri = itr.next();
        VirtualPool vPool = dbClient.queryObject(VirtualPool.class, URI.create(uri));
        if (vPool != null && !vPool.getInactive()) {
          // generate unmanaged volume's policyId
          String autoTierPolicyId =
              NativeGUIDGenerator.generateAutoTierPolicyNativeGuid(
                  system.getNativeGuid(),
                  policyName,
                  NativeGUIDGenerator.getTieringPolicyKeyForSystem(system));
          if (!checkVPoolValidForUnManagedVolumeAutoTieringPolicy(
              vPool, autoTierPolicyId, system)) {
            String msg =
                "Removing vPool %s from SUPPORTED_VPOOL_LIST in UnManagedVolume %s "
                    + "since Auto-tiering Policy %s in UnManaged Volume does not match with vPool's (%s)";
            _log.info(
                String.format(
                    msg,
                    new Object[] {
                      uri, unManagedVolume.getId(), autoTierPolicyId, vPool.getAutoTierPolicyName()
                    }));
            vPoolsToRemove.add(uri);
          }
        } else {
          // remove Inactive vPool URI
          vPoolsToRemove.add(uri);
        }
      }
    }
    for (String uri : vPoolsToRemove) { // UnManagedVolume object is persisted by caller
      supportedVpoolURIs.remove(uri);
    }
  }
  /**
   * Discovers the RP CGs and all the volumes therein. It updates/creates the UnManagedProtectionSet
   * objects and updates (if it exists) the UnManagedVolume objects with RP information needed for
   * ingestion
   *
   * @param accessProfile access profile
   * @param dbClient db client
   * @param partitionManager partition manager
   * @throws Exception
   */
  public void discoverUnManagedObjects(
      AccessProfile accessProfile, DbClient dbClient, PartitionManager partitionManager)
      throws Exception {

    this.partitionManager = partitionManager;

    log.info("Started discovery of UnManagedVolumes for system {}", accessProfile.getSystemId());
    ProtectionSystem protectionSystem =
        dbClient.queryObject(ProtectionSystem.class, accessProfile.getSystemId());
    if (protectionSystem == null) {
      log.error(
          "Discovery is not run!  Protection System not found: " + accessProfile.getSystemId());
      return;
    }

    RecoverPointClient rp = RPHelper.getRecoverPointClient(protectionSystem);

    unManagedCGsInsert = new ArrayList<UnManagedProtectionSet>();
    unManagedCGsUpdate = new ArrayList<UnManagedProtectionSet>();
    unManagedVolumesToDelete = new ArrayList<UnManagedVolume>();
    unManagedVolumesToUpdateByWwn = new HashMap<String, UnManagedVolume>();
    unManagedCGsReturnedFromProvider = new HashSet<URI>();

    // Get all of the consistency groups (and their volumes) from RP
    Set<GetCGsResponse> cgs = rp.getAllCGs();

    if (cgs == null) {
      log.warn("No CGs were found on protection system: " + protectionSystem.getLabel());
      return;
    }

    // This section of code allows us to cache XIO native GUID to workaround an issue
    // with RP's understanding of XIO volume WWNs (128-bit) and the rest of the world's
    // understanding of the XIO volume WWN once it's exported (64-bit)
    Map<String, String> rpWwnToNativeWwn = new HashMap<String, String>();
    List<URI> storageSystemIds = dbClient.queryByType(StorageSystem.class, true);
    List<String> storageNativeIdPrefixes = new ArrayList<String>();
    if (storageSystemIds != null) {
      Iterator<StorageSystem> storageSystemsItr =
          dbClient.queryIterativeObjects(StorageSystem.class, storageSystemIds);
      while (storageSystemsItr.hasNext()) {
        StorageSystem storageSystem = storageSystemsItr.next();
        if (storageSystem.getSystemType().equalsIgnoreCase(Type.xtremio.name())) {
          storageNativeIdPrefixes.add(storageSystem.getNativeGuid());
        }
      }
    }

    for (GetCGsResponse cg : cgs) {
      try {
        log.info("Processing returned CG: " + cg.getCgName());
        boolean newCG = false;

        // UnManagedProtectionSet native GUID is protection system GUID + consistency group ID
        String nativeGuid = protectionSystem.getNativeGuid() + Constants.PLUS + cg.getCgId();

        // First check to see if this protection set is already part of our managed DB
        if (null != DiscoveryUtils.checkProtectionSetExistsInDB(dbClient, nativeGuid)) {
          log.info(
              "Protection Set "
                  + nativeGuid
                  + " already is managed by ViPR, skipping unmanaged discovery");
          continue;
        }

        // Now check to see if the unmanaged CG exists in the database
        UnManagedProtectionSet unManagedProtectionSet =
            DiscoveryUtils.checkUnManagedProtectionSetExistsInDB(dbClient, nativeGuid);

        if (null == unManagedProtectionSet) {
          log.info("Creating new unmanaged protection set for CG: " + cg.getCgName());
          unManagedProtectionSet = new UnManagedProtectionSet();
          unManagedProtectionSet.setId(URIUtil.createId(UnManagedProtectionSet.class));
          unManagedProtectionSet.setNativeGuid(nativeGuid);
          unManagedProtectionSet.setProtectionSystemUri(protectionSystem.getId());

          StringSet protectionId = new StringSet();
          protectionId.add("" + cg.getCgId());
          unManagedProtectionSet.putCGInfo(
              SupportedCGInformation.PROTECTION_ID.toString(), protectionId);

          // Default MP to false until proven otherwise
          unManagedProtectionSet
              .getCGCharacteristics()
              .put(
                  UnManagedProtectionSet.SupportedCGCharacteristics.IS_MP.name(),
                  Boolean.FALSE.toString());

          newCG = true;
        } else {
          log.info(
              "Found existing unmanaged protection set for CG: "
                  + cg.getCgName()
                  + ", using "
                  + unManagedProtectionSet.getId().toString());
        }

        unManagedCGsReturnedFromProvider.add(unManagedProtectionSet.getId());

        // Update the fields for the CG
        unManagedProtectionSet.setCgName(cg.getCgName());
        unManagedProtectionSet.setLabel(cg.getCgName());

        // Indicate whether the CG is in a healthy state or not to ingest.
        unManagedProtectionSet
            .getCGCharacteristics()
            .put(
                UnManagedProtectionSet.SupportedCGCharacteristics.IS_HEALTHY.name(),
                cg.getCgState().equals(GetCGStateResponse.HEALTHY)
                    ? Boolean.TRUE.toString()
                    : Boolean.FALSE.toString());

        // Indicate whether the CG is sync or async
        unManagedProtectionSet
            .getCGCharacteristics()
            .put(
                UnManagedProtectionSet.SupportedCGCharacteristics.IS_SYNC.name(),
                cg.getCgPolicy().synchronous ? Boolean.TRUE.toString() : Boolean.FALSE.toString());

        // Fill in RPO type and value information
        StringSet rpoType = new StringSet();
        rpoType.add(cg.getCgPolicy().rpoType);
        unManagedProtectionSet.putCGInfo(SupportedCGInformation.RPO_TYPE.toString(), rpoType);

        StringSet rpoValue = new StringSet();
        rpoValue.add(cg.getCgPolicy().rpoValue.toString());
        unManagedProtectionSet.putCGInfo(SupportedCGInformation.RPO_VALUE.toString(), rpoValue);

        if (null == cg.getCopies()) {
          log.info("Protection Set " + nativeGuid + " does not contain any copies.  Skipping...");
          continue;
        }
        if (null == cg.getRsets()) {
          log.info(
              "Protection Set "
                  + nativeGuid
                  + " does not contain any replication sets.  Skipping...");
          continue;
        }

        // clean up the existing journal and replicationsets info in the unmanaged protection set,
        // so that updated info is populated
        if (!newCG) {
          cleanUpUnManagedResources(
              unManagedProtectionSet, unManagedVolumesToUpdateByWwn, dbClient);
        }

        // Now map UnManagedVolume objects to the journal and rset (sources/targets) and put RP
        // fields in them
        Map<String, String> rpCopyAccessStateMap = new HashMap<String, String>();

        mapCgJournals(
            unManagedProtectionSet,
            cg,
            rpCopyAccessStateMap,
            rpWwnToNativeWwn,
            storageNativeIdPrefixes,
            dbClient);

        mapCgSourceAndTargets(
            unManagedProtectionSet,
            cg,
            rpCopyAccessStateMap,
            rpWwnToNativeWwn,
            storageNativeIdPrefixes,
            dbClient);

        if (newCG) {
          unManagedCGsInsert.add(unManagedProtectionSet);
        } else {
          unManagedCGsUpdate.add(unManagedProtectionSet);
        }
      } catch (Exception ex) {
        log.error("Error processing RP CG {}", cg.getCgName(), ex);
      }
    }

    handlePersistence(dbClient, false);
    cleanUp(protectionSystem, dbClient);
  }