public static Optional<AllocatableResources> from(ClusterResources wantedResources,
                                                      ApplicationId applicationId,
                                                      ClusterSpec clusterSpec,
                                                      Limits applicationLimits,
                                                      List<NodeResources> availableRealHostResources,
                                                      ClusterModel model,
                                                      NodeRepository nodeRepository) {
        var systemLimits = nodeRepository.nodeResourceLimits();
        boolean exclusive = nodeRepository.exclusivity().allocation(clusterSpec);
        if (! exclusive) {
            // We decide resources: Add overhead to what we'll request (advertised) to make sure real becomes (at least) cappedNodeResources
            var allocatableResources = calculateAllocatableResources(wantedResources,
                                                                     nodeRepository,
                                                                     model.cloudAccount(),
                                                                     clusterSpec,
                                                                     applicationLimits,
                                                                     exclusive,
                                                                     true);

            var worstCaseRealResources = nodeRepository.resourcesCalculator().requestToReal(allocatableResources.advertisedResources,
                                                                                            model.cloudAccount(),
                                                                                            exclusive,
                                                                                            false);
            if ( ! systemLimits.isWithinRealLimits(worstCaseRealResources, clusterSpec)) {
                allocatableResources = calculateAllocatableResources(wantedResources,
                                                                     nodeRepository,
                                                                     model.cloudAccount(),
                                                                     clusterSpec,
                                                                     applicationLimits,
                                                                     exclusive,
                                                                     false);
            }

            if ( ! systemLimits.isWithinRealLimits(allocatableResources.realResources, clusterSpec))
                return Optional.empty();
            if ( ! anySatisfies(allocatableResources.realResources, availableRealHostResources))
                return Optional.empty();
            return Optional.of(allocatableResources);
        }
        else { // Return the cheapest flavor satisfying the requested resources, if any
            NodeResources cappedWantedResources = applicationLimits.cap(wantedResources.nodeResources());
            Optional<AllocatableResources> best = Optional.empty();
            Optional<AllocatableResources> bestDisregardingDiskLimit = Optional.empty();
            for (Flavor flavor : nodeRepository.flavors().getFlavors()) {
                // Flavor decide resources: Real resources are the worst case real resources we'll get if we ask for these advertised resources
                NodeResources advertisedResources = nodeRepository.resourcesCalculator().advertisedResourcesOf(flavor);
                NodeResources realResources = nodeRepository.resourcesCalculator().requestToReal(advertisedResources, model.cloudAccount(), exclusive, false);

                // Adjust where we don't need exact match to the flavor
                if (flavor.resources().storageType() == NodeResources.StorageType.remote) {
                    double diskGb = systemLimits.enlargeToLegal(cappedWantedResources, clusterSpec, exclusive, true).diskGb();
                    if (diskGb > applicationLimits.max().nodeResources().diskGb() || diskGb < applicationLimits.min().nodeResources().diskGb()) // TODO: Remove when disk limit is enforced
                        diskGb = systemLimits.enlargeToLegal(cappedWantedResources, clusterSpec, exclusive, false).diskGb();
                    advertisedResources = advertisedResources.withDiskGb(diskGb);
                    realResources = realResources.withDiskGb(diskGb);
                }
                if (flavor.resources().bandwidthGbps() >= advertisedResources.bandwidthGbps()) {
                    advertisedResources = advertisedResources.withBandwidthGbps(cappedWantedResources.bandwidthGbps());
                    realResources = realResources.withBandwidthGbps(cappedWantedResources.bandwidthGbps());
                }

                if ( ! between(applicationLimits.min().nodeResources(), applicationLimits.max().nodeResources(), advertisedResources)) continue;
                if ( ! systemLimits.isWithinRealLimits(realResources, clusterSpec)) continue;

                var candidate = new AllocatableResources(wantedResources.with(realResources),
                                                         advertisedResources,
                                                         wantedResources,
                                                         clusterSpec);

                if ( ! systemLimits.isWithinAdvertisedDiskLimits(advertisedResources, clusterSpec)) { // TODO: Remove when disk limit is enforced
                    if (bestDisregardingDiskLimit.isEmpty() || candidate.preferableTo(bestDisregardingDiskLimit.get(), model)) {
                        bestDisregardingDiskLimit = Optional.of(candidate);
                    }
                    continue;
                }
                if (best.isEmpty() || candidate.preferableTo(best.get(), model)) {
                    best = Optional.of(candidate);
                }
            }
            if (best.isEmpty())
                best = bestDisregardingDiskLimit;
            return best;
        }
    }
The estimated cost of changing from the given current resources to this.
node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableResources.java