aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobList.java
blob: 3c72fd69e425082af541a6d052ec63e2b7b49532 (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
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;

import com.google.common.collect.ImmutableList;
import com.yahoo.component.Version;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobType;
import com.yahoo.vespa.hosted.controller.application.JobStatus.JobRun;

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

import static com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError.outOfCapacity;

/**
 * A list of deployment jobs that can be filtered in various ways.
 *
 * @author jvenstad
 */
public class JobList {

    private final ImmutableList<JobStatus> list;
    private final boolean negate;

    private JobList(Iterable<JobStatus> jobs, boolean negate) {
        this.list = ImmutableList.copyOf(jobs);
        this.negate = negate;
    }

    private JobList(Iterable<JobStatus> jobs) {
        this(jobs, false);
    }

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

    public static JobList from(Iterable<JobStatus> jobs) {
        return new JobList(jobs);
    }

    public static JobList from(Application application) {
        return from(application.deploymentJobs().jobStatus().values());
    }

    // ----------------------------------- Accessors

    // TODO: Add sorting based on various stuff, such as deployment order, time of last completion, etc..

    /** Returns the jobstatuses in this as an immutable list */
    public List<JobStatus> asList() { return list; }

    /** Returns the jobstatuses in this as an immutable list after mapping with the given function */
    public <Type> List<Type> mapToList(Function<JobStatus, Type> mapper) {
        return ImmutableList.copyOf(list.stream().map(mapper)::iterator);
    }

    public boolean isEmpty() { return list.isEmpty(); }

    public int size() { return list.size(); }

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

    /** Negates the next filter operation */
    public JobList not() {
        return new JobList(list, ! negate);
    }

    /** Returns the subset of jobs which are current upgrading */
    public JobList upgrading() { // TODO: Centralise and standardise reasoning about upgrades and revisions.
        return filter(job ->      job.lastSuccess().isPresent()
                             &&   job.lastTriggered().isPresent()
                             && ! job.lastTriggered().get().at().isBefore(job.lastCompleted().get().at())
                             &&   job.lastSuccess().get().version().isBefore(job.lastTriggered().get().version()));
    }

    /** Returns the subset of jobs which are currently running, according to the given timeout */
    public JobList running(Instant timeoutLimit) {
        return filter(job -> job.isRunning(timeoutLimit));
    }

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

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

    /** Returns the subset of jobs which are failing with the given job error */
    public JobList failingBecause(DeploymentJobs.JobError error) {
        return filter(job -> job.jobError().filter(error::equals).isPresent());
    }

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

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

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

    /** Returns the list in a state where the next filter is for the lastTriggered run type */
    public JobRunFilter lastTriggered() {
        return new JobRunFilter(job -> job.lastTriggered());
    }

    /** Returns the list in a state where the next filter is for the lastCompleted run type */
    public JobRunFilter lastCompleted() {
        return new JobRunFilter(job -> job.lastCompleted());
    }

    /** Returns the list in a state where the next filter is for the lastSuccess run type */
    public JobRunFilter lastSuccess() {
        return new JobRunFilter(job -> job.lastSuccess());
    }

    /** Returns the list in a state where the next filter is for the firstFailing run type */
    public JobRunFilter firstFailing() {
        return new JobRunFilter(job -> job.firstFailing());
    }


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

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

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

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

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

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

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

        public JobList upgrade() {
            return filter(run -> run.upgrade());
        }

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

    }


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

    private static boolean failingApplicationChange(JobStatus job) {
        if (   job.isSuccess()) return false;
        if ( ! job.lastSuccess().isPresent()) return true; // An application which never succeeded is surely bad.
        if ( ! job.lastSuccess().get().revision().isPresent()) return true; // Indicates the component job, which is always an application change.
        if ( ! job.firstFailing().get().version().equals(job.lastSuccess().get().version())) return false; // Version change may be to blame.
        return ! job.firstFailing().get().revision().equals(job.lastSuccess().get().revision()); // Return whether there is an application change.
    }

    /** Returns a new JobList which is the result of filtering with the -- possibly negated -- condition */
    private JobList filter(Predicate<JobStatus> condition) {
        return from(list.stream().filter(negate ? condition.negate() : condition)::iterator);
    }

}