diff options
author | Jon Bratseth <bratseth@verizonmedia.com> | 2019-05-02 21:29:29 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@verizonmedia.com> | 2019-05-02 21:29:29 +0200 |
commit | 8457cd74f0ac3d876d1f7fd6cd7ea7b503cae491 (patch) | |
tree | 4a2eb5ed891f5da85397797fc4c943c33ade8b65 /config-provisioning/src | |
parent | 75e2698805c454d54afb4b5a8bc62b046c4e3246 (diff) |
Allow continuous node resource specs
Diffstat (limited to 'config-provisioning/src')
4 files changed, 174 insertions, 13 deletions
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java index 6df617ea335..f635b986558 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java @@ -17,11 +17,11 @@ public final class Capacity { private final boolean canFail; - private final Optional<String> flavor; + private final Optional<FlavorSpec> flavor; private final NodeType type; - private Capacity(int nodeCount, Optional<String> flavor, boolean required, boolean canFail, NodeType type) { + private Capacity(int nodeCount, Optional<FlavorSpec> flavor, boolean required, boolean canFail, NodeType type) { this.nodeCount = nodeCount; this.required = required; this.canFail = canFail; @@ -36,7 +36,10 @@ public final class Capacity { * The node flavor requested, or empty if no particular flavor is specified. * This may be satisfied by the requested flavor or a suitable replacement */ - public Optional<String> flavor() { return flavor; } + public Optional<String> flavor() { return flavor.map(FlavorSpec::legacyFlavorName); } + + /** Returns the capacity specified for each node, or empty to leave this decision to provisioning */ + public Optional<FlavorSpec> flavorSpec() { return flavor; } /** Returns whether the requested number of nodes must be met exactly for a request for this to succeed */ public boolean isRequired() { return required; } @@ -65,10 +68,18 @@ public final class Capacity { return fromNodeCount(capacity, Optional.empty(), false, true); } - public static Capacity fromNodeCount(int nodeCount, Optional<String> flavor, boolean required, boolean canFail) { + public static Capacity fromCount(int nodeCount, FlavorSpec flavor, boolean required, boolean canFail) { + return new Capacity(nodeCount, Optional.of(flavor), required, canFail, NodeType.tenant); + } + + public static Capacity fromCount(int nodeCount, Optional<FlavorSpec> flavor, boolean required, boolean canFail) { return new Capacity(nodeCount, flavor, required, canFail, NodeType.tenant); } + public static Capacity fromNodeCount(int nodeCount, Optional<String> flavor, boolean required, boolean canFail) { + return new Capacity(nodeCount, flavor.map(FlavorSpec::fromLegacyFlavorName), required, canFail, NodeType.tenant); + } + /** Creates this from a node type */ public static Capacity fromRequiredNodeType(NodeType type) { return new Capacity(0, Optional.empty(), true, false, type); 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..8b6fa863af6 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 @@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableList; import com.yahoo.config.provisioning.FlavorsConfig; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -31,6 +32,7 @@ public class Flavor { /** * Creates a Flavor, but does not set the replacesFlavors. + * * @param flavorConfig config to be used for Flavor. */ public Flavor(FlavorsConfig.Flavor flavorConfig) { @@ -49,6 +51,25 @@ public class Flavor { this.idealHeadroom = flavorConfig.idealHeadroom(); } + /** Create a Flavor from a Flavor spec and all other fields set to Docker defaults */ + public Flavor(FlavorSpec spec) { + if (spec.allocateByLegacyName()) + throw new IllegalArgumentException("Can not create flavor '" + spec.legacyFlavorName() + "' from a spec: " + + "Non-docker flavors must be of a configured flavor"); + this.name = spec.legacyFlavorName(); + this.cost = 0; + this.isStock = true; + this.type = Type.DOCKER_CONTAINER; + this.minCpuCores = spec.cpuCores(); + this.minMainMemoryAvailableGb = spec.memoryGb(); + this.minDiskAvailableGb = spec.diskGb(); + this.fastDisk = true; + this.bandwidth = 1; + this.description = ""; + this.retired = false; + this.replacesFlavors = Collections.emptyList(); + } + /** Returns the unique identity of this flavor */ public String name() { return name; } @@ -147,6 +168,13 @@ public class Flavor { this.fastDisk || ! other.fastDisk; } + public FlavorSpec asSpec() { + if (isDocker()) + return new FlavorSpec(minCpuCores, minMainMemoryAvailableGb, minDiskAvailableGb); + else + return FlavorSpec.fromLegacyFlavorName(name); + } + @Override public int hashCode() { return name.hashCode(); } diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/FlavorSpec.java b/config-provisioning/src/main/java/com/yahoo/config/provision/FlavorSpec.java new file mode 100644 index 00000000000..62cfb59c51c --- /dev/null +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/FlavorSpec.java @@ -0,0 +1,107 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.config.provision; + +import java.util.Objects; + +/** + * The node capacity specified by an application, which is matched to an actual flavor during provisioning. + * + * @author bratseth + */ +public class FlavorSpec { + + private final double cpuCores; + private final double memoryGb; + private final double diskGb; + + private final boolean allocateByLegacyName; + private final String legacyFlavorName; + + public FlavorSpec(double cpuCores, double memoryGb, double diskGb) { + this.cpuCores = cpuCores; + this.memoryGb = memoryGb; + this.diskGb = diskGb; + this.allocateByLegacyName = false; + this.legacyFlavorName = null; + } + + private FlavorSpec(double cpuCores, double memoryGb, double diskGb, boolean allocateByLegacyName, String legacyFlavorName) { + this.cpuCores = cpuCores; + this.memoryGb = memoryGb; + this.diskGb = diskGb; + this.allocateByLegacyName = allocateByLegacyName; + this.legacyFlavorName = legacyFlavorName; + } + + public double cpuCores() { return cpuCores; } + public double memoryGb() { return memoryGb; } + public double diskGb() { return diskGb; } + + /** + * If this is true, a non-docker legacy name was used to specify this and we'll respect that by mapping directly. + * The other getters of this will return 0. + */ + public boolean allocateByLegacyName() { return allocateByLegacyName; } + + /** Returns the legacy flavor string of this. This is never null. */ + public String legacyFlavorName() { + if (legacyFlavorName != null) + return legacyFlavorName; + else + return "d-" + (int)Math.ceil(cpuCores) + "-" + (int)Math.ceil(memoryGb) + "-" + (int)Math.ceil(diskGb); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if ( ! (o instanceof FlavorSpec)) return false; + FlavorSpec other = (FlavorSpec)o; + if (allocateByLegacyName) { + return this.legacyFlavorName.equals(other.legacyFlavorName); + } + else { + if (this.cpuCores != other.cpuCores) return false; + if (this.memoryGb != other.memoryGb) return false; + if (this.diskGb != other.diskGb) return false; + return true; + } + } + + @Override + public int hashCode() { + if (allocateByLegacyName) + return legacyFlavorName.hashCode(); + else + return (int)(2503 * cpuCores + 22123 * memoryGb + 26987 * diskGb); + } + + @Override + public String toString() { + if (allocateByLegacyName) + return "flavor '" + legacyFlavorName + "'"; + else + return "cpu cores: " + cpuCores + ", memory: " + memoryGb + " Gb, disk " + diskGb + " Gb"; + } + + /** + * Create this from a legacy flavor string. + * + * @throws IllegalArgumentException if the given string does not map to a legacy flavor + */ + public static FlavorSpec fromLegacyFlavorName(String flavorString) { + if (flavorString.startsWith("d-")) { // A docker flavor + String[] parts = flavorString.split("-"); + double cpu = Integer.parseInt(parts[1]); + double mem = Integer.parseInt(parts[2]); + double dsk = Integer.parseInt(parts[3]); + if (cpu == 0) cpu = 0.5; + if (cpu == 2 && mem == 8 ) cpu = 1.5; + if (cpu == 2 && mem == 12 ) cpu = 2.3; + return new FlavorSpec(cpu, mem, dsk, false, flavorString); + } + else { + return new FlavorSpec(0, 0, 0, true, flavorString); + } + } + +} 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..b87f6eeec31 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 @@ -20,34 +20,49 @@ import java.util.stream.Collectors; */ public class NodeFlavors { - /** Flavors <b>which are configured</b> in this zone */ - private final ImmutableMap<String, Flavor> flavors; + /** Flavors which are configured in this zone */ + private final ImmutableMap<String, Flavor> configuredFlavors; @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(); + this.configuredFlavors = b.build(); } public List<Flavor> getFlavors() { - return new ArrayList<>(flavors.values()); + return new ArrayList<>(configuredFlavors.values()); } - /** Returns a flavor by name, or empty if there is no flavor with this name. */ + /** Returns a flavor by name, or empty if there is no flavor with this name and it cannot be created on the fly. */ public Optional<Flavor> getFlavor(String name) { - return Optional.ofNullable(flavors.get(name)); + if (configuredFlavors.containsKey(name)) + return Optional.of(configuredFlavors.get(name)); + + FlavorSpec flavorSpec = FlavorSpec.fromLegacyFlavorName(name); + if (flavorSpec.allocateByLegacyName()) + return Optional.empty(); + else + return Optional.of(new Flavor(flavorSpec)); } - /** Returns the flavor with the given name or throws an IllegalArgumentException if it does not exist */ + /** + * Returns the flavor with the given name or throws an IllegalArgumentException if it does not exist + * and cannot be created on the fly. + */ public Flavor getFlavorOrThrow(String flavorName) { return getFlavor(flavorName).orElseThrow(() -> new IllegalArgumentException("Unknown flavor '" + flavorName + - "'. Flavors are " + canonicalFlavorNames())); + "'. Flavors are " + canonicalFlavorNames())); + } + + /** Returns true if this flavor is configured or can be created on the fly */ + public boolean exists(String flavorName) { + return getFlavor(flavorName).isPresent(); } private List<String> canonicalFlavorNames() { - return flavors.values().stream().map(Flavor::canonicalName).distinct().sorted().collect(Collectors.toList()); + return configuredFlavors.values().stream().map(Flavor::canonicalName).distinct().sorted().collect(Collectors.toList()); } private static Collection<Flavor> toFlavors(FlavorsConfig config) { |