diff options
author | Håkon Hallingstad <hakon@verizonmedia.com> | 2020-11-26 15:22:19 +0100 |
---|---|---|
committer | Håkon Hallingstad <hakon@verizonmedia.com> | 2020-11-26 15:22:19 +0100 |
commit | d6e727e30087e321911aa240fee03053cc22b1f4 (patch) | |
tree | f20b90d16103704adcad2085a6084252ef826fc8 /flags | |
parent | c1303724a101117e5c7cbf0d3ff9997705c01033 (diff) |
Allow preprovision capacity on partially filled hosts
Adds new functionality that can be disabled by setting the
compact-preprovision-capacity flag to false.
preprovision-capacity can be satisfied by hosts with spare resources. The
DynamicProvisioningMaintainer does this as follows:
1. For each cluster in preprovision-capacity, try to
a. allocate the cluster using NodePrioritizer
b. If there is a deficit, provision the deficit with HostProvisioner, which
may provision larger shared hosts depending on shared-hosts, and retry
(1) from the first cluster again.
c. Otherwise, pretend the nodes are allocated and go to next cluster.
2. All of preprovision-capacity was successfully allocated, and empty hosts
are therefore excess that can be deprovisioned.
Diffstat (limited to 'flags')
4 files changed, 142 insertions, 79 deletions
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java index cd60a082472..bdbe439dcdf 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -3,7 +3,7 @@ package com.yahoo.vespa.flags; import com.yahoo.component.Vtag; import com.yahoo.vespa.defaults.Defaults; -import com.yahoo.vespa.flags.custom.HostCapacity; +import com.yahoo.vespa.flags.custom.ClusterCapacity; import com.yahoo.vespa.flags.custom.SharedHost; import java.util.List; @@ -83,11 +83,17 @@ public class Flags { "Takes effect on the next run of RetiredExpirer.", HOSTNAME); - public static final UnboundListFlag<HostCapacity> TARGET_CAPACITY = defineListFlag( - "preprovision-capacity", List.of(), HostCapacity.class, - "List of node resources and their count that should be provisioned." + - "In a dynamically provisioned zone this specifies the unallocated (i.e. pre-provisioned) capacity. " + - "Otherwise it specifies the total (unallocated or not) capacity.", + public static final UnboundListFlag<ClusterCapacity> PREPROVISION_CAPACITY = defineListFlag( + "preprovision-capacity", List.of(), ClusterCapacity.class, + "Specifies the resources that ought to be immediately available for additional cluster " + + "allocations. If the resources are not available, additional hosts will be provisioned. " + + "Only applies to dynamically provisioned zones.", + "Takes effect on next iteration of DynamicProvisioningMaintainer."); + + public static final UnboundBooleanFlag COMPACT_PREPROVISION_CAPACITY = defineFeatureFlag( + "compact-preprovision-capacity", true, + "Whether preprovision capacity can be satisfied with available capacity on hosts with " + + "existing allocations. Historically preprovision-capacity referred to empty hosts.", "Takes effect on next iteration of DynamicProvisioningMaintainer."); public static final UnboundJacksonFlag<SharedHost> SHARED_HOST = defineJacksonFlag( diff --git a/flags/src/main/java/com/yahoo/vespa/flags/custom/ClusterCapacity.java b/flags/src/main/java/com/yahoo/vespa/flags/custom/ClusterCapacity.java new file mode 100644 index 00000000000..8970d421681 --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/custom/ClusterCapacity.java @@ -0,0 +1,89 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags.custom; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; +import java.util.Optional; + +/** + * @author freva + */ +// @Immutable +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(value = JsonInclude.Include.NON_NULL) +public class ClusterCapacity { + private final int count; + private final double vcpu; + private final double memoryGb; + private final double diskGb; + private final Optional<Double> bandwidthGbps; + + @JsonCreator + public ClusterCapacity(@JsonProperty("count") int count, + @JsonProperty("vcpu") double vcpu, + @JsonProperty("memoryGb") double memoryGb, + @JsonProperty("diskGb") double diskGb, + @JsonProperty("bandwidthGbps") Double bandwidthGbps) { + this.count = (int) requireNonNegative("count", count); + this.vcpu = requireNonNegative("vcpu", vcpu); + this.memoryGb = requireNonNegative("memoryGb", memoryGb); + this.diskGb = requireNonNegative("diskGb", diskGb); + this.bandwidthGbps = Optional.ofNullable(bandwidthGbps); + } + + /** Returns a new ClusterCapacity equal to {@code this}, but with the given count. */ + public ClusterCapacity withCount(int count) { + return new ClusterCapacity(count, vcpu, memoryGb, diskGb, bandwidthGbps.orElse(null)); + } + + @JsonGetter("count") public int count() { return count; } + @JsonGetter("vcpu") public double vcpu() { return vcpu; } + @JsonGetter("memoryGb") public double memoryGb() { return memoryGb; } + @JsonGetter("diskGb") public double diskGb() { return diskGb; } + @JsonGetter("bandwidthGbps") public Double bandwidthGbpsOrNull() { return bandwidthGbps.orElse(null); } + + @JsonIgnore + public double bandwidthGbps() { return bandwidthGbps.orElse(1.0); } + + @Override + public String toString() { + return "ClusterCapacity{" + + "count=" + count + + ", vcpu=" + vcpu + + ", memoryGb=" + memoryGb + + ", diskGb=" + diskGb + + ", bandwidthGbps=" + bandwidthGbps + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ClusterCapacity that = (ClusterCapacity) o; + return count == that.count && + Double.compare(that.vcpu, vcpu) == 0 && + Double.compare(that.memoryGb, memoryGb) == 0 && + Double.compare(that.diskGb, diskGb) == 0 && + ((bandwidthGbps.isEmpty() && that.bandwidthGbps.isEmpty()) || + ((bandwidthGbps.isPresent() && that.bandwidthGbps.isPresent() && + Double.compare(that.bandwidthGbps.get(), bandwidthGbps.get()) == 0))); + } + + @Override + public int hashCode() { + return Objects.hash(count, vcpu, memoryGb, diskGb, bandwidthGbps); + } + + private static double requireNonNegative(String name, double value) { + if (value < 0) + throw new IllegalArgumentException("'" + name + "' must be positive, was " + value); + return value; + } +} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/custom/HostCapacity.java b/flags/src/main/java/com/yahoo/vespa/flags/custom/HostCapacity.java deleted file mode 100644 index 947520ca2d7..00000000000 --- a/flags/src/main/java/com/yahoo/vespa/flags/custom/HostCapacity.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.flags.custom; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.Objects; - -/** - * @author freva - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class HostCapacity { - @JsonProperty("vcpu") - private final double vcpu; - - @JsonProperty("memoryGb") - private final double memoryGb; - - @JsonProperty("diskGb") - private final double diskGb; - - @JsonProperty("count") - private final int count; - - public HostCapacity(@JsonProperty("vcpu") double vcpu, - @JsonProperty("memoryGb") double memoryGb, - @JsonProperty("diskGb") double diskGb, - @JsonProperty("count") int count) { - this.vcpu = requirePositive("vcpu", vcpu); - this.memoryGb = requirePositive("memoryGb", memoryGb); - this.diskGb = requirePositive("diskGb", diskGb); - this.count = (int) requirePositive("count", count); - } - - public double getVcpu() { - return vcpu; - } - - public double getMemoryGb() { - return memoryGb; - } - - public double getDiskGb() { - return diskGb; - } - - public int getCount() { - return count; - } - - private static double requirePositive(String name, double value) { - if (value <= 0) - throw new IllegalArgumentException("'" + name + "' must be positive, was " + value); - return value; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - HostCapacity that = (HostCapacity) o; - return Double.compare(that.vcpu, vcpu) == 0 && - Double.compare(that.memoryGb, memoryGb) == 0 && - Double.compare(that.diskGb, diskGb) == 0 && - count == that.count; - } - - @Override - public int hashCode() { - return Objects.hash(vcpu, memoryGb, diskGb, count); - } -} diff --git a/flags/src/test/java/com/yahoo/vespa/flags/custom/ClusterCapacityTest.java b/flags/src/test/java/com/yahoo/vespa/flags/custom/ClusterCapacityTest.java new file mode 100644 index 00000000000..0258b562897 --- /dev/null +++ b/flags/src/test/java/com/yahoo/vespa/flags/custom/ClusterCapacityTest.java @@ -0,0 +1,41 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags.custom; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + +public class ClusterCapacityTest { + @Test + public void serialization() throws IOException { + ClusterCapacity clusterCapacity = new ClusterCapacity(7, 1.2, 3.4, 5.6, null); + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(clusterCapacity); + assertEquals("{\"count\":7,\"vcpu\":1.2,\"memoryGb\":3.4,\"diskGb\":5.6}", json); + + ClusterCapacity deserialized = mapper.readValue(json, ClusterCapacity.class); + assertEquals(1.2, deserialized.vcpu(), 0.0001); + assertEquals(3.4, deserialized.memoryGb(), 0.0001); + assertEquals(5.6, deserialized.diskGb(), 0.0001); + assertEquals(1.0, deserialized.bandwidthGbps(), 0.0001); + assertEquals(7, deserialized.count()); + } + + @Test + public void serialization2() throws IOException { + ClusterCapacity clusterCapacity = new ClusterCapacity(7, 1.2, 3.4, 5.6, 2.3); + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(clusterCapacity); + assertEquals("{\"count\":7,\"vcpu\":1.2,\"memoryGb\":3.4,\"diskGb\":5.6,\"bandwidthGbps\":2.3}", json); + + ClusterCapacity deserialized = mapper.readValue(json, ClusterCapacity.class); + assertEquals(1.2, deserialized.vcpu(), 0.0001); + assertEquals(3.4, deserialized.memoryGb(), 0.0001); + assertEquals(5.6, deserialized.diskGb(), 0.0001); + assertEquals(2.3, deserialized.bandwidthGbps(), 0.0001); + assertEquals(7, deserialized.count()); + } +}
\ No newline at end of file |