diff options
author | Jon Bratseth <jonbratseth@yahoo.com> | 2017-06-16 11:14:13 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-06-16 11:14:13 +0200 |
commit | fa64d4836b8df9bfa0ed57daee66a6fc84c4b6bf (patch) | |
tree | cd5832f9fee4cc17dac334d6dc93c94b8c2a02a2 | |
parent | d91ef9c2ea42ca7c5d8925c4ff21664adb43892b (diff) | |
parent | 95eef9b4bc9e4607ab63f8a5e9bec6bcaaebbc2e (diff) |
Merge pull request #2794 from yahoo/bratseth/move-validation-overrides
Bratseth/move validation overrides
67 files changed, 748 insertions, 605 deletions
diff --git a/config-model-api/pom.xml b/config-model-api/pom.xml index fd62a5c4c2f..6618612df31 100644 --- a/config-model-api/pom.xml +++ b/config-model-api/pom.xml @@ -63,6 +63,12 @@ <scope>test</scope> </dependency> <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>testutil</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>com.google.guava</groupId> <artifactId>guava-testlib</artifactId> <version>17.0</version> diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java index 6c996961e59..2842ac5bd30 100644 --- a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java @@ -128,7 +128,7 @@ public class DeploymentSpec { .collect(Collectors.toList()); } - /** Returns the XML form of this spec, or null if it was not created by fromXml or is the empty spec */ + /** Returns the XML form of this spec, or null if it was not created by fromXml, nor is empty */ public String xmlForm() { return xmlForm; } /** Returns whether this deployment spec specifies the given zone, either implicitly or explicitly */ diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/ValidationId.java b/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationId.java index 8f9b0081db1..37cb6e40f3d 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/ValidationId.java +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationId.java @@ -1,5 +1,5 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.model.application.validation; +package com.yahoo.config.application.api; import java.util.Optional; diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationOverrides.java b/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationOverrides.java new file mode 100644 index 00000000000..11f9add6b25 --- /dev/null +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationOverrides.java @@ -0,0 +1,162 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.config.application.api; + +import com.google.common.collect.ImmutableList; +import com.yahoo.io.IOUtils; +import com.yahoo.text.XML; +import org.w3c.dom.Element; + +import java.io.IOException; +import java.io.Reader; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * A set of allows which suppresses specific validations in limited time periods. + * This is useful to be able to complete a deployment in cases where the application + * owner believes that the changes to be deployed have acceptable consequences. + * Immutable. + * + * @author bratseth + */ +public class ValidationOverrides { + + public static final ValidationOverrides empty = new ValidationOverrides(ImmutableList.of(), "<deployment version='1.0'/>"); + + private final List<Allow> overrides; + + private final String xmlForm; + + /** Creates a validation overrides which does not have an xml form */ + public ValidationOverrides(List<Allow> overrides) { + this(overrides, null); + } + + private ValidationOverrides(List<Allow> overrides, String xmlForm) { + this.overrides = ImmutableList.copyOf(overrides); + this.xmlForm = xmlForm; + } + + /** Throws a ValidationException unless this validation is overridden at this time */ + public void invalid(ValidationId validationId, String message, Instant now) { + if ( ! allows(validationId, now)) + throw new ValidationException(validationId, message); + } + + public boolean allows(String validationIdString, Instant now) { + Optional<ValidationId> validationId = ValidationId.from(validationIdString); + if ( ! validationId.isPresent()) return false; // unknown id -> not allowed + return allows(validationId.get(), now); + } + + /** Returns whether the given (assumed invalid) change is allowed by this at the moment */ + public boolean allows(ValidationId validationId, Instant now) { + for (Allow override : overrides) { + if (now.plus(Duration.ofDays(30)).isBefore(override.until)) + throw new IllegalArgumentException(override + " is too far in the future: Max 30 days is allowed"); + if (override.allows(validationId, now)) + return true; + } + return false; + } + + /** Returns the XML form of this, or null if it was not created by fromXml, nor is empty */ + public String xmlForm() { return xmlForm; } + + /** + * Returns a ValidationOverrides instance with the content of the given Reader. + * + * @param reader the reader containing a validation-overrides XML structure + * @return a ValidationOverrides from the argument + * @throws IllegalArgumentException if the validation-allows.xml file exists but is invalid + */ + public static ValidationOverrides fromXml(Reader reader) { + try { + return fromXml(IOUtils.readAll(reader)); + } catch (IOException e) { + throw new IllegalArgumentException("Could not read deployment spec", e); + } + } + + /** + * Returns a ValidationOverrides instance with the content of the given XML string. + * An empty ValidationOverrides is returned if the argument is empty. + * + * @param xmlForm the string which optionally contains a validation-overrides XML structure + * @return a ValidationOverrides from the argument + * @throws IllegalArgumentException if the validation-allows.xml file exists but is invalid + */ + public static ValidationOverrides fromXml(String xmlForm) { + if ( xmlForm.isEmpty()) return ValidationOverrides.empty; + + try { + // Assume valid structure is ensured by schema validation + Element root = XML.getDocument(xmlForm).getDocumentElement(); + List<ValidationOverrides.Allow> overrides = new ArrayList<>(); + for (Element allow : XML.getChildren(root, "allow")) { + Instant until = LocalDate.parse(allow.getAttribute("until"), DateTimeFormatter.ISO_DATE) + .atStartOfDay().atZone(ZoneOffset.UTC).toInstant() + .plus(Duration.ofDays(1)); // Make the override valid *on* the "until" date + Optional<ValidationId> validationId = ValidationId.from(XML.getValue(allow)); + if (validationId.isPresent()) // skip unknown ids as they may be valid for other model versions + overrides.add(new ValidationOverrides.Allow(validationId.get(), until)); + } + return new ValidationOverrides(overrides, xmlForm); + } + catch (IllegalArgumentException e) { + throw new IllegalArgumentException("validation-overrides is invalid", e); + } + } + + /** A validation override which allows a particular change. Immutable. */ + public static class Allow { + + private final ValidationId validationId; + private final Instant until; + + public Allow(ValidationId validationId, Instant until) { + this.validationId = validationId; + this.until = until; + } + + public boolean allows(ValidationId validationId, Instant now) { + return this.validationId.equals(validationId) && now.isBefore(until); + } + + @Override + public String toString() { return "allow '" + validationId + "' until " + until; } + + } + + /** + * A deployment validation exception. + * Deployment validations can be {@link ValidationOverrides overridden} based on their id. + * The purpose of this exception is to model that id as a separate field. + */ + public static class ValidationException extends IllegalArgumentException { + + static final long serialVersionUID = 789984668; + + private final ValidationId validationId; + + private ValidationException(ValidationId validationId, String message) { + super(message); + this.validationId = validationId; + } + + /** Returns the unique id of this validation, which can be used to {@link ValidationOverrides override} it */ + public ValidationId validationId() { return validationId; } + + /** Returns "validationId: message" */ + @Override + public String getMessage() { return validationId + ": " + super.getMessage(); } + + } + +} diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/Model.java b/config-model-api/src/main/java/com/yahoo/config/model/api/Model.java index c86859d2357..0c038077fe4 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/Model.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/Model.java @@ -6,6 +6,8 @@ import com.yahoo.vespa.config.ConfigKey; import com.yahoo.vespa.config.ConfigPayload; import com.yahoo.vespa.config.buildergen.ConfigDefinition; +import java.time.Clock; +import java.time.Instant; import java.util.Optional; import java.util.Set; import java.util.Collection; @@ -67,7 +69,12 @@ public interface Model { * This is a validation override which is useful when we skip loading old config models * due to some problem, or when we need to try a newer version of the platform on some node. */ - default boolean allowModelVersionMismatch() { return false; } + default boolean allowModelVersionMismatch(Instant now) { return false; } + + /** @deprecated pass now. */ + // TODO: Remove this when no version older than 6.115 is deployed anywhere + @Deprecated + default boolean allowModelVersionMismatch() { return allowModelVersionMismatch(Clock.systemUTC().instant()); } /** * Returns whether old config models should be loaded (default) or not. @@ -79,6 +86,11 @@ public interface Model { * If a model returns true to this it should also return true to {@link #allowModelVersionMismatch} * or clients requesting config for those old models will not get config at all. */ - default boolean skipOldConfigModels() { return false; } + default boolean skipOldConfigModels(Instant now) { return false; } + + /** @deprecated pass now. */ + // TODO: Remove this when no version older than 6.115 is deployed anywhere + @Deprecated + default boolean skipOldConfigModels() { return skipOldConfigModels(Clock.systemUTC().instant()); } } diff --git a/config-model-api/src/test/java/com/yahoo/config/application/api/ValidationOverrideTest.java b/config-model-api/src/test/java/com/yahoo/config/application/api/ValidationOverrideTest.java new file mode 100644 index 00000000000..c17b9a6f220 --- /dev/null +++ b/config-model-api/src/test/java/com/yahoo/config/application/api/ValidationOverrideTest.java @@ -0,0 +1,96 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.config.application.api; + +import com.yahoo.test.ManualClock; +import org.junit.Assert; +import org.junit.Test; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.io.StringReader; +import java.time.Instant; +import java.util.Optional; + +import static org.junit.Assert.assertEquals; + +/** + * @author bratseth + */ +public class ValidationOverrideTest { + + @Test + public void testValidationOverridesInIsolation() throws IOException, SAXException { + String validationOverrides = + "<validation-overrides>" + + " <allow until='2000-01-01'>indexing-change</allow>" + + " <allow until='2000-01-03' comment='any text'>indexing-mode-change</allow>" + + "</validation-overrides>"; + { + + ValidationOverrides overrides = ValidationOverrides.fromXml(new StringReader(validationOverrides)); + Instant now = ManualClock.at("2000-01-01T23:59:00"); + assertOverridden("indexing-change", overrides, now); + assertOverridden("indexing-mode-change", overrides, now); + assertNotOverridden("field-type-change", overrides, now); + + assertEquals(validationOverrides, overrides.xmlForm()); + } + + { + ValidationOverrides overrides = ValidationOverrides.fromXml(new StringReader(validationOverrides)); + Instant now = ManualClock.at("2000-01-02T00:00:00"); + assertNotOverridden("indexing-change", overrides, now); + assertOverridden("indexing-mode-change", overrides, now); + assertNotOverridden("field-type-change", overrides, now); + } + + { + ValidationOverrides overrides = ValidationOverrides.fromXml(new StringReader(validationOverrides)); + Instant now = ManualClock.at("2000-01-04T00:00:00"); + assertNotOverridden("indexing-change", overrides, now); + assertNotOverridden("indexing-mode-change", overrides, now); + assertNotOverridden("field-type-change", overrides, now); + } + + } + + @Test + public void testInvalidOverridePeriod() throws IOException, SAXException { + String validationOverrides = + "<validation-overrides>" + + " <allow until='2000-02-02'>indexing-change</allow>" + + "</validation-overrides>"; + + try { + ValidationOverrides overrides = ValidationOverrides.fromXml(new StringReader(validationOverrides)); + Instant now = ManualClock.at("2000-01-01T23:59:00"); + overrides.allows("indexing-change", now); + Assert.fail("Expected validation interval override validation validation failure"); + } + catch (IllegalArgumentException e) { + Assert.assertEquals("allow 'indexing-change' until 2000-02-03T00:00:00Z is too far in the future: Max 30 days is allowed", + e.getMessage()); + } + } + + @Test + public void testEmpty() { + ValidationOverrides empty = ValidationOverrides.empty; + ValidationOverrides emptyReserialized = ValidationOverrides.fromXml(empty.xmlForm()); + assertEquals(empty.xmlForm(), emptyReserialized.xmlForm()); + } + + private void assertOverridden(String validationId, ValidationOverrides overrides, Instant now) { + overrides.invalid(ValidationId.from(validationId).get(), "message", now); // should not throw exception + } + + private void assertNotOverridden(String validationId, ValidationOverrides overrides, Instant now) { + try { + overrides.invalid(ValidationId.from(validationId).get(), "message", now); + Assert.fail("Expected '" + validationId + "' to not be overridden"); + } + catch (ValidationOverrides.ValidationException expected) { + } + } + +} 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 2499f2f625d..25d45dee234 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 @@ -7,7 +7,6 @@ import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.application.api.DeployLogger; import com.yahoo.config.application.api.FileRegistry; import com.yahoo.config.application.api.UnparsedConfigDefinition; -import com.yahoo.config.codegen.CNode; import com.yahoo.config.model.api.ConfigDefinitionRepo; import com.yahoo.config.model.api.HostProvisioner; import com.yahoo.config.model.api.Model; @@ -19,7 +18,6 @@ import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.config.provision.Rotation; import com.yahoo.config.provision.Zone; import com.yahoo.io.reader.NamedReader; -import com.yahoo.log.LogLevel; import com.yahoo.searchdefinition.RankProfileRegistry; import com.yahoo.searchdefinition.SearchBuilder; import com.yahoo.searchdefinition.parser.ParseException; @@ -27,8 +25,7 @@ import com.yahoo.vespa.config.ConfigDefinition; import com.yahoo.vespa.config.ConfigDefinitionBuilder; import com.yahoo.vespa.config.ConfigDefinitionKey; import com.yahoo.vespa.documentmodel.DocumentModel; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; -import com.yahoo.vespa.model.application.validation.xml.ValidationOverridesXMLReader; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.container.search.QueryProfiles; import com.yahoo.vespa.model.container.search.QueryProfilesBuilder; import com.yahoo.vespa.model.container.search.SemanticRuleBuilder; @@ -46,7 +43,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.logging.Logger; /** * Contains various state during deploy that should be available in all builders of a {@link com.yahoo.config.model.ConfigModel} @@ -71,6 +67,7 @@ public class DeployState implements ConfigDefinitionStore { private final SemanticRules semanticRules; private final ValidationOverrides validationOverrides; private final Version wantedNodeVespaVersion; + private final Instant now; private final HostProvisioner provisioner; @@ -102,8 +99,9 @@ public class DeployState implements ConfigDefinitionStore { this.zone = zone; this.queryProfiles = queryProfiles; // TODO: Remove this by seeing how pagetemplates are propagated this.semanticRules = semanticRules; // TODO: Remove this by seeing how pagetemplates are propagated - this.validationOverrides = new ValidationOverridesXMLReader().read(applicationPackage.getValidationOverrides(), now); + this.validationOverrides = applicationPackage.getValidationOverrides().map(ValidationOverrides::fromXml).orElse(ValidationOverrides.empty); this.wantedNodeVespaVersion = wantedNodeVespaVersion; + this.now = now; } public static HostProvisioner getDefaultModelHostProvisioner(ApplicationPackage applicationPackage) { @@ -213,6 +211,8 @@ public class DeployState implements ConfigDefinitionStore { public SemanticRules getSemanticRules() { return semanticRules; } public Version getWantedNodeVespaVersion() { return wantedNodeVespaVersion; } + + public Instant now() { return now; } public static class Builder { 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 1be22be1947..c61435ca831 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 @@ -29,8 +29,8 @@ import com.yahoo.vespa.config.ConfigPayloadBuilder; import com.yahoo.vespa.config.GenericConfig; import com.yahoo.vespa.config.buildergen.ConfigDefinition; import com.yahoo.vespa.model.admin.Admin; -import com.yahoo.vespa.model.application.validation.ValidationId; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; +import com.yahoo.config.application.api.ValidationId; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.builder.VespaModelBuilder; import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder; import com.yahoo.vespa.model.clients.Clients; @@ -51,6 +51,7 @@ import java.io.File; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Constructor; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; @@ -212,14 +213,14 @@ public final class VespaModel extends AbstractConfigProducerRoot implements Seri public ApplicationConfigProducerRoot getVespa() { return root; } @Override - public boolean allowModelVersionMismatch() { - return validationOverrides.allows(ValidationId.configModelVersionMismatch) || - validationOverrides.allows(ValidationId.skipOldConfigModels); // implies this + public boolean allowModelVersionMismatch(Instant now) { + return validationOverrides.allows(ValidationId.configModelVersionMismatch, now) || + validationOverrides.allows(ValidationId.skipOldConfigModels, now); // implies this } @Override - public boolean skipOldConfigModels() { - return validationOverrides.allows(ValidationId.skipOldConfigModels); + public boolean skipOldConfigModels(Instant now) { + return validationOverrides.allows(ValidationId.skipOldConfigModels, now); } /** diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java index d1b8ae63641..490627e7b45 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.model.application.validation; import com.yahoo.config.application.api.DeployLogger; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.config.model.api.ConfigChangeAction; import com.yahoo.config.model.api.Model; import com.yahoo.config.model.deploy.DeployState; @@ -15,6 +16,7 @@ import com.yahoo.vespa.model.application.validation.change.IndexedSearchClusterC import com.yahoo.vespa.model.application.validation.change.IndexingModeChangeValidator; import com.yahoo.vespa.model.application.validation.change.StartupCommandChangeValidator; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -56,13 +58,14 @@ public class Validation { Optional<Model> currentActiveModel = deployState.getPreviousModel(); if (currentActiveModel.isPresent() && (currentActiveModel.get() instanceof VespaModel)) return validateChanges((VespaModel)currentActiveModel.get(), model, - deployState.validationOverrides(), deployState.getDeployLogger()); + deployState.validationOverrides(), deployState.getDeployLogger(), deployState.now()); else return new ArrayList<>(); } private static List<ConfigChangeAction> validateChanges(VespaModel currentModel, VespaModel nextModel, - ValidationOverrides overrides, DeployLogger logger) { + ValidationOverrides overrides, DeployLogger logger, + Instant now) { ChangeValidator[] validators = new ChangeValidator[] { new IndexingModeChangeValidator(), new IndexedSearchClusterChangeValidator(), @@ -73,7 +76,7 @@ public class Validation { new ContainerRestartValidator(), }; return Arrays.stream(validators) - .flatMap(v -> v.validate(currentModel, nextModel, overrides).stream()) + .flatMap(v -> v.validate(currentModel, nextModel, overrides, now).stream()) .collect(toList()); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/ValidationOverrides.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/ValidationOverrides.java deleted file mode 100644 index 25464ce40ba..00000000000 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/ValidationOverrides.java +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.model.application.validation; - -import com.google.common.collect.ImmutableList; - -import java.time.Duration; -import java.time.Instant; -import java.util.List; -import java.util.Optional; - -/** - * A set of allows which suppresses specific validations in limited time periods. - * This is useful to be able to complete a deployment in cases where the application - * owner believes that the changes to be deployed have acceptable consequences. - * Immutable. - * - * @author bratseth - */ -public class ValidationOverrides { - - private final List<Allow> overrides; - - /** Instant to use as "now". This is a field to allow unit testing. */ - private final Instant now; - - /** Creates validation overrides for the current instant */ - public ValidationOverrides(List<Allow> overrides) { - this(overrides, Instant.now()); - } - - public ValidationOverrides(List<Allow> overrides, Instant now) { - this.overrides = ImmutableList.copyOf(overrides); - this.now = now; - for (Allow override : overrides) - if (now.plus(Duration.ofDays(30)).isBefore(override.until)) - throw new IllegalArgumentException(override + " is too far in the future: Max 30 days is allowed"); - } - - /** Throws a ValidationException unless this validation is overridden at this time */ - public void invalid(ValidationId validationId, String message) { - if ( ! allows(validationId)) - throw new ValidationException(validationId, message); - } - - public boolean allows(String validationIdString) { - Optional<ValidationId> validationId = ValidationId.from(validationIdString); - if ( ! validationId.isPresent()) return false; // unknown id -> not allowed - return allows(validationId.get()); - } - - /** Returns whether the given (assumed invalid) change is allowed by this at the moment */ - public boolean allows(ValidationId validationId) { - for (Allow override : overrides) - if (override.allows(validationId, now)) - return true; - return false; - } - - public static ValidationOverrides empty() { return new ValidationOverrides(ImmutableList.of()); } - - /** A validation override which allows a particular change. Immutable. */ - public static class Allow { - - private final ValidationId validationId; - private final Instant until; - - public Allow(ValidationId validationId, Instant until) { - this.validationId = validationId; - this.until = until; - } - - public boolean allows(ValidationId validationId, Instant now) { - return this.validationId.equals(validationId) && now.isBefore(until); - } - - @Override - public String toString() { return "allow '" + validationId + "' until " + until; } - - } - - /** - * A deployment validation exception. - * Deployment validations can be {@link ValidationOverrides overridden} based on their id. - * The purpose of this exception is to model that id as a separate field. - */ - public static class ValidationException extends IllegalArgumentException { - - private final ValidationId validationId; - - private ValidationException(ValidationId validationId, String message) { - super(message); - this.validationId = validationId; - } - - /** Returns the unique id of this validation, which can be used to {@link ValidationOverrides override} it */ - public ValidationId validationId() { return validationId; } - - /** Returns "validationId: message" */ - @Override - public String getMessage() { return validationId + ": " + super.getMessage(); } - - } - -} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ChangeValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ChangeValidator.java index 72eda0ae3ec..bec7fd1518f 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ChangeValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ChangeValidator.java @@ -3,8 +3,9 @@ package com.yahoo.vespa.model.application.validation.change; import com.yahoo.config.model.api.ConfigChangeAction; import com.yahoo.vespa.model.VespaModel; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; +import com.yahoo.config.application.api.ValidationOverrides; +import java.time.Instant; import java.util.List; /** @@ -22,9 +23,10 @@ public interface ChangeValidator { * @param current the current active model * @param next the next model we would like to activate * @param overrides validation overrides + * @param now the instant to use as now * @return a list of actions specifying what needs to be done in order to activate the new model. * Return an empty list if nothing needs to be done */ - List<ConfigChangeAction> validate(VespaModel current, VespaModel next, ValidationOverrides overrides); + List<ConfigChangeAction> validate(VespaModel current, VespaModel next, ValidationOverrides overrides, Instant now); } 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 056b1a80d23..d14fe91a53b 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 @@ -3,11 +3,12 @@ package com.yahoo.vespa.model.application.validation.change; import com.yahoo.config.model.api.ConfigChangeAction; import com.yahoo.vespa.model.VespaModel; -import com.yahoo.vespa.model.application.validation.ValidationId; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; +import com.yahoo.config.application.api.ValidationId; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.container.ContainerCluster; import com.yahoo.vespa.model.content.cluster.ContentCluster; +import java.time.Instant; import java.util.Collections; import java.util.List; @@ -19,14 +20,15 @@ import java.util.List; public class ClusterSizeReductionValidator implements ChangeValidator { @Override - public List<ConfigChangeAction> validate(VespaModel current, VespaModel next, ValidationOverrides overrides) { + 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(), - overrides); + overrides, + now); } for (ContentCluster currentCluster : current.getContentClusters().values()) { @@ -35,18 +37,20 @@ public class ClusterSizeReductionValidator implements ChangeValidator { validate(currentCluster.getSearch().getSearchNodes().size(), nextCluster.getSearch().getSearchNodes().size(), currentCluster.getName(), - overrides); + overrides, + now); } return Collections.emptyList(); } - private void validate(int currentSize, int nextSize, String clusterName, ValidationOverrides overrides) { + private void validate(int currentSize, int nextSize, String clusterName, ValidationOverrides overrides, Instant now) { // 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"); + ", new size: " + nextSize + ". New size must be at least 50% of the current size", + now); } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ConfigValueChangeValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ConfigValueChangeValidator.java index d1d5a1cbfcb..3a97bf4b876 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ConfigValueChangeValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ConfigValueChangeValidator.java @@ -9,12 +9,11 @@ import com.yahoo.config.model.producer.AbstractConfigProducerRoot; import com.yahoo.vespa.model.Service; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.application.validation.RestartConfigs; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; -import com.yahoo.vespa.model.application.validation.change.ChangeValidator; -import com.yahoo.vespa.model.application.validation.change.VespaRestartAction; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.utils.internal.ReflectionUtil; import org.apache.commons.lang3.ClassUtils; +import java.time.Instant; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -42,7 +41,7 @@ public class ConfigValueChangeValidator implements ChangeValidator { /** Inspects the configuration in the new and old Vespa model to determine which services that require restart */ @Override public List<ConfigChangeAction> validate(VespaModel currentModel, VespaModel nextModel, - ValidationOverrides overrides) { + ValidationOverrides overrides, Instant now) { return findConfigChangesFromModels(currentModel, nextModel).collect(Collectors.toList()); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ContainerRestartValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ContainerRestartValidator.java index c2286264771..c27600578b5 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ContainerRestartValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ContainerRestartValidator.java @@ -4,9 +4,10 @@ package com.yahoo.vespa.model.application.validation.change; import com.yahoo.config.model.api.ConfigChangeAction; import com.yahoo.container.QrConfig; import com.yahoo.vespa.model.VespaModel; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.container.Container; +import java.time.Instant; import java.util.List; import static java.util.stream.Collectors.toList; @@ -19,7 +20,8 @@ import static java.util.stream.Collectors.toList; public class ContainerRestartValidator implements ChangeValidator { @Override - public List<ConfigChangeAction> validate(VespaModel currentModel, VespaModel nextModel, ValidationOverrides ignored) { + public List<ConfigChangeAction> validate(VespaModel currentModel, VespaModel nextModel, ValidationOverrides ignored, + Instant now) { return nextModel.getContainerClusters().values().stream() .flatMap(cluster -> cluster.getContainers().stream()) .filter(container -> isExistingContainer(container, currentModel)) diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ContentClusterRemovalValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ContentClusterRemovalValidator.java index 5441592b391..52253411419 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ContentClusterRemovalValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/ContentClusterRemovalValidator.java @@ -3,10 +3,11 @@ package com.yahoo.vespa.model.application.validation.change; import com.yahoo.config.model.api.ConfigChangeAction; import com.yahoo.vespa.model.VespaModel; -import com.yahoo.vespa.model.application.validation.ValidationId; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; +import com.yahoo.config.application.api.ValidationId; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.content.cluster.ContentCluster; +import java.time.Instant; import java.util.Collections; import java.util.List; @@ -19,13 +20,14 @@ import java.util.List; public class ContentClusterRemovalValidator implements ChangeValidator { @Override - public List<ConfigChangeAction> validate(VespaModel current, VespaModel next, ValidationOverrides overrides) { + public List<ConfigChangeAction> validate(VespaModel current, VespaModel next, ValidationOverrides overrides, Instant now) { for (String currentClusterId : current.getContentClusters().keySet()) { ContentCluster nextCluster = next.getContentClusters().get(currentClusterId); if (nextCluster == null) overrides.invalid(ValidationId.contentClusterRemoval, "Content cluster '" + currentClusterId + "' is removed. " + - "This will cause loss of all data in this cluster"); + "This will cause loss of all data in this cluster", + now); } return Collections.emptyList(); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/IndexedSearchClusterChangeValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/IndexedSearchClusterChangeValidator.java index ca8bc7a3eeb..4ed28331467 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/IndexedSearchClusterChangeValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/IndexedSearchClusterChangeValidator.java @@ -5,13 +5,14 @@ import com.yahoo.config.model.api.ConfigChangeAction; import com.yahoo.config.model.api.ServiceInfo; import com.yahoo.documentmodel.NewDocumentType; import com.yahoo.vespa.model.VespaModel; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.application.validation.change.search.DocumentDatabaseChangeValidator; import com.yahoo.vespa.model.content.ContentSearchCluster; import com.yahoo.vespa.model.content.cluster.ContentCluster; import com.yahoo.vespa.model.search.DocumentDatabase; import com.yahoo.vespa.model.search.IndexedSearchCluster; +import java.time.Instant; import java.util.*; import java.util.stream.Collectors; @@ -24,12 +25,12 @@ import java.util.stream.Collectors; public class IndexedSearchClusterChangeValidator implements ChangeValidator { @Override - public List<ConfigChangeAction> validate(VespaModel current, VespaModel next, ValidationOverrides overrides) { + public List<ConfigChangeAction> validate(VespaModel current, VespaModel next, ValidationOverrides overrides, Instant now) { List<ConfigChangeAction> result = new ArrayList<>(); for (Map.Entry<String, ContentCluster> currentEntry : current.getContentClusters().entrySet()) { ContentCluster nextCluster = next.getContentClusters().get(currentEntry.getKey()); if (nextCluster != null && nextCluster.getSearch().hasIndexedCluster()) { - result.addAll(validateContentCluster(currentEntry.getValue(), nextCluster, overrides)); + result.addAll(validateContentCluster(currentEntry.getValue(), nextCluster, overrides, now)); } } return result; @@ -37,15 +38,17 @@ public class IndexedSearchClusterChangeValidator implements ChangeValidator { private static List<ConfigChangeAction> validateContentCluster(ContentCluster currentCluster, ContentCluster nextCluster, - ValidationOverrides overrides) { + ValidationOverrides overrides, + Instant now) { List<ConfigChangeAction> result = new ArrayList<>(); - result.addAll(validateDocumentDatabases(currentCluster, nextCluster, overrides)); + result.addAll(validateDocumentDatabases(currentCluster, nextCluster, overrides, now)); return result; } private static List<ConfigChangeAction> validateDocumentDatabases(ContentCluster currentCluster, ContentCluster nextCluster, - ValidationOverrides overrides) { + ValidationOverrides overrides, + Instant now) { List<ConfigChangeAction> result = new ArrayList<>(); for (DocumentDatabase currentDb : getDocumentDbs(currentCluster.getSearch())) { String docTypeName = currentDb.getName(); @@ -53,7 +56,7 @@ public class IndexedSearchClusterChangeValidator implements ChangeValidator { filter(db -> db.getName().equals(docTypeName)).findFirst(); if (nextDb.isPresent()) { result.addAll(validateDocumentDatabase(currentCluster, nextCluster, docTypeName, - currentDb, nextDb.get(), overrides)); + currentDb, nextDb.get(), overrides, now)); } } return result; @@ -64,11 +67,12 @@ public class IndexedSearchClusterChangeValidator implements ChangeValidator { String docTypeName, DocumentDatabase currentDb, DocumentDatabase nextDb, - ValidationOverrides overrides) { + ValidationOverrides overrides, + Instant now) { NewDocumentType currentDocType = currentCluster.getDocumentDefinitions().get(docTypeName); NewDocumentType nextDocType = nextCluster.getDocumentDefinitions().get(docTypeName); List<VespaConfigChangeAction> result = - new DocumentDatabaseChangeValidator(currentDb, currentDocType, nextDb, nextDocType).validate(overrides); + new DocumentDatabaseChangeValidator(currentDb, currentDocType, nextDb, nextDocType).validate(overrides, now); return modifyActions(result, getSearchNodeServices(nextCluster.getSearch().getIndexed()), docTypeName); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/IndexingModeChangeValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/IndexingModeChangeValidator.java index e876630a401..908809b4b44 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/IndexingModeChangeValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/IndexingModeChangeValidator.java @@ -3,12 +3,11 @@ package com.yahoo.vespa.model.application.validation.change; import com.yahoo.config.model.api.ConfigChangeAction; import com.yahoo.vespa.model.VespaModel; -import com.yahoo.vespa.model.application.validation.ValidationId; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; -import com.yahoo.vespa.model.application.validation.change.ChangeValidator; -import com.yahoo.vespa.model.application.validation.change.VespaRefeedAction; +import com.yahoo.config.application.api.ValidationId; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.content.cluster.ContentCluster; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -22,13 +21,14 @@ import java.util.Optional; public class IndexingModeChangeValidator implements ChangeValidator { @Override - public List<ConfigChangeAction> validate(VespaModel currentModel, VespaModel nextModel, ValidationOverrides overrides) { + public List<ConfigChangeAction> validate(VespaModel currentModel, VespaModel nextModel, + ValidationOverrides overrides, Instant now) { List<ConfigChangeAction> actions = new ArrayList<>(); for (Map.Entry<String, ContentCluster> currentEntry : currentModel.getContentClusters().entrySet()) { ContentCluster nextCluster = nextModel.getContentClusters().get(currentEntry.getKey()); if (nextCluster == null) continue; - Optional<ConfigChangeAction> change = validateContentCluster(currentEntry.getValue(), nextCluster, overrides); + Optional<ConfigChangeAction> change = validateContentCluster(currentEntry.getValue(), nextCluster, overrides, now); if (change.isPresent()) actions.add(change.get()); } @@ -36,16 +36,17 @@ public class IndexingModeChangeValidator implements ChangeValidator { } private Optional<ConfigChangeAction> validateContentCluster(ContentCluster currentCluster, ContentCluster nextCluster, - ValidationOverrides overrides) { - final boolean currentClusterIsIndexed = currentCluster.getSearch().hasIndexedCluster(); - final boolean nextClusterIsIndexed = nextCluster.getSearch().hasIndexedCluster(); + ValidationOverrides overrides, Instant now) { + boolean currentClusterIsIndexed = currentCluster.getSearch().hasIndexedCluster(); + boolean nextClusterIsIndexed = nextCluster.getSearch().hasIndexedCluster(); if (currentClusterIsIndexed == nextClusterIsIndexed) return Optional.empty(); return Optional.of(VespaRefeedAction.of(ValidationId.indexModeChange.value(), overrides, "Cluster '" + currentCluster.getName() + "' changed indexing mode from '" + - indexingMode(currentClusterIsIndexed) + "' to '" + indexingMode(nextClusterIsIndexed) + "'")); + indexingMode(currentClusterIsIndexed) + "' to '" + indexingMode(nextClusterIsIndexed) + "'", + now)); } private String indexingMode(boolean isIndexed) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/StartupCommandChangeValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/StartupCommandChangeValidator.java index 7ead547e042..840ab69ba08 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/StartupCommandChangeValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/StartupCommandChangeValidator.java @@ -5,10 +5,9 @@ import com.yahoo.config.model.api.ConfigChangeAction; import com.yahoo.config.model.producer.AbstractConfigProducerRoot; import com.yahoo.vespa.model.Service; import com.yahoo.vespa.model.VespaModel; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; -import com.yahoo.vespa.model.application.validation.change.ChangeValidator; -import com.yahoo.vespa.model.application.validation.change.VespaRestartAction; +import com.yahoo.config.application.api.ValidationOverrides; +import java.time.Instant; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -25,7 +24,7 @@ public class StartupCommandChangeValidator implements ChangeValidator { @Override public List<ConfigChangeAction> validate(VespaModel currentModel, VespaModel nextModel, - ValidationOverrides overrides) { + ValidationOverrides overrides, Instant now) { return findServicesWithChangedStartupCommmand(currentModel, nextModel).collect(Collectors.toList()); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/VespaRefeedAction.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/VespaRefeedAction.java index 502b1b42aba..0fd38e5dbdd 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/VespaRefeedAction.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/VespaRefeedAction.java @@ -3,8 +3,9 @@ package com.yahoo.vespa.model.application.validation.change; import com.yahoo.config.model.api.ConfigChangeRefeedAction; import com.yahoo.config.model.api.ServiceInfo; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; +import com.yahoo.config.application.api.ValidationOverrides; +import java.time.Instant; import java.util.Collections; import java.util.List; @@ -26,29 +27,31 @@ public class VespaRefeedAction extends VespaConfigChangeAction implements Config private final String documentType; private final boolean allowed; + private final Instant now; - private VespaRefeedAction(String name, String message, List<ServiceInfo> services, String documentType, boolean allowed) { + private VespaRefeedAction(String name, String message, List<ServiceInfo> services, String documentType, boolean allowed, Instant now) { super(message, services); this.name = name; this.documentType = documentType; this.allowed = allowed; + this.now = now; } /** Creates a refeed action with some missing information */ // TODO: We should require document type or model its absence properly - public static VespaRefeedAction of(String name, ValidationOverrides overrides, String message) { - return new VespaRefeedAction(name, message, Collections.emptyList(), "", overrides.allows(name)); + public static VespaRefeedAction of(String name, ValidationOverrides overrides, String message, Instant now) { + return new VespaRefeedAction(name, message, Collections.emptyList(), "", overrides.allows(name, now), now); } /** Creates a refeed action */ public static VespaRefeedAction of(String name, ValidationOverrides overrides, String message, - List<ServiceInfo> services, String documentType) { - return new VespaRefeedAction(name, message, services, documentType, overrides.allows(name)); + List<ServiceInfo> services, String documentType, Instant now) { + return new VespaRefeedAction(name, message, services, documentType, overrides.allows(name, now), now); } @Override public VespaConfigChangeAction modifyAction(String newMessage, List<ServiceInfo> newServices, String documentType) { - return new VespaRefeedAction(name, newMessage, newServices, documentType, allowed); + return new VespaRefeedAction(name, newMessage, newServices, documentType, allowed, now); } @Override diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/AttributeChangeValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/AttributeChangeValidator.java index e534bdfc225..95ae03d4e5f 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/AttributeChangeValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/AttributeChangeValidator.java @@ -5,11 +5,12 @@ import com.yahoo.documentmodel.NewDocumentType; import com.yahoo.searchdefinition.derived.AttributeFields; import com.yahoo.searchdefinition.derived.IndexSchema; import com.yahoo.searchdefinition.document.Attribute; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.application.validation.change.VespaConfigChangeAction; import com.yahoo.vespa.model.application.validation.change.VespaRefeedAction; import com.yahoo.vespa.model.application.validation.change.VespaRestartAction; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -46,12 +47,12 @@ public class AttributeChangeValidator { this.nextDocType = nextDocType; } - public List<VespaConfigChangeAction> validate(final ValidationOverrides overrides) { + public List<VespaConfigChangeAction> validate(ValidationOverrides overrides, Instant now) { List<VespaConfigChangeAction> result = new ArrayList<>(); result.addAll(validateAddAttributeAspect()); result.addAll(validateRemoveAttributeAspect()); result.addAll(validateAttributeSettings()); - result.addAll(validateTensorTypes(overrides)); + result.addAll(validateTensorTypes(overrides, now)); return result; } @@ -94,7 +95,7 @@ public class AttributeChangeValidator { return result; } - private List<VespaConfigChangeAction> validateTensorTypes(final ValidationOverrides overrides) { + private List<VespaConfigChangeAction> validateTensorTypes(final ValidationOverrides overrides, Instant now) { final List<VespaConfigChangeAction> result = new ArrayList<>(); for (final Attribute nextAttr : nextFields.attributes()) { @@ -109,7 +110,7 @@ public class AttributeChangeValidator { // Tensor attribute has changed type if (!nextAttr.tensorType().get().equals(currentAttr.tensorType().get())) { - result.add(createTensorTypeChangedRefeedAction(currentAttr, nextAttr, overrides)); + result.add(createTensorTypeChangedRefeedAction(currentAttr, nextAttr, overrides, now)); } } } @@ -117,7 +118,7 @@ public class AttributeChangeValidator { return result; } - private static VespaRefeedAction createTensorTypeChangedRefeedAction(Attribute currentAttr, Attribute nextAttr, ValidationOverrides overrides) { + private static VespaRefeedAction createTensorTypeChangedRefeedAction(Attribute currentAttr, Attribute nextAttr, ValidationOverrides overrides, Instant now) { return VespaRefeedAction.of( "tensor-type-change", overrides, @@ -125,7 +126,7 @@ public class AttributeChangeValidator { .addChange( "tensor type", currentAttr.tensorType().get().toString(), - nextAttr.tensorType().get().toString()).build()); + nextAttr.tensorType().get().toString()).build(), now); } private static void validateAttributeSetting(Attribute currentAttr, Attribute nextAttr, diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/DocumentDatabaseChangeValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/DocumentDatabaseChangeValidator.java index dd5aa6cf363..bd287f83a1a 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/DocumentDatabaseChangeValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/DocumentDatabaseChangeValidator.java @@ -2,10 +2,11 @@ package com.yahoo.vespa.model.application.validation.change.search; import com.yahoo.documentmodel.NewDocumentType; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.application.validation.change.VespaConfigChangeAction; import com.yahoo.vespa.model.search.DocumentDatabase; +import java.time.Instant; import java.util.ArrayList; import java.util.List; @@ -32,29 +33,29 @@ public class DocumentDatabaseChangeValidator { this.nextDocType = nextDocType; } - public List<VespaConfigChangeAction> validate(final ValidationOverrides overrides) { + public List<VespaConfigChangeAction> validate(ValidationOverrides overrides, Instant now) { List<VespaConfigChangeAction> result = new ArrayList<>(); - result.addAll(validateAttributeChanges(overrides)); - result.addAll(validateIndexingScriptChanges(overrides)); - result.addAll(validateDocumentTypeChanges(overrides)); + result.addAll(validateAttributeChanges(overrides, now)); + result.addAll(validateIndexingScriptChanges(overrides, now)); + result.addAll(validateDocumentTypeChanges(overrides, now)); return result; } - private List<VespaConfigChangeAction> validateAttributeChanges(final ValidationOverrides overrides) { + private List<VespaConfigChangeAction> validateAttributeChanges(ValidationOverrides overrides, Instant now) { return new AttributeChangeValidator( currentDatabase.getDerivedConfiguration().getAttributeFields(), currentDatabase.getDerivedConfiguration().getIndexSchema(), currentDocType, nextDatabase.getDerivedConfiguration().getAttributeFields(), - nextDatabase.getDerivedConfiguration().getIndexSchema(), nextDocType).validate(overrides); + nextDatabase.getDerivedConfiguration().getIndexSchema(), nextDocType).validate(overrides, now); } - private List<VespaConfigChangeAction> validateIndexingScriptChanges(ValidationOverrides overrides) { + private List<VespaConfigChangeAction> validateIndexingScriptChanges(ValidationOverrides overrides, Instant now) { return new IndexingScriptChangeValidator(currentDatabase.getDerivedConfiguration().getSearch(), - nextDatabase.getDerivedConfiguration().getSearch()).validate(overrides); + nextDatabase.getDerivedConfiguration().getSearch()).validate(overrides, now); } - private List<VespaConfigChangeAction> validateDocumentTypeChanges(ValidationOverrides overrides) { - return new DocumentTypeChangeValidator(currentDocType, nextDocType).validate(overrides); + private List<VespaConfigChangeAction> validateDocumentTypeChanges(ValidationOverrides overrides, Instant now) { + return new DocumentTypeChangeValidator(currentDocType, nextDocType).validate(overrides, now); } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/DocumentTypeChangeValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/DocumentTypeChangeValidator.java index 745ecd58d34..4fd5e82e93f 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/DocumentTypeChangeValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/DocumentTypeChangeValidator.java @@ -4,10 +4,11 @@ package com.yahoo.vespa.model.application.validation.change.search; import com.yahoo.document.StructDataType; import com.yahoo.documentmodel.NewDocumentType; import com.yahoo.document.Field; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.application.validation.change.VespaConfigChangeAction; import com.yahoo.vespa.model.application.validation.change.VespaRefeedAction; +import java.time.Instant; import java.util.List; import java.util.stream.Collectors; @@ -132,7 +133,7 @@ public class DocumentTypeChangeValidator { this.nextDocType = nextDocType; } - public List<VespaConfigChangeAction> validate(ValidationOverrides overrides) { + public List<VespaConfigChangeAction> validate(ValidationOverrides overrides, Instant now) { return currentDocType.getAllFields().stream(). map(field -> createFieldChange(field, nextDocType)). filter(fieldChange -> fieldChange.valid() && fieldChange.changedType()). @@ -140,7 +141,8 @@ public class DocumentTypeChangeValidator { overrides, new ChangeMessageBuilder(fieldChange.fieldName()). addChange("data type", fieldChange.currentTypeName(), - fieldChange.nextTypeName()).build())). + fieldChange.nextTypeName()).build(), + now)). collect(Collectors.toList()); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/IndexingScriptChangeValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/IndexingScriptChangeValidator.java index cb3f0d40742..ff9230b34f3 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/IndexingScriptChangeValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/search/IndexingScriptChangeValidator.java @@ -7,11 +7,12 @@ import com.yahoo.vespa.indexinglanguage.ExpressionConverter; import com.yahoo.vespa.indexinglanguage.expressions.Expression; import com.yahoo.vespa.indexinglanguage.expressions.OutputExpression; import com.yahoo.vespa.indexinglanguage.expressions.ScriptExpression; -import com.yahoo.vespa.model.application.validation.ValidationId; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; +import com.yahoo.config.application.api.ValidationId; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.application.validation.change.VespaConfigChangeAction; import com.yahoo.vespa.model.application.validation.change.VespaRefeedAction; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -32,27 +33,27 @@ public class IndexingScriptChangeValidator { this.nextSearch = nextSearch; } - public List<VespaConfigChangeAction> validate(ValidationOverrides overrides) { + public List<VespaConfigChangeAction> validate(ValidationOverrides overrides, Instant now) { List<VespaConfigChangeAction> result = new ArrayList<>(); for (SDField nextField : nextSearch.allConcreteFields()) { String fieldName = nextField.getName(); SDField currentField = currentSearch.getConcreteField(fieldName); if (currentField != null) { - validateScripts(currentField, nextField, overrides).ifPresent(r -> result.add(r)); + validateScripts(currentField, nextField, overrides, now).ifPresent(r -> result.add(r)); } } return result; } private Optional<VespaConfigChangeAction> validateScripts(SDField currentField, SDField nextField, - ValidationOverrides overrides) { + ValidationOverrides overrides, Instant now) { ScriptExpression currentScript = currentField.getIndexingScript(); ScriptExpression nextScript = nextField.getIndexingScript(); if ( ! equalScripts(currentScript, nextScript)) { ChangeMessageBuilder messageBuilder = new ChangeMessageBuilder(nextField.getName()); new IndexingScriptChangeMessageBuilder(currentSearch, currentField, nextSearch, nextField).populate(messageBuilder); messageBuilder.addChange("indexing script", currentScript.toString(), nextScript.toString()); - return Optional.of(VespaRefeedAction.of(ValidationId.indexingChange.value(), overrides, messageBuilder.build())); + return Optional.of(VespaRefeedAction.of(ValidationId.indexingChange.value(), overrides, messageBuilder.build(), now)); } return Optional.empty(); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/xml/ValidationOverridesXMLReader.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/xml/ValidationOverridesXMLReader.java deleted file mode 100644 index 75a7dc84eaa..00000000000 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/xml/ValidationOverridesXMLReader.java +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.model.application.validation.xml; - -import com.yahoo.text.XML; -import com.yahoo.vespa.model.application.validation.ValidationId; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; -import org.w3c.dom.Element; - -import java.io.Reader; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDate; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -/** - * Reader of the validation-allows.xml file in application packages. - * - * @author bratseth - */ -public class ValidationOverridesXMLReader { - - /** - * Returns a ValidationOverrides instance with the content of the given Reader. - * An empty ValidationOverrides is returned if the argument is empty. - * - * @param reader the reader which optionally contains a validation-overrides XML structure - * @param now the instant to use as "now", settable for unit testing - * @return a ValidationOverrides from the argument - * @throws IllegalArgumentException if the validation-allows.xml file exists but is invalid - */ - public ValidationOverrides read(Optional<Reader> reader, Instant now) { - if ( ! reader.isPresent()) return ValidationOverrides.empty(); - - try { - // Assume valid structure is ensured by schema validation - Element root = XML.getDocument(reader.get()).getDocumentElement(); - List<ValidationOverrides.Allow> overrides = new ArrayList<>(); - for (Element allow : XML.getChildren(root, "allow")) { - Instant until = LocalDate.parse(allow.getAttribute("until"), DateTimeFormatter.ISO_DATE) - .atStartOfDay().atZone(ZoneOffset.UTC).toInstant() - .plus(Duration.ofDays(1)); // Make the override valid *on* the "until" date - Optional<ValidationId> validationId = ValidationId.from(XML.getValue(allow)); - if (validationId.isPresent()) // skip unknonw ids as they may be valid for other model versions - overrides.add(new ValidationOverrides.Allow(validationId.get(), until)); - } - return new ValidationOverrides(overrides, now); - } - catch (IllegalArgumentException e) { - throw new IllegalArgumentException("validation-overrides is invalid", e); - } - } - -} diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationOverrideTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationOverrideTest.java deleted file mode 100644 index 61aa847edeb..00000000000 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationOverrideTest.java +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.model.application.validation; - -import com.yahoo.test.ManualClock; -import com.yahoo.vespa.model.application.validation.xml.ValidationOverridesXMLReader; -import org.junit.Test; -import org.xml.sax.SAXException; - -import java.io.IOException; -import java.io.StringReader; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.util.Optional; - -import static org.junit.Assert.fail; -import static org.junit.Assert.assertEquals; - -/** - * @author bratseth - */ -public class ValidationOverrideTest { - - @Test - public void testValidationOverridesInIsolation() throws IOException, SAXException { - String validationOverrides = - "<validation-overrides>" + - " <allow until='2000-01-01'>indexing-change</allow>" + - " <allow until='2000-01-03' comment='any text'>indexing-mode-change</allow>" + - "</validation-overrides>"; - - { - - ValidationOverrides overrides = new ValidationOverridesXMLReader().read(Optional.of(new StringReader(validationOverrides)), - ManualClock.at("2000-01-01T23:59:00")); - assertOverridden("indexing-change", overrides); - assertOverridden("indexing-mode-change", overrides); - assertNotOverridden("field-type-change", overrides); - } - - { - ValidationOverrides overrides = new ValidationOverridesXMLReader().read(Optional.of(new StringReader(validationOverrides)), - ManualClock.at("2000-01-02T00:00:00")); - assertNotOverridden("indexing-change", overrides); - assertOverridden("indexing-mode-change", overrides); - assertNotOverridden("field-type-change", overrides); - } - - { - ValidationOverrides overrides = new ValidationOverridesXMLReader().read(Optional.of(new StringReader(validationOverrides)), - ManualClock.at("2000-01-04T00:00:00")); - assertNotOverridden("indexing-change", overrides); - assertNotOverridden("indexing-mode-change", overrides); - assertNotOverridden("field-type-change", overrides); - } - - } - - @Test - public void testInvalidOverridePeriod() throws IOException, SAXException { - String validationOverrides = - "<validation-overrides>" + - " <allow until='2000-02-02'>indexing-change</allow>" + - "</validation-overrides>"; - - try { - new ValidationOverridesXMLReader().read(Optional.of(new StringReader(validationOverrides)), - ManualClock.at("2000-01-01T23:59:00")); - fail("Expected validation interval override validation validation failure"); - } - catch (IllegalArgumentException e) { - assertEquals("validation-overrides is invalid", e.getMessage()); - assertEquals("allow 'indexing-change' until 2000-02-03T00:00:00Z is too far in the future: Max 30 days is allowed", - e.getCause().getMessage()); - } - } - - private void assertOverridden(String validationId, ValidationOverrides overrides) { - overrides.invalid(ValidationId.from(validationId).get(), "message"); // should not throw exception - } - - private void assertNotOverridden(String validationId, ValidationOverrides overrides) { - try { - overrides.invalid(ValidationId.from(validationId).get(), "message"); - fail("Expected '" + validationId + "' to not be overridden"); - } - catch (ValidationOverrides.ValidationException expected) { - } - } - -} diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ConfigChangeTestUtils.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ConfigChangeTestUtils.java index 8a9d1e9963b..3738add1a6d 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ConfigChangeTestUtils.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ConfigChangeTestUtils.java @@ -2,11 +2,9 @@ package com.yahoo.vespa.model.application.validation.change; import com.yahoo.config.model.api.ServiceInfo; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; -import com.yahoo.vespa.model.application.validation.change.VespaConfigChangeAction; -import com.yahoo.vespa.model.application.validation.change.VespaRefeedAction; -import com.yahoo.vespa.model.application.validation.change.VespaRestartAction; +import com.yahoo.config.application.api.ValidationOverrides; +import java.time.Instant; import java.util.List; public class ConfigChangeTestUtils { @@ -19,12 +17,12 @@ public class ConfigChangeTestUtils { return new VespaRestartAction(message, services); } - public static VespaConfigChangeAction newRefeedAction(String name, ValidationOverrides overrides, String message) { - return VespaRefeedAction.of(name, overrides, message); + public static VespaConfigChangeAction newRefeedAction(String name, ValidationOverrides overrides, String message, Instant now) { + return VespaRefeedAction.of(name, overrides, message, now); } public static VespaConfigChangeAction newRefeedAction(String name, ValidationOverrides overrides, String message, - List<ServiceInfo> services, String documentType) { - return VespaRefeedAction.of(name, overrides, message, services, documentType); + List<ServiceInfo> services, String documentType, Instant now) { + return VespaRefeedAction.of(name, overrides, message, services, documentType, now); } } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ConfigValueChangeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ConfigValueChangeValidatorTest.java index 0b7ef914abb..cbb725987eb 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ConfigValueChangeValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ConfigValueChangeValidatorTest.java @@ -14,12 +14,13 @@ import com.yahoo.vespa.model.Host; import com.yahoo.vespa.model.HostResource; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.application.validation.RestartConfigs; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.test.utils.DeployLoggerStub; import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithMockPkg; import org.junit.Before; import org.junit.Test; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -143,7 +144,7 @@ public class ConfigValueChangeValidatorTest { private List<ConfigChangeAction> getConfigChanges(VespaModel currentModel, VespaModel nextModel) { ConfigValueChangeValidator validator = new ConfigValueChangeValidator(logger); - return validator.validate(currentModel, nextModel, ValidationOverrides.empty()); + return validator.validate(currentModel, nextModel, ValidationOverrides.empty, Instant.now()); } private List<ConfigChangeAction> getConfigChanges(AbstractConfigProducerRoot currentModel, diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ContainerRestartValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ContainerRestartValidatorTest.java index 952377dcb4c..1a6bd89bb3f 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ContainerRestartValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/ContainerRestartValidatorTest.java @@ -4,10 +4,11 @@ package com.yahoo.vespa.model.application.validation.change; import com.yahoo.config.model.api.ConfigChangeAction; import com.yahoo.vespa.defaults.Defaults; import com.yahoo.vespa.model.VespaModel; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithMockPkg; import org.junit.Test; +import java.time.Instant; import java.util.Collections; import java.util.List; @@ -45,7 +46,7 @@ public class ContainerRestartValidatorTest { private static List<ConfigChangeAction> validateModel(VespaModel current, VespaModel next) { return new ContainerRestartValidator() - .validate(current, next, new ValidationOverrides(Collections.emptyList())); + .validate(current, next, new ValidationOverrides(Collections.emptyList()), Instant.now()); } private static VespaModel createModel(boolean restartOnDeploy) { diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/IndexedSearchClusterChangeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/IndexedSearchClusterChangeValidatorTest.java index 164b5a79e11..387b8912018 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/IndexedSearchClusterChangeValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/IndexedSearchClusterChangeValidatorTest.java @@ -4,15 +4,13 @@ package com.yahoo.vespa.model.application.validation.change; import com.yahoo.config.model.api.ConfigChangeAction; import com.yahoo.config.model.api.ServiceInfo; import com.yahoo.vespa.model.VespaModel; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; -import com.yahoo.vespa.model.application.validation.change.IndexedSearchClusterChangeValidator; -import com.yahoo.vespa.model.application.validation.change.VespaConfigChangeAction; -import com.yahoo.vespa.model.application.validation.change.VespaRefeedAction; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.content.utils.ApplicationPackageBuilder; import com.yahoo.vespa.model.content.utils.ContentClusterBuilder; import com.yahoo.vespa.model.content.utils.SearchDefinitionBuilder; import org.junit.Test; +import java.time.Instant; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -78,7 +76,7 @@ public class IndexedSearchClusterChangeValidatorTest { public void assertValidation() { List<ConfigChangeAction> act = normalizeServicesInActions(validator.validate(currentModel, nextModel, - ValidationOverrides.empty())); + ValidationOverrides.empty, Instant.now())); assertThat(act.size(), is(0)); } @@ -105,7 +103,7 @@ public class IndexedSearchClusterChangeValidatorTest { public void assertValidation(List<ConfigChangeAction> exp) { List<ConfigChangeAction> act = normalizeServicesInActions(validator.validate(currentModel, nextModel, - ValidationOverrides.empty())); + ValidationOverrides.empty, Instant.now())); exp.sort((lhs, rhs) -> lhs.getMessage().compareTo(rhs.getMessage())); act.sort((lhs, rhs) -> lhs.getMessage().compareTo(rhs.getMessage())); assertThat(act, equalTo(exp)); @@ -168,8 +166,8 @@ public class IndexedSearchClusterChangeValidatorTest { public void requireThatChangingFieldTypeIsDiscovered() { Fixture f = Fixture.newOneDocFixture(STRING_FIELD, INT_FIELD); f.assertValidation(Arrays.asList(newRefeedAction("field-type-change", - ValidationOverrides.empty(), - "Document type 'd1': " + FIELD_TYPE_CHANGE_MSG, FOO_SERVICE, "d1"))); + ValidationOverrides.empty, + "Document type 'd1': " + FIELD_TYPE_CHANGE_MSG, FOO_SERVICE, "d1", Instant.now()))); } } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/AttributeChangeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/AttributeChangeValidatorTest.java index a7cce283354..eab12db20ef 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/AttributeChangeValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/AttributeChangeValidatorTest.java @@ -1,10 +1,11 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.application.validation.change.search; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.application.validation.change.VespaConfigChangeAction; import org.junit.Test; +import java.time.Instant; import java.util.List; import static com.yahoo.vespa.model.application.validation.change.ConfigChangeTestUtils.newRefeedAction; @@ -28,7 +29,7 @@ public class AttributeChangeValidatorTest { @Override public List<VespaConfigChangeAction> validate() { - return validator.validate(ValidationOverrides.empty()); + return validator.validate(ValidationOverrides.empty, Instant.now()); } } @@ -115,8 +116,8 @@ public class AttributeChangeValidatorTest { "field f1 type tensor(y[]) { indexing: attribute \n attribute: tensor(y[]) }") .assertValidation(newRefeedAction( "tensor-type-change", - ValidationOverrides.empty(), - "Field 'f1' changed: tensor type: 'tensor(x[100])' -> 'tensor(y[])'")); + ValidationOverrides.empty, + "Field 'f1' changed: tensor type: 'tensor(x[100])' -> 'tensor(y[])'", Instant.now())); } @Test diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/DocumentDatabaseChangeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/DocumentDatabaseChangeValidatorTest.java index 1a82fed42b7..ba736af2159 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/DocumentDatabaseChangeValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/DocumentDatabaseChangeValidatorTest.java @@ -1,10 +1,11 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.application.validation.change.search; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.application.validation.change.VespaConfigChangeAction; import org.junit.Test; +import java.time.Instant; import java.util.Arrays; import java.util.List; @@ -23,7 +24,7 @@ public class DocumentDatabaseChangeValidatorTest { @Override public List<VespaConfigChangeAction> validate() { - return validator.validate(ValidationOverrides.empty()); + return validator.validate(ValidationOverrides.empty, Instant.now()); } } @@ -39,12 +40,12 @@ public class DocumentDatabaseChangeValidatorTest { f.assertValidation(Arrays.asList( newRestartAction("Field 'f1' changed: add attribute aspect"), newRefeedAction("indexing-change", - ValidationOverrides.empty(), + ValidationOverrides.empty, "Field 'f2' changed: add index aspect, indexing script: '{ input f2 | summary f2; }' -> " + - "'{ input f2 | tokenize normalize stem:\"SHORTEST\" | index f2 | summary f2; }'"), + "'{ input f2 | tokenize normalize stem:\"SHORTEST\" | index f2 | summary f2; }'", Instant.now()), newRefeedAction("field-type-change", - ValidationOverrides.empty(), - "Field 'f3' changed: data type: 'int' -> 'string'"))); + ValidationOverrides.empty, + "Field 'f3' changed: data type: 'int' -> 'string'", Instant.now()))); } @Test diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/DocumentTypeChangeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/DocumentTypeChangeValidatorTest.java index 4bf7be82318..a4ab5ebdb5e 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/DocumentTypeChangeValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/DocumentTypeChangeValidatorTest.java @@ -7,11 +7,12 @@ import com.yahoo.document.ReferenceDataType; import com.yahoo.document.StructDataType; import com.yahoo.documentmodel.NewDocumentType; import com.yahoo.searchdefinition.FieldSets; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.application.validation.change.VespaConfigChangeAction; import com.yahoo.vespa.model.application.validation.change.VespaRefeedAction; import org.junit.Test; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -29,6 +30,7 @@ import static org.junit.Assert.assertTrue; public class DocumentTypeChangeValidatorTest { private static class Fixture extends ContentClusterFixture { + DocumentTypeChangeValidator validator; public Fixture(String currentSd, String nextSd) throws Exception { @@ -38,7 +40,7 @@ public class DocumentTypeChangeValidatorTest { @Override public List<VespaConfigChangeAction> validate() { - return validator.validate(ValidationOverrides.empty()); + return validator.validate(ValidationOverrides.empty, Instant.now()); } } @@ -62,8 +64,9 @@ public class DocumentTypeChangeValidatorTest { Fixture f = new Fixture("field f1 type string { indexing: summary }", "field f1 type int { indexing: summary }"); f.assertValidation(newRefeedAction("field-type-change", - ValidationOverrides.empty(), - "Field 'f1' changed: data type: 'string' -> 'int'")); + ValidationOverrides.empty, + "Field 'f1' changed: data type: 'string' -> 'int'", + Instant.now())); } @Test @@ -71,8 +74,8 @@ public class DocumentTypeChangeValidatorTest { Fixture f = new Fixture("field f1 type string { indexing: summary }", "field f1 type array<string> { indexing: summary }"); f.assertValidation(newRefeedAction("field-type-change", - ValidationOverrides.empty(), - "Field 'f1' changed: data type: 'string' -> 'Array<string>'")); + ValidationOverrides.empty, + "Field 'f1' changed: data type: 'string' -> 'Array<string>'", Instant.now())); } @@ -88,8 +91,8 @@ public class DocumentTypeChangeValidatorTest { Fixture f = new Fixture("field f1 type array<string> { indexing: summary }", "field f1 type array<int> { indexing: summary }"); f.assertValidation(newRefeedAction("field-type-change", - ValidationOverrides.empty(), - "Field 'f1' changed: data type: 'Array<string>' -> 'Array<int>'")); + ValidationOverrides.empty, + "Field 'f1' changed: data type: 'Array<string>' -> 'Array<int>'", Instant.now())); } @Test @@ -97,8 +100,8 @@ public class DocumentTypeChangeValidatorTest { Fixture f = new Fixture("field f1 type array<string> { indexing: summary }", "field f1 type weightedset<string> { indexing: summary }"); f.assertValidation(newRefeedAction("field-type-change", - ValidationOverrides.empty(), - "Field 'f1' changed: data type: 'Array<string>' -> 'WeightedSet<string>'")); + ValidationOverrides.empty, + "Field 'f1' changed: data type: 'Array<string>' -> 'WeightedSet<string>'", Instant.now())); } @Test @@ -106,11 +109,11 @@ public class DocumentTypeChangeValidatorTest { Fixture f = new Fixture("field f1 type string { indexing: summary } field f2 type int { indexing: summary }" , "field f2 type string { indexing: summary } field f1 type int { indexing: summary }"); f.assertValidation(Arrays.asList(newRefeedAction("field-type-change", - ValidationOverrides.empty(), - "Field 'f1' changed: data type: 'string' -> 'int'"), + ValidationOverrides.empty, + "Field 'f1' changed: data type: 'string' -> 'int'", Instant.now()), newRefeedAction("field-type-change", - ValidationOverrides.empty(), - "Field 'f2' changed: data type: 'int' -> 'string'"))); + ValidationOverrides.empty, + "Field 'f2' changed: data type: 'int' -> 'string'", Instant.now()))); } @Test @@ -146,8 +149,8 @@ public class DocumentTypeChangeValidatorTest { Fixture f = new Fixture("struct s1 { field f1 type string {} } field f2 type s1 { indexing: summary }", "struct s1 { field f1 type int {} } field f2 type s1 { indexing: summary }"); f.assertValidation(newRefeedAction("field-type-change", - ValidationOverrides.empty(), - "Field 'f2' changed: data type: 's1:{f1:string}' -> 's1:{f1:int}'")); + ValidationOverrides.empty, + "Field 'f2' changed: data type: 's1:{f1:string}' -> 's1:{f1:int}'", Instant.now())); } @Test @@ -155,8 +158,8 @@ public class DocumentTypeChangeValidatorTest { Fixture f = new Fixture("struct s1 { field f1 type array<string> {} } field f2 type s1 { indexing: summary }", "struct s1 { field f1 type array<int> {} } field f2 type s1 { indexing: summary }"); f.assertValidation(newRefeedAction("field-type-change", - ValidationOverrides.empty(), - "Field 'f2' changed: data type: 's1:{f1:Array<string>}' -> 's1:{f1:Array<int>}'")); + ValidationOverrides.empty, + "Field 'f2' changed: data type: 's1:{f1:Array<string>}' -> 's1:{f1:Array<int>}'", Instant.now())); } @Test @@ -164,8 +167,8 @@ public class DocumentTypeChangeValidatorTest { Fixture f = new Fixture("struct s1 { field f1 type string {} } struct s2 { field f2 type s1 {} } field f3 type s2 { indexing: summary }", "struct s1 { field f1 type int {} } struct s2 { field f2 type s1 {} } field f3 type s2 { indexing: summary }"); f.assertValidation(newRefeedAction("field-type-change", - ValidationOverrides.empty(), - "Field 'f3' changed: data type: 's2:{s1:{f1:string}}' -> 's2:{s1:{f1:int}}'")); + ValidationOverrides.empty, + "Field 'f3' changed: data type: 's2:{s1:{f1:string}}' -> 's2:{s1:{f1:int}}'", Instant.now())); } @Test @@ -173,8 +176,8 @@ public class DocumentTypeChangeValidatorTest { Fixture f = new Fixture("struct s1 { field f1 type string {} field f2 type int {} } field f3 type s1 { indexing: summary }", "struct s1 { field f1 type int {} field f2 type string {} } field f3 type s1 { indexing: summary }"); f.assertValidation(newRefeedAction("field-type-change", - ValidationOverrides.empty(), - "Field 'f3' changed: data type: 's1:{f1:string,f2:int}' -> 's1:{f1:int,f2:string}'")); + ValidationOverrides.empty, + "Field 'f3' changed: data type: 's1:{f1:string,f2:int}' -> 's1:{f1:int,f2:string}'", Instant.now())); } @Test @@ -182,7 +185,7 @@ public class DocumentTypeChangeValidatorTest { DocumentTypeChangeValidator validator = new DocumentTypeChangeValidator( createDocumentTypeWithReferenceField("oldDoc"), createDocumentTypeWithReferenceField("newDoc")); - List<VespaConfigChangeAction> result = validator.validate(ValidationOverrides.empty()); + List<VespaConfigChangeAction> result = validator.validate(ValidationOverrides.empty, Instant.now()); assertEquals(1, result.size()); VespaConfigChangeAction action = result.get(0); assertTrue(action instanceof VespaRefeedAction); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/IndexingScriptChangeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/IndexingScriptChangeValidatorTest.java index dde8479ad19..3fe3c27a648 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/IndexingScriptChangeValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/search/IndexingScriptChangeValidatorTest.java @@ -2,11 +2,12 @@ package com.yahoo.vespa.model.application.validation.change.search; import com.yahoo.vespa.indexinglanguage.expressions.ScriptExpression; -import com.yahoo.vespa.model.application.validation.ValidationOverrides; +import com.yahoo.config.application.api.ValidationOverrides; import com.yahoo.vespa.model.application.validation.change.VespaConfigChangeAction; import com.yahoo.vespa.model.application.validation.change.VespaRefeedAction; import org.junit.Test; +import java.time.Instant; import java.util.Arrays; import java.util.List; @@ -25,7 +26,7 @@ public class IndexingScriptChangeValidatorTest { @Override public List<VespaConfigChangeAction> validate() { - return validator.validate(ValidationOverrides.empty()); + return validator.validate(ValidationOverrides.empty, Instant.now()); } } @@ -52,10 +53,11 @@ public class IndexingScriptChangeValidatorTest { private static VespaConfigChangeAction expectedAction(String field, String changedMsg, String fromScript, String toScript) { return VespaRefeedAction.of("indexing-change", - ValidationOverrides.empty(), + ValidationOverrides.empty, "Field '" + field + "' changed: " + (changedMsg.isEmpty() ? "" : changedMsg + ", ") + - "indexing script: '" + fromScript + "' -> '" + toScript + "'"); + "indexing script: '" + fromScript + "' -> '" + toScript + "'", + Instant.now()); } @Test diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java index 5d74760ca9a..61458d871ac 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java @@ -207,7 +207,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye private Application getApplication(Tenant tenant, ApplicationId applicationId) { long sessionId = getSessionIdForApplication(tenant, applicationId); RemoteSession session = tenant.getRemoteSessionRepo().getSession(sessionId, 0); - return session.ensureApplicationLoaded().getForVersionOrLatest(Optional.empty()); + return session.ensureApplicationLoaded().getForVersionOrLatest(Optional.empty(), clock.instant()); } public long getSessionIdForApplication(Tenant tenant, ApplicationId applicationId) { @@ -307,7 +307,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye LocalSession session = getLocalSession(tenant, sessionId); ApplicationId appId = params.getApplicationId(); Optional<ApplicationSet> currentActiveApplicationSet = getCurrentActiveApplicationSet(tenant, appId); - return session.prepare(logger, params, currentActiveApplicationSet, tenant.getPath()); + return session.prepare(logger, params, currentActiveApplicationSet, tenant.getPath(), clock.instant()); } private List<ApplicationId> listApplicationIds(Tenant tenant) { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/SuperModelRequestHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/SuperModelRequestHandler.java index 6b9c82a04ee..105bd9a5b2a 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/SuperModelRequestHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/SuperModelRequestHandler.java @@ -22,6 +22,7 @@ import com.yahoo.vespa.config.server.rpc.ConfigResponseFactory; import com.yahoo.vespa.config.server.rpc.ConfigResponseFactoryFactory; import java.io.IOException; +import java.time.Instant; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -74,7 +75,7 @@ public class SuperModelRequestHandler implements RequestHandler { newModels.put(tenant, new LinkedHashMap<>()); } // TODO: Should supermodel care about multiple versions? - newModels.get(tenant).put(applicationSet.getId(), applicationSet.getForVersionOrLatest(Optional.empty())); + newModels.get(tenant).put(applicationSet.getId(), applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now())); handler = createNewHandler(newModels); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ApplicationMapper.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ApplicationMapper.java index 019a8123648..459bee99d75 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ApplicationMapper.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ApplicationMapper.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.config.server.application; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Version; +import java.time.Instant; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -48,19 +49,19 @@ public final class ApplicationMapper { * * @return the matching application, or null if none matches */ - public Application getForVersion(ApplicationId applicationId, Optional<Version> vespaVersion) throws VersionDoesNotExistException { - return getApplicationSet(applicationId).getForVersionOrLatest(vespaVersion); + public Application getForVersion(ApplicationId applicationId, Optional<Version> vespaVersion, Instant now) throws VersionDoesNotExistException { + return getApplicationSet(applicationId).getForVersionOrLatest(vespaVersion, now); } /** Returns whether this registry has an application for the given application id */ - public boolean hasApplication(ApplicationId applicationId) { - return hasApplicationForVersion(applicationId, Optional.<Version>empty()); + public boolean hasApplication(ApplicationId applicationId, Instant now) { + return hasApplicationForVersion(applicationId, Optional.<Version>empty(), now); } /** Returns whether this registry has an application for the given application id and vespa version */ - public boolean hasApplicationForVersion(ApplicationId applicationId, Optional<Version> vespaVersion) { + public boolean hasApplicationForVersion(ApplicationId applicationId, Optional<Version> vespaVersion, Instant now) { try { - return getForVersion(applicationId, vespaVersion) != null; + return getForVersion(applicationId, vespaVersion, now) != null; } catch (VersionDoesNotExistException | NotFoundException ex) { return false; diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ApplicationSet.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ApplicationSet.java index 6d6ed925ebd..7d46152dd4d 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ApplicationSet.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ApplicationSet.java @@ -4,6 +4,7 @@ import com.yahoo.config.model.api.HostInfo; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Version; +import java.time.Instant; import java.util.*; import java.util.stream.Collectors; @@ -32,18 +33,18 @@ public final class ApplicationSet { latestVersion = applications.keySet().stream().max((a, b) -> a.compareTo(b)).get(); } - public Application getForVersionOrLatest(Optional<Version> optionalVersion) { - return resolveForVersion(optionalVersion.orElse(latestVersion)); + public Application getForVersionOrLatest(Optional<Version> optionalVersion, Instant now) { + return resolveForVersion(optionalVersion.orElse(latestVersion), now); } - private Application resolveForVersion(Version vespaVersion) { + private Application resolveForVersion(Version vespaVersion, Instant now) { Application application = applications.get(vespaVersion); if (application != null) return application; // Does the latest version specify we can use it regardless? Application latest = applications.get(latestVersion); - if (latest.getModel().allowModelVersionMismatch()) + if (latest.getModel().allowModelVersionMismatch(now)) return latest; throw new VersionDoesNotExistException(String.format("No application with vespa version %s exists", vespaVersion.toString())); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java index b04b00b8223..61382af6a30 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java @@ -110,7 +110,8 @@ public class Deployment implements com.yahoo.config.provision.Deployment { .vespaVersion(version.toString()) .build(), Optional.empty(), - tenantPath); + tenantPath, + clock.instant()); this.prepared = true; } 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 a4a98dd3431..a805d335c2e 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 @@ -30,6 +30,7 @@ import com.yahoo.vespa.config.server.session.SessionZooKeeperClient; import com.yahoo.vespa.config.server.session.SilentDeployLogger; import com.yahoo.vespa.curator.Curator; +import java.time.Instant; import java.util.Map; import java.util.Optional; import java.util.logging.Logger; @@ -71,8 +72,11 @@ public class ActivatedModelsBuilder extends ModelsBuilder<Application> { } @Override - protected Application buildModelVersion(ModelFactory modelFactory, ApplicationPackage applicationPackage, - ApplicationId applicationId, com.yahoo.component.Version wantedNodeVespaVersion) { + protected Application buildModelVersion(ModelFactory modelFactory, + ApplicationPackage applicationPackage, + ApplicationId applicationId, + com.yahoo.component.Version wantedNodeVespaVersion, + Instant now) { log.log(LogLevel.DEBUG, String.format("Loading model version %s for session %s application %s", modelFactory.getVersion(), appGeneration, applicationId)); ServerCache cache = zkClient.loadServerCache(); 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 0faee6a4eb7..509a26370ff 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 @@ -14,6 +14,7 @@ import com.yahoo.vespa.config.server.ConfigServerSpec; import com.yahoo.vespa.config.server.deploy.ModelContextImpl; import com.yahoo.vespa.config.server.http.UnknownVespaVersionException; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -42,7 +43,9 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { } public List<MODELRESULT> buildModels(ApplicationId applicationId, - com.yahoo.component.Version wantedNodeVespaVersion, ApplicationPackage applicationPackage) { + com.yahoo.component.Version wantedNodeVespaVersion, + ApplicationPackage applicationPackage, + Instant now) { Set<Version> versions = modelFactoryRegistry.allVersions(); // If the application specifies a major, load models only for that @@ -63,10 +66,10 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { for (int i = 0; i < majorVersions.size(); i++) { try { allApplicationModels.addAll(buildModelVersion(filterByMajorVersion(majorVersions.get(i), versions), - applicationId, wantedNodeVespaVersion, applicationPackage)); + applicationId, wantedNodeVespaVersion, applicationPackage, now)); // skip old config models if requested after we have found a major version which works - if (allApplicationModels.size() > 0 && allApplicationModels.get(0).getModel().skipOldConfigModels()) + if (allApplicationModels.size() > 0 && allApplicationModels.get(0).getModel().skipOldConfigModels(now)) break; } catch (OutOfCapacityException e) { @@ -88,11 +91,16 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { private List<MODELRESULT> buildModelVersion(Set<Version> versions, ApplicationId applicationId, com.yahoo.component.Version wantedNodeVespaVersion, - ApplicationPackage applicationPackage) { + ApplicationPackage applicationPackage, + Instant now) { Version latest = findLatest(versions); // load latest application version - MODELRESULT latestApplicationVersion = buildModelVersion(modelFactoryRegistry.getFactory(latest), applicationPackage, applicationId, wantedNodeVespaVersion); - if (latestApplicationVersion.getModel().skipOldConfigModels()) { + MODELRESULT latestApplicationVersion = buildModelVersion(modelFactoryRegistry.getFactory(latest), + applicationPackage, + applicationId, + wantedNodeVespaVersion, + now); + if (latestApplicationVersion.getModel().skipOldConfigModels(now)) { return Collections.singletonList(latestApplicationVersion); } else { // load old model versions @@ -100,7 +108,11 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { allApplicationVersions.add(latestApplicationVersion); for (Version version : versions) { if (version.equals(latest)) continue; // already loaded - allApplicationVersions.add(buildModelVersion(modelFactoryRegistry.getFactory(version), applicationPackage, applicationId, wantedNodeVespaVersion)); + allApplicationVersions.add(buildModelVersion(modelFactoryRegistry.getFactory(version), + applicationPackage, + applicationId, + wantedNodeVespaVersion, + now)); } return allApplicationVersions; } @@ -121,7 +133,8 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { protected abstract MODELRESULT buildModelVersion(ModelFactory modelFactory, ApplicationPackage applicationPackage, ApplicationId applicationId, - com.yahoo.component.Version wantedNodeVespaVersion); + com.yahoo.component.Version wantedNodeVespaVersion, + Instant now); protected ModelContext.Properties createModelContextProperties(ApplicationId applicationId, ConfigserverConfig configserverConfig, 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 4e87de70759..98741c631b1 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 @@ -28,6 +28,7 @@ import com.yahoo.vespa.config.server.session.SessionContext; import java.io.File; import java.io.IOException; +import java.time.Instant; import java.util.List; import java.util.Optional; import java.util.logging.Logger; @@ -79,7 +80,7 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P protected PreparedModelResult buildModelVersion(ModelFactory modelFactory, ApplicationPackage applicationPackage, ApplicationId applicationId, - com.yahoo.component.Version wantedNodeVespaVersion) { + com.yahoo.component.Version wantedNodeVespaVersion, Instant now) { Version modelVersion = modelFactory.getVersion(); log.log(LogLevel.DEBUG, "Start building model for Vespa version " + modelVersion); FileDistributionProvider fileDistributionProvider = fileDistributionFactory.createProvider( @@ -88,7 +89,7 @@ public class PreparedModelsBuilder extends ModelsBuilder<PreparedModelsBuilder.P Optional<HostProvisioner> hostProvisioner = createHostProvisionerAdapter(properties); Optional<Model> previousModel = currentActiveApplicationSet - .map(set -> set.getForVersionOrLatest(Optional.of(modelVersion)).getModel()); + .map(set -> set.getForVersionOrLatest(Optional.of(modelVersion), now).getModel()); ModelContext modelContext = new ModelContextImpl( applicationPackage, previousModel, diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java index baa4dc2ca79..a222182f87a 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java @@ -22,6 +22,7 @@ import com.yahoo.vespa.config.server.tenant.Tenants; import com.yahoo.vespa.curator.Curator; import java.io.File; +import java.time.Instant; import java.util.Optional; /** @@ -62,10 +63,11 @@ public class LocalSession extends Session implements Comparable<LocalSession> { public ConfigChangeActions prepare(DeployLogger logger, PrepareParams params, Optional<ApplicationSet> currentActiveApplicationSet, - Path tenantPath) { + Path tenantPath, + Instant now) { Curator.CompletionWaiter waiter = zooKeeperClient.createPrepareWaiter(); - ConfigChangeActions actions = sessionPreparer.prepare(sessionContext, logger, params, - currentActiveApplicationSet, tenantPath); + ConfigChangeActions actions = sessionPreparer.prepare(sessionContext, logger, params, + currentActiveApplicationSet, tenantPath, now); setPrepared(); waiter.awaitCompletion(params.getTimeoutBudget().timeLeft()); return actions; diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java index df0a407c408..c20f6e0b853 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSession.java @@ -10,6 +10,8 @@ import com.yahoo.vespa.config.server.tenant.Tenants; import com.yahoo.vespa.curator.Curator; import org.apache.zookeeper.KeeperException; +import java.time.Clock; +import java.time.Instant; import java.util.*; import java.util.logging.Logger; @@ -25,6 +27,7 @@ public class RemoteSession extends Session { private static final Logger log = Logger.getLogger(RemoteSession.class.getName()); private volatile ApplicationSet applicationSet = null; private final ActivatedModelsBuilder applicationLoader; + private final Clock clock; /** * Creates a session. This involves loading the application, validating it and distributing it. @@ -37,9 +40,11 @@ public class RemoteSession extends Session { public RemoteSession(TenantName tenant, long sessionId, GlobalComponentRegistry globalComponentRegistry, - SessionZooKeeperClient zooKeeperClient) { + SessionZooKeeperClient zooKeeperClient, + Clock clock) { super(tenant, sessionId, zooKeeperClient); this.applicationLoader = new ActivatedModelsBuilder(tenant, sessionId, zooKeeperClient, globalComponentRegistry); + this.clock = clock; } public void loadPrepared() { @@ -51,7 +56,8 @@ public class RemoteSession extends Session { private ApplicationSet loadApplication() { return ApplicationSet.fromList(applicationLoader.buildModels(zooKeeperClient.readApplicationId(), zooKeeperClient.readVespaVersion(), - zooKeeperClient.loadApplicationPackage())); + zooKeeperClient.loadApplicationPackage(), + clock.instant())); } public ApplicationSet ensureApplicationLoaded() { @@ -83,7 +89,7 @@ public class RemoteSession extends Session { @Override public String logPre() { if (applicationSet != null) { - return Tenants.logPre(applicationSet.getForVersionOrLatest(Optional.empty()).getId()); + return Tenants.logPre(applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now()).getId()); } return Tenants.logPre(getTenant()); diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionFactory.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionFactory.java index e4e368716ee..0f1e65aaf2b 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionFactory.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/RemoteSessionFactory.java @@ -9,6 +9,8 @@ import com.yahoo.vespa.config.server.GlobalComponentRegistry; import com.yahoo.vespa.config.server.zookeeper.ConfigCurator; import com.yahoo.vespa.curator.Curator; +import java.time.Clock; + /** * @author lulf * @since 5.1.24 @@ -22,10 +24,12 @@ public class RemoteSessionFactory { private final ConfigDefinitionRepo defRepo; private final TenantName tenant; private final ConfigserverConfig configserverConfig; + private final Clock clock; public RemoteSessionFactory(GlobalComponentRegistry componentRegistry, Path sessionsPath, - TenantName tenant) { + TenantName tenant, + Clock clock) { this.componentRegistry = componentRegistry; this.curator = componentRegistry.getCurator(); this.configCurator = componentRegistry.getConfigCurator(); @@ -33,6 +37,7 @@ public class RemoteSessionFactory { this.tenant = tenant; this.defRepo = componentRegistry.getConfigDefinitionRepo(); this.configserverConfig = componentRegistry.getConfigserverConfig(); + this.clock = clock; } public RemoteSession createSession(long sessionId) { @@ -43,7 +48,7 @@ public class RemoteSessionFactory { defRepo, configserverConfig.serverId(), componentRegistry.getZone().nodeFlavors()); - return new RemoteSession(tenant, sessionId, componentRegistry, sessionZKClient); + return new RemoteSession(tenant, sessionId, componentRegistry, sessionZKClient, clock); } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java index 23c3b4bf8c4..0b76c57e142 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java @@ -28,6 +28,7 @@ import com.yahoo.vespa.curator.Curator; import org.xml.sax.SAXException; import java.io.IOException; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.Optional; @@ -86,11 +87,12 @@ public class SessionPreparer { * @return the config change actions that must be done to handle the activation of the models prepared. */ public ConfigChangeActions prepare(SessionContext context, DeployLogger logger, PrepareParams params, - Optional<ApplicationSet> currentActiveApplicationSet, Path tenantPath) { + Optional<ApplicationSet> currentActiveApplicationSet, Path tenantPath, + Instant now) { Preparation preparation = new Preparation(context, logger, params, currentActiveApplicationSet, tenantPath); preparation.preprocess(); try { - preparation.buildModels(); + preparation.buildModels(now); preparation.makeResult(); if ( ! params.isDryRun()) { preparation.writeStateZK(); @@ -175,8 +177,8 @@ public class SessionPreparer { checkTimeout("preprocess"); } - void buildModels() { - this.modelResultList = preparedModelsBuilder.buildModels(applicationId, vespaVersion, applicationPackage); + void buildModels(Instant now) { + this.modelResultList = preparedModelsBuilder.buildModels(applicationId, vespaVersion, applicationPackage, now); checkTimeout("build models"); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantBuilder.java index 075888168e2..144bbf10dc6 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantBuilder.java @@ -31,6 +31,7 @@ import java.util.concurrent.Executors; * @since 5.1 */ public class TenantBuilder { + private final Path tenantPath; private final GlobalComponentRegistry componentRegistry; private final TenantName tenant; @@ -46,6 +47,7 @@ public class TenantBuilder { private RemoteSessionFactory remoteSessionFactory; private TenantFileSystemDirs tenantFileSystemDirs; private HostValidator<ApplicationId> hostValidator; + private Clock clock = Clock.systemUTC(); private TenantBuilder(GlobalComponentRegistry componentRegistry, TenantName tenant, Path zkPath) { this.componentRegistry = componentRegistry; @@ -88,6 +90,11 @@ public class TenantBuilder { return this; } + public TenantBuilder withClock(Clock clock) { + this.clock = clock; + return this; + } + /** * Create a real tenant from the properties given by this builder. * @@ -97,7 +104,7 @@ public class TenantBuilder { public Tenant build() throws Exception { createTenantRequestHandler(); createApplicationRepo(); - createRemoteSessionFactory(); + createRemoteSessionFactory(clock); createRemoteSessionRepo(); createSessionCounter(); createServerDbDirs(); @@ -164,12 +171,9 @@ public class TenantBuilder { } } - private void createRemoteSessionFactory() { + private void createRemoteSessionFactory(Clock clock) { if (remoteSessionFactory == null) { - remoteSessionFactory = new RemoteSessionFactory( - componentRegistry, - sessionsPath, - tenant); + remoteSessionFactory = new RemoteSessionFactory(componentRegistry, sessionsPath, tenant, clock); } } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRequestHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRequestHandler.java index bc9ff9b79be..20d1e88440f 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRequestHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/TenantRequestHandler.java @@ -1,6 +1,8 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.config.server.tenant; +import java.time.Clock; +import java.time.Instant; import java.util.*; import com.yahoo.config.provision.Version; @@ -43,6 +45,7 @@ public class TenantRequestHandler implements RequestHandler, ReloadHandler, Host private final HostRegistry<ApplicationId> hostRegistry; private final ApplicationMapper applicationMapper = new ApplicationMapper(); private final MetricUpdater tenantMetricUpdater; + private final Clock clock = Clock.systemUTC(); public TenantRequestHandler(Metrics metrics, TenantName tenant, @@ -93,7 +96,7 @@ public class TenantRequestHandler implements RequestHandler, ReloadHandler, Host @Override public void removeApplication(ApplicationId applicationId) { - if (applicationMapper.hasApplication(applicationId)) { + if (applicationMapper.hasApplication(applicationId, clock.instant())) { applicationMapper.remove(applicationId); hostRegistry.removeHostsForKey(applicationId); reloadListenersOnRemove(applicationId); @@ -182,7 +185,7 @@ public class TenantRequestHandler implements RequestHandler, ReloadHandler, Host private Application getApplication(ApplicationId appId, Optional<Version> vespaVersion) { try { - return applicationMapper.getForVersion(appId, vespaVersion); + return applicationMapper.getForVersion(appId, vespaVersion, clock.instant()); } catch (VersionDoesNotExistException ex) { throw new NotFoundException(String.format("%sNo such application (id %s): %s", Tenants.logPre(tenant), appId, ex.getMessage())); } @@ -200,7 +203,7 @@ public class TenantRequestHandler implements RequestHandler, ReloadHandler, Host } private boolean hasHandler(ApplicationId appId, Optional<Version> vespaVersion) { - return applicationMapper.hasApplicationForVersion(appId, vespaVersion); + return applicationMapper.hasApplicationForVersion(appId, vespaVersion, clock.instant()); } @Override diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationMapperTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationMapperTest.java index 8748db808b3..a83f2676a4d 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationMapperTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationMapperTest.java @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.config.server.application; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Optional; @@ -16,6 +17,7 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; public class ApplicationMapperTest { + ApplicationId appId; ApplicationMapper applicationMapper; ArrayList<Version> vespaVersions = new ArrayList<>(); @@ -37,22 +39,22 @@ public class ApplicationMapperTest { @Test public void testGetForVersionReturnsCorrectVersion() { applicationMapper.register(appId, ApplicationSet.fromList(applications)); - assertEquals(applicationMapper.getForVersion(appId, Optional.of(vespaVersions.get(0))), applications.get(0)); - assertEquals(applicationMapper.getForVersion(appId, Optional.of(vespaVersions.get(1))), applications.get(1)); - assertEquals(applicationMapper.getForVersion(appId, Optional.of(vespaVersions.get(2))), applications.get(2)); + assertEquals(applicationMapper.getForVersion(appId, Optional.of(vespaVersions.get(0)), Instant.now()), applications.get(0)); + assertEquals(applicationMapper.getForVersion(appId, Optional.of(vespaVersions.get(1)), Instant.now()), applications.get(1)); + assertEquals(applicationMapper.getForVersion(appId, Optional.of(vespaVersions.get(2)), Instant.now()), applications.get(2)); } @Test public void testGetForVersionReturnsLatestVersion() { applicationMapper.register(appId, ApplicationSet.fromList(applications)); - assertEquals(applicationMapper.getForVersion(appId, Optional.empty()), applications.get(2)); + assertEquals(applicationMapper.getForVersion(appId, Optional.empty(), Instant.now()), applications.get(2)); } @Test (expected = VersionDoesNotExistException.class) public void testGetForVersionThrows() { applicationMapper.register(appId, ApplicationSet.fromList(Arrays.asList(applications.get(0), applications.get(2)))); - applicationMapper.getForVersion(appId, Optional.of(vespaVersions.get(1))); + applicationMapper.getForVersion(appId, Optional.of(vespaVersions.get(1)), Instant.now()); } @Test (expected = NotFoundException.class) @@ -61,6 +63,7 @@ public class ApplicationMapperTest { applicationMapper.getForVersion(new ApplicationId.Builder() .tenant("different").applicationName("different").instanceName("different").build(), - Optional.of(vespaVersions.get(1))); + Optional.of(vespaVersions.get(1)), + Instant.now()); } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationSetTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationSetTest.java index 8d54ee6b530..98bedb76599 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationSetTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ApplicationSetTest.java @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.config.server.application; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -36,21 +37,21 @@ public class ApplicationSetTest { @Test public void testGetForVersionOrLatestReturnsCorrectVersion() { applicationSet = ApplicationSet.fromList(applications); - assertEquals(applicationSet.getForVersionOrLatest(Optional.of(vespaVersions.get(0))), applications.get(0)); - assertEquals(applicationSet.getForVersionOrLatest(Optional.of(vespaVersions.get(1))), applications.get(1)); - assertEquals(applicationSet.getForVersionOrLatest(Optional.of(vespaVersions.get(2))), applications.get(2)); + assertEquals(applicationSet.getForVersionOrLatest(Optional.of(vespaVersions.get(0)), Instant.now()), applications.get(0)); + assertEquals(applicationSet.getForVersionOrLatest(Optional.of(vespaVersions.get(1)), Instant.now()), applications.get(1)); + assertEquals(applicationSet.getForVersionOrLatest(Optional.of(vespaVersions.get(2)), Instant.now()), applications.get(2)); } @Test public void testGetForVersionOrLatestReturnsLatestVersion() { applicationSet = ApplicationSet.fromList(applications); - assertEquals(applicationSet.getForVersionOrLatest(Optional.empty()), applications.get(2)); + assertEquals(applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now()), applications.get(2)); } @Test (expected = VersionDoesNotExistException.class) public void testGetForVersionOrLatestThrows() { applicationSet = ApplicationSet.fromList(Arrays.asList(applications.get(0), applications.get(2))); - applicationSet.getForVersionOrLatest(Optional.of(vespaVersions.get(1))); + applicationSet.getForVersionOrLatest(Optional.of(vespaVersions.get(1)), Instant.now()); } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java index 861cbe467ae..c9556425dda 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java @@ -108,14 +108,14 @@ public class DeployTester { /** * Do the initial "deploy" with the existing API-less code as the deploy API doesn't support first deploys yet. */ - public ApplicationId deployApp(String appName) { - return deployApp(appName, Optional.empty()); + public ApplicationId deployApp(String appName, Instant now) { + return deployApp(appName, Optional.empty(), now); } /** * Do the initial "deploy" with the existing API-less code as the deploy API doesn't support first deploys yet. */ - public ApplicationId deployApp(String appName, Optional<String> vespaVersion) { + public ApplicationId deployApp(String appName, Optional<String> vespaVersion, Instant now) { Tenant tenant = tenant(); LocalSession session = tenant.getSessionFactory().createSession(testApp, appName, new TimeoutBudget(Clock.systemUTC(), Duration.ofSeconds(60))); ApplicationId id = ApplicationId.from(tenant.getName(), ApplicationName.from(appName), InstanceName.defaultName()); @@ -126,7 +126,8 @@ public class DeployTester { session.prepare(new SilentDeployLogger(), paramsBuilder.build(), Optional.empty(), - tenant.getPath()); + tenant.getPath(), + now); session.createActivateTransaction().commit(); tenant.getLocalSessionRepo().addSession(session); this.id = id; diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java index e5d8d1b7b94..31e92cc9f93 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java @@ -12,6 +12,7 @@ import org.junit.Test; import java.io.IOException; import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -31,7 +32,7 @@ public class HostedDeployTest { @Test public void testRedeployWithVersion() throws InterruptedException, IOException { DeployTester tester = new DeployTester("src/test/apps/hosted/", createConfigserverConfig()); - tester.deployApp("myApp", Optional.of("4.5.6")); + tester.deployApp("myApp", Optional.of("4.5.6"), Instant.now()); Optional<com.yahoo.config.provision.Deployment> deployment = tester.redeployFromLocalActive(); assertTrue(deployment.isPresent()); @@ -43,7 +44,7 @@ public class HostedDeployTest { @Test public void testRedeploy() throws InterruptedException, IOException { DeployTester tester = new DeployTester("src/test/apps/hosted/", createConfigserverConfig()); - tester.deployApp("myApp"); + tester.deployApp("myApp", Instant.now()); Optional<com.yahoo.config.provision.Deployment> deployment = tester.redeployFromLocalActive(); assertTrue(deployment.isPresent()); @@ -59,7 +60,7 @@ public class HostedDeployTest { modelFactories.add(DeployTester.createDefaultModelFactory(clock)); modelFactories.add(DeployTester.createFailingModelFactory(Version.fromIntValues(1, 0, 0))); // older than default DeployTester tester = new DeployTester("src/test/apps/validationOverride/", modelFactories, createConfigserverConfig()); - tester.deployApp("myApp"); + tester.deployApp("myApp", clock.instant()); // Redeployment from local active works { @@ -82,7 +83,7 @@ public class HostedDeployTest { // However, redeployment from the outside fails after this date { try { - tester.deployApp("myApp"); + tester.deployApp("myApp", Instant.now()); fail("Expected redeployment to fail"); } catch (Exception expected) { @@ -96,7 +97,7 @@ public class HostedDeployTest { public void testDeployWithDockerImage() throws InterruptedException, IOException { final String vespaVersion = "6.51.1"; DeployTester tester = new DeployTester("src/test/apps/hosted/", createConfigserverConfig()); - ApplicationId applicationId = tester.deployApp("myApp", Optional.of(vespaVersion)); + ApplicationId applicationId = tester.deployApp("myApp", Optional.of(vespaVersion), Instant.now()); assertProvisionInfo(vespaVersion, tester, applicationId); System.out.println("Redeploy"); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java index b3dc14a1178..5658e0fb2aa 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/RedeployTest.java @@ -11,6 +11,7 @@ import org.junit.Test; import java.io.IOException; import java.time.Clock; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -29,7 +30,7 @@ public class RedeployTest { @Test public void testRedeploy() throws InterruptedException, IOException { DeployTester tester = new DeployTester("src/test/apps/app"); - tester.deployApp("myapp"); + tester.deployApp("myapp", Instant.now()); Optional<com.yahoo.config.provision.Deployment> deployment = tester.redeployFromLocalActive(); assertTrue(deployment.isPresent()); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionActiveHandlerTestBase.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionActiveHandlerTestBase.java index 1f181e74b73..eef0058e13e 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionActiveHandlerTestBase.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionActiveHandlerTestBase.java @@ -10,6 +10,7 @@ import static org.junit.Assert.assertThat; import java.io.File; import java.io.IOException; +import java.time.Clock; import java.util.Collections; import java.util.Optional; @@ -17,6 +18,7 @@ import com.yahoo.config.application.api.ApplicationMetaData; import com.yahoo.config.model.application.provider.FilesApplicationPackage; import com.yahoo.config.model.application.provider.MockFileRegistry; import com.yahoo.config.provision.*; +import com.yahoo.test.ManualClock; import com.yahoo.vespa.config.server.modelfactory.ModelFactoryRegistry; import com.yahoo.vespa.config.server.SuperModelGenerationCounter; import com.yahoo.vespa.config.server.TestComponentRegistry; @@ -56,15 +58,17 @@ public abstract class SessionActiveHandlerTestBase extends SessionHandlerTest { @Test public void testThatPreviousSessionIsDeactivated() throws Exception { - RemoteSession firstSession = activateAndAssertOK(90l, 0l); - activateAndAssertOK(91l, 90l); + Clock clock = Clock.systemUTC(); + RemoteSession firstSession = activateAndAssertOK(90l, 0l, clock); + activateAndAssertOK(91l, 90l, clock); assertThat(firstSession.getStatus(), is(Session.Status.DEACTIVATE)); } @Test public void testForceActivationWithActivationInBetween() throws Exception { - activateAndAssertOK(90l, 0l); - activateAndAssertOK(92l, 89l, "?force=true"); + Clock clock = Clock.systemUTC(); + activateAndAssertOK(90l, 0l, clock); + activateAndAssertOK(92l, 89l, "?force=true", clock); } @Test @@ -75,8 +79,9 @@ public abstract class SessionActiveHandlerTestBase extends SessionHandlerTest { @Test public void testActivationWithActivationInBetween() throws Exception { - activateAndAssertOK(90l, 0l); - activateAndAssertError(92l, 89l, + Clock clock = Clock.systemUTC(); + activateAndAssertOK(90l, 0l, clock); + activateAndAssertError(92l, 89l, clock, HttpErrorResponse.errorCodes.BAD_REQUEST, getActivateLogPre() + "Cannot activate session 92 because the currently active session (90) has changed since session 92 was created (was 89 at creation time)"); @@ -87,9 +92,9 @@ public abstract class SessionActiveHandlerTestBase extends SessionHandlerTest { @Test public void testActivationOfUnpreparedSession() throws Exception { // Needed so we can test that previous active session is still active after a failed activation - RemoteSession firstSession = activateAndAssertOK(90l, 0l); + RemoteSession firstSession = activateAndAssertOK(90l, 0l, Clock.systemUTC()); long sessionId = 91l; - ActivateRequest activateRequest = new ActivateRequest(sessionId, 0l, Session.Status.NEW, "").invoke(); + ActivateRequest activateRequest = new ActivateRequest(sessionId, 0l, Session.Status.NEW, "", Clock.systemUTC()).invoke(); HttpResponse actResponse = activateRequest.getActResponse(); RemoteSession session = activateRequest.getSession(); assertThat(actResponse.getStatus(), is(BAD_REQUEST)); @@ -101,16 +106,16 @@ public abstract class SessionActiveHandlerTestBase extends SessionHandlerTest { @Test public void testActivationWithBarrierTimeout() throws Exception { // Needed so we can test that previous active session is still active after a failed activation - activateAndAssertOK(90l, 0l); + activateAndAssertOK(90l, 0l, Clock.systemUTC()); ((MockCurator) curator).timeoutBarrierOnEnter(true); - ActivateRequest activateRequest = new ActivateRequest(91l, 90l, "").invoke(); + ActivateRequest activateRequest = new ActivateRequest(91l, 90l, "", Clock.systemUTC()).invoke(); HttpResponse actResponse = activateRequest.getActResponse(); assertThat(actResponse.getStatus(), is(INTERNAL_SERVER_ERROR)); } @Test public void testActivationOfSessionThatDoesNotExistAsLocalSession() throws Exception { - ActivateRequest activateRequest = new ActivateRequest(90l, 0l, "").invoke(false); + ActivateRequest activateRequest = new ActivateRequest(90l, 0l, "", Clock.systemUTC()).invoke(false); HttpResponse actResponse = activateRequest.getActResponse(); assertThat(actResponse.getStatus(), is(NOT_FOUND)); String message = getRenderedString(actResponse); @@ -119,13 +124,15 @@ public abstract class SessionActiveHandlerTestBase extends SessionHandlerTest { @Test public void require_that_session_created_from_active_that_is_no_longer_active_cannot_be_activated() throws Exception { + Clock clock = Clock.systemUTC(); + long sessionId = 1; - activateAndAssertOK(1, 0); + activateAndAssertOK(1, 0, clock); sessionId++; - activateAndAssertOK(sessionId, 1); + activateAndAssertOK(sessionId, 1, clock); sessionId++; - ActivateRequest activateRequest = new ActivateRequest(sessionId, 1, "").invoke(); + ActivateRequest activateRequest = new ActivateRequest(sessionId, 1, "", Clock.systemUTC()).invoke(); HttpResponse actResponse = activateRequest.getActResponse(); String message = getRenderedString(actResponse); assertThat(message, actResponse.getStatus(), Is.is(BAD_REQUEST)); @@ -135,7 +142,7 @@ public abstract class SessionActiveHandlerTestBase extends SessionHandlerTest { @Test public void testAlreadyActivatedSession() throws Exception { - activateAndAssertOK(1, 0); + activateAndAssertOK(1, 0, Clock.systemUTC()); HttpResponse response = createHandler().handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.ACTIVE, 1l)); String message = getRenderedString(response); assertThat(message, response.getStatus(), Is.is(BAD_REQUEST)); @@ -144,7 +151,7 @@ public abstract class SessionActiveHandlerTestBase extends SessionHandlerTest { protected abstract SessionHandler createHandler() throws Exception; - private RemoteSession createRemoteSession(long sessionId, Session.Status status, SessionZooKeeperClient zkClient) throws IOException { + private RemoteSession createRemoteSession(long sessionId, Session.Status status, SessionZooKeeperClient zkClient, Clock clock) throws IOException { zkClient.writeStatus(status); ZooKeeperClient zkC = new ZooKeeperClient(configCurator, new BaseDeployLogger(), false, pathProvider.getSessionDirs().append(String.valueOf(sessionId))); VespaModelFactory modelFactory = new VespaModelFactory(new NullConfigModelRegistry()); @@ -155,7 +162,7 @@ public abstract class SessionActiveHandlerTestBase extends SessionHandlerTest { .configCurator(configCurator) .modelFactoryRegistry(new ModelFactoryRegistry(Collections.singletonList(modelFactory))) .build(); - RemoteSession session = new RemoteSession(TenantName.from("default"), sessionId, componentRegistry, zkClient); + RemoteSession session = new RemoteSession(TenantName.from("default"), sessionId, componentRegistry, zkClient, clock); remoteSessionRepo.addSession(session); return session; } @@ -170,12 +177,12 @@ public abstract class SessionActiveHandlerTestBase extends SessionHandlerTest { protected abstract void writeApplicationId(SessionZooKeeperClient zkc, String applicationName); - protected abstract Session activateAndAssertOK(long sessionId, long previousSessionId, String subPath) throws Exception; + protected abstract Session activateAndAssertOK(long sessionId, long previousSessionId, String subPath, Clock clock) throws Exception; - protected abstract RemoteSession activateAndAssertOK(long sessionId, long previousSessionId) throws Exception; + protected abstract RemoteSession activateAndAssertOK(long sessionId, long previousSessionId, Clock clock) throws Exception; - protected ActivateRequest activateAndAssertOKPut(long sessionId, long previousSessionId, String subPath) throws Exception { - ActivateRequest activateRequest = new ActivateRequest(sessionId, previousSessionId, subPath); + protected ActivateRequest activateAndAssertOKPut(long sessionId, long previousSessionId, String subPath, Clock clock) throws Exception { + ActivateRequest activateRequest = new ActivateRequest(sessionId, previousSessionId, subPath, clock); activateRequest.invoke(); HttpResponse actResponse = activateRequest.getActResponse(); String message = getRenderedString(actResponse); @@ -188,10 +195,10 @@ public abstract class SessionActiveHandlerTestBase extends SessionHandlerTest { protected abstract void assertActivationMessageOK(ActivateRequest activateRequest, String message) throws IOException; - protected abstract void activateAndAssertError(long sessionId, long previousSessionId, HttpErrorResponse.errorCodes errorCode, String expectedError) throws Exception; + protected abstract void activateAndAssertError(long sessionId, long previousSessionId, Clock clock, HttpErrorResponse.errorCodes errorCode, String expectedError) throws Exception; - protected ActivateRequest activateAndAssertErrorPut(long sessionId, long previousSessionId, HttpErrorResponse.errorCodes errorCode, String expectedError) throws Exception { - ActivateRequest activateRequest = new ActivateRequest(sessionId, previousSessionId, ""); + protected ActivateRequest activateAndAssertErrorPut(long sessionId, long previousSessionId, Clock clock, HttpErrorResponse.errorCodes errorCode, String expectedError) throws Exception { + ActivateRequest activateRequest = new ActivateRequest(sessionId, previousSessionId, "", clock); activateRequest.invoke(); HttpResponse actResponse = activateRequest.getActResponse(); RemoteSession session = activateRequest.getSession(); @@ -219,16 +226,18 @@ public abstract class SessionActiveHandlerTestBase extends SessionHandlerTest { private DeployData deployData; private ApplicationMetaData metaData; private String subPath; + private Clock clock; - public ActivateRequest(long sessionId, long previousSessionId, String subPath) { - this(sessionId, previousSessionId, Session.Status.PREPARE, subPath); + public ActivateRequest(long sessionId, long previousSessionId, String subPath, Clock clock) { + this(sessionId, previousSessionId, Session.Status.PREPARE, subPath, clock); } - public ActivateRequest(long sessionId, long previousSessionId, Session.Status initialStatus, String subPath) { + public ActivateRequest(long sessionId, long previousSessionId, Session.Status initialStatus, String subPath, Clock clock) { this.sessionId = sessionId; this.initialStatus = initialStatus; this.deployData = new DeployData("foo", "bar", appName, 0l, sessionId, previousSessionId); this.subPath = subPath; + this.clock = clock; } public RemoteSession getSession() { @@ -258,7 +267,7 @@ public abstract class SessionActiveHandlerTestBase extends SessionHandlerTest { public ActivateRequest invoke(boolean createLocalSession) throws Exception { SessionZooKeeperClient zkClient = new MockSessionZKClient(curator, pathProvider.getSessionDirs().append(String.valueOf(sessionId)), Optional.of(ProvisionInfo.withHosts(Collections.singleton(new HostSpec("bar", Collections.emptyList()))))); - session = createRemoteSession(sessionId, initialStatus, zkClient); + session = createRemoteSession(sessionId, initialStatus, zkClient, clock); if (createLocalSession) { LocalSessionRepo repo = addLocalSession(sessionId, deployData, zkClient); metaData = repo.getSession(sessionId).getMetaData(); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java index 7379e4a2fab..448b2db2b71 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java @@ -23,6 +23,7 @@ import com.yahoo.vespa.config.server.configchange.ConfigChangeActions; import com.yahoo.vespa.config.server.session.*; import java.io.*; +import java.time.Instant; import java.util.Optional; /** @@ -95,7 +96,7 @@ public class SessionHandlerTest { } @Override - public ConfigChangeActions prepare(DeployLogger logger, PrepareParams params, Optional<ApplicationSet> application, Path tenantPath) { + public ConfigChangeActions prepare(DeployLogger logger, PrepareParams params, Optional<ApplicationSet> application, Path tenantPath, Instant now) { status = Session.Status.PREPARE; if (doVerboseLogging) { logger.log(LogLevel.DEBUG, "debuglog"); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java index 3fd338fbfde..8c4fa1ffc6c 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java @@ -53,6 +53,7 @@ import javax.ws.rs.client.Client; import java.io.File; import java.io.IOException; import java.net.URI; +import java.time.Clock; import java.util.Collections; import static org.hamcrest.CoreMatchers.is; @@ -135,6 +136,7 @@ public class ApplicationHandlerTest { @Test public void testDelete() throws Exception { + Clock clock = Clock.systemUTC(); ApplicationId defaultId = new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(mytenantName).build(); assertApplicationExists(mytenantName, null, Zone.defaultZone()); @@ -162,7 +164,7 @@ public class ApplicationHandlerTest { sessionId++; { - addMockApplication(tenants.getTenant(mytenantName), defaultId, sessionId); + addMockApplication(tenants.getTenant(mytenantName), defaultId, sessionId, clock); deleteAndAssertOKResponseMocked(defaultId, true); ApplicationId fooId = new ApplicationId.Builder() @@ -171,8 +173,8 @@ public class ApplicationHandlerTest { sessionId++; - addMockApplication(tenants.getTenant(mytenantName), fooId, sessionId); - addMockApplication(tenants.getTenant(foobar), fooId, sessionId); + addMockApplication(tenants.getTenant(mytenantName), fooId, sessionId, clock); + addMockApplication(tenants.getTenant(foobar), fooId, sessionId, clock); assertApplicationExists(mytenantName, fooId, Zone.defaultZone()); assertApplicationExists(foobar, fooId, Zone.defaultZone()); deleteAndAssertOKResponseMocked(fooId, true); @@ -187,7 +189,7 @@ public class ApplicationHandlerTest { ApplicationId baliId = new ApplicationId.Builder() .tenant(mytenantName) .applicationName("bali").instanceName("quux").build(); - addMockApplication(tenants.getTenant(mytenantName), baliId, sessionId); + addMockApplication(tenants.getTenant(mytenantName), baliId, sessionId, clock); deleteAndAssertOKResponseMocked(baliId, true); assertApplicationExists(mytenantName, null, Zone.defaultZone()); } @@ -197,7 +199,7 @@ public class ApplicationHandlerTest { public void testGet() throws Exception { long sessionId = 1; ApplicationId defaultId = new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(mytenantName).build(); - addMockApplication(tenants.getTenant(mytenantName), defaultId, sessionId); + addMockApplication(tenants.getTenant(mytenantName), defaultId, sessionId, Clock.systemUTC()); assertApplicationGeneration(defaultId, Zone.defaultZone(), 1, true); assertApplicationGeneration(defaultId, Zone.defaultZone(), 1, false); } @@ -206,7 +208,7 @@ public class ApplicationHandlerTest { public void testRestart() throws Exception { long sessionId = 1; ApplicationId application = new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(mytenantName).build(); - addMockApplication(tenants.getTenant(mytenantName), application, sessionId); + addMockApplication(tenants.getTenant(mytenantName), application, sessionId, Clock.systemUTC()); assertFalse(provisioner.restarted); restart(application, Zone.defaultZone()); assertTrue(provisioner.restarted); @@ -217,7 +219,7 @@ public class ApplicationHandlerTest { public void testConverge() throws Exception { long sessionId = 1; ApplicationId application = new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(mytenantName).build(); - addMockApplication(tenants.getTenant(mytenantName), application, sessionId); + addMockApplication(tenants.getTenant(mytenantName), application, sessionId, Clock.systemUTC()); converge(application, Zone.defaultZone()); } @@ -225,7 +227,7 @@ public class ApplicationHandlerTest { public void testGrabLog() throws Exception { long sessionId = 1; ApplicationId application = new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(mytenantName).build(); - addMockApplication(tenants.getTenant(mytenantName), application, sessionId); + addMockApplication(tenants.getTenant(mytenantName), application, sessionId, Clock.systemUTC()); assertEquals("log line", grabLog(application, Zone.defaultZone())); } @@ -233,7 +235,7 @@ public class ApplicationHandlerTest { public void testClusterControllerStatus() throws Exception { long sessionId = 1; ApplicationId application = new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(mytenantName).build(); - addMockApplication(tenants.getTenant(mytenantName), application, sessionId); + addMockApplication(tenants.getTenant(mytenantName), application, sessionId, Clock.systemUTC()); String host = "foo.yahoo.com"; String url = toUrlPath(application, Zone.defaultZone(), true) + "/clustercontroller/" + host + "/status/v1/clusterName1"; @@ -259,7 +261,7 @@ public class ApplicationHandlerTest { new HttpProxy(new SimpleHttpFetcher()), new LogServerLogGrabber()); final ApplicationId applicationId = ApplicationId.defaultId(); - addMockApplication(tenants.getTenant(mytenantName), applicationId, 1); + addMockApplication(tenants.getTenant(mytenantName), applicationId, 1, Clock.systemUTC()); assertApplicationExists(mytenantName, applicationId, Zone.defaultZone()); provisioner.activated = true; @@ -269,14 +271,14 @@ public class ApplicationHandlerTest { Assert.assertTrue(provisioner.activated); } - static void addMockApplication(Tenant tenant, ApplicationId applicationId, long sessionId) { + static void addMockApplication(Tenant tenant, ApplicationId applicationId, long sessionId, Clock clock) { tenant.getApplicationRepo().createPutApplicationTransaction(applicationId, sessionId).commit(); ApplicationPackage app = FilesApplicationPackage.fromFile(testApp); tenant.getLocalSessionRepo().addSession(new SessionHandlerTest.MockSession(sessionId, app, applicationId)); TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder() .modelFactoryRegistry(new ModelFactoryRegistry(Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry())))) .build(); - tenant.getRemoteSessionRepo().addSession(new RemoteSession(tenant.getName(), sessionId, componentRegistry, new MockSessionZKClient(app))); + tenant.getRemoteSessionRepo().addSession(new RemoteSession(tenant.getName(), sessionId, componentRegistry, new MockSessionZKClient(app), clock)); } static Tenants addApplication(ApplicationId applicationId, long sessionId) throws Exception { @@ -322,7 +324,8 @@ public class ApplicationHandlerTest { .modelFactoryRegistry(new ModelFactoryRegistry( Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry())))) .build(), - sessionClient)); + sessionClient, + Clock.systemUTC())); return tenants; } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java index 288c05e7a6f..e439f424c45 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/HostHandlerTest.java @@ -16,6 +16,7 @@ import org.junit.Before; import org.junit.Test; import java.io.IOException; +import java.time.Clock; import java.util.Collections; import static org.hamcrest.CoreMatchers.is; @@ -62,14 +63,14 @@ public class HostHandlerTest { assertThat(hostRegistries, is(hostHandler.hostRegistries)); long sessionId = 1; ApplicationId id = ApplicationId.from(mytenant, ApplicationName.defaultName(), InstanceName.defaultName()); - ApplicationHandlerTest.addMockApplication(tenants.getTenant(mytenant), id, sessionId); + ApplicationHandlerTest.addMockApplication(tenants.getTenant(mytenant), id, sessionId, Clock.systemUTC()); assertApplicationForHost(hostname, mytenant, id, Zone.defaultZone()); } @Test public void require_that_handler_gives_error_for_unknown_hostname() throws Exception { long sessionId = 1; - ApplicationHandlerTest.addMockApplication(tenants.getTenant(mytenant), ApplicationId.defaultId(), sessionId); + ApplicationHandlerTest.addMockApplication(tenants.getTenant(mytenant), ApplicationId.defaultId(), sessionId, Clock.systemUTC()); final String hostname = "unknown"; assertErrorForHost(hostname, Response.Status.NOT_FOUND, diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java index 5adc3a108f7..01855637005 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java @@ -46,6 +46,7 @@ import org.junit.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.time.Clock; import java.util.Collection; import java.util.List; @@ -76,13 +77,14 @@ public class SessionActiveHandlerTest extends SessionActiveHandlerTestBase { @Test public void testActivation() throws Exception { - activateAndAssertOK(1, 0); + activateAndAssertOK(1, 0, Clock.systemUTC()); } @Test public void testActivationWithActivationInBetween() throws Exception { - activateAndAssertOK(90l, 0l); - activateAndAssertError(92l, 89l, + Clock clock = Clock.systemUTC(); + activateAndAssertOK(90l, 0l, clock); + activateAndAssertError(92l, 89l, clock, HttpErrorResponse.errorCodes.BAD_REQUEST, "tenant:"+tenant+" app:default:default Cannot activate session 92 because the currently active session (90) has changed since session 92 was created (was 89 at creation time)"); } @@ -90,10 +92,11 @@ public class SessionActiveHandlerTest extends SessionActiveHandlerTestBase { @Test public void testActivationOfUnpreparedSession() throws Exception { + Clock clock = Clock.systemUTC(); // Needed so we can test that previous active session is still active after a failed activation - RemoteSession firstSession = activateAndAssertOK(90l, 0l); + RemoteSession firstSession = activateAndAssertOK(90l, 0l, clock); long sessionId = 91L; - ActivateRequest activateRequest = new ActivateRequest(sessionId, 0l, Session.Status.NEW, "").invoke(); + ActivateRequest activateRequest = new ActivateRequest(sessionId, 0l, Session.Status.NEW, "", clock).invoke(); HttpResponse actResponse = activateRequest.getActResponse(); RemoteSession session = activateRequest.getSession(); assertThat(actResponse.getStatus(), is(Response.Status.BAD_REQUEST)); @@ -115,19 +118,19 @@ public class SessionActiveHandlerTest extends SessionActiveHandlerTestBase { public void require_that_handler_gives_error_when_provisioner_activated_fails() throws Exception { hostProvisioner = new FailingMockProvisioner(); hostProvisioner.activated = false; - activateAndAssertError(1, 0, HttpErrorResponse.errorCodes.BAD_REQUEST, "Cannot activate application"); + activateAndAssertError(1, 0, Clock.systemUTC(), HttpErrorResponse.errorCodes.BAD_REQUEST, "Cannot activate application"); assertFalse(hostProvisioner.activated); } @Override - protected RemoteSession activateAndAssertOK(long sessionId, long previousSessionId) throws Exception { - ActivateRequest activateRequest = activateAndAssertOKPut(sessionId, previousSessionId, ""); + protected RemoteSession activateAndAssertOK(long sessionId, long previousSessionId, Clock clock) throws Exception { + ActivateRequest activateRequest = activateAndAssertOKPut(sessionId, previousSessionId, "", clock); return activateRequest.getSession(); } @Override - protected Session activateAndAssertOK(long sessionId, long previousSessionId, String subPath) throws Exception { - ActivateRequest activateRequest = activateAndAssertOKPut(sessionId, previousSessionId, subPath); + protected Session activateAndAssertOK(long sessionId, long previousSessionId, String subPath, Clock clock) throws Exception { + ActivateRequest activateRequest = activateAndAssertOKPut(sessionId, previousSessionId, subPath, clock); return activateRequest.getSession(); } @@ -146,9 +149,9 @@ public class SessionActiveHandlerTest extends SessionActiveHandlerTestBase { } @Override - protected void activateAndAssertError(long sessionId, long previousSessionId, HttpErrorResponse.errorCodes errorCode, String expectedError) throws Exception { + protected void activateAndAssertError(long sessionId, long previousSessionId, Clock clock, HttpErrorResponse.errorCodes errorCode, String expectedError) throws Exception { hostProvisioner.activated = false; - activateAndAssertErrorPut(sessionId, previousSessionId, errorCode, expectedError); + activateAndAssertErrorPut(sessionId, previousSessionId, clock, errorCode, expectedError); assertFalse(hostProvisioner.activated); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java index f321f6f67ef..2d63dcf9b0b 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java @@ -42,6 +42,8 @@ import org.junit.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.time.Clock; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -146,7 +148,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { /** * A mock remote session repo based on contents of local repo */ - private RemoteSessionRepo fromLocalSessionRepo(LocalSessionRepo localRepo) { + private RemoteSessionRepo fromLocalSessionRepo(LocalSessionRepo localRepo, Clock clock) { RemoteSessionRepo remoteRepo = new RemoteSessionRepo(); PathProvider pathProvider = new PathProvider(Path.createRoot()); for (LocalSession ls : localRepo.listSessions()) { @@ -155,7 +157,8 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { if (ls.getStatus()!=null) zooKeeperClient.writeStatus(ls.getStatus()); RemoteSession remSess = new RemoteSession(TenantName.from("default"), ls.getSessionId(), new TestComponentRegistry.Builder().curator(curator).build(), - zooKeeperClient); + zooKeeperClient, + clock); remoteRepo.addSession(remSess); } return remoteRepo; @@ -165,7 +168,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { public void require_get_response_activate_url_on_ok() throws Exception { MockSession session = new MockSession(1, null); localRepo.addSession(session); - SessionHandler sessHandler = createHandler(fromLocalSessionRepo(localRepo)); + SessionHandler sessHandler = createHandler(fromLocalSessionRepo(localRepo, Clock.systemUTC())); sessHandler.handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L)); session.setStatus(Session.Status.PREPARE); zooKeeperClient.writeStatus(Session.Status.PREPARE); @@ -177,7 +180,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { public void require_get_response_error_on_not_prepared() throws Exception { MockSession session = new MockSession(1, null); localRepo.addSession(session); - SessionHandler sessHandler = createHandler(fromLocalSessionRepo(localRepo)); + SessionHandler sessHandler = createHandler(fromLocalSessionRepo(localRepo, Clock.systemUTC())); session.setStatus(Session.Status.NEW); zooKeeperClient.writeStatus(Session.Status.NEW); HttpResponse getResponse = sessHandler.handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.GET, Cmd.PREPARED, 1L)); @@ -197,7 +200,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { MockSession session = new MockSession(1, null); localRepo.addSession(session); session.setStatus(Session.Status.ACTIVATE); - SessionHandler sessionHandler = createHandler(fromLocalSessionRepo(localRepo)); + SessionHandler sessionHandler = createHandler(fromLocalSessionRepo(localRepo, Clock.systemUTC())); HttpResponse putResponse = sessionHandler.handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.PUT, Cmd.PREPARED, 1L)); HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(putResponse, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, @@ -208,7 +211,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { public void require_get_response_error_when_session_id_does_not_exist() throws Exception { MockSession session = new MockSession(1, null); localRepo.addSession(session); - SessionHandler sessHandler = createHandler(fromLocalSessionRepo(localRepo)); + SessionHandler sessHandler = createHandler(fromLocalSessionRepo(localRepo, Clock.systemUTC())); HttpResponse getResponse = sessHandler.handle(SessionHandlerTest.createTestRequest(pathPrefix, HttpRequest.Method.GET, Cmd.PREPARED, 9999L)); HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(getResponse, NOT_FOUND, HttpErrorResponse.errorCodes.NOT_FOUND, @@ -397,7 +400,7 @@ public class SessionPrepareHandlerTest extends SessionHandlerTest { } @Override - public ConfigChangeActions prepare(DeployLogger logger, PrepareParams params, Optional<ApplicationSet> application, Path tenantPath) { + public ConfigChangeActions prepare(DeployLogger logger, PrepareParams params, Optional<ApplicationSet> application, Path tenantPath, Instant now) { throw exception; } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java index eb51627bb95..d5f4dd573c6 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java @@ -5,6 +5,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.*; import java.io.IOException; +import java.time.Clock; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationName; @@ -84,13 +85,13 @@ public class TenantHandlerTest extends TenantTest { @Test public void testDeleteTenantWithActiveApplications() throws Exception { putSync(HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/" + a, Method.PUT)); - final Tenant tenant = tenants.getTenant(a); + Tenant tenant = tenants.getTenant(a); assertEquals(a, tenant.getName()); - final int sessionId = 1; + int sessionId = 1; ApplicationId app = ApplicationId.from(a, ApplicationName.from("foo"), InstanceName.defaultName()); - ApplicationHandlerTest.addMockApplication(tenant, app, sessionId); + ApplicationHandlerTest.addMockApplication(tenant, app, sessionId, Clock.systemUTC()); try { handler.handleDELETE(HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/" + a, Method.DELETE)); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java index cf9c046dada..f0086eabd26 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/LocalSessionTest.java @@ -21,6 +21,7 @@ import org.junit.Before; import org.junit.Test; import java.io.File; +import java.time.Instant; import java.util.*; import static org.hamcrest.core.Is.is; @@ -59,7 +60,7 @@ public class LocalSessionTest { public void require_that_session_status_is_updated() throws Exception { LocalSession session = createSession(TenantName.defaultName(), 3); assertThat(session.getStatus(), is(Session.Status.NEW)); - doPrepare(session); + doPrepare(session, Instant.now()); assertThat(session.getStatus(), is(Session.Status.PREPARE)); session.createActivateTransaction().commit(); assertThat(session.getStatus(), is(Session.Status.ACTIVATE)); @@ -68,7 +69,7 @@ public class LocalSessionTest { @Test public void require_that_marking_session_modified_changes_status_to_new() throws Exception { LocalSession session = createSession(TenantName.defaultName(), 3); - doPrepare(session); + doPrepare(session, Instant.now()); assertThat(session.getStatus(), is(Session.Status.PREPARE)); session.getApplicationFile(Path.createRoot(), LocalSession.Mode.READ); assertThat(session.getStatus(), is(Session.Status.PREPARE)); @@ -81,7 +82,7 @@ public class LocalSessionTest { SessionTest.MockSessionPreparer preparer = new SessionTest.MockSessionPreparer(); LocalSession session = createSession(TenantName.defaultName(), 3, preparer); assertFalse(preparer.isPrepared); - doPrepare(session); + doPrepare(session, Instant.now()); assertTrue(preparer.isPrepared); assertThat(session.getStatus(), is(Session.Status.PREPARE)); } @@ -130,7 +131,7 @@ public class LocalSessionTest { ApplicationId origId = new ApplicationId.Builder() .tenant("tenant") .applicationName("foo").instanceName("quux").build(); - doPrepare(session, new PrepareParams.Builder().applicationId(origId).build()); + doPrepare(session, new PrepareParams.Builder().applicationId(origId).build(), Instant.now()); ProvisionInfo info = session.getProvisionInfo(); assertNotNull(info); assertThat(info.getHosts().size(), is(1)); @@ -140,7 +141,7 @@ public class LocalSessionTest { @Test public void require_that_application_metadata_is_correct() throws Exception { LocalSession session = createSession(TenantName.defaultName(), 3); - doPrepare(session, new PrepareParams.Builder().build()); + doPrepare(session, new PrepareParams.Builder().build(), Instant.now()); assertThat(session.getMetaData().toString(), is("n/a, n/a, 0, 0, , 0")); } @@ -167,12 +168,12 @@ public class LocalSessionTest { return new LocalSession(tenant, sessionId, preparer, new SessionContext(FilesApplicationPackage.fromFile(testApp), zkc, sessionDir, new MemoryTenantApplications(), new HostRegistry<>(), superModelGenerationCounter)); } - private void doPrepare(LocalSession session) { - doPrepare(session, new PrepareParams.Builder().build()); + private void doPrepare(LocalSession session, Instant now) { + doPrepare(session, new PrepareParams.Builder().build(), now); } - private void doPrepare(LocalSession session, PrepareParams params) { - session.prepare(getLogger(false), params, Optional.empty(), tenantPath); + private void doPrepare(LocalSession session, PrepareParams params, Instant now) { + session.prepare(getLogger(false), params, Optional.empty(), tenantPath, now); } DeployHandlerLogger getLogger(boolean verbose) { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionTest.java index d6b7a38345e..44f304847ba 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/RemoteSessionTest.java @@ -27,6 +27,7 @@ import org.junit.Test; import org.xml.sax.SAXException; import java.io.IOException; +import java.time.Clock; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneOffset; @@ -59,28 +60,29 @@ public class RemoteSessionTest { @Test public void require_that_session_is_initialized() { - Session session = createSession(2); + Clock clock = Clock.systemUTC(); + Session session = createSession(2, clock); assertThat(session.getSessionId(), is(2l)); - session = createSession(Long.MAX_VALUE); + session = createSession(Long.MAX_VALUE, clock); assertThat(session.getSessionId(), is(Long.MAX_VALUE)); } @Test public void require_that_applications_are_loaded() throws IOException, SAXException { - RemoteSession session = createSession(3, Arrays.asList(new MockModelFactory(), new VespaModelFactory(new NullConfigModelRegistry()))); + RemoteSession session = createSession(3, Arrays.asList(new MockModelFactory(), new VespaModelFactory(new NullConfigModelRegistry())), Clock.systemUTC()); session.loadPrepared(); ApplicationSet applicationSet = session.ensureApplicationLoaded(); assertNotNull(applicationSet); assertThat(applicationSet.getApplicationGeneration(), is(3l)); - assertThat(applicationSet.getForVersionOrLatest(Optional.empty()).getId().application().value(), is("foo")); - assertNotNull(applicationSet.getForVersionOrLatest(Optional.empty()).getModel()); + assertThat(applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now()).getId().application().value(), is("foo")); + assertNotNull(applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now()).getModel()); session.deactivate(); applicationSet = session.ensureApplicationLoaded(); assertNotNull(applicationSet); assertThat(applicationSet.getApplicationGeneration(), is(3l)); - assertThat(applicationSet.getForVersionOrLatest(Optional.empty()).getId().application().value(), is("foo")); - assertNotNull(applicationSet.getForVersionOrLatest(Optional.empty()).getModel()); + assertThat(applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now()).getId().application().value(), is("foo")); + assertNotNull(applicationSet.getForVersionOrLatest(Optional.empty(), Instant.now()).getModel()); } @Test(expected = IllegalArgumentException.class) @@ -93,7 +95,7 @@ public class RemoteSessionTest { okFactory.vespaVersion = Version.fromIntValues(1, 1, 0); okFactory.throwOnLoad = false; - RemoteSession session = createSession(3, Arrays.asList(okFactory, failingFactory)); + RemoteSession session = createSession(3, Arrays.asList(okFactory, failingFactory), failingFactory.clock()); session.loadPrepared(); } @@ -111,7 +113,7 @@ public class RemoteSessionTest { failingFactory.vespaVersion = Version.fromIntValues(2, 0, 0); failingFactory.throwOnLoad = true; - RemoteSession session = createSession(3, Arrays.asList(okFactory1, failingFactory, okFactory2)); + RemoteSession session = createSession(3, Arrays.asList(okFactory1, failingFactory, okFactory2), failingFactory.clock()); session.loadPrepared(); } @@ -126,7 +128,7 @@ public class RemoteSessionTest { okFactory.vespaVersion = Version.fromIntValues(1, 2, 0); okFactory.throwOnLoad = false; - RemoteSession session = createSession(3, Arrays.asList(okFactory, failingFactory)); + RemoteSession session = createSession(3, Arrays.asList(okFactory, failingFactory), failingFactory.clock()); session.loadPrepared(); } @@ -141,7 +143,7 @@ public class RemoteSessionTest { okFactory.vespaVersion = Version.fromIntValues(2, 0, 0); okFactory.throwOnLoad = false; - RemoteSession session = createSession(3, Arrays.asList(okFactory, failingFactory)); + RemoteSession session = createSession(3, Arrays.asList(okFactory, failingFactory), failingFactory.clock()); session.loadPrepared(); } @@ -161,7 +163,7 @@ public class RemoteSessionTest { tooNewFactory.vespaVersion = Version.fromIntValues(2, 0, 0); tooNewFactory.throwOnLoad = true; - RemoteSession session = createSession(3, Arrays.asList(tooNewFactory, okFactory, failingFactory)); + RemoteSession session = createSession(3, Arrays.asList(tooNewFactory, okFactory, failingFactory), failingFactory.clock()); session.loadPrepared(); } @@ -179,7 +181,7 @@ public class RemoteSessionTest { okFactory.throwOnLoad = false; SessionZooKeeperClient zkc = new MockSessionZKClient(curator, pathProvider.getSessionDir(3), application); - RemoteSession session = createSession(3, zkc, Arrays.asList(okFactory, failingFactory)); + RemoteSession session = createSession(3, zkc, Arrays.asList(okFactory, failingFactory), failingFactory.clock()); session.loadPrepared(); // Does not cause an exception because model version 3 is skipped @@ -188,7 +190,7 @@ public class RemoteSessionTest { @Test public void require_that_session_status_is_updated() throws IOException, SAXException { SessionZooKeeperClient zkc = new MockSessionZKClient(curator, pathProvider.getSessionDir(3)); - RemoteSession session = createSession(3, zkc); + RemoteSession session = createSession(3, zkc, Clock.systemUTC()); assertThat(session.getStatus(), is(Session.Status.NEW)); zkc.writeStatus(Session.Status.PREPARE); assertThat(session.getStatus(), is(Session.Status.PREPARE)); @@ -202,7 +204,7 @@ public class RemoteSessionTest { try { int sessionId = 3; SessionZooKeeperClient zkc = new MockSessionZKClient(curator, pathProvider.getSessionDir(sessionId)); - createSession(sessionId, zkc, Collections.singletonList(mockModelFactory), permanentApp).ensureApplicationLoaded(); + createSession(sessionId, zkc, Collections.singletonList(mockModelFactory), permanentApp, mockModelFactory.clock()).ensureApplicationLoaded(); } catch (Exception e) { e.printStackTrace(); // ignore, we're not interested in deploy errors as long as the below state is OK. @@ -211,22 +213,23 @@ public class RemoteSessionTest { assertTrue(mockModelFactory.modelContext.permanentApplicationPackage().isPresent()); } - private RemoteSession createSession(long sessionId) { - return createSession(sessionId, Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry()))); + private RemoteSession createSession(long sessionId, Clock clock) { + return createSession(sessionId, Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry())), clock); } - private RemoteSession createSession(long sessionId, SessionZooKeeperClient zkc) { - return createSession(sessionId, zkc, Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry()))); + private RemoteSession createSession(long sessionId, SessionZooKeeperClient zkc, Clock clock) { + return createSession(sessionId, zkc, Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry())), clock); } - private RemoteSession createSession(long sessionId, List<ModelFactory> modelFactories) { + private RemoteSession createSession(long sessionId, List<ModelFactory> modelFactories, Clock clock) { SessionZooKeeperClient zkc = new MockSessionZKClient(curator, pathProvider.getSessionDir(sessionId)); - return createSession(sessionId, zkc, modelFactories); + return createSession(sessionId, zkc, modelFactories, clock); } - private RemoteSession createSession(long sessionId, SessionZooKeeperClient zkc, List<ModelFactory> modelFactories) { - return createSession(sessionId, zkc, modelFactories, Optional.empty()); + private RemoteSession createSession(long sessionId, SessionZooKeeperClient zkc, List<ModelFactory> modelFactories, Clock clock) { + return createSession(sessionId, zkc, modelFactories, Optional.empty(), clock); } - private RemoteSession createSession(long sessionId, SessionZooKeeperClient zkc, List<ModelFactory> modelFactories, Optional<PermanentApplicationPackage> permanentApplicationPackage) { + private RemoteSession createSession(long sessionId, SessionZooKeeperClient zkc, List<ModelFactory> modelFactories, + Optional<PermanentApplicationPackage> permanentApplicationPackage, Clock clock) { zkc.writeStatus(Session.Status.NEW); zkc.writeApplicationId(new ApplicationId.Builder().applicationName("foo").instanceName("bim").build()); TestComponentRegistry.Builder registryBuilder = new TestComponentRegistry.Builder() @@ -237,7 +240,8 @@ public class RemoteSessionTest { return new RemoteSession(TenantName.from("default"), sessionId, registryBuilder.build(), - zkc); + zkc, + clock); } private class MockModelFactory implements ModelFactory { @@ -248,6 +252,8 @@ public class RemoteSessionTest { /** The validation overrides of this, or null if none */ private final String validationOverrides; + + private Clock clock = Clock.fixed(LocalDate.parse("2000-01-01", DateTimeFormatter.ISO_DATE).atStartOfDay().atZone(ZoneOffset.UTC).toInstant(), ZoneOffset.UTC); public MockModelFactory() { this(null); } @@ -259,6 +265,9 @@ public class RemoteSessionTest { public Version getVersion() { return vespaVersion; } + + /** Returns the clock used by this, which is fixed at the instant 2000-01-01T00:00:00 */ + public Clock clock() { return clock; } @Override public Model createModel(ModelContext modelContext) { @@ -271,9 +280,8 @@ public class RemoteSessionTest { public Model loadModel() { try { - Instant now = LocalDate.parse("2000-01-01", DateTimeFormatter.ISO_DATE).atStartOfDay().atZone(ZoneOffset.UTC).toInstant(); ApplicationPackage application = new MockApplicationPackage.Builder().withEmptyHosts().withEmptyServices().withValidationOverrides(validationOverrides).build(); - DeployState deployState = new DeployState.Builder().applicationPackage(application).now(now).build(); + DeployState deployState = new DeployState.Builder().applicationPackage(application).now(clock.instant()).build(); return new VespaModel(deployState); } catch (Exception e) { throw new RuntimeException(e); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java index 1ef350461ab..13815e30bbd 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java @@ -40,6 +40,7 @@ import org.xml.sax.SAXException; import java.io.File; import java.io.IOException; +import java.time.Instant; import java.util.*; import static org.hamcrest.CoreMatchers.equalTo; @@ -99,7 +100,7 @@ public class SessionPreparerTest extends TestWithCurator { @Test(expected = InvalidApplicationException.class) public void require_that_application_validation_exception_is_not_caught() throws IOException, SAXException { FilesApplicationPackage app = getApplicationPackage(invalidTestApp); - preparer.prepare(getContext(app), getLogger(), new PrepareParams.Builder().build(), Optional.empty(), tenantPath); + preparer.prepare(getContext(app), getLogger(), new PrepareParams.Builder().build(), Optional.empty(), tenantPath, Instant.now()); } @Test @@ -107,14 +108,14 @@ public class SessionPreparerTest extends TestWithCurator { FilesApplicationPackage app = getApplicationPackage(invalidTestApp); preparer.prepare(getContext(app), getLogger(), new PrepareParams.Builder().ignoreValidationErrors(true).timeoutBudget(TimeoutBudgetTest.day()).build(), - Optional.empty(), tenantPath); + Optional.empty(), tenantPath, Instant.now()); } @Test public void require_that_zookeeper_is_not_written_to_if_dryrun() throws IOException { preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), new PrepareParams.Builder().dryRun(true).timeoutBudget(TimeoutBudgetTest.day()).build(), - Optional.empty(), tenantPath); + Optional.empty(), tenantPath, Instant.now()); assertFalse(configCurator.exists(appPath.append(ConfigCurator.USERAPP_ZK_SUBPATH).append("services.xml").getAbsolute())); } @@ -122,7 +123,7 @@ public class SessionPreparerTest extends TestWithCurator { public void require_that_filedistribution_is_ignored_on_dryrun() throws IOException { preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), new PrepareParams.Builder().dryRun(true).timeoutBudget(TimeoutBudgetTest.day()).build(), - Optional.empty(), tenantPath); + Optional.empty(), tenantPath, Instant.now()); assertThat(fileDistributionFactory.mockFileDistributionProvider.getMockFileDBHandler().sendDeployedFilesCalled, is(0)); assertThat(fileDistributionFactory.mockFileDistributionProvider.getMockFileDBHandler().limitSendingOfDeployedFilesToCalled, is(0)); assertThat(fileDistributionFactory.mockFileDistributionProvider.getMockFileDBHandler().reloadDeployFileDistributorCalled, is(0)); @@ -130,7 +131,7 @@ public class SessionPreparerTest extends TestWithCurator { @Test public void require_that_application_is_prepared() throws Exception { - preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), new PrepareParams.Builder().build(), Optional.empty(), tenantPath); + preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), new PrepareParams.Builder().build(), Optional.empty(), tenantPath, Instant.now()); assertThat(fileDistributionFactory.mockFileDistributionProvider.getMockFileDBHandler().sendDeployedFilesCalled, is(2)); assertThat(fileDistributionFactory.mockFileDistributionProvider.getMockFileDBHandler().limitSendingOfDeployedFilesToCalled, is(2)); // Should be called only once no matter how many model versions are built @@ -144,7 +145,7 @@ public class SessionPreparerTest extends TestWithCurator { new TestModelFactory(Version.fromIntValues(1, 2, 3)), new FailingModelFactory(Version.fromIntValues(3, 2, 1), new IllegalArgumentException("BOOHOO")))); preparer = createPreparer(modelFactoryRegistry, HostProvisionerProvider.empty()); - preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), new PrepareParams.Builder().build(), Optional.empty(), tenantPath); + preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), new PrepareParams.Builder().build(), Optional.empty(), tenantPath, Instant.now()); } @Test(expected = InvalidApplicationException.class) @@ -153,14 +154,14 @@ public class SessionPreparerTest extends TestWithCurator { new TestModelFactory(Version.fromIntValues(3, 2, 3)), new FailingModelFactory(Version.fromIntValues(1, 2, 1), new IllegalArgumentException("BOOHOO")))); preparer = createPreparer(modelFactoryRegistry, HostProvisionerProvider.empty()); - preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), new PrepareParams.Builder().build(), Optional.empty(), tenantPath); + preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), new PrepareParams.Builder().build(), Optional.empty(), tenantPath, Instant.now()); } @Test(expected = InvalidApplicationException.class) public void require_exception_for_overlapping_host() throws IOException { SessionContext ctx = getContext(getApplicationPackage(testApp)); ((HostRegistry<ApplicationId>)ctx.getHostValidator()).update(applicationId("foo"), Collections.singletonList("mytesthost")); - preparer.prepare(ctx, new BaseDeployLogger(), new PrepareParams.Builder().build(), Optional.empty(), tenantPath); + preparer.prepare(ctx, new BaseDeployLogger(), new PrepareParams.Builder().build(), Optional.empty(), tenantPath, Instant.now()); } @Test @@ -172,7 +173,7 @@ public class SessionPreparerTest extends TestWithCurator { System.out.println(level + ": "+message); if (level.equals(LogLevel.WARNING) && message.contains("The host mytesthost is already in use")) logged.append("ok"); }; - preparer.prepare(ctx, logger, new PrepareParams.Builder().build(), Optional.empty(), tenantPath); + preparer.prepare(ctx, logger, new PrepareParams.Builder().build(), Optional.empty(), tenantPath, Instant.now()); assertEquals(logged.toString(), ""); } @@ -183,7 +184,7 @@ public class SessionPreparerTest extends TestWithCurator { .tenant(tenant) .applicationName("foo").instanceName("quux").build(); PrepareParams params = new PrepareParams.Builder().applicationId(origId).build(); - preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), params, Optional.empty(), tenantPath); + preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), params, Optional.empty(), tenantPath, Instant.now()); SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, appPath); assertTrue(configCurator.exists(appPath.append(SessionZooKeeperClient.APPLICATION_ID_PATH).getAbsolute())); assertThat(zkc.readApplicationId(), is(origId)); @@ -200,7 +201,7 @@ public class SessionPreparerTest extends TestWithCurator { preparer = createPreparer(modelFactoryRegistry, HostProvisionerProvider.empty()); List<RestartActions.Entry> actions = preparer.prepare(getContext(getApplicationPackage(testApp)), getLogger(), - new PrepareParams.Builder().build(), Optional.empty(), tenantPath) + new PrepareParams.Builder().build(), Optional.empty(), tenantPath, Instant.now()) .getRestartActions().getEntries(); assertThat(actions.size(), is(1)); assertThat(actions.get(0).getMessages(), equalTo(ImmutableSet.of("change", "other change"))); @@ -216,7 +217,7 @@ public class SessionPreparerTest extends TestWithCurator { final ApplicationId applicationId = applicationId("test"); PrepareParams params = new PrepareParams.Builder().applicationId(applicationId).rotations(rotations).build(); File app = new File("src/test/resources/deploy/app"); - preparer.prepare(getContext(getApplicationPackage(app)), getLogger(), params, Optional.empty(), tenantPath); + preparer.prepare(getContext(getApplicationPackage(app)), getLogger(), params, Optional.empty(), tenantPath, Instant.now()); assertThat(readRotationsFromZK(applicationId), contains(new Rotation(rotations))); } @@ -232,7 +233,7 @@ public class SessionPreparerTest extends TestWithCurator { new Rotations(curator, tenantPath).writeRotationsToZooKeeper(applicationId, Collections.singleton(new Rotation(rotations))); final PrepareParams params = new PrepareParams.Builder().applicationId(applicationId).build(); final File app = new File("src/test/resources/deploy/app"); - preparer.prepare(getContext(getApplicationPackage(app)), getLogger(), params, Optional.empty(), tenantPath); + preparer.prepare(getContext(getApplicationPackage(app)), getLogger(), params, Optional.empty(), tenantPath, Instant.now()); // check that the rotation from zookeeper were used final ModelContext modelContext = modelFactory.getModelContext(); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java index 9d5890377c5..71b31754c5d 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionTest.java @@ -7,6 +7,7 @@ import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.configchange.ConfigChangeActions; import com.yahoo.vespa.curator.mock.MockCurator; +import java.time.Instant; import java.util.ArrayList; import java.util.Optional; @@ -24,7 +25,7 @@ public class SessionTest { } @Override - public ConfigChangeActions prepare(SessionContext context, DeployLogger logger, PrepareParams params, Optional<ApplicationSet> currentActiveApplicationSet, Path tenantPath) { + public ConfigChangeActions prepare(SessionContext context, DeployLogger logger, PrepareParams params, Optional<ApplicationSet> currentActiveApplicationSet, Path tenantPath, Instant now) { isPrepared = true; return new ConfigChangeActions(new ArrayList<>()); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRequestHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRequestHandlerTest.java index a5b4739091b..65f546e149a 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRequestHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantRequestHandlerTest.java @@ -47,6 +47,7 @@ import org.xml.sax.SAXException; import java.io.File; import java.io.IOException; +import java.time.Clock; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; @@ -96,17 +97,17 @@ public class TenantRequestHandlerTest extends TestWithCurator { deployer.deploy(FilesApplicationPackage.fromFile(appDir), Collections.singletonMap(vespaVersion, new MockFileRegistry()), Collections.emptyMap()); } - private ApplicationSet reloadConfig(long id) { - return reloadConfig(id, "default"); + private ApplicationSet reloadConfig(long id, Clock clock) { + return reloadConfig(id, "default", clock); } - private ApplicationSet reloadConfig(long id, String application) { + private ApplicationSet reloadConfig(long id, String application, Clock clock) { SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, configCurator, new PathProvider(Path.createRoot()).getSessionDir(id), new TestConfigDefinitionRepo(), "", Optional.empty()); zkc.writeApplicationId(new ApplicationId.Builder().tenant(tenant).applicationName(application).build()); - RemoteSession session = new RemoteSession(tenant, id, componentRegistry, zkc); + RemoteSession session = new RemoteSession(tenant, id, componentRegistry, zkc, clock); return session.ensureApplicationLoaded(); } @@ -146,15 +147,17 @@ public class TenantRequestHandlerTest extends TestWithCurator { @Test public void testReloadConfig() throws IOException, SAXException { + Clock clock = Clock.systemUTC(); ApplicationId applicationId = new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(tenant).build(); - server.reloadConfig(reloadConfig(1)); + + server.reloadConfig(reloadConfig(1, clock)); assertThat(listener.reloaded.get(), is(1)); // Using only payload list for this simple test SimpletypesConfig config = resolve(SimpletypesConfig.class, server, ""); assertThat(config.intval(), is(1337)); assertThat(server.getApplicationGeneration(applicationId, Optional.of(vespaVersion)), is(1l)); - server.reloadConfig(reloadConfig(1l)); + server.reloadConfig(reloadConfig(1l, clock)); config = resolve(SimpletypesConfig.class, server, ""); assertThat(config.intval(), is(1337)); assertThat(listener.reloaded.get(), is(2)); @@ -164,7 +167,7 @@ public class TenantRequestHandlerTest extends TestWithCurator { listener.reloaded.set(0); feedApp(app2, 2); - server.reloadConfig(reloadConfig(2l)); + server.reloadConfig(reloadConfig(2l, clock)); config = resolve(SimpletypesConfig.class, server, ""); assertThat(config.intval(), is(1330)); assertThat(listener.reloaded.get(), is(1)); @@ -173,7 +176,7 @@ public class TenantRequestHandlerTest extends TestWithCurator { @Test public void testRemoveApplication() { - server.reloadConfig(reloadConfig(1)); + server.reloadConfig(reloadConfig(1, Clock.systemUTC())); assertThat(listener.removed.get(), is(0)); server.removeApplication(new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(tenant).build()); assertThat(listener.removed.get(), is(1)); @@ -190,7 +193,7 @@ public class TenantRequestHandlerTest extends TestWithCurator { .tenant(tenant) .applicationName("myapp").instanceName("myinst").build(); zkc.writeApplicationId(appId); - RemoteSession session = new RemoteSession(appId.tenant(), id, componentRegistry, zkc); + RemoteSession session = new RemoteSession(appId.tenant(), id, componentRegistry, zkc, Clock.systemUTC()); server.reloadConfig(session.ensureApplicationLoaded()); SimpletypesConfig config = resolve(SimpletypesConfig.class, server, appId, vespaVersion, ""); assertThat(config.intval(), is(1337)); @@ -229,7 +232,7 @@ public class TenantRequestHandlerTest extends TestWithCurator { feedApp(appDir, sessionId, appId); SessionZooKeeperClient zkc = new SessionZooKeeperClient(curator, new PathProvider(Path.createRoot()).getSessionDir(sessionId)); zkc.writeApplicationId(appId); - RemoteSession session = new RemoteSession(tenant, sessionId, componentRegistry, zkc); + RemoteSession session = new RemoteSession(tenant, sessionId, componentRegistry, zkc, Clock.systemUTC()); server.reloadConfig(session.ensureApplicationLoaded()); } @@ -260,7 +263,7 @@ public class TenantRequestHandlerTest extends TestWithCurator { @Test public void testHasApplication() throws IOException, SAXException { assertdefaultAppNotFound(); - server.reloadConfig(reloadConfig(1l)); + server.reloadConfig(reloadConfig(1l, Clock.systemUTC())); assertTrue(server.hasApplication(new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(tenant).build(), Optional.of(vespaVersion))); } @@ -271,7 +274,7 @@ public class TenantRequestHandlerTest extends TestWithCurator { @Test public void testMultipleApplicationsReload() { assertdefaultAppNotFound(); - server.reloadConfig(reloadConfig(1l, "foo")); + server.reloadConfig(reloadConfig(1l, "foo", Clock.systemUTC())); assertdefaultAppNotFound(); assertTrue(server.hasApplication(new ApplicationId.Builder().tenant(tenant).applicationName("foo").build(), Optional.of(vespaVersion))); @@ -19,7 +19,7 @@ <developers> <developer> - <name>Yahoo Inc.</name> + <name>Vespa</name> <url>https://github.com/yahoo</url> </developer> </developers> diff --git a/searchlib/src/vespa/searchlib/features/create-class-cpp.sh b/searchlib/src/vespa/searchlib/features/create-class-cpp.sh index 188143f02ee..1e38a837609 100755 --- a/searchlib/src/vespa/searchlib/features/create-class-cpp.sh +++ b/searchlib/src/vespa/searchlib/features/create-class-cpp.sh @@ -6,7 +6,7 @@ guard=`echo $class | tr 'a-z' 'A-Z'` name=`echo $class | tr 'A-Z' 'a-z'` cat <<EOF -// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "$name.h" diff --git a/testutil/src/main/java/com/yahoo/test/ManualClock.java b/testutil/src/main/java/com/yahoo/test/ManualClock.java index 785e69e4bfd..28ccdc8d27e 100644 --- a/testutil/src/main/java/com/yahoo/test/ManualClock.java +++ b/testutil/src/main/java/com/yahoo/test/ManualClock.java @@ -10,7 +10,11 @@ import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAmount; -/** A clock which initially has the time of its creation but can only be advanced by calling advance */ +/** + * A clock which initially has the time of its creation but can only be advanced by calling advance + * + * @author bratseth + */ public class ManualClock extends Clock { private Instant currentTime = Instant.now(); |