All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yahoo.vespa.model.application.validation.Validation Maven / Gradle / Ivy

There is a newer version: 8.409.18
Show newest version
// Copyright Vespa.ai. 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.ValidationId;
import com.yahoo.config.application.api.ValidationOverrides;
import com.yahoo.config.application.api.ValidationOverrides.ValidationException;
import com.yahoo.config.model.api.ConfigChangeAction;
import com.yahoo.config.model.api.ValidationParameters;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.vespa.model.VespaModel;
import com.yahoo.vespa.model.application.validation.change.CertificateRemovalChangeValidator;
import com.yahoo.vespa.model.application.validation.change.ConfigValueChangeValidator;
import com.yahoo.vespa.model.application.validation.change.ContainerRestartValidator;
import com.yahoo.vespa.model.application.validation.change.ContentClusterRemovalValidator;
import com.yahoo.vespa.model.application.validation.change.ContentTypeRemovalValidator;
import com.yahoo.vespa.model.application.validation.change.GlobalDocumentChangeValidator;
import com.yahoo.vespa.model.application.validation.change.IndexedSearchClusterChangeValidator;
import com.yahoo.vespa.model.application.validation.change.IndexingModeChangeValidator;
import com.yahoo.vespa.model.application.validation.change.NodeResourceChangeValidator;
import com.yahoo.vespa.model.application.validation.change.RedundancyIncreaseValidator;
import com.yahoo.vespa.model.application.validation.change.ResourcesReductionValidator;
import com.yahoo.vespa.model.application.validation.change.RestartOnDeployForLocalLLMValidator;
import com.yahoo.vespa.model.application.validation.change.RestartOnDeployForOnnxModelChangesValidator;
import com.yahoo.vespa.model.application.validation.change.StartupCommandChangeValidator;
import com.yahoo.vespa.model.application.validation.change.StreamingSearchClusterChangeValidator;
import com.yahoo.vespa.model.application.validation.change.VespaRestartAction;
import com.yahoo.vespa.model.application.validation.first.RedundancyValidator;
import com.yahoo.yolean.Exceptions;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;

/**
 * Executor of validators. This defines the right order of validator execution.
 *
 * @author hmusum
 */
public class Validation {

    private final List additionalValidators;

    public Validation() { this(List.of()); }

    /** Create instance taking additional validators (e.g., for cloud applications) */
    public Validation(List additionalValidators) { this.additionalValidators = additionalValidators; }

    /**
     * Validates the model supplied, and if there already exists a model for the application validates changes
     * between the previous and current model
     *
     * @return a list of required changes needed to make this configuration live
     * @throws ValidationOverrides.ValidationException if the change fails validation
     */
    public List validate(VespaModel model, ValidationParameters validationParameters, DeployState deployState) {
        Execution execution = new Execution(model, deployState);
        if (validationParameters.checkRouting()) {
            validateRouting(execution);
        }

        validateModel(validationParameters, execution);

        for (Validator validator : additionalValidators) {
            validator.validate(execution);
        }

        if (deployState.getProperties().isFirstTimeDeployment()) {
            validateFirstTimeDeployment(execution);
        }
        else if (deployState.getPreviousModel().isPresent() && (deployState.getPreviousModel().get() instanceof VespaModel)) {
            validateChanges(execution);
        }

        execution.throwIfFailed();
        return execution.actions;
    }

    private static void validateRouting(Execution execution) {
        new RoutingValidator().validate(execution);
    }

    private static void validateModel(ValidationParameters validationParameters, Execution execution) {
        new SchemasDirValidator().validate(execution);
        new BundleValidator().validate(execution);
        new PublicApiBundleValidator().validate(execution);
        new SearchDataTypeValidator().validate(execution);
        new ComplexFieldsWithStructFieldAttributesValidator().validate(execution);
        new ComplexFieldsWithStructFieldIndexesValidator().validate(execution);
        new StreamingValidator().validate(execution);
        new RankSetupValidator(validationParameters.ignoreValidationErrors()).validate(execution);
        new NoPrefixForIndexes().validate(execution);
        new ContainerInCloudValidator().validate(execution);
        new DeploymentSpecValidator().validate(execution);
        new ValidationOverridesValidator().validate(execution);
        new ConstantValidator().validate(execution);
        new SecretStoreValidator().validate(execution);
        new AccessControlFilterValidator().validate(execution);
        new QuotaValidator().validate(execution);
        new UriBindingsValidator().validate(execution);
        new CloudDataPlaneFilterValidator().validate(execution);
        new AccessControlFilterExcludeValidator().validate(execution);
        new CloudUserFilterValidator().validate(execution);
        new CloudHttpConnectorValidator().validate(execution);
        new UrlConfigValidator().validate(execution);
        new JvmHeapSizeValidator().validate(execution);
        new InfrastructureDeploymentValidator().validate(execution);
        new EndpointCertificateSecretsValidator().validate(execution);
        new CloudClientsValidator().validate(execution);
        new PagedAttributesRemoteStorageValidator().validate(execution);
    }

