diff options
18 files changed, 179 insertions, 69 deletions
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java index c5477bf55b3..b8f03794301 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java @@ -27,6 +27,7 @@ public interface ModelContext { Optional<Model> previousModel(); Optional<ApplicationPackage> permanentApplicationPackage(); Optional<HostProvisioner> hostProvisioner(); + Provisioned provisioned(); DeployLogger deployLogger(); ConfigDefinitionRepo configDefinitionRepo(); FileRegistry getFileRegistry(); diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/Provisioned.java b/config-model-api/src/main/java/com/yahoo/config/model/api/Provisioned.java new file mode 100644 index 00000000000..e4201836e57 --- /dev/null +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/Provisioned.java @@ -0,0 +1,28 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.config.model.api; + +import com.yahoo.config.provision.Capacity; +import com.yahoo.config.provision.ClusterSpec; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * A recording of the capacity requests issued during a model build. + * Requests are only recorded here if provision requests are issued to the node repo. + * + * @author bratseth + */ +public class Provisioned { + + private final Map<ClusterSpec.Id, Capacity> provisioned = new HashMap<>(); + + public void add(ClusterSpec.Id id, Capacity capacity) { + provisioned.put(id, capacity); + } + + /** Returns an unmodifiable map of all the provision requests recorded during build of the model this belongs to */ + public Map<ClusterSpec.Id, Capacity> all() { return Collections.unmodifiableMap(provisioned); } + +} diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java index 696ce4195eb..3fb7ba6bc3a 100644 --- a/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java +++ b/config-model/src/main/java/com/yahoo/config/model/deploy/DeployState.java @@ -16,6 +16,7 @@ import com.yahoo.config.model.api.EndpointCertificateSecrets; import com.yahoo.config.model.api.HostProvisioner; import com.yahoo.config.model.api.Model; import com.yahoo.config.model.api.ModelContext; +import com.yahoo.config.model.api.Provisioned; import com.yahoo.config.model.api.ValidationParameters; import com.yahoo.config.model.application.provider.BaseDeployLogger; import com.yahoo.config.model.application.provider.MockFileRegistry; @@ -79,6 +80,7 @@ public class DeployState implements ConfigDefinitionStore { private final Optional<String> wantedDockerImageRepo; private final Instant now; private final HostProvisioner provisioner; + private final Provisioned provisioned; public static DeployState createTestState() { return new Builder().build(); @@ -98,6 +100,7 @@ public class DeployState implements ConfigDefinitionStore { FileRegistry fileRegistry, DeployLogger deployLogger, Optional<HostProvisioner> hostProvisioner, + Provisioned provisioned, ModelContext.Properties properties, Version vespaVersion, Optional<ApplicationPackage> permanentApplicationPackage, @@ -121,6 +124,7 @@ public class DeployState implements ConfigDefinitionStore { this.previousModel = previousModel; this.accessLoggingEnabledByDefault = accessLoggingEnabledByDefault; this.provisioner = hostProvisioner.orElse(getDefaultModelHostProvisioner(applicationPackage)); + this.provisioned = provisioned; this.schemas = searchDocumentModel.getSchemas(); this.documentModel = searchDocumentModel.getDocumentModel(); this.permanentApplicationPackage = permanentApplicationPackage; @@ -152,6 +156,8 @@ public class DeployState implements ConfigDefinitionStore { } } + public Provisioned provisioned() { return provisioned; } + /** Get the global rank profile registry for this application. */ public final RankProfileRegistry rankProfileRegistry() { return rankProfileRegistry; } @@ -288,6 +294,7 @@ public class DeployState implements ConfigDefinitionStore { private FileRegistry fileRegistry = new MockFileRegistry(); private DeployLogger logger = new BaseDeployLogger(); private Optional<HostProvisioner> hostProvisioner = Optional.empty(); + private Provisioned provisioned = new Provisioned(); private Optional<ApplicationPackage> permanentApplicationPackage = Optional.empty(); private ModelContext.Properties properties = new TestProperties(); private Version version = new Version(1, 0, 0); @@ -321,6 +328,11 @@ public class DeployState implements ConfigDefinitionStore { return this; } + public Builder provisioned(Provisioned provisioned) { + this.provisioned = provisioned; + return this; + } + public Builder permanentApplicationPackage(Optional<ApplicationPackage> permanentApplicationPackage) { this.permanentApplicationPackage = permanentApplicationPackage; return this; @@ -400,6 +412,7 @@ public class DeployState implements ConfigDefinitionStore { fileRegistry, logger, hostProvisioner, + provisioned, properties, version, permanentApplicationPackage, diff --git a/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java b/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java index 298517b85f6..8706bb44ded 100644 --- a/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java +++ b/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java @@ -4,6 +4,7 @@ package com.yahoo.config.model.provision; import com.yahoo.collections.ListMap; import com.yahoo.collections.Pair; import com.yahoo.config.model.api.HostProvisioner; +import com.yahoo.config.model.api.Provisioned; import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterMembership; import com.yahoo.config.provision.ClusterResources; @@ -60,6 +61,8 @@ public class InMemoryProvisioner implements HostProvisioner { private final boolean useMaxResources; + private Provisioned provisioned = new Provisioned(); + /** Creates this with a number of nodes with resources 1, 3, 9, 1 */ public InMemoryProvisioner(int nodeCount) { this(nodeCount, defaultResources); @@ -85,9 +88,11 @@ public class InMemoryProvisioner implements HostProvisioner { this(Map.of(defaultResources, hosts.asCollection()), failOnOutOfCapacity, false, startIndexForClusters, retiredHostNames); } - public InMemoryProvisioner(Map<NodeResources, Collection<Host>> hosts, boolean failOnOutOfCapacity, + public InMemoryProvisioner(Map<NodeResources, Collection<Host>> hosts, + boolean failOnOutOfCapacity, boolean useMaxResources, - int startIndexForClusters, String ... retiredHostNames) { + int startIndexForClusters, + String ... retiredHostNames) { this.failOnOutOfCapacity = failOnOutOfCapacity; this.useMaxResources = useMaxResources; for (Map.Entry<NodeResources, Collection<Host>> hostsWithResources : hosts.entrySet()) @@ -125,6 +130,7 @@ public class InMemoryProvisioner implements HostProvisioner { @Override public List<HostSpec> prepare(ClusterSpec cluster, Capacity requested, ProvisionLogger logger) { + provisioned.add(cluster.id(), requested); if (useMaxResources) return prepare(cluster, requested.maxResources(), requested.isRequired(), requested.canFail()); else @@ -165,6 +171,12 @@ public class InMemoryProvisioner implements HostProvisioner { return allocation; } + /** Create a new provisioned instance to record provision requests to this and returns it */ + public Provisioned startProvisionedRecording() { + provisioned = new Provisioned(); + return provisioned; + } + private HostSpec retire(HostSpec host) { return new HostSpec(host.hostname(), host.aliases(), diff --git a/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java b/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java index c8f708e84f4..89cd5c2e735 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/VespaModel.java @@ -23,11 +23,13 @@ import com.yahoo.config.model.api.FileDistribution; import com.yahoo.config.model.api.HostInfo; import ai.vespa.rankingexpression.importer.configmodelview.ImportedMlModels; import com.yahoo.config.model.api.Model; +import com.yahoo.config.model.api.Provisioned; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.config.model.producer.AbstractConfigProducerRoot; import com.yahoo.config.model.producer.UserConfigRepo; import com.yahoo.config.provision.AllocatedHosts; +import com.yahoo.config.provision.ClusterSpec; import com.yahoo.log.LogLevel; import com.yahoo.searchdefinition.RankProfile; import com.yahoo.searchdefinition.RankProfileRegistry; @@ -122,6 +124,8 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri private final FileDistributor fileDistributor; + private final Provisioned provisioned; + /** Creates a Vespa Model from internal model types only */ public VespaModel(ApplicationPackage app) throws IOException, SAXException { this(app, new NullConfigModelRegistry()); @@ -162,6 +166,7 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri configModelRegistry = new VespaConfigModelRegistry(configModelRegistry); VespaModelBuilder builder = new VespaDomBuilder(); this.applicationPackage = deployState.getApplicationPackage(); + this.provisioned = deployState.provisioned(); root = builder.getRoot(VespaModel.ROOT_CONFIGID, deployState, this); createGlobalRankProfiles(deployState.getDeployLogger(), deployState.getImportedModels(), @@ -611,11 +616,23 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri return Collections.unmodifiableMap(id2producer); } - /** - * Returns this root's model repository - */ + /** Returns this root's model repository */ public ConfigModelRepo configModelRepo() { return configModelRepo; } + /** If provisioning through the node repo, returns the provision requests issued during build of this */ + public Provisioned provisioned() { return provisioned; } + + /** Returns the id of all clusters in this */ + public Set<ClusterSpec.Id> allClusters() { + return hostSystem().getHosts().stream() + .map(HostResource::spec) + .filter(spec -> spec.membership().isPresent()) + .map(spec -> spec.membership().get().cluster().id()) + .collect(Collectors.toSet()); + } + + + } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java index f0e119606bf..631f4dab1a7 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/VespaModelFactory.java @@ -146,6 +146,7 @@ public class VespaModelFactory implements ModelFactory { .properties(modelContext.properties()) .vespaVersion(version()) .modelHostProvisioner(createHostProvisioner(modelContext)) + .provisioned(modelContext.provisioned()) .endpoints(modelContext.properties().endpoints()) .modelImporters(modelImporters) .zone(zone) diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidator.java index d14fe91a53b..162f6798462 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidator.java @@ -2,6 +2,8 @@ package com.yahoo.vespa.model.application.validation.change; import com.yahoo.config.model.api.ConfigChangeAction; +import com.yahoo.config.provision.Capacity; +import com.yahoo.config.provision.ClusterSpec; import com.yahoo.vespa.model.VespaModel; import com.yahoo.config.application.api.ValidationId; import com.yahoo.config.application.api.ValidationOverrides; @@ -21,35 +23,32 @@ public class ClusterSizeReductionValidator implements ChangeValidator { @Override public List<ConfigChangeAction> validate(VespaModel current, VespaModel next, ValidationOverrides overrides, Instant now) { - for (ContainerCluster currentCluster : current.getContainerClusters().values()) { - ContainerCluster nextCluster = next.getContainerClusters().get(currentCluster.getName()); - if (nextCluster == null) continue; - validate(currentCluster.getContainers().size(), - nextCluster.getContainers().size(), - currentCluster.getName(), + for (var clusterId : current.allClusters()) { + Capacity currentCapacity = current.provisioned().all().get(clusterId); + Capacity nextCapacity = next.provisioned().all().get(clusterId); + if (currentCapacity == null || nextCapacity == null) continue; + validate(currentCapacity, + nextCapacity, + clusterId, overrides, now); } - - for (ContentCluster currentCluster : current.getContentClusters().values()) { - ContentCluster nextCluster = next.getContentClusters().get(currentCluster.getName()); - if (nextCluster == null) continue; - validate(currentCluster.getSearch().getSearchNodes().size(), - nextCluster.getSearch().getSearchNodes().size(), - currentCluster.getName(), - overrides, - now); - } - return Collections.emptyList(); } - private void validate(int currentSize, int nextSize, String clusterName, ValidationOverrides overrides, Instant now) { + private void validate(Capacity current, + Capacity next, + ClusterSpec.Id clusterId, + ValidationOverrides overrides, + Instant now) { + int currentSize = current.minResources().nodes(); + int nextSize = next.minResources().nodes(); // don't allow more than 50% reduction, but always allow to reduce size with 1 if ( nextSize < ((double)currentSize) * 0.5 && nextSize != currentSize - 1) overrides.invalid(ValidationId.clusterSizeReduction, - "Size reduction in '" + clusterName + "' is too large. Current size: " + currentSize + - ", new size: " + nextSize + ". New size must be at least 50% of the current size", + "Size reduction in '" + clusterId.value() + "' is too large: " + + "New min size must be at least 50% of the current min size. " + + "Current size: " + currentSize + ", new size: " + nextSize, now); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidator.java index 8fdcf249bbc..5343a322382 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidator.java @@ -5,6 +5,7 @@ import com.yahoo.collections.Pair; import com.yahoo.config.application.api.ValidationId; import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.config.model.api.ConfigChangeAction; +import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.NodeResources; import com.yahoo.vespa.model.HostResource; @@ -15,6 +16,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -27,35 +29,43 @@ public class ResourcesReductionValidator implements ChangeValidator { @Override public List<ConfigChangeAction> validate(VespaModel current, VespaModel next, ValidationOverrides overrides, Instant now) { - var currentRequestedResourcesByClusterId = getRequestedResourcesByClusterId(current); - var nextRequestedResourcesByClusterId = getRequestedResourcesByClusterId(next); - - for (var clusterTypeAndId : currentRequestedResourcesByClusterId.keySet()) { - if (!nextRequestedResourcesByClusterId.containsKey(clusterTypeAndId)) continue; - validate(currentRequestedResourcesByClusterId.get(clusterTypeAndId), - nextRequestedResourcesByClusterId.get(clusterTypeAndId), - clusterTypeAndId.getSecond(), - overrides, - now); + for (var clusterId : current.allClusters()) { + Capacity currentCapacity = current.provisioned().all().get(clusterId); + Capacity nextCapacity = next.provisioned().all().get(clusterId); + if (currentCapacity == null || nextCapacity == null) continue; + validate(currentCapacity, nextCapacity, clusterId, overrides, now); } return List.of(); } - private void validate(NodeResources currentResources, NodeResources nextResources, ClusterSpec.Id clusterId, - ValidationOverrides overrides, Instant now) { + private void validate(Capacity current, + Capacity next, + ClusterSpec.Id clusterId, + ValidationOverrides overrides, + Instant now) { + if (current.minResources().nodeResources() == NodeResources.unspecified) return; + if (next.minResources().nodeResources() == NodeResources.unspecified) return; + List<String> illegalChanges = Stream.of( - validateResource("vCPU", currentResources.vcpu(), nextResources.vcpu()), - validateResource("memory GB", currentResources.memoryGb(), nextResources.memoryGb()), - validateResource("disk GB", currentResources.diskGb(), nextResources.diskGb())) + validateResource("vCPU", + current.minResources().nodeResources().vcpu(), + next.minResources().nodeResources().vcpu()), + validateResource("memory GB", + current.minResources().nodeResources().memoryGb(), + next.minResources().nodeResources().memoryGb()), + validateResource("disk GB", + current.minResources().nodeResources().diskGb(), + next.minResources().nodeResources().diskGb())) .flatMap(Optional::stream) .collect(Collectors.toList()); if (illegalChanges.isEmpty()) return; overrides.invalid(ValidationId.resourcesReduction, - "Resource reduction in '" + clusterId.value() + "' is too large. " + - String.join(" ", illegalChanges) + " New resources must be at least 50% of the current resources", - now); + "Resource reduction in '" + clusterId.value() + "' is too large. " + + String.join(" ", illegalChanges) + + " New min resources must be at least 50% of the current min resources", + now); } private static Optional<String> validateResource(String resourceName, double currentValue, double nextValue) { @@ -64,15 +74,4 @@ public class ResourcesReductionValidator implements ChangeValidator { return Optional.of(String.format(Locale.ENGLISH ,"Current %s: %.2f, new: %.2f.", resourceName, currentValue, nextValue)); } - private static Map<Pair<ClusterSpec.Type, ClusterSpec.Id>, NodeResources> getRequestedResourcesByClusterId(VespaModel vespaModel) { - return vespaModel.hostSystem().getHosts().stream() - .map(HostResource::spec) - .filter(spec -> spec.membership().isPresent() && spec.requestedResources().isPresent()) - .filter(spec -> !spec.membership().get().retired()) - .collect(Collectors.toMap( - spec -> new Pair<>(spec.membership().get().cluster().type(), spec.membership().get().cluster().id()), - spec -> spec.requestedResources().get(), - (e1, e2) -> e1)); - } - } diff --git a/config-model/src/test/java/com/yahoo/config/model/MockModelContext.java b/config-model/src/test/java/com/yahoo/config/model/MockModelContext.java index 38e438e4d3a..f8ab3cc54c8 100644 --- a/config-model/src/test/java/com/yahoo/config/model/MockModelContext.java +++ b/config-model/src/test/java/com/yahoo/config/model/MockModelContext.java @@ -9,6 +9,7 @@ import com.yahoo.config.model.api.ConfigDefinitionRepo; import com.yahoo.config.model.api.HostProvisioner; import com.yahoo.config.model.api.Model; import com.yahoo.config.model.api.ModelContext; +import com.yahoo.config.model.api.Provisioned; import com.yahoo.config.model.application.provider.BaseDeployLogger; import com.yahoo.config.model.application.provider.MockFileRegistry; import com.yahoo.config.model.application.provider.StaticConfigDefinitionRepo; @@ -53,6 +54,9 @@ public class MockModelContext implements ModelContext { } @Override + public Provisioned provisioned() { return new Provisioned(); } + + @Override public DeployLogger deployLogger() { return new BaseDeployLogger(); } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationTester.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationTester.java index 09e4b377085..915b3c01e1b 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationTester.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationTester.java @@ -6,6 +6,7 @@ import com.yahoo.collections.Pair; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.model.api.ConfigChangeAction; import com.yahoo.config.model.api.HostProvisioner; +import com.yahoo.config.model.api.Provisioned; import com.yahoo.config.model.deploy.DeployState; import com.yahoo.config.model.deploy.TestProperties; import com.yahoo.config.model.provision.InMemoryProvisioner; @@ -32,7 +33,7 @@ import static com.yahoo.config.model.test.MockApplicationPackage.MUSIC_SEARCHDEF */ public class ValidationTester { - private final HostProvisioner hostProvisioner; + private final InMemoryProvisioner hostProvisioner; /** Creates a validation tester with 1 node available */ public ValidationTester() { @@ -45,7 +46,7 @@ public class ValidationTester { } /** Creates a validation tester with a given host provisioner */ - public ValidationTester(HostProvisioner hostProvisioner) { + public ValidationTester(InMemoryProvisioner hostProvisioner) { this.hostProvisioner = hostProvisioner; } @@ -63,6 +64,7 @@ public class ValidationTester { Environment environment, String validationOverrides) { Instant now = LocalDate.parse("2000-01-01", DateTimeFormatter.ISO_DATE).atStartOfDay().atZone(ZoneOffset.UTC).toInstant(); + Provisioned provisioned = hostProvisioner.startProvisionedRecording(); ApplicationPackage newApp = new MockApplicationPackage.Builder() .withServices(services) .withSchemas(ImmutableList.of(MUSIC_SEARCHDEFINITION, BOOK_SEARCHDEFINITION)) @@ -77,6 +79,7 @@ public class ValidationTester { .applicationPackage(newApp) .properties(new TestProperties().setHostedVespa(true)) .modelHostProvisioner(hostProvisioner) + .provisioned(provisioned) .now(now); if (previousModel != null) deployStateBuilder.previousModel(previousModel); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidatorTest.java index 895aa4f6a36..87684aca174 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ClusterSizeReductionValidatorTest.java @@ -27,7 +27,9 @@ public class ClusterSizeReductionValidatorTest { fail("Expected exception due to cluster size reduction"); } catch (IllegalArgumentException expected) { - assertEquals("cluster-size-reduction: Size reduction in 'default' is too large. Current size: 30, new size: 14. New size must be at least 50% of the current size. " + + assertEquals("cluster-size-reduction: Size reduction in 'default' is too large: " + + "New min size must be at least 50% of the current min size. " + + "Current size: 30, new size: 14. " + ValidationOverrides.toAllowMessage(ValidationId.clusterSizeReduction), Exceptions.toMessageString(expected)); } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidatorTest.java index 1322a9061ed..9a363789798 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ResourcesReductionValidatorTest.java @@ -30,7 +30,7 @@ public class ResourcesReductionValidatorTest { fail("Expected exception due to resources reduction"); } catch (IllegalArgumentException expected) { assertEquals("resources-reduction: Resource reduction in 'default' is too large. " + - "Current memory GB: 64.00, new: 16.00. New resources must be at least 50% of the current resources. " + + "Current memory GB: 64.00, new: 16.00. New min resources must be at least 50% of the current min resources. " + ValidationOverrides.toAllowMessage(ValidationId.resourcesReduction), Exceptions.toMessageString(expected)); } @@ -45,7 +45,7 @@ public class ResourcesReductionValidatorTest { } catch (IllegalArgumentException expected) { assertEquals("resources-reduction: Resource reduction in 'default' is too large. " + "Current vCPU: 8.00, new: 3.00. Current memory GB: 64.00, new: 16.00. Current disk GB: 800.00, new: 200.00. " + - "New resources must be at least 50% of the current resources. " + + "New min resources must be at least 50% of the current min resources. " + ValidationOverrides.toAllowMessage(ValidationId.resourcesReduction), Exceptions.toMessageString(expected)); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java index aaa79d6572a..2b25b69d09c 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java @@ -12,6 +12,7 @@ import com.yahoo.config.model.api.HostProvisioner; import com.yahoo.config.model.api.Model; import com.yahoo.config.model.api.ModelContext; import com.yahoo.config.model.api.EndpointCertificateSecrets; +import com.yahoo.config.model.api.Provisioned; import com.yahoo.config.model.api.TlsSecrets; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.AthenzDomain; @@ -41,6 +42,7 @@ public class ModelContextImpl implements ModelContext { private final ConfigDefinitionRepo configDefinitionRepo; private final FileRegistry fileRegistry; private final Optional<HostProvisioner> hostProvisioner; + private final Provisioned provisioned; private final ModelContext.Properties properties; private final Optional<File> appDir; @@ -65,6 +67,7 @@ public class ModelContextImpl implements ModelContext { ConfigDefinitionRepo configDefinitionRepo, FileRegistry fileRegistry, Optional<HostProvisioner> hostProvisioner, + Provisioned provisioned, ModelContext.Properties properties, Optional<File> appDir, Optional<String> wantedDockerImageRepository, @@ -77,6 +80,7 @@ public class ModelContextImpl implements ModelContext { this.configDefinitionRepo = configDefinitionRepo; this.fileRegistry = fileRegistry; this.hostProvisioner = hostProvisioner; + this.provisioned = provisioned; this.properties = properties; this.appDir = appDir; this.wantedDockerImageRepository = wantedDockerImageRepository; @@ -102,6 +106,9 @@ public class ModelContextImpl implements ModelContext { public Optional<HostProvisioner> hostProvisioner() { return hostProvisioner; } @Override + public Provisioned provisioned() { return provisioned; } + + @Override public DeployLogger deployLogger() { return deployLogger; } @Override diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java index 46a0c44674b..99bec2c9db7 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ActivatedModelsBuilder.java @@ -8,6 +8,7 @@ import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.model.api.ConfigDefinitionRepo; import com.yahoo.config.model.api.ModelContext; import com.yahoo.config.model.api.ModelFactory; +import com.yahoo.config.model.api.Provisioned; import com.yahoo.config.model.application.provider.MockFileRegistry; import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.ApplicationId; @@ -90,6 +91,7 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { log.log(LogLevel.DEBUG, String.format("Loading model version %s for session %s application %s", modelFactory.version(), appGeneration, applicationId)); ModelContext.Properties modelContextProperties = createModelContextProperties(applicationId); + Provisioned provisioned = new Provisioned(); ModelContext modelContext = new ModelContextImpl( applicationPackage, Optional.empty(), @@ -97,7 +99,10 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { logger, configDefinitionRepo, getForVersionOrLatest(applicationPackage.getFileRegistries(), modelFactory.version()).orElse(new MockFileRegistry()), - createStaticProvisioner(applicationPackage.getAllocatedHosts(), modelContextProperties), + createStaticProvisioner(applicationPackage.getAllocatedHosts(), + modelContextProperties.applicationId(), + provisioned), + provisioned, modelContextProperties, Optional.empty(), wantedDockerImageRepository, diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java index f26c172b7f9..cc4378ae05e 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java @@ -7,6 +7,7 @@ import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.model.api.HostProvisioner; import com.yahoo.config.model.api.ModelContext; import com.yahoo.config.model.api.ModelFactory; +import com.yahoo.config.model.api.Provisioned; import com.yahoo.config.provision.AllocatedHosts; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationLockException; @@ -252,15 +253,17 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { * returns empty otherwise, which may either mean that no hosts are allocated or that we are running * non-hosted and should default to use hosts defined in the application package, depending on context */ - Optional<HostProvisioner> createStaticProvisioner(Optional<AllocatedHosts> allocatedHosts, ModelContext.Properties properties) { + Optional<HostProvisioner> createStaticProvisioner(Optional<AllocatedHosts> allocatedHosts, + ApplicationId applicationId, + Provisioned provisioned) { if (hosted && allocatedHosts.isPresent()) - return Optional.of(new StaticProvisioner(allocatedHosts.get(), createNodeRepositoryProvisioner(properties).get())); + return Optional.of(new StaticProvisioner(allocatedHosts.get(), createNodeRepositoryProvisioner(applicationId, provisioned).get())); return Optional.empty(); } - Optional<HostProvisioner> createNodeRepositoryProvisioner(ModelContext.Properties properties) { + Optional<HostProvisioner> createNodeRepositoryProvisioner(ApplicationId applicationId, Provisioned provisioned) { return hostProvisionerProvider.getHostProvisioner().map( - provisioner -> new ProvisionerAdapter(provisioner, properties.applicationId())); + provisioner -> new ProvisionerAdapter(provisioner, applicationId, provisioned)); } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java index 598cec325b6..6d0403f21c8 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/PreparedModelsBuilder.java @@ -11,6 +11,7 @@ import com.yahoo.config.model.api.Model; import com.yahoo.config.model.api.ModelContext; import com.yahoo.config.model.api.ModelCreateResult; import com.yahoo.config.model.api.ModelFactory; +import com.yahoo.config.model.api.Provisioned; import com.yahoo.config.model.api.ValidationParameters; import com.yahoo.config.model.api.ValidationParameters.IgnoreValidationErrors; import com.yahoo.config.model.application.provider.FilesApplicationPackage; @@ -92,6 +93,7 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P FileDistributionProvider fileDistributionProvider = fileDistributionFactory.createProvider(context.getServerDBSessionDir()); // Use empty on non-hosted systems, use already allocated hosts if available, create connection to a host provisioner otherwise + Provisioned provisioned = new Provisioned(); ModelContext modelContext = new ModelContextImpl( applicationPackage, modelOf(modelVersion), @@ -99,7 +101,8 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P logger, configDefinitionRepo, fileDistributionProvider.getFileRegistry(), - createHostProvisioner(allocatedHosts), + createHostProvisioner(allocatedHosts, provisioned), + provisioned, properties, getAppDir(applicationPackage), wantedDockerImageRepository, @@ -122,11 +125,15 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P // This method is an excellent demonstration of what happens when one is too liberal with Optional // -bratseth, who had to write the below :-\ - private Optional<HostProvisioner> createHostProvisioner(Optional<AllocatedHosts> allocatedHosts) { - Optional<HostProvisioner> nodeRepositoryProvisioner = createNodeRepositoryProvisioner(properties); + private Optional<HostProvisioner> createHostProvisioner(Optional<AllocatedHosts> allocatedHosts, + Provisioned provisioned) { + Optional<HostProvisioner> nodeRepositoryProvisioner = createNodeRepositoryProvisioner(properties.applicationId(), + provisioned); if ( ! allocatedHosts.isPresent()) return nodeRepositoryProvisioner; - Optional<HostProvisioner> staticProvisioner = createStaticProvisioner(allocatedHosts, properties); + Optional<HostProvisioner> staticProvisioner = createStaticProvisioner(allocatedHosts, + properties.applicationId(), + provisioned); if ( ! staticProvisioner.isPresent()) return Optional.empty(); // Since we have hosts allocated this means we are on non-hosted // Nodes are already allocated by a model and we should use them unless this model requests hosts from a diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/provision/ProvisionerAdapter.java b/configserver/src/main/java/com/yahoo/vespa/config/server/provision/ProvisionerAdapter.java index daacdf9fd50..7ce255910e6 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/provision/ProvisionerAdapter.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/provision/ProvisionerAdapter.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.config.server.provision; import com.yahoo.config.model.api.HostProvisioner; +import com.yahoo.config.model.api.Provisioned; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterSpec; @@ -23,10 +24,12 @@ public class ProvisionerAdapter implements HostProvisioner { private final Provisioner provisioner; private final ApplicationId applicationId; + private final Provisioned provisioned; - public ProvisionerAdapter(Provisioner provisioner, ApplicationId applicationId) { + public ProvisionerAdapter(Provisioner provisioner, ApplicationId applicationId, Provisioned provisioned) { this.provisioner = provisioner; this.applicationId = applicationId; + this.provisioned = provisioned; } @Override @@ -38,12 +41,16 @@ public class ProvisionerAdapter implements HostProvisioner { @Override @Deprecated // TODO: Remove after April 2020 public List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) { + provisioned.add(cluster.id(), capacity); return provisioner.prepare(applicationId, cluster, capacity.withGroups(groups), logger); } @Override public List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, ProvisionLogger logger) { + provisioned.add(cluster.id(), capacity); return provisioner.prepare(applicationId, cluster, capacity, logger); } + + } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java index c3124fd3ed6..39dccc6b482 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ModelContextImplTest.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.config.server; import com.yahoo.component.Version; import com.yahoo.config.model.api.ContainerEndpoint; import com.yahoo.config.model.api.ModelContext; +import com.yahoo.config.model.api.Provisioned; import com.yahoo.config.model.application.provider.BaseDeployLogger; import com.yahoo.config.model.application.provider.MockFileRegistry; import com.yahoo.config.model.test.MockApplicationPackage; @@ -46,6 +47,7 @@ public class ModelContextImplTest { new StaticConfigDefinitionRepo(), new MockFileRegistry(), Optional.empty(), + new Provisioned(), new ModelContextImpl.Properties( ApplicationId.defaultId(), true, |