diff options
author | Valerij Fredriksen <valerijf@verizonmedia.com> | 2019-02-26 16:54:22 +0100 |
---|---|---|
committer | Valerij Fredriksen <valerijf@verizonmedia.com> | 2019-03-08 09:35:34 +0100 |
commit | b85e50a93d0af8537898526f93f1278eda3dfcca (patch) | |
tree | eb58606427e9e5b66a351b12a3f91d4c02a3ea58 /config-provisioning/src/main/java/com/yahoo/config/provision | |
parent | 85e978f6c57edafb6d24c4a9f47ae9760dc65c53 (diff) |
Make Flavor interface
Diffstat (limited to 'config-provisioning/src/main/java/com/yahoo/config/provision')
4 files changed, 319 insertions, 182 deletions
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Flavor.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Flavor.java index 79a17c23dd7..e2c39d6efda 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/Flavor.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Flavor.java @@ -1,93 +1,17 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.config.provision; -import com.google.common.collect.ImmutableList; -import com.yahoo.config.provisioning.FlavorsConfig; - -import java.util.ArrayList; import java.util.List; /** - * A host flavor (type). This is a value object where the identity is the name. - * Use {@link NodeFlavors} to create a flavor. + * A host flavor (type). * * @author bratseth */ -public class Flavor { - - private final String name; - private final int cost; - private final boolean isStock; - private final Type type; - private final double minCpuCores; - private final double minMainMemoryAvailableGb; - private final double minDiskAvailableGb; - private final boolean fastDisk; - private final double bandwidth; - private final String description; - private final boolean retired; - private List<Flavor> replacesFlavors; - private int idealHeadroom; // Note: Not used after Vespa 6.282 - - /** - * Creates a Flavor, but does not set the replacesFlavors. - * @param flavorConfig config to be used for Flavor. - */ - public Flavor(FlavorsConfig.Flavor flavorConfig) { - this.name = flavorConfig.name(); - this.replacesFlavors = new ArrayList<>(); - this.cost = flavorConfig.cost(); - this.isStock = flavorConfig.stock(); - this.type = Type.valueOf(flavorConfig.environment()); - this.minCpuCores = flavorConfig.minCpuCores(); - this.minMainMemoryAvailableGb = flavorConfig.minMainMemoryAvailableGb(); - this.minDiskAvailableGb = flavorConfig.minDiskAvailableGb(); - this.fastDisk = flavorConfig.fastDisk(); - this.bandwidth = flavorConfig.bandwidth(); - this.description = flavorConfig.description(); - this.retired = flavorConfig.retired(); - this.idealHeadroom = flavorConfig.idealHeadroom(); - } - - /** Returns the unique identity of this flavor */ - public String name() { return name; } - - /** - * Get the monthly cost (total cost of ownership) in USD for this flavor, typically total cost - * divided by 36 months. - * - * @return monthly cost in USD - */ - public int cost() { return cost; } - - public boolean isStock() { return isStock; } - - public double getMinMainMemoryAvailableGb() { return minMainMemoryAvailableGb; } +public interface Flavor { - public double getMinDiskAvailableGb() { return minDiskAvailableGb; } - - public boolean hasFastDisk() { return fastDisk; } - - public double getBandwidth() { return bandwidth; } - - public double getMinCpuCores() { return minCpuCores; } - - public String getDescription() { return description; } - - /** Returns whether the flavor is retired */ - public boolean isRetired() { - return retired; - } - - public Type getType() { return type; } - - /** Convenience, returns getType() == Type.DOCKER_CONTAINER */ - public boolean isDocker() { return type == Type.DOCKER_CONTAINER; } - - /** The free capacity we would like to preserve for this flavor */ - public int getIdealHeadroom() { - return idealHeadroom; - } + /** @return the unique identity of this flavor */ + String flavorName(); /** * Returns the canonical name of this flavor - which is the name which should be used as an interface to users. @@ -102,69 +26,85 @@ public class Flavor { * replace the canonical name we want. However, if a node replaces multiple names, we have no basis for choosing one * of them as the canonical, so we return the current as canonical. */ - public String canonicalName() { - return isCanonical() ? name : replacesFlavors.get(0).canonicalName(); - } - - /** Returns whether this is a canonical flavor */ - public boolean isCanonical() { - return replacesFlavors.size() != 1; + default String canonicalName() { + return replaces().size() != 1 ? flavorName() : replaces().get(0).canonicalName(); } + /** @return the cost associated with usage of this flavor */ + int cost(); + /** - * The flavors this (directly) replaces. - * This is immutable if this is frozen, and a mutable list otherwise. + * A stock flavor is any flavor we expect more of in the future. + * Stock flavors are assigned to applications by cost priority. + * + * Non-stock flavors are used for nodes for which a fixed amount has already been added + * to the system for some historical reason. These nodes are assigned to applications + * when available by exact match and ignoring cost. */ - public List<Flavor> replaces() { return replacesFlavors; } + boolean isStock(); + + /** Returns whether the flavor is retired (should no longer be allocated) */ + boolean isRetired(); /** * Returns whether this flavor satisfies the requested flavor, either directly * (by being the same), or by directly or indirectly replacing it */ - public boolean satisfies(Flavor flavor) { - if (this.equals(flavor)) { + default boolean satisfies(Flavor flavor) { + if (equals(flavor)) { return true; } - if (this.retired) { + if (isRetired()) { return false; } - for (Flavor replaces : replacesFlavors) + for (Flavor replaces : replaces()) if (replaces.satisfies(flavor)) return true; return false; } - /** Irreversibly freezes the content of this */ - public void freeze() { - replacesFlavors = ImmutableList.copyOf(replacesFlavors); - } - - /** Returns whether this flavor has at least as much as each hardware resource as the given flavor */ - public boolean isLargerThan(Flavor other) { - return this.minCpuCores >= other.minCpuCores && - this.minDiskAvailableGb >= other.minDiskAvailableGb && - this.minMainMemoryAvailableGb >= other.minMainMemoryAvailableGb && - this.fastDisk || ! other.fastDisk; + Cpu cpu(); + + Memory memory(); + + Disk disk(); + + Bandwidth bandwidth(); + + Environment environment(); + + /** The flavors this (directly) replaces. */ + List<Flavor> replaces(); + + + interface Disk { + + /** @return Disk size in GB in base 10 (1GB = 10^9 bytes) */ + double sizeInBase10Gb(); + + /** @return Disk size in GB in base 2, also known as GiB (1GiB = 2^30 bytes), rounded to nearest integer value */ + default double sizeInBase2Gb() { + return Math.round(sizeInBase10Gb() / Math.pow(1.024, 3)); + } + + boolean isFast(); } - @Override - public int hashCode() { return name.hashCode(); } + interface Memory { + double sizeInGb(); + } - @Override - public boolean equals(Object other) { - if (other == this) return true; - if ( ! (other instanceof Flavor)) return false; - return ((Flavor)other).name.equals(this.name); + interface Cpu { + double cores(); } - @Override - public String toString() { return "flavor '" + name + "'"; } + interface Bandwidth { + double mbits(); + } - public enum Type { - undefined, // Default value in config (flavors.def) + enum Environment { BARE_METAL, VIRTUAL_MACHINE, DOCKER_CONTAINER } - } diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeFlavors.java b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeFlavors.java index e64028e216f..43ef5602d8a 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeFlavors.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeFlavors.java @@ -1,88 +1,30 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.config.provision; -import com.google.common.collect.ImmutableMap; -import com.google.inject.Inject; -import com.yahoo.config.provisioning.FlavorsConfig; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; /** * All the available node flavors. * - * @author bratseth + * @author freva */ -public class NodeFlavors { - - /** Flavors <b>which are configured</b> in this zone */ - private final ImmutableMap<String, Flavor> flavors; - - @Inject - public NodeFlavors(FlavorsConfig config) { - ImmutableMap.Builder<String, Flavor> b = new ImmutableMap.Builder<>(); - for (Flavor flavor : toFlavors(config)) - b.put(flavor.name(), flavor); - this.flavors = b.build(); - } +public interface NodeFlavors { - public List<Flavor> getFlavors() { - return new ArrayList<>(flavors.values()); - } + /** Returns list of all available flavors in the system */ + List<Flavor> getFlavors(); /** Returns a flavor by name, or empty if there is no flavor with this name. */ - public Optional<Flavor> getFlavor(String name) { - return Optional.ofNullable(flavors.get(name)); - } + Optional<Flavor> getFlavor(String name); /** Returns the flavor with the given name or throws an IllegalArgumentException if it does not exist */ - public Flavor getFlavorOrThrow(String flavorName) { + default Flavor getFlavorOrThrow(String flavorName) { return getFlavor(flavorName).orElseThrow(() -> new IllegalArgumentException("Unknown flavor '" + flavorName + "'. Flavors are " + canonicalFlavorNames())); } private List<String> canonicalFlavorNames() { - return flavors.values().stream().map(Flavor::canonicalName).distinct().sorted().collect(Collectors.toList()); + return getFlavors().stream().map(Flavor::canonicalName).distinct().sorted().collect(Collectors.toList()); } - - private static Collection<Flavor> toFlavors(FlavorsConfig config) { - Map<String, Flavor> flavors = new HashMap<>(); - // First pass, create all flavors, but do not include flavorReplacesConfig. - for (FlavorsConfig.Flavor flavorConfig : config.flavor()) { - flavors.put(flavorConfig.name(), new Flavor(flavorConfig)); - } - // Second pass, set flavorReplacesConfig to point to correct flavor. - for (FlavorsConfig.Flavor flavorConfig : config.flavor()) { - Flavor flavor = flavors.get(flavorConfig.name()); - for (FlavorsConfig.Flavor.Replaces flavorReplacesConfig : flavorConfig.replaces()) { - if (! flavors.containsKey(flavorReplacesConfig.name())) { - throw new IllegalStateException("Replaces for " + flavor.name() + - " pointing to a non existing flavor: " + flavorReplacesConfig.name()); - } - flavor.replaces().add(flavors.get(flavorReplacesConfig.name())); - } - flavor.freeze(); - } - // Third pass, ensure that retired flavors have a replacement - for (Flavor flavor : flavors.values()) { - if (flavor.isRetired() && !hasReplacement(flavors.values(), flavor)) { - throw new IllegalStateException( - String.format("Flavor '%s' is retired, but has no replacement", flavor.name()) - ); - } - } - return flavors.values(); - } - - private static boolean hasReplacement(Collection<Flavor> flavors, Flavor flavor) { - return flavors.stream() - .filter(f -> !f.equals(flavor)) - .anyMatch(f -> f.satisfies(flavor)); - } - } diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/internal/ConfigFlavor.java b/config-provisioning/src/main/java/com/yahoo/config/provision/internal/ConfigFlavor.java new file mode 100644 index 00000000000..e70476455cd --- /dev/null +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/internal/ConfigFlavor.java @@ -0,0 +1,172 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.config.provision.internal; + +import com.google.common.collect.ImmutableList; +import com.yahoo.config.provision.Flavor; +import com.yahoo.config.provisioning.FlavorsConfig; + +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link Flavor} generated from config. + * + * @author freva + */ +public class ConfigFlavor implements Flavor { + + private final String name; + private final int cost; + private final boolean isStock; + private final Environment environment; + private final Cpu cpu; + private final Memory memory; + private final Disk disk; + private final Bandwidth bandwidth; + private final boolean retired; + private List<Flavor> replacesFlavors; + + /** + * Creates a Flavor, but does not set the replacesFlavors. + * @param flavorConfig config to be used for Flavor. + */ + public ConfigFlavor(FlavorsConfig.Flavor flavorConfig) { + this.name = flavorConfig.name(); + this.replacesFlavors = new ArrayList<>(); + this.cost = flavorConfig.cost(); + this.isStock = flavorConfig.stock(); + this.environment = Environment.valueOf(flavorConfig.environment()); + this.cpu = new ConfigCpu(flavorConfig.cpu()); + this.memory = new ConfigMemory(flavorConfig.memory()); + this.disk = new ConfigDisk(flavorConfig.disk()); + this.bandwidth = new ConfigBandwidth(flavorConfig.bandwidth()); + this.retired = flavorConfig.retired(); + } + + @Override + public String flavorName() { + return name; + } + + @Override + public int cost() { + return cost; + } + + @Override + public boolean isStock() { + return isStock; + } + + @Override + public boolean isRetired() { + return retired; + } + + @Override + public Cpu cpu() { + return cpu; + } + + @Override + public Memory memory() { + return memory; + } + + @Override + public Disk disk() { + return disk; + } + + @Override + public Bandwidth bandwidth() { + return bandwidth; + } + + @Override + public Environment environment() { + return environment; + } + + @Override + public List<Flavor> replaces() { + return replacesFlavors; + } + + /** Irreversibly freezes the content of this */ + public void freeze() { + replacesFlavors = ImmutableList.copyOf(replacesFlavors); + } + + @Override + public int hashCode() { return name.hashCode(); } + + @Override + public boolean equals(Object other) { + if (other == this) return true; + if ( ! (other instanceof ConfigFlavor)) return false; + return ((ConfigFlavor)other).name.equals(this.name); + } + + @Override + public String toString() { return "flavor '" + name + "'"; } + + private class ConfigCpu implements Flavor.Cpu { + private final double cores; + + private ConfigCpu(FlavorsConfig.Flavor.Cpu cpu) { + this.cores = cpu.cores(); + } + + @Override + public double cores() { + return cores; + } + } + + private class ConfigMemory implements Flavor.Memory { + private final double sizeInGb; + + private ConfigMemory(FlavorsConfig.Flavor.Memory memory) { + this.sizeInGb = memory.sizeInGb(); + } + + @Override + public double sizeInGb() { + return sizeInGb; + } + } + + private class ConfigDisk implements Flavor.Disk { + private final double sizeInBase10; + private final boolean isFast; + + private ConfigDisk(FlavorsConfig.Flavor.Disk disk) { + this.sizeInBase10 = disk.sizeInGb(); + this.isFast = disk.fast(); + } + + @Override + public double sizeInBase10Gb() { + return sizeInBase10; + } + + @Override + public boolean isFast() { + return isFast; + } + } + + private class ConfigBandwidth implements Flavor.Bandwidth { + private final double mbits; + + private ConfigBandwidth(FlavorsConfig.Flavor.Bandwidth bandwidth) { + this.mbits = bandwidth.mbits(); + } + + @Override + public double mbits() { + return mbits; + } + } +} diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/internal/ConfigNodeFlavors.java b/config-provisioning/src/main/java/com/yahoo/config/provision/internal/ConfigNodeFlavors.java new file mode 100644 index 00000000000..c67a3faf36f --- /dev/null +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/internal/ConfigNodeFlavors.java @@ -0,0 +1,83 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.config.provision.internal; + +import com.google.common.collect.ImmutableMap; +import com.google.inject.Inject; +import com.yahoo.config.provision.Flavor; +import com.yahoo.config.provision.NodeFlavors; +import com.yahoo.config.provisioning.FlavorsConfig; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * {@link NodeFlavors} generated from config + * + * @author bratseth + */ +public class ConfigNodeFlavors implements NodeFlavors { + + /** Flavors <b>which are configured</b> in this zone */ + private final Map<String, Flavor> flavors; + + @Inject + public ConfigNodeFlavors(FlavorsConfig config) { + this(toFlavors(config)); + } + + public ConfigNodeFlavors(Collection<Flavor> flavors) { + ImmutableMap.Builder<String, Flavor> b = new ImmutableMap.Builder<>(); + for (Flavor flavor : flavors) + b.put(flavor.flavorName(), flavor); + this.flavors = b.build(); + } + + public List<Flavor> getFlavors() { + return new ArrayList<>(flavors.values()); + } + + /** Returns a flavor by name, or empty if there is no flavor with this name. */ + public Optional<Flavor> getFlavor(String name) { + return Optional.ofNullable(flavors.get(name)); + } + + private static Collection<Flavor> toFlavors(FlavorsConfig config) { + Map<String, Flavor> flavors = new HashMap<>(); + // First pass, create all flavors, but do not include flavorReplacesConfig. + for (FlavorsConfig.Flavor flavorConfig : config.flavor()) { + flavors.put(flavorConfig.name(), new ConfigFlavor(flavorConfig)); + } + // Second pass, set flavorReplacesConfig to point to correct flavor. + for (FlavorsConfig.Flavor flavorConfig : config.flavor()) { + Flavor flavor = flavors.get(flavorConfig.name()); + for (FlavorsConfig.Flavor.Replaces flavorReplacesConfig : flavorConfig.replaces()) { + if (! flavors.containsKey(flavorReplacesConfig.name())) { + throw new IllegalStateException("Replaces for " + flavor.flavorName() + + " pointing to a non existing flavor: " + flavorReplacesConfig.name()); + } + flavor.replaces().add(flavors.get(flavorReplacesConfig.name())); + } + ((ConfigFlavor) flavor).freeze(); + } + // Third pass, ensure that retired flavors have a replacement + for (Flavor flavor : flavors.values()) { + if (flavor.isRetired() && !hasReplacement(flavors.values(), flavor)) { + throw new IllegalStateException( + String.format("Flavor '%s' is retired, but has no replacement", flavor.flavorName()) + ); + } + } + return flavors.values(); + } + + private static boolean hasReplacement(Collection<Flavor> flavors, Flavor flavor) { + return flavors.stream() + .filter(f -> !f.equals(flavor)) + .anyMatch(f -> f.satisfies(flavor)); + } + +} |