aboutsummaryrefslogtreecommitdiffstats
path: root/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/JobType.java
blob: 4505fe3ceb5c67fa7e3eb9d17812cefc1718ec12 (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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.api.integration.deployment;

import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static ai.vespa.validation.Validation.require;
import static com.yahoo.config.provision.Environment.prod;
import static com.yahoo.config.provision.Environment.staging;
import static com.yahoo.config.provision.Environment.test;
import static com.yahoo.config.provision.SystemName.Public;
import static com.yahoo.config.provision.SystemName.PublicCd;
import static com.yahoo.config.provision.SystemName.cd;
import static com.yahoo.config.provision.SystemName.main;

/** Job types that exist in the build system */
public final class JobType implements Comparable<JobType> {
//     | enum name ------------| job name ------------------| Zone in main system ---------------------------------------| Zone in CD system -------------------------------------------
    public static final JobType systemTest         = of("system-test",
                            Map.of(main    , ZoneId.from("test", "us-east-1"),
                                   cd      , ZoneId.from("test", "cd-us-west-1"),
                                   PublicCd, ZoneId.from("test", "aws-us-east-1c"),
                                   Public  , ZoneId.from("test", "aws-us-east-1c")));

    public static final JobType stagingTest        = of("staging-test",
                            Map.of(main    , ZoneId.from("staging", "us-east-3"),
                                   cd      , ZoneId.from("staging", "cd-us-west-1"),
                                   PublicCd, ZoneId.from("staging", "aws-us-east-1c"),
                                   Public  , ZoneId.from("staging", "aws-us-east-1c")));

    public static final JobType productionUsEast3  = prod("us-east-3");

    public static final JobType testUsEast3        = test("us-east-3");

    public static final JobType productionUsWest1  = prod("us-west-1");

    public static final JobType testUsWest1        = test("us-west-1");

    public static final JobType productionUsCentral1 = prod("us-central-1");

    public static final JobType testUsCentral1     = test("us-central-1");

    public static final JobType productionApNortheast1 = prod("ap-northeast-1");

    public static final JobType testApNortheast1   = test("ap-northeast-1");

    public static final JobType productionApNortheast2 = prod("ap-northeast-2");

    public static final JobType testApNortheast2   = test("ap-northeast-2");

    public static final JobType productionApSoutheast1 = prod("ap-southeast-1");

    public static final JobType testApSoutheast1   = test("ap-southeast-1");

    public static final JobType productionEuWest1  = prod("eu-west-1");

    public static final JobType testEuWest1        = test("eu-west-1");

    public static final JobType productionAwsUsEast1a= prod("aws-us-east-1a");

    public static final JobType testAwsUsEast1a    = test("aws-us-east-1a");

    public static final JobType productionAwsUsEast1c= prod("aws-us-east-1c");

    public static final JobType testAwsUsEast1c    = test("aws-us-east-1c");

    public static final JobType productionAwsApNortheast1a= prod("aws-ap-northeast-1a");

    public static final JobType testAwsApNortheast1a = test("aws-ap-northeast-1a");

    public static final JobType productionAwsEuWest1a= prod("aws-eu-west-1a");

    public static final JobType testAwsEuWest1a     = test("aws-eu-west-1a");

    public static final JobType productionAwsUsWest2a= prod("aws-us-west-2a");

    public static final JobType testAwsUsWest2a    = test("aws-us-west-2a");

    public static final JobType productionAwsUsEast1b= prod("aws-us-east-1b");

    public static final JobType testAwsUsEast1b    = test("aws-us-east-1b");

    public static final JobType devUsEast1         = dev("us-east-1");

    public static final JobType devAwsUsEast2a     = dev("aws-us-east-2a");

    public static final JobType productionCdAwsUsEast1a = prod("cd-aws-us-east-1a");

    public static final JobType testCdAwsUsEast1a  = test("cd-aws-us-east-1a");

    public static final JobType productionCdUsCentral1  = prod("cd-us-central-1");

    public static final JobType testCdUsCentral1   = test("cd-us-central-1");

    public static final JobType productionCdUsCentral2  = prod("cd-us-central-2");

    public static final JobType testCdUsCentral2   = test("cd-us-central-2");

    public static final JobType productionCdUsEast1= prod("cd-us-east-1");

    public static final JobType testCdUsEast1      = test("cd-us-east-1");

    public static final JobType productionCdUsWest1= prod("cd-us-west-1");

    public static final JobType testCdUsWest1      = test("cd-us-west-1");

    public static final JobType devCdUsCentral1    = dev("cd-us-central-1");

    public static final JobType devCdUsWest1       = dev("cd-us-west-1");

    public static final JobType devAwsUsEast1c     = dev("aws-us-east-1c");

    public static final JobType perfAwsUsEast1c     = perf("aws-us-east-1c");

    public static final JobType perfUsEast3        = perf("us-east-3");

    private static final JobType[] values = new JobType[] {
            systemTest,
            stagingTest,
            productionUsEast3,
            testUsEast3,
            productionUsWest1,
            testUsWest1,
            productionUsCentral1,
            testUsCentral1,
            productionApNortheast1,
            testApNortheast1,
            productionApNortheast2,
            testApNortheast2,
            productionApSoutheast1,
            testApSoutheast1,
            productionEuWest1,
            testEuWest1,
            productionAwsUsEast1a,
            testAwsUsEast1a,
            productionAwsUsEast1c,
            testAwsUsEast1c,
            productionAwsApNortheast1a,
            testAwsApNortheast1a,
            productionAwsEuWest1a,
            testAwsEuWest1a,
            productionAwsUsWest2a,
            testAwsUsWest2a,
            productionAwsUsEast1b,
            testAwsUsEast1b,
            devUsEast1,
            devAwsUsEast2a,
            productionCdAwsUsEast1a,
            testCdAwsUsEast1a,
            productionCdUsCentral1,
            testCdUsCentral1,
            productionCdUsCentral2,
            testCdUsCentral2,
            productionCdUsEast1,
            testCdUsEast1,
            productionCdUsWest1,
            testCdUsWest1,
            devCdUsCentral1,
            devCdUsWest1,
            devAwsUsEast1c,
            perfAwsUsEast1c,
            perfUsEast3
    };

    private final String jobName;
    final Map<SystemName, ZoneId> zones;
    private final boolean isProductionTest;

    private JobType(String jobName, Map<SystemName, ZoneId> zones, boolean isProductionTest) {
        if (zones.values().stream().map(ZoneId::environment).distinct().count() > 1)
            throw new IllegalArgumentException("All zones of a job must be in the same environment");

        this.jobName = jobName;
        this.zones = zones;
        this.isProductionTest = isProductionTest;
    }

    private static JobType of(String jobName, Map<SystemName, ZoneId> zones, boolean isProductionTest) {
        return new JobType(jobName, zones, isProductionTest);
    }

    private static JobType of(String jobName, Map<SystemName, ZoneId> zones) {
        return of(jobName, zones, false);
    }

    public String jobName() { return jobName; }

    /** Returns the zone for this job in the given system. */
    public ZoneId zone() {
        throw new UnsupportedOperationException();
    }

    /** Returns the zone for this job in the given system, or throws if this job does not have a zone */
    public ZoneId zone(SystemName system) {
        if ( ! zones.containsKey(system))
            throw new IllegalArgumentException(this + " does not have any zones in " + system);

        return zones.get(system);
    }

    /** A system test in a test zone, or throws if no test zones are present.. */
    public static JobType systemTest(ZoneRegistry zones) {
        return testIn(test, zones);
    }

    /** A staging test in a staging zone, or throws if no staging zones are present. */
    public static JobType stagingTest(ZoneRegistry zones){
        return testIn(staging, zones);
    }

    private static JobType testIn(Environment environment, ZoneRegistry zones) {
        return zones.zones().controllerUpgraded().in(environment).zones().stream().map(zone -> deploymentTo(zone.getId()))
                    .findFirst().orElseThrow(() -> new IllegalArgumentException("no zones in " + environment + " among " + zones.zones().controllerUpgraded().zones()));
    }

    /** A deployment to the given dev region. */
    public static JobType dev(String region) {
        return deploymentTo(ZoneId.from("dev", region));
    }

    /** A deployment to the given perf region. */
    public static JobType perf(String region) {
        return deploymentTo(ZoneId.from("perf", region));
    }

    /** A deployment to the given prod region. */
    public static JobType prod(String region) {
        return deploymentTo(ZoneId.from("prod", region));
    }

    /** A production test in the given region. */
    public static JobType test(String region) {
        return productionTestOf(ZoneId.from("prod", region));
    }

    public static JobType deploymentTo(ZoneId zone) {
        String name;
        switch (zone.environment()) {
            case prod: name = "production-" + zone.region().value(); break;
            case test: name = "system-test"; break;
            case staging: name = "staging-test"; break;
            default: name = zone.environment().value() + "-" + zone.region().value();
        }
        return of(name, dummy(zone), false);
    }

    public static JobType productionTestOf(ZoneId zone) {
        return of("test-" + require(zone.environment() == prod, zone, "must be prod zone").region().value(),  dummy(zone), true);
    }

    private static Map<SystemName, ZoneId> dummy(ZoneId zone) {
        return Stream.of(SystemName.values()).collect(Collectors.toMap(Function.identity(), __ -> zone));
    }

    // TODO jonmv: use for serialisation
    public static JobType ofSerialized(String raw) {
        String[] parts = raw.split("\\.");
        if (parts.length == 2) return deploymentTo(ZoneId.from(parts[0], parts[1]));
        if (parts.length == 3 && "test".equals(parts[2])) return productionTestOf(ZoneId.from(parts[0], parts[1]));
        throw new IllegalArgumentException("illegal serialized job type '" + raw + "'");
    }

    public String serialized(SystemName system) {
        ZoneId zone = zone(system);
        return zone.environment().value() + "." + zone.region().value() + (isProductionTest ? ".test" : "");
    }

    public static List<JobType> allIn(ZoneRegistry zones) {
        return zones.zones().controllerUpgraded().zones().stream()
                    .flatMap(zone -> zone.getEnvironment().isProduction() ? Stream.of(deploymentTo(zone.getId()), productionTestOf(zone.getId()))
                                                                          : Stream.of(deploymentTo(zone.getId())))
                    .collect(Collectors.toUnmodifiableList());
    }

    static JobType[] values() {
        return Arrays.copyOf(values, values.length);
    }

    public boolean isSystemTest() {
        return environment() == test;
    }

    public boolean isStagingTest() {
        return environment() == staging;
    }

    /** Returns whether this is a production job */
    public boolean isProduction() { return environment() == prod; }

    /** Returns whether this job runs tests */
    public boolean isTest() { return isProductionTest || environment().isTest(); }

    /** Returns whether this job deploys to a zone */
    public boolean isDeployment() { return ! (isProduction() && isProductionTest); }

    /** Returns the environment of this job type */
    public Environment environment() {
        return zones.values().iterator().next().environment();
    }

    // TODO jonmv: require zones
    public static Optional<JobType> fromOptionalJobName(String jobName) {
        if (jobName.contains(".")) return Optional.of(ofSerialized(jobName)); // TODO jonmv: remove
        return Stream.of(values)
                     .filter(jobType -> jobType.jobName.equals(jobName))
                     .findAny();
    }

    // TODO jonmv: require zones
    public static JobType fromJobName(String jobName) {
        return fromOptionalJobName(jobName)
                .orElseThrow(() -> new IllegalArgumentException("Unknown job name '" + jobName + "'"));
    }

    // TODO jonmv: require zones
    public static JobType fromJobName(String jobName, ZoneRegistry zones) {
        String[] parts = jobName.split("-", 2);
        if (parts.length != 2) throw new IllegalArgumentException("job names must be 'system-test', 'staging-test', or environment and region parts, separated by '-', but got: " + jobName);
        switch (parts[0]) {
            case "system": return systemTest(zones);
            case "staging": return stagingTest(zones);
            case "production": return prod(parts[1]);
            case "test": return test(parts[1]);
            case "dev": return dev(parts[1]);
            case "perf": return perf(parts[1]);
            default: throw new IllegalArgumentException("job names must begin with one of: system, staging, production, test, dev, perf; but got: " + jobName);
        }
    }

    /** Returns the job type for the given zone */
    public static Optional<JobType> from(SystemName system, ZoneId zone, boolean isTest) {
        return Stream.of(values)
                     .filter(job -> zone.equals(job.zones.get(system)) && job.isTest() == isTest)
                     .findAny();
    }

    /** Returns the job type for the given zone */
    public static Optional<JobType> from(SystemName system, ZoneId zone) {
        return from(system, zone, zone.environment().isTest());
    }

    /** Returns the production test job type for the given environment and region or null if none */
    public static Optional<JobType> testFrom(SystemName system, RegionName region) {
        return from(system, ZoneId.from(prod, region), true);
    }

    /** Returns the job job type for the given environment and region or null if none */
    public static Optional<JobType> from(SystemName system, Environment environment, RegionName region) {
        switch (environment) {
            case test: return Optional.of(systemTest);
            case staging: return Optional.of(stagingTest);
        }
        return from(system, ZoneId.from(environment, region));
    }


    private static final Comparator<JobType> comparator = Comparator.comparing(JobType::environment)
                                                                    .thenComparing(JobType::isDeployment)
                                                                    .thenComparing(JobType::jobName);
    @Override
    public int compareTo(JobType other) {
        return comparator.compare(this, other);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        JobType jobType = (JobType) o;
        return jobName.equals(jobType.jobName);
    }

    @Override
    public int hashCode() {
        return jobName.hashCode();
    }

    @Override
    public String toString() {
        return jobName;
    }

}