diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2017-01-22 14:47:45 +0100 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2017-01-22 14:47:45 +0100 |
commit | 0501d0d549caf74111ab5da8498744d35e2aea52 (patch) | |
tree | 41f6f3bf01cad46329920a59d33d316a62cdbc74 /config-provisioning | |
parent | d7640ecc7ab7d1f776756d78efe1e46a3769575f (diff) |
Move node-repository config to provisioning
Diffstat (limited to 'config-provisioning')
3 files changed, 244 insertions, 0 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 new file mode 100644 index 00000000000..835325bc0f8 --- /dev/null +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Flavor.java @@ -0,0 +1,132 @@ +// Copyright 2016 Yahoo Inc. 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. + * + * @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 String description; + private List<Flavor> replacesFlavors; + + /** + * 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.description = flavorConfig.description(); + } + + /** 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 double getMinDiskAvailableGb() { return minDiskAvailableGb; } + + public double getMinCpuCores() { return minCpuCores; } + + public String getDescription() { return description; } + + public Type getType() { return type; } + + /** + * Returns the canonical name of this flavor - which is the name which should be used as an interface to users. + * The canonical name of this flavor is: + * <ul> + * <li>If it replaces one flavor, the canonical name of the flavor it replaces + * <li>If it replaces multiple or no flavors - itself + * </ul> + * + * The logic is that we can use this to capture the gritty details of configurations in exact flavor names + * but also encourage users to refer to them by a common name by letting such flavor variants declare that they + * 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; + } + + /** + * The flavors this (directly) replaces. + * This is immutable if this is frozen, and a mutable list otherwise. + */ + 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 + */ + public boolean satisfies(Flavor flavor) { + if (this.equals(flavor)) return true; + for (Flavor replaces : replacesFlavors) + if (replaces.satisfies(flavor)) + return true; + return false; + } + + /** 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 Flavor)) return false; + return ((Flavor)other).name.equals(this.name); + } + + @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 new file mode 100644 index 00000000000..9451e47cbc3 --- /dev/null +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeFlavors.java @@ -0,0 +1,71 @@ +// Copyright 2016 Yahoo Inc. 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 java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import com.yahoo.config.provisioning.FlavorsConfig; + +/** + * All the available node flavors. + * + * @author bratseth + */ +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 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)); + } + + /** Returns the flavor with the given name or throws an IllegalArgumentException if it does not exist */ + public Flavor getFlavorOrThrow(String flavorName) { + Optional<Flavor> flavor = getFlavor(flavorName); + if ( flavor.isPresent()) return flavor.get(); + throw new IllegalArgumentException("Unknown flavor '" + flavorName + "'. Flavors are " + canonicalFlavorNames()); + } + + private List<String> canonicalFlavorNames() { + 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(); + } + return flavors.values(); + } + +} diff --git a/config-provisioning/src/main/resources/configdefinitions/flavors.def b/config-provisioning/src/main/resources/configdefinitions/flavors.def new file mode 100644 index 00000000000..26006c25df8 --- /dev/null +++ b/config-provisioning/src/main/resources/configdefinitions/flavors.def @@ -0,0 +1,41 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +# Configuration of the node repository +namespace=config.provisioning + +# A node flavor which (may) be available in this zone. +# This is to allow configuration per flavor. +# If a certain flavor has no config it is not necessary to list it here to use it. +flavor[].name string + +# Names of other flavors (whether mentioned in this config or not) which this flavor +# is a replacement for: If one of these flavor names are requested, this flavor may +# be assigned instead. +# Replacements are transitive: If flavor a replaces b replaces c, then a request for flavor +# c may be satisfied by assigning nodes of flavor a. +flavor[].replaces[].name string + +# The monthly Total Cost of Ownership (TCO) in USD. Typically calculated as TCO divered by +# the expected lifetime of the node (usually three years). +flavor[].cost int default=0 + +# A stock flavor is any flavor which we expect to buy 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 purchased +# for some historical reason. These nodes are assigned to applications by exact match and ignoring cost. +flavor[].stock bool default=true + +# The type of node (e.g. bare metal, docker..). +flavor[].environment string default="undefined" + +# The minimum number of CPU cores available. +flavor[].minCpuCores double default=0.0 + +# The minimum amount of main memory available. +flavor[].minMainMemoryAvailableGb double default=0.0 + +# The minimum amount of disk available. +flavor[].minDiskAvailableGb double default=0.0 + +# Human readable free text for description of node. +flavor[].description string default="" |