aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java
blob: bc03d46a30a945878c409b7516a19e59dbb348da (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
146
147
148
149
150
151
152
153
154
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;

import com.yahoo.config.provision.ApplicationId;
import com.yahoo.test.ManualClock;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.ApplicationController;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzDbMock;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockTesterCloud;
import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock;
import com.yahoo.vespa.hosted.controller.maintenance.JobRunner;
import com.yahoo.vespa.hosted.controller.maintenance.JobRunnerTest;
import com.yahoo.vespa.hosted.controller.maintenance.OutstandingChangeDeployer;
import com.yahoo.vespa.hosted.controller.maintenance.ReadyJobsTrigger;
import com.yahoo.vespa.hosted.controller.maintenance.Upgrader;

import java.time.DayOfWeek;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.temporal.TemporalAdjusters;
import java.util.logging.Level;
import java.util.logging.Logger;

import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * @author jonmv
 */
public class DeploymentTester {

    // Set a long interval so that maintainers never do scheduled runs during tests
    private static final Duration maintenanceInterval = Duration.ofDays(1);

    private static final String ATHENZ_DOMAIN = "domain";
    private static final String ATHENZ_SERVICE = "service";

    public static final TenantAndApplicationId appId = TenantAndApplicationId.from("tenant", "application");
    public static final ApplicationId instanceId = appId.defaultInstance();

    private final ControllerTester tester;
    private final JobController jobs;
    private final MockTesterCloud cloud;
    private final JobRunner runner;
    private final Upgrader upgrader;
    private final ReadyJobsTrigger readyJobsTrigger;
    private final OutstandingChangeDeployer outstandingChangeDeployer;

    public JobController jobs() { return jobs; }
    public MockTesterCloud cloud() { return cloud; }
    public JobRunner runner() { return runner; }
    public ConfigServerMock configServer() { return tester.configServer(); }
    public Controller controller() { return tester.controller(); }
    public DeploymentTrigger deploymentTrigger() { return applications().deploymentTrigger(); }
    public ControllerTester controllerTester() { return tester; }
    public Upgrader upgrader() { return upgrader; }
    public ApplicationController applications() { return tester.controller().applications(); }
    public ManualClock clock() { return tester.clock(); }
    public Application application() { return application(appId); }
    public Application application(TenantAndApplicationId id ) { return applications().requireApplication(id); }
    public Instance instance() { return instance(instanceId); }
    public Instance instance(ApplicationId id) { return applications().requireInstance(id); }
    public DeploymentStatusList deploymentStatuses() { return jobs.deploymentStatuses(ApplicationList.from(applications().asList())); }

    public DeploymentTester() {
        this(new ControllerTester());
    }

    public DeploymentTester(ControllerTester controllerTester) {
        tester = controllerTester;
        jobs = tester.controller().jobController();
        cloud = (MockTesterCloud) tester.controller().jobController().cloud();
        runner = new JobRunner(tester.controller(), maintenanceInterval, JobRunnerTest.inThreadInOrderExecutor(), new InternalStepRunner(tester.controller()));
        upgrader = new Upgrader(tester.controller(), maintenanceInterval);
        upgrader.setUpgradesPerMinute(1); // Anything that makes it at least one for any maintenance period is fine.
        readyJobsTrigger = new ReadyJobsTrigger(tester.controller(), maintenanceInterval);
        outstandingChangeDeployer = new OutstandingChangeDeployer(tester.controller(), maintenanceInterval);

        // Get deployment job logs to stderr.
        Logger.getLogger("").setLevel(Level.FINE);
        Logger.getLogger(InternalStepRunner.class.getName()).setLevel(Level.FINE);
        tester.configureDefaultLogHandler(handler -> handler.setLevel(Level.FINE));

        // Mock Athenz domain to allow launch of service
        AthenzDbMock.Domain domain = tester.athenzDb().getOrCreateDomain(new com.yahoo.vespa.athenz.api.AthenzDomain(ATHENZ_DOMAIN));
        domain.services.put(ATHENZ_SERVICE, new AthenzDbMock.Service(true));
    }

    public ReadyJobsTrigger readyJobsTrigger() {
        return readyJobsTrigger;
    }

    public OutstandingChangeDeployer outstandingChangeDeployer() { return outstandingChangeDeployer; }

    /** A tester with clock configured to a time when confidence can freely change */
    public DeploymentTester atMondayMorning() {
        return at(tester.clock().instant().atZone(ZoneOffset.UTC)
                        .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
                        .withHour(5)
                        .toInstant());
    }

    public DeploymentTester at(Instant instant) {
        tester.clock().setInstant(instant);
        return this;
    }

    /** Create the deployment context for the default instance id  */
    public DeploymentContext newDeploymentContext() {
        return newDeploymentContext(instanceId);
    }

    /** Create a new deployment context for given application */
    public DeploymentContext newDeploymentContext(String tenantName, String applicationName, String instanceName) {
        return newDeploymentContext(ApplicationId.from(tenantName, applicationName, instanceName));
    }

    /** Create a new deployment context for given application */
    public DeploymentContext newDeploymentContext(ApplicationId instance) {
        return new DeploymentContext(instance, this);
    }

    /** Create a new application with given tenant and application name */
    public Application createApplication(String tenantName, String applicationName, String instanceName) {
        return newDeploymentContext(tenantName, applicationName, instanceName).application();
    }

    /** Aborts and finishes all running jobs. */
    public void abortAll() {
        triggerJobs();
        for (Run run : jobs.active()) {
            jobs.abort(run.id(), "DeploymentTester.abortAll", false);
            runner.advance(jobs.run(run.id()));
            assertTrue(jobs.run(run.id()).hasEnded());
        }
    }

    /** Triggers jobs until nothing more triggers, and returns the number of triggered jobs. */
    public int triggerJobs() {
        int triggered;
        int triggeredTotal = 0;
        do {
            triggered = (int) deploymentTrigger().triggerReadyJobs().triggered();
            triggeredTotal += triggered;
        } while (triggered > 0);
        return triggeredTotal;
    }

}