private void addUpdateInstances(String dcName, Map<String, Set<String>> partitionsToInstancesInDc,
      boolean resourcesAreFullAutoCompatible) {
    ClusterMapConfig config = getClusterMapConfig(clusterName, dcName, null);
    String zkConnectStr = dataCenterToZkAddress.get(dcName).getZkConnectStrs().get(0);
    try (PropertyStoreToDataNodeConfigAdapter propertyStoreAdapter = new PropertyStoreToDataNodeConfigAdapter(
        zkConnectStr, config)) {
      InstanceConfigToDataNodeConfigAdapter.Converter instanceConfigConverter =
          new InstanceConfigToDataNodeConfigAdapter.Converter(config);
      info("[{}] Getting list of instances in {}", dcName.toUpperCase(), dcName);
      Set<String> instancesInHelix = new HashSet<>(getInstanceNamesInHelix(dcName, propertyStoreAdapter));
      Set<String> instancesInStatic = dcToInstanceNameToDataNodeId.get(dcName) == null ? new HashSet<>()
          : new HashSet<>(dcToInstanceNameToDataNodeId.get(dcName).keySet());
      Set<String> instancesInBoth = new HashSet<>(instancesInHelix);
      // set instances in both correctly.
      instancesInBoth.retainAll(instancesInStatic);
      // set instances in Helix only correctly.
      instancesInHelix.removeAll(instancesInBoth);
      // set instances in Static only correctly.
      instancesInStatic.removeAll(instancesInBoth);
      int totalInstances = instancesInBoth.size() + instancesInHelix.size() + instancesInStatic.size();
      for (String instanceName : instancesInBoth) {
        DataNodeConfig nodeConfigFromHelix =
            getDataNodeConfigFromHelix(clusterName, instanceName, propertyStoreAdapter, instanceConfigConverter,
                adminForDc.get(dcName), dataNodeConfigSourceType);
        DataNodeConfig nodeConfigFromStatic =
            createDataNodeConfigFromStatic(dcName, instanceName, nodeConfigFromHelix, partitionsToInstancesInDc,
                instanceConfigConverter);
        if (isInstanceInFullAutoResources(instanceName, dcName, resourcesAreFullAutoCompatible)) {
          info("Instance {} in dc {} is in FULL AUTO resources, ignore this instance", instanceName, dcName);
          continue;
        }
        if (!nodeConfigFromStatic.equals(nodeConfigFromHelix, !overrideReplicaStatus)) {
          if (helixAdminOperation == HelixAdminOperation.BootstrapCluster) {
            if (!dryRun) {
              info(
                  "[{}] Instance {} already present in Helix {}, but config has changed, updating. Remaining instances: {}",
                  dcName.toUpperCase(), instanceName, dataNodeConfigSourceType.name(), --totalInstances);
              // Continuing on the note above, if there is indeed a change, we must make a call on whether RO/RW, replica
              // availability and so on should be updated at all (if not, nodeConfigFromStatic should be replaced with
              // the appropriate dataNodeConfig that is constructed with the correct values from both).
              // For now, only bootstrapping cluster is allowed to directly change DataNodeConfig
              setDataNodeConfigInHelix(dcName, instanceName, nodeConfigFromStatic, propertyStoreAdapter,
                  instanceConfigConverter);
            } else {
              info(
                  "[{}] Instance {} already present in Helix {}, but config has changed, no action as dry run. Remaining instances: {}",
                  dcName.toUpperCase(), instanceName, dataNodeConfigSourceType.name(), --totalInstances);
              logger.debug("[{}] Previous config: {} \n New config: {}", dcName.toUpperCase(), nodeConfigFromHelix,
                  nodeConfigFromStatic);
            }
            // for dryRun, we update counter but don't really change the DataNodeConfig in Helix
            instancesUpdated.getAndIncrement();
          }
        } else {
          if (!dryRun) {
            info("[{}] Instance {} already present in Helix {}, with same Data, skipping. Remaining instances: {}",
                dcName.toUpperCase(), instanceName, dataNodeConfigSourceType.name(), --totalInstances);
          }
        }
      }

      for (String instanceName : instancesInStatic) {
        DataNodeConfig nodeConfigFromStatic =
            createDataNodeConfigFromStatic(dcName, instanceName, null, partitionsToInstancesInDc,
                instanceConfigConverter);
        if (dataNodeHasPartitionsInFullAutoResources(instanceName, dcName, resourcesAreFullAutoCompatible)) {
          info("Instance {} in dc {} is in FULL AUTO resources, ignore this instance", instanceName, dcName);
          continue;
        }
        info("[{}] Instance {} is new, {}. Remaining instances: {}", dcName.toUpperCase(), instanceName,
            dryRun ? "no action as dry run" : "adding to Helix " + dataNodeConfigSourceType.name(), --totalInstances);
        // Note: if we want to move replica to new instance (not present in cluster yet), we can prepare a transient
        // clustermap in which we keep existing replicas and add new replicas/instances. We should be able to upgrade cluster
        // normally (update both datanode configs and IdealState). Helix controller will notify new instance to perform
        // replica addition.
        if (helixAdminOperation == HelixAdminOperation.BootstrapCluster) {
          if (!dryRun) {
            addDataNodeConfigToHelix(dcName, nodeConfigFromStatic, propertyStoreAdapter, instanceConfigConverter);
          }
          instancesAdded.getAndIncrement();
        }
      }

      for (String instanceName : instancesInHelix) {
        if (isInstanceInFullAutoResources(instanceName, dcName, resourcesAreFullAutoCompatible)) {
          info("Instance {} in dc {} is in FULL AUTO resources, ignore this instance", instanceName, dcName);
          continue;
        }
        if (forceRemove) {
          info("[{}] Instance {} is in Helix {}, but not in static. {}. Remaining instances: {}", dcName.toUpperCase(),
              instanceName, dataNodeConfigSourceType.name(), dryRun ? "No action as dry run" : "Forcefully removing",
              --totalInstances);
          if (helixAdminOperation == HelixAdminOperation.BootstrapCluster) {
            if (!dryRun) {
              removeDataNodeConfigFromHelix(dcName, instanceName, propertyStoreAdapter);
            }
            instancesDropped.getAndIncrement();
          }
        } else {
          info(
              "[{}] Instance {} is in Helix {}, but not in static. Ignoring for now (use --forceRemove to forcefully remove). "
                  + "Remaining instances: {}", dcName.toUpperCase(), instanceName, dataNodeConfigSourceType.name(),
              --totalInstances);
          expectMoreInHelixDuringValidate = true;
          instancesNotForceRemovedByDc.computeIfAbsent(dcName, k -> ConcurrentHashMap.newKeySet()).add(instanceName);
        }
      }
    }
    info(
        "[{}] Done adding all instances in {}, now scanning resources in Helix and ensuring instance set for partitions are the same.",
        dcName.toUpperCase(), dcName);
  }
Add and/or update instances in Helix based on the information in the static cluster map.
@param dcName the name of the datacenter being processed.
@param partitionsToInstancesInDc a map to be filled with the mapping of partitions to their instance sets in the
                                 given datacenter.
@param resourcesAreFullAutoCompatible true if the resources are constructed as full auto compatible
ambry-clustermap/src/main/java/com/github/ambry/clustermap/HelixBootstrapUpgradeUtil.java