summaryrefslogtreecommitdiffstats
path: root/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationTester.java
blob: 29279635918245b7e9e56ac5ac1ef6eab72c3a35 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// 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.collections.Pair;
import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.model.api.ApplicationClusterEndpoint;
import com.yahoo.config.model.api.ConfigChangeAction;
import com.yahoo.config.model.api.ContainerEndpoint;
import com.yahoo.config.model.api.Provisioned;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.deploy.TestProperties;
import com.yahoo.config.model.provision.InMemoryProvisioner;
import com.yahoo.config.model.test.MockApplicationPackage;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.Zone;
import com.yahoo.vespa.model.VespaModel;
import com.yahoo.vespa.model.application.validation.Validation.Execution;
import com.yahoo.vespa.model.application.validation.change.ChangeValidator;
import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithMockPkg;

import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.yahoo.config.model.test.MockApplicationPackage.BOOK_SCHEMA;
import static com.yahoo.config.model.test.MockApplicationPackage.MUSIC_SCHEMA;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * @author bratseth
 */
public class ValidationTester {

    private final TestProperties properties;
    private final InMemoryProvisioner hostProvisioner;

    /** Creates a validation tester with 1 node available (in addition to cluster controllers) */
    public ValidationTester() {
        this(5);
    }

    /** Creates a validation tester with number of nodes available and the given test properties */
    public ValidationTester(int nodeCount, boolean sharedHosts, TestProperties properties) {
        this(new InMemoryProvisioner(nodeCount, sharedHosts), properties);
    }

    /** Creates a validation tester with a given host provisioner */
    public ValidationTester(InMemoryProvisioner hostProvisioner) {
        this(hostProvisioner, new TestProperties().setHostedVespa(true));
    }

    /** Creates a validation tester with a number of nodes available */
    public ValidationTester(int nodeCount) {
        this(new InMemoryProvisioner(nodeCount, false), new TestProperties().setHostedVespa(true));
    }

    /** Creates a validation tester with a given host provisioner */
    public ValidationTester(InMemoryProvisioner hostProvisioner, TestProperties testProperties) {
        this.hostProvisioner = hostProvisioner;
        this.properties = testProperties;
        hostProvisioner.setEnvironment(testProperties.zone().environment());
    }

    /**
     * Deploys an application
     *
     * @param previousModel the previous model, or null if no previous
     * @param services the services file content
     * @param environment the environment this deploys to
     * @param validationOverrides the validation overrides file content, or null if none
     * @param containerCluster container cluster(s) which are declared in services
     * @return the new model and any change actions
     */
    public Pair<VespaModel, List<ConfigChangeAction>> deploy(VespaModel previousModel,
                                                             String services,
                                                             Environment environment,
                                                             String validationOverrides,
                                                             String... containerCluster) {
        Instant now = LocalDate.parse("2000-01-01", DateTimeFormatter.ISO_DATE).atStartOfDay().atZone(ZoneOffset.UTC).toInstant();
        Provisioned provisioned = hostProvisioner.startProvisionedRecording();
        ApplicationPackage newApp = new MockApplicationPackage.Builder()
                .withServices(services)
                .withSchemas(List.of(MUSIC_SCHEMA, BOOK_SCHEMA))
                .withValidationOverrides(validationOverrides)
                .build();
        VespaModelCreatorWithMockPkg newModelCreator = new VespaModelCreatorWithMockPkg(newApp);
        Stream<String> clusters = containerCluster.length == 0 ? Stream.of("default") : Arrays.stream(containerCluster);
        Set<ContainerEndpoint> containerEndpoints = clusters.map(name -> new ContainerEndpoint(name,
                                                                                               ApplicationClusterEndpoint.Scope.zone,
                                                                                               List.of(name + ".example.com")))
                                                            .collect(Collectors.toSet());
        DeployState.Builder deployStateBuilder = new DeployState.Builder()
                                                             .zone(new Zone(SystemName.defaultSystem(),
                                                                            environment,
                                                                            RegionName.defaultName()))
                                                             .endpoints(containerEndpoints)
                                                             .applicationPackage(newApp)
                                                             .properties(properties)
                                                             .modelHostProvisioner(hostProvisioner)
                                                             .provisioned(provisioned)
                                                             .now(now);
        if (previousModel != null)
            deployStateBuilder.previousModel(previousModel);
        VespaModel newModel = newModelCreator.create(deployStateBuilder);
        return new Pair<>(newModel, newModelCreator.configChangeActions);
    }

    public static String censorNumbers(String s) {
        return s.replaceAll("\\d", "-");
    }

    public static void expect(Validator validator, VespaModel model, DeployState deployState, String... expectedMessages) {
        Execution execution = new Execution(model, deployState);
        validator.validate(execution);
        assertTrue(   execution.errors().stream().allMatch(error -> Arrays.stream(expectedMessages).anyMatch(error::contains))
                   && Arrays.stream(expectedMessages).allMatch(expected -> execution.errors().stream().anyMatch(error -> error.contains(expected))),
                   "Expected errors: " + Arrays.toString(expectedMessages) + "\nActual errors: " + execution.errors());
    }

    /** Runs validation, and throws on illegalities. */
    public static void validate(Validator validator, VespaModel model, DeployState deployState) {
        Execution execution = new Execution(model, deployState);
        validator.validate(execution);
        execution.throwIfFailed();
    }

    /** Runs validation and returns the resulting config chance actions, without checking whether they're currently allowed; or throws on illegalities. */
    public static List<ConfigChangeAction> validateChanges(ChangeValidator validator, VespaModel model, DeployState deployState) {
        Execution execution = new Execution(model, deployState);
        validator.validate(execution);
        if ( ! execution.errors().isEmpty()) execution.throwIfFailed();
        return execution.actions();
    }

}