aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java
blob: b94779994e46634da3f53754b9e07ef7b8b6e759 (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
// Copyright Yahoo. 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.yahoo.collections.AbstractFilteringList;
import com.yahoo.component.Version;
import com.yahoo.component.VersionCompatibility;
import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatus;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatusList;
import com.yahoo.vespa.hosted.controller.versions.VersionStatus;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion.Confidence;

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

import static java.util.Comparator.comparing;
import static java.util.Comparator.naturalOrder;

/**
 * @author jonmv
 */
public class InstanceList extends AbstractFilteringList<ApplicationId, InstanceList> {

    private final Map<ApplicationId, DeploymentStatus> instances;

    private InstanceList(Collection<? extends ApplicationId> items, boolean negate, Map<ApplicationId, DeploymentStatus> instances) {
        super(items, negate, (i, n) -> new InstanceList(i, n, instances));
        this.instances = Map.copyOf(instances);
    }

    /**
     * Returns the subset of instances where all production deployments are compatible with the given version,
     * and at least one known build is compatible with the given version.
     *
     * @param platform the version which applications returned are compatible with
     */
    public InstanceList compatibleWithPlatform(Version platform, Function<ApplicationId, VersionCompatibility> compatibility) {
        return matching(id ->    instance(id).productionDeployments().values().stream()
                                             .flatMap(deployment -> application(id).revisions().get(deployment.revision()).compileVersion().stream())
                                             .noneMatch(version -> compatibility.apply(id).refuse(platform, version))
                              && application(id).revisions().production().stream()
                                                .anyMatch(revision -> revision.compileVersion()
                                                                              .map(compiled -> compatibility.apply(id).accept(platform, compiled))
                                                                              .orElse(true)));
    }

    /**
     * Returns the subset of instances whose application have a deployment on the given major,
     * or specify it in deployment spec,
     * or which are on a {@link VespaVersion.Confidence#legacy} platform, and do not specify that in deployment spec.
     *
     * @param targetMajorVersion the target major version which applications returned allows upgrading to
     */
    public InstanceList allowingMajorVersion(int targetMajorVersion, VersionStatus versions) {
        return matching(id -> {
            Application application = application(id);
            Optional<Integer> majorVersion = application.deploymentSpec().majorVersion();
            if (majorVersion.isPresent())
                return majorVersion.get() >= targetMajorVersion;

            for (List<Deployment> deployments : application.productionDeployments().values())
                for (Deployment deployment : deployments) {
                    if (deployment.version().getMajor() >= targetMajorVersion) return true;
                    if (versions.version(deployment.version()).confidence() == Confidence.legacy) return true;
                }
            return false;
        });
    }

    /** Returns the subset of instances that are allowed to upgrade to the given version at the given time */
    public InstanceList canUpgradeAt(Version version, Instant instant) {
        return matching(id -> instances.get(id).instanceSteps().get(id.instance())
                                       .readiness(Change.of(version)).okAt(instant));
    }

    /** Returns the subset of instances which have at least one production deployment */
    public InstanceList withProductionDeployment() {
        return matching(id -> instance(id).productionDeployments().size() > 0);
    }

    /** Returns the subset of instances which contain declared jobs */
    public InstanceList withDeclaredJobs() {
        return matching(id ->    instances.get(id).application().revisions().last().isPresent()
                              && instances.get(id).jobSteps().values().stream()
                                          .anyMatch(job -> job.isDeclared() && job.job().get().application().equals(id)));
    }

    /** Returns the subset of instances which have at least one deployment on a lower version than the given one, or which have no production deployments */
    public InstanceList onLowerVersionThan(Version version) {
        return matching(id ->    instance(id).productionDeployments().isEmpty()
                              || instance(id).productionDeployments().values().stream()
                                             .anyMatch(deployment -> deployment.version().isBefore(version)));
    }

    /** Returns the subset of instances that has completed deployment of given change */
    public InstanceList hasCompleted(Change change) {
        return matching(id -> instances.get(id).hasCompleted(id.instance(), change));
    }

    /** Returns the subset of instances which are currently deploying a change */
    public InstanceList deploying() {
        return matching(id -> instance(id).change().hasTargets());
    }

    /** Returns the subset of instances which are currently deploying a new revision */
    public InstanceList changingRevision() {
        return matching(id -> instance(id).change().revision().isPresent());
    }

    /** Returns the subset of instances which currently have failing jobs on the given version */
    public InstanceList failingOn(Version version) {
        return matching(id -> ! instances.get(id).instanceJobs().get(id).failingHard()
                                         .lastCompleted().on(version).isEmpty());
    }

    /** Returns the subset of instances which are not pinned to a certain Vespa version. */
    public InstanceList unpinned() {
        return matching(id -> ! instance(id).change().isPlatformPinned());
    }

    /** Returns the subset of instances which are currently failing a job. */
    public InstanceList failing() {
        return matching(id -> ! instances.get(id).instanceJobs().get(id).failingHard().isEmpty());
    }

    /** Returns the subset of instances which are currently failing an upgrade. */
    public InstanceList failingUpgrade() {
        return matching(id -> ! instances.get(id).instanceJobs().get(id).failingHard().not().failingApplicationChange().isEmpty());
    }

    /** Returns the subset of instances which are upgrading (to any version), not considering block windows. */
    public InstanceList upgrading() {
        return matching(id -> instance(id).change().platform().isPresent());
    }

    /** Returns the subset of instances which are currently upgrading to the given version */
    public InstanceList upgradingTo(Version version) {
        return upgradingTo(List.of(version));
    }


    /** Returns the subset of instances which are currently upgrading to the given version */
    public InstanceList upgradingTo(Collection<Version> versions) {
        return matching(id -> versions.stream().anyMatch(version -> instance(id).change().platform().equals(Optional.of(version))));
    }

    public InstanceList with(DeploymentSpec.UpgradePolicy policy) {
        return matching(id -> application(id).deploymentSpec().requireInstance(id.instance()).upgradePolicy() == policy);
    }

    /** Returns the subset of instances which started failing on the given version */
    public InstanceList startedFailingOn(Version version) {
        return matching(id -> ! instances.get(id).instanceJobs().get(id).firstFailing().on(version).isEmpty());
    }

    /** Returns this list sorted by increasing oldest production deployment version. Applications without any deployments are ordered first. */
    public InstanceList byIncreasingDeployedVersion() {
        return sortedBy(comparing(id -> instance(id).productionDeployments().values().stream()
                                                    .map(Deployment::version)
                                                    .min(naturalOrder())
                                                    .orElse(Version.emptyVersion)));
    }

    private Application application(ApplicationId id) {
        return instances.get(id).application();
    }

    private Instance instance(ApplicationId id) {
        return application(id).require(id.instance());
    }

    public static InstanceList from(DeploymentStatusList statuses) {
        Map<ApplicationId, DeploymentStatus> instances = new HashMap<>();
        for (DeploymentStatus status : statuses.asList())
            for (InstanceName instance : status.application().deploymentSpec().instanceNames())
                instances.put(status.application().id().instance(instance), status);
        return new InstanceList(instances.keySet(), false, instances);
    }

}