void PreferredLocationConstraint::GetTargetNodes(
    TempSolution const& tempSolution,
    PlacementReplica const* replica,
    NodeSet & candidateNodes,
    NodeToConstraintDiagnosticsDataMapSPtr const nodeToConstraintDiagnosticsDataMapSPtr) const
{
    UNREFERENCED_PARAMETER(nodeToConstraintDiagnosticsDataMapSPtr);

    PartitionEntry const* partition = replica->Partition;
    if (partition->Service->OnEveryNode)
    {
        return;
    }

    bool hasPreferredLocations = partition->ExistsUpgradeLocation || !partition->StandByLocations.empty();

    set<Common::TreeNodeIndex> const& upgradedUDs = PreferredLocationConstraint::GetUpgradedUDs(tempSolution, partition);
    bool hasUpgradCompletedUDs = !upgradedUDs.empty();

    if (replica->IsNew && !hasPreferredLocations && !hasUpgradCompletedUDs)
    {
        return;
    }

    // Use prefer locations if exist, otherwise, use completed UDs
    NodeSet nodesUpgradedUDs(candidateNodes);
    if (!hasPreferredLocations && hasUpgradCompletedUDs)
    {
        // Only prefer upgraded nodes if SB/Upgrade location is empty
        nodesUpgradedUDs.Filter([&](NodeEntry const *node) -> bool
        {
            return CheckUpgradedUDs(upgradedUDs, node);
        });
    }

    candidateNodes.Intersect([=](function<void(NodeEntry const *)> f)
    {
        // During singleton replica upgrade, there could be replicas which are not in upgrade,
        // but are correlated to the ones that are in upgrade (affinity or scaleout), 
        // and need to be moved together with replicas which are in upgrade
        if ((partition->IsInUpgrade || partition->IsInSingletonReplicaUpgradeOptimization()) && partition->ExistsUpgradeLocation)
        {
            if (partition->PrimaryUpgradeLocation)
            {
                f(partition->PrimaryUpgradeLocation);
            }
            else
            {
                for (auto it = partition->SecondaryUpgradeLocations.begin(); it != partition->SecondaryUpgradeLocations.end(); ++it)
                {
                    f(*it);
                }
            }
        }

        for (auto itNode = partition->StandByLocations.begin(); itNode != partition->StandByLocations.end(); ++itNode)
        {
            f(*itNode);
        }

        partition->ForEachExistingReplica([&f](PlacementReplica const* r)
        {
            f(r->Node);
        }, true, false);

    });

    if (!hasPreferredLocations && hasUpgradCompletedUDs)
    {
        candidateNodes.Union(nodesUpgradedUDs);
    }
}
void PreferredLocationSubspace::GetNodesForReplicaDrop(
    TempSolution const& tempSolution,
    PartitionEntry const& partition,
    NodeSet & candidateNodes) const
{
    // Number of elements in these vectors will be at most equal to number of replicas.
    // In general case, that is orders of magnitude smaller than number of nodes in the cluster.
    // Using std::vector, NodeSet::Intersect will have smaller complexity than if we used NodeSet here.
    std::vector<NodeEntry const*> blocklistedAndMoveInProgressNodes;
    std::vector<NodeEntry const*> moveInProgressReplicasNodes;
    std::vector<NodeEntry const*> blocklistedNodesWithReplicas;
    std::vector<NodeEntry const*> fdNodesWithMoreThanQuorumReplicas;
    std::vector<NodeEntry const*> udNodesWithMoreThanQuorumReplicas;

    ServiceEntry const* service = partition.Service;

    // Active replicas marked with MoveInProgress should be prioritized for dropping
    partition.ForEachExistingReplica([&](PlacementReplica const* r)
    {
        NodeEntry const* node = tempSolution.GetReplicaLocation(r);

        if (nullptr == node)
        {
            return;
        }

        if (r->IsMoveInProgress && service->IsNodeInBlockList(node))
        {
            blocklistedAndMoveInProgressNodes.push_back(node);
        }
        else if (r->IsMoveInProgress)
        {
            moveInProgressReplicasNodes.push_back(node);
        }
        else if (service->IsNodeInBlockList(node))
        {
            blocklistedNodesWithReplicas.push_back(node);
        }
        else if (!service->OnEveryNode)
        {
            auto& settings = tempSolution.OriginalPlacement->Settings;

            if (settings.FaultDomainEnabled &&
                settings.FaultDomainConstraintPriority != -1 &&
                settings.QuorumBasedReplicaDistributionPerFaultDomains &&
                service->FDDistribution != Service::Type::Ignore &&
                !tempSolution.OriginalPlacement->BalanceCheckerObj->FaultDomainStructure.IsEmpty)
            {
                if (IsNodeInOverloadedDomain(tempSolution, r->Partition, node, true))
                {
                    fdNodesWithMoreThanQuorumReplicas.push_back(node);
                }
            }

            if (settings.UpgradeDomainEnabled &&
                settings.UpgradeDomainConstraintPriority != -1 &&
                settings.QuorumBasedReplicaDistributionPerUpgradeDomains &&
                !tempSolution.OriginalPlacement->BalanceCheckerObj->UpgradeDomainStructure.IsEmpty)
            {
                if (IsNodeInOverloadedDomain(tempSolution, r->Partition, node, false))
                {
                    udNodesWithMoreThanQuorumReplicas.push_back(node);
                }
            }
        }
    }, false, true);

    if (!blocklistedAndMoveInProgressNodes.empty())
    {
        candidateNodes.Intersect([&](function<void(NodeEntry const *)> f)
        {
            for (auto itNode = blocklistedAndMoveInProgressNodes.begin(); itNode != blocklistedAndMoveInProgressNodes.end(); ++itNode)
            {
                f(*itNode);
            }
        });
    }
    else if (!moveInProgressReplicasNodes.empty())
    {
        candidateNodes.Intersect([&](function<void(NodeEntry const *)> f)
        {
            for (auto itNode = moveInProgressReplicasNodes.begin(); itNode != moveInProgressReplicasNodes.end(); ++itNode)
            {
                f(*itNode);
            }
        });
    }
    else if (!blocklistedNodesWithReplicas.empty())
    {
        candidateNodes.Intersect([&](function<void(NodeEntry const *)> f)
        {
            for (auto itNode = blocklistedNodesWithReplicas.begin(); itNode != blocklistedNodesWithReplicas.end(); ++itNode)
            {
                f(*itNode);
            }
        });
    }
    else if (!udNodesWithMoreThanQuorumReplicas.empty())
    {
        candidateNodes.Intersect([&](function<void(NodeEntry const *)> f)
        {
            for (auto itNode = udNodesWithMoreThanQuorumReplicas.begin(); itNode != udNodesWithMoreThanQuorumReplicas.end(); ++itNode)
            {
                f(*itNode);
            }
        });
    }
    else if (!fdNodesWithMoreThanQuorumReplicas.empty())
    {
        candidateNodes.Intersect([&](function<void(NodeEntry const *)> f)
        {
            for (auto itNode = fdNodesWithMoreThanQuorumReplicas.begin(); itNode != fdNodesWithMoreThanQuorumReplicas.end(); ++itNode)
            {
                f(*itNode);
            }
        });
    }
}