From e5a1f22a814923e4f6a2a298a76630d6567e02b8 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Thu, 15 Jun 2017 12:13:18 +0200 Subject: Make ValidationOverrides available from the outside --- .../yahoo/config/application/api/ValidationId.java | 45 +++++++ .../application/api/ValidationOverrides.java | 144 +++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 config-model-api/src/main/java/com/yahoo/config/application/api/ValidationId.java create mode 100644 config-model-api/src/main/java/com/yahoo/config/application/api/ValidationOverrides.java (limited to 'config-model-api/src/main/java') diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationId.java b/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationId.java new file mode 100644 index 00000000000..37cb6e40f3d --- /dev/null +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationId.java @@ -0,0 +1,45 @@ +// 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 java.util.Optional; + +/** + * Ids of validations that can be overridden + * + * @author bratseth + */ +public enum ValidationId { + + indexingChange("indexing-change"), // Changing what tokens are expected and stored in field indexes + indexModeChange("indexing-mode-change"), // Changing the index mode (streaming, indexed, store-only) of documents + fieldTypeChange("field-type-change"), // Field type changes + clusterSizeReduction("cluster-size-reduction"), // Large reductions in cluster size + contentClusterRemoval("content-cluster-removal"), // Removal (or id change) of content clusters + deploymentRemoval("deployment-removal"), // Removal of production zones from deployment.xml + skipAutomaticTenantUpgradeTests("skip-automatic-tenant-upgrade-test"), // Skip platform supplied staging tests + configModelVersionMismatch("config-model-version-mismatch"), // Internal use + skipOldConfigModels("skip-old-config-models"), // Internal use + forceAutomaticTenantUpgradeTests("force-automatic-tenant-upgrade-test"); // Internal use + + private final String id; + + ValidationId(String id) { this.id = id; } + + public String value() { return id; } + + @Override + public String toString() { return id; } + + /** + * Returns the validation id from this string. + * Use this instead of valueOf to match string on the (canonical) dash-separated form. + * + * @return the matching validation id or empty if none + */ + public static Optional from(String id) { + for (ValidationId candidate : ValidationId.values()) + if (id.equals(candidate.toString())) return Optional.of(candidate); + return Optional.empty(); + } + +} 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..9fd2b7f2bf8 --- /dev/null +++ b/config-model-api/src/main/java/com/yahoo/config/application/api/ValidationOverrides.java @@ -0,0 +1,144 @@ +// 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.text.XML; +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; + +/** + * 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 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 overrides) { + this(overrides, Instant.now()); + } + + public ValidationOverrides(List 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.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()); } + + /** + * 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 static ValidationOverrides read(Optional 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 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.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); + } + } + + /** 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(); } + + } + +} -- cgit v1.2.3