aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobList.java
blob: 56a6bec5777834d4c74e0d2a1f9983c232f8a74f (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
// Copyright 2019 Oath Inc. 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.collections.AbstractFilteringList;
import com.yahoo.component.Version;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;

import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * A list of deployment jobs that can be filtered in various ways.
 *
 * @author jonmv
 */
public class JobList extends AbstractFilteringList<JobStatus, JobList> {

    private JobList(Collection<? extends JobStatus> jobs, boolean negate) {
        super(jobs, negate, JobList::new);
    }

    // ----------------------------------- Factories

    public static JobList from(Collection<? extends JobStatus> jobs) {
        return new JobList(jobs, false);
    }

    // ----------------------------------- Basic filters

    /** Returns the subset of jobs which are currently upgrading */
    public JobList upgrading() {
        return matching(job ->    job.isRunning()
                               && job.lastSuccess().isPresent()
                               && job.lastSuccess().get().versions().targetPlatform().isBefore(job.lastTriggered().get().versions().targetPlatform()));
    }

    /** Returns the subset of jobs which are currently failing */
    public JobList failing() {
        return matching(job -> ! job.isSuccess());
    }

    public JobList running() {
        return matching(job -> job.isRunning());
    }

    /** Returns the subset of jobs which must be failing due to an application change */
    public JobList failingApplicationChange() {
        return matching(JobList::failingApplicationChange);
    }

    /** Returns the subset of jobs which are failing with the given run status */
    public JobList withStatus(RunStatus status) {
        return matching(job -> job.lastStatus().map(status::equals).orElse(false));
    }

    /** Returns the subset of jobs of the given type -- most useful when negated */
    public JobList type(Collection<? extends JobType> types) {
        return matching(job -> types.contains(job.id().type()));
    }

    /** Returns the subset of jobs of the given type -- most useful when negated */
    public JobList type(JobType... types) {
        return type(List.of(types));
    }

    /** Returns the subset of jobs of which are production jobs */
    public JobList production() {
        return matching(job -> job.id().type().isProduction());
    }

    /** Returns the jobs with any runs matching the given versions — targets only for system test, everything present otherwise. */
    public JobList triggeredOn(Versions versions) {
        return matching(job -> ! RunList.from(job).on(versions).isEmpty());
    }

    /** Returns the jobs with successful runs matching the given versions — targets only for system test, everything present otherwise. */
    public JobList successOn(Versions versions) {
        return matching(job -> ! RunList.from(job).status(RunStatus.success).on(versions).isEmpty());
    }

    // ----------------------------------- JobRun filtering

    /** Returns the list in a state where the next filter is for the lastTriggered run type */
    public RunFilter lastTriggered() {
        return new RunFilter(JobStatus::lastTriggered);
    }

    /** Returns the list in a state where the next filter is for the lastCompleted run type */
    public RunFilter lastCompleted() {
        return new RunFilter(JobStatus::lastCompleted);
    }

    /** Returns the list in a state where the next filter is for the lastSuccess run type */
    public RunFilter lastSuccess() {
        return new RunFilter(JobStatus::lastSuccess);
    }

    /** Returns the list in a state where the next filter is for the firstFailing run type */
    public RunFilter firstFailing() {
        return new RunFilter(JobStatus::firstFailing);
    }


    /** Allows sub-filters for runs of the given kind */
    public class RunFilter {

        private final Function<JobStatus, Optional<Run>> which;

        private RunFilter(Function<JobStatus, Optional<Run>> which) {
            this.which = which;
        }

        /** Returns the subset of jobs where the run of the given type exists */
        public JobList present() {
            return matching(run -> true);
        }

        /** Returns the subset of jobs where the run of the given type occurred before the given instant */
        public JobList startedBefore(Instant threshold) {
            return matching(run -> run.start().isBefore(threshold));
        }

        /** Returns the subset of jobs where the run of the given type occurred after the given instant */
        public JobList startedAfter(Instant threshold) {
            return matching(run -> run.start().isAfter(threshold));
        }

        /** Returns the subset of jobs where the run of the given type was on the given version */
        public JobList on(ApplicationVersion version) {
            return matching(run -> run.versions().targetApplication().equals(version));
        }

        /** Returns the subset of jobs where the run of the given type was on the given version */
        public JobList on(Version version) {
            return matching(run -> run.versions().targetPlatform().equals(version));
        }

        /** Transforms the JobRun condition to a JobStatus condition, by considering only the JobRun mapped by which, and executes */
        private JobList matching(Predicate<Run> condition) {
            return JobList.this.matching(job -> which.apply(job).filter(condition).isPresent());
        }

    }

    // ----------------------------------- Internal helpers

    private static boolean failingApplicationChange(JobStatus job) {
        if (job.isSuccess()) return false;
        if (job.lastSuccess().isEmpty()) return true; // An application which never succeeded is surely bad.
        if ( ! job.firstFailing().get().versions().targetPlatform().equals(job.lastSuccess().get().versions().targetPlatform())) return false; // Version change may be to blame.
        return ! job.firstFailing().get().versions().targetApplication().equals(job.lastSuccess().get().versions().targetApplication()); // Return whether there is an application change.
    }

}