diff options
Diffstat (limited to 'config-provisioning/src/main/java/com')
5 files changed, 183 insertions, 320 deletions
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/AllocatedHosts.java b/config-provisioning/src/main/java/com/yahoo/config/provision/AllocatedHosts.java index 8cb72a36e7a..e2b2933ede3 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/AllocatedHosts.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/AllocatedHosts.java @@ -59,7 +59,7 @@ public class AllocatedHosts { cursor.setString(hostSpecMembership, membership.stringValue()); cursor.setString(hostSpecVespaVersion, membership.cluster().vespaVersion().toFullString()); }); - host.flavor().ifPresent(flavor -> cursor.setString(hostSpecFlavor, flavor.flavorName())); + host.flavor().ifPresent(flavor -> cursor.setString(hostSpecFlavor, flavor.name())); host.version().ifPresent(version -> cursor.setString(hostSpecCurrentVespaVersion, version.toFullString())); host.networkPorts().ifPresent(ports -> NetworkPortsSerializer.toSlime(ports, cursor.setArray(hostSpecNetworkPorts))); } 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 e2c39d6efda..79a17c23dd7 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,17 +1,93 @@ // 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). + * A host flavor (type). This is a value object where the identity is the name. + * Use {@link NodeFlavors} to create a flavor. * * @author bratseth */ -public interface Flavor { +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; } - /** @return the unique identity of this flavor */ - String flavorName(); + 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; + } /** * Returns the canonical name of this flavor - which is the name which should be used as an interface to users. @@ -26,85 +102,69 @@ public interface 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. */ - default String canonicalName() { - return replaces().size() != 1 ? flavorName() : replaces().get(0).canonicalName(); + public String canonicalName() { + return isCanonical() ? name : replacesFlavors.get(0).canonicalName(); + } + + /** Returns whether this is a canonical flavor */ + public boolean isCanonical() { + return replacesFlavors.size() != 1; } - - /** @return the cost associated with usage of this flavor */ - int cost(); /** - * 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. + * The flavors this (directly) replaces. + * This is immutable if this is frozen, and a mutable list otherwise. */ - boolean isStock(); - - /** Returns whether the flavor is retired (should no longer be allocated) */ - boolean isRetired(); + public List<Flavor> replaces() { return replacesFlavors; } /** * Returns whether this flavor satisfies the requested flavor, either directly * (by being the same), or by directly or indirectly replacing it */ - default boolean satisfies(Flavor flavor) { - if (equals(flavor)) { + public boolean satisfies(Flavor flavor) { + if (this.equals(flavor)) { return true; } - if (isRetired()) { + if (this.retired) { return false; } - for (Flavor replaces : replaces()) + for (Flavor replaces : replacesFlavors) if (replaces.satisfies(flavor)) return true; return false; } - 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(); + /** Irreversibly freezes the content of this */ + public void freeze() { + replacesFlavors = ImmutableList.copyOf(replacesFlavors); } - - interface Memory { - double sizeInGb(); + + /** 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; } - interface Cpu { - double cores(); - } + @Override + public int hashCode() { return name.hashCode(); } - interface Bandwidth { - double mbits(); + @Override + public boolean equals(Object other) { + if (other == this) return true; + if ( ! (other instanceof Flavor)) return false; + return ((Flavor)other).name.equals(this.name); } - enum Environment { + @Override + public String toString() { return "flavor '" + name + "'"; } + + public enum Type { + undefined, // Default value in config (flavors.def) 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 43ef5602d8a..e64028e216f 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,30 +1,88 @@ // 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 freva + * @author bratseth */ -public interface NodeFlavors { +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(); + } - /** Returns list of all available flavors in the system */ - List<Flavor> getFlavors(); + public List<Flavor> getFlavors() { + return new ArrayList<>(flavors.values()); + } /** Returns a flavor by name, or empty if there is no flavor with this name. */ - Optional<Flavor> getFlavor(String name); + public Optional<Flavor> getFlavor(String name) { + return Optional.ofNullable(flavors.get(name)); + } /** Returns the flavor with the given name or throws an IllegalArgumentException if it does not exist */ - default Flavor getFlavorOrThrow(String flavorName) { + public Flavor getFlavorOrThrow(String flavorName) { return getFlavor(flavorName).orElseThrow(() -> new IllegalArgumentException("Unknown flavor '" + flavorName + "'. Flavors are " + canonicalFlavorNames())); } private List<String> canonicalFlavorNames() { - return getFlavors().stream().map(Flavor::canonicalName).distinct().sorted().collect(Collectors.toList()); + return flavors.values().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 deleted file mode 100644 index e70476455cd..00000000000 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/internal/ConfigFlavor.java +++ /dev/null @@ -1,172 +0,0 @@ -// 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 deleted file mode 100644 index c67a3faf36f..00000000000 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/internal/ConfigNodeFlavors.java +++ /dev/null @@ -1,83 +0,0 @@ -// 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)); - } - -} |