    private static void validateFirstTimeDeployment(Execution execution) {
        new RedundancyValidator().validate((Context) execution);
    }

    private static void validateChanges(Execution execution) {
        new IndexingModeChangeValidator().validate(execution);
        new GlobalDocumentChangeValidator().validate(execution);
        new IndexedSearchClusterChangeValidator().validate(execution);
        new StreamingSearchClusterChangeValidator().validate(execution);
        new ConfigValueChangeValidator().validate(execution);
        new StartupCommandChangeValidator().validate(execution);
        new ContentTypeRemovalValidator().validate(execution);
        new ContentClusterRemovalValidator().validate(execution);
        new ResourcesReductionValidator().validate(execution);
        new ContainerRestartValidator().validate(execution);
        new NodeResourceChangeValidator().validate(execution);
        new RedundancyIncreaseValidator().validate(execution);
        new CertificateRemovalChangeValidator().validate(execution);
        new RedundancyValidator().validate(execution);
        new RestartOnDeployForOnnxModelChangesValidator().validate(execution);
        new RestartOnDeployForLocalLLMValidator().validate(execution);
    }

    public interface Context {
        /** Auxiliary deploy state of the application. */
        DeployState deployState();
        /** The model to validate. */
        VespaModel model();
        /** Report a failed validation which cannot be overridden; this results in an {@link IllegalArgumentException}. */
        default void illegal(String message) { illegal(message, null); }
        /** Report a failed validation which cannot be overridden; this results in an {@link IllegalArgumentException}. */
        void illegal(String message, Throwable cause);
        /** Report a failed validation which can be overridden; this results in a {@link ValidationException}. */
        void invalid(ValidationId id, String message);
    }

    public interface ChangeContext extends Context {
        /** The previous model, if any. */
        VespaModel previousModel();
        /**
         * Report an action the user must take to change to the new configuration.
         * If the action has a {@link ValidationId}, {@link #invalid} is also called for this id, and the action's message.
         */
        void require(ConfigChangeAction action);
    }

    static class Execution implements ChangeContext {

        private final List errors = new ArrayList<>();
        private final Map> failures = new LinkedHashMap<>();
        private final VespaModel model;
        private final DeployState deployState;
        private final List actions = new ArrayList<>();

        Execution(VespaModel model, DeployState deployState) {
            this.model = model;
            this.deployState = deployState;
        }

        void throwIfFailed() {
            Optional invalidException = deployState.validationOverrides().invalidException(failures, deployState.now());
            if (invalidException.isPresent() && deployState.isHosted() && deployState.zone().environment().isManuallyDeployed()) {
                deployState.getDeployLogger().logApplicationPackage(Level.WARNING,
                                                                    "Auto-overriding validation which would be disallowed in production: " +
                                                                    Exceptions.toMessageString(invalidException.get()));
                invalidException = Optional.empty();
            }

            if ( ! errors.isEmpty()) {
                String illegalMessage = errors.size() == 1 ? errors.get(0)
                                                           : "multiple errors:\n\t" + String.join("\n\t", errors);
                if (invalidException.isPresent())
                    illegalMessage += "\n" + invalidException.get().getMessage();

                throw new IllegalArgumentException(illegalMessage);
            }

            invalidException.ifPresent(e -> { throw e; });
        }

        List actions() {
            return actions;
        }

        List errors() {
            return errors;
        }

        @Override
        public DeployState deployState() {
            return deployState;
        }

        @Override
        public VespaModel model() {
            return model;
        }

        @Override
        public VespaModel previousModel() {
            return (VespaModel) deployState.getPreviousModel().get();
        }

        @Override
        public void require(ConfigChangeAction action) {
            if (action instanceof VespaRestartAction && action.getServices().isEmpty())
                throw new IllegalStateException("restart actions must have services specified");

            actions.add(action);
            action.validationId().ifPresent(id -> invalid(id, action.getMessage()));
        }

        @Override
        public void illegal(String message, Throwable cause) {
            if (cause != null) message += ": " + Exceptions.toMessageString(cause);
            errors.add(message);
        }

        @Override
        public void invalid(ValidationId id, String message) {
            failures.computeIfAbsent(id, __ -> new ArrayList<>()).add(message);
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy