diff options
author | Harald Musum <musum@verizonmedia.com> | 2021-06-28 14:11:41 +0200 |
---|---|---|
committer | Harald Musum <musum@verizonmedia.com> | 2021-06-28 14:11:41 +0200 |
commit | 6f01029ff835739c3aa1df78da4cc396633efa1c (patch) | |
tree | d0650693f0a4517a7664ec3977efab2847e1264b | |
parent | 51b4f15b25068cab7eff193046cb9f18512f4a84 (diff) |
Validate validation-overrides.xml when validating model
4 files changed, 141 insertions, 18 deletions
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 index 3221df38d4f..82601e6685b 100644 --- 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 @@ -49,6 +49,7 @@ public class ValidationOverrides { private ValidationOverrides(List<Allow> overrides, String xmlForm) { this.overrides = ImmutableList.copyOf(overrides); this.xmlForm = xmlForm; + validate(Instant.now()); } /** Throws a ValidationException unless all given validation is overridden at this time */ @@ -74,14 +75,22 @@ public class ValidationOverrides { /** 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; } + /** Validates overrides (checks 'until' date') */ + public boolean validate(Instant now) { + for (Allow override : overrides) { + if (now.plus(Duration.ofDays(30)).isBefore(override.until)) + throw new IllegalArgumentException("validation-overrides is invalid: " + override + + " is too far in the future: Max 30 days is allowed"); + } + 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; } @@ -116,23 +125,18 @@ public class ValidationOverrides { 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)); - // skip unknown ids as they may be valid for other model versions - validationId.ifPresent(id -> overrides.add(new Allow(id, until))); - } - return new ValidationOverrides(overrides, xmlForm); - } - catch (IllegalArgumentException e) { - throw new IllegalArgumentException("validation-overrides is invalid", e); + // 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)); + // skip unknown ids as they may be valid for other model versions + validationId.ifPresent(id -> overrides.add(new Allow(id, until))); } + return new ValidationOverrides(overrides, xmlForm); } /** A validation override which allows a particular change. Immutable. */ 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 84c7a48a998..d59ad5f6252 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 @@ -70,6 +70,7 @@ public class Validation { new RankSetupValidator(validationParameters.ignoreValidationErrors()).validate(model, deployState); new NoPrefixForIndexes().validate(model, deployState); new DeploymentSpecValidator().validate(model, deployState); + new ValidationOverridesValidator().validate(model, deployState); new RankingConstantsValidator().validate(model, deployState); new SecretStoreValidator().validate(model, deployState); new EndpointCertificateSecretsValidator().validate(model, deployState); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/ValidationOverridesValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/ValidationOverridesValidator.java new file mode 100644 index 00000000000..0095c2c335d --- /dev/null +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/ValidationOverridesValidator.java @@ -0,0 +1,29 @@ +// Copyright Verizon Media. 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.config.application.api.ValidationOverrides; +import com.yahoo.config.model.deploy.DeployState; +import com.yahoo.vespa.model.VespaModel; + +import java.io.Reader; +import java.time.Duration; +import java.util.Optional; + +/** + * Validate validation overrides (validation-overrides.xml). Done as a validator to make sure this is + * done when validating the mode and not when building the model + * + * @author hmusum + */ +public class ValidationOverridesValidator extends Validator { + + @Override + public void validate(VespaModel model, DeployState deployState) { + Optional<Reader> overrides = deployState.getApplicationPackage().getValidationOverrides(); + if (overrides.isEmpty()) return; + + // Dates are validated in constructor + ValidationOverrides.fromXml(overrides.get()); + } + +} diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationOverridesValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationOverridesValidatorTest.java new file mode 100644 index 00000000000..453bdf98ce7 --- /dev/null +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationOverridesValidatorTest.java @@ -0,0 +1,89 @@ +// Copyright Verizon Media. 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.config.model.NullConfigModelRegistry; +import com.yahoo.config.model.deploy.DeployState; +import com.yahoo.config.model.test.MockApplicationPackage; +import com.yahoo.vespa.model.VespaModel; +import org.junit.Test; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * @author hmusum + */ +public class ValidationOverridesValidatorTest { + + private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + .withZone(ZoneId.systemDefault()); + + @Test + public void testValidationOverride() throws IOException, SAXException { + String tenDays = dateTimeFormatter.format(Instant.now().plus(Duration.ofDays(10))); + + var validationOverridesXml = "<?xml version='1.0' encoding='UTF-8'?>\n" + + " <validation-overrides>\n" + + " <allow until='" + tenDays + "'>deployment-removal</allow>\n" + + " </validation-overrides>"; + + var deployState = createDeployState(validationOverridesXml); + new VespaModel(new NullConfigModelRegistry(), deployState); + } + + @Test + public void testFailsWhenValidationOverrideIsTooFarInFuture() { + Instant now = Instant.now(); + String sixtyDays = dateTimeFormatter.format(now.plus(Duration.ofDays(60))); + String sixtyOneDays = dateTimeFormatter.format(now.plus(Duration.ofDays(61))); + + var validationOverrides = "<?xml version='1.0' encoding='UTF-8'?>\n" + + "<validation-overrides>\n" + + " <allow until='" + sixtyDays + "'>deployment-removal</allow>\n" + + "</validation-overrides>"; + assertValidationError("validation-overrides is invalid: allow 'deployment-removal' until " + + sixtyOneDays + "T00:00:00Z is too far in the future: Max 30 days is allowed", validationOverrides); + } + + private static void assertValidationError(String message, String validationOverridesXml) { + try { + var deployState = createDeployState(validationOverridesXml); + new VespaModel(new NullConfigModelRegistry(), deployState); + fail("Did not get expected exception"); + } catch (IllegalArgumentException e) { + assertEquals(message, e.getMessage()); + } catch (SAXException|IOException e) { + throw new RuntimeException(e); + } + } + + private static DeployState createDeployState(String validationOverridesXml) { + var services = "<services version='1.0'>" + + " <admin version='2.0'>" + + " <adminserver hostalias='node0' />" + + " </admin>" + + " <container id='default' version='1.0'>" + + " <search/>" + + " <nodes>" + + " <node hostalias='node0'/>" + + " </nodes>" + + " </container>" + + "</services>"; + + var app = new MockApplicationPackage.Builder() + .withValidationOverrides(validationOverridesXml) + .withServices(services) + .build(); + var builder = new DeployState.Builder().applicationPackage(app); + return builder.build(); + } + +} + |