summaryrefslogtreecommitdiffstats
path: root/config-provisioning
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2017-01-22 14:47:45 +0100
committerJon Bratseth <bratseth@yahoo-inc.com>2017-01-22 14:47:45 +0100
commit0501d0d549caf74111ab5da8498744d35e2aea52 (patch)
tree41f6f3bf01cad46329920a59d33d316a62cdbc74 /config-provisioning
parentd7640ecc7ab7d1f776756d78efe1e46a3769575f (diff)
Move node-repository config to provisioning
Diffstat (limited to 'config-provisioning')
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Flavor.java132
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/NodeFlavors.java71
-rw-r--r--config-provisioning/src/main/resources/configdefinitions/flavors.def41
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=""