aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationFormatter.java
blob: e9b38f7a12260e5ab855c1bc59aee9bb19f2842e (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
// 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.notification;

import com.yahoo.config.provision.ApplicationId;
import com.yahoo.text.Text;
import com.yahoo.vespa.hosted.controller.api.integration.ConsoleUrls;

import java.util.Objects;
import java.util.Optional;

import static com.yahoo.vespa.hosted.controller.notification.Notifier.notificationLink;

/**
 * Created a NotificationContent for a given Notification.
 *
 * The formatter will create specific summary, message start and URI for a given Notification.
 *
 * @author enygaard
 */
public class NotificationFormatter {
    private final ConsoleUrls consoleUrls;

    public NotificationFormatter(ConsoleUrls consoleUrls) {
        this.consoleUrls = Objects.requireNonNull(consoleUrls);
    }

    public FormattedNotification format(Notification n) {
        return switch (n.type()) {
            case applicationPackage, submission -> applicationPackage(n);
            case deployment -> deployment(n);
            case testPackage -> testPackage(n);
            case reindex -> reindex(n);
            case feedBlock -> feedBlock(n);
            default -> new FormattedNotification(n, n.type().name(), "", consoleUrls.tenantOverview(n.source().tenant()));
        };
    }

    private FormattedNotification applicationPackage(Notification n) {
        var source = n.source();
        var application = requirePresent(source.application(), "application");
        var message = Text.format("Application package for %s%s has %s",
                application,
                source.instance().map(instance -> "." + instance.value()).orElse(""),
                levelText(n.level(), n.messages().size()));
        return new FormattedNotification(n, "Application package", message, notificationLink(consoleUrls, n.source()));
    }

    private FormattedNotification deployment(Notification n) {
        var source = n.source();
        var message = Text.format("%s for %s.%s has %s",
                jobText(source),
                requirePresent(source.application(), "application"),
                requirePresent(source.instance(), "instance"),
                levelText(n.level(), n.messages().size()));
        return new FormattedNotification(n,"Deployment", message, notificationLink(consoleUrls, n.source()));
    }

    private FormattedNotification testPackage(Notification n) {
        var source = n.source();
        var application = requirePresent(source.application(), "application");
        var message = Text.format("There %s with tests for %s%s",
                n.messages().size() > 1 ? "are problems" : "is a problem",
                application,
                source.instance().map(i -> "."+i).orElse(""));
        return new FormattedNotification(n, "Test package", message, notificationLink(consoleUrls, n.source()));
    }

    private FormattedNotification reindex(Notification n) {
        var message = Text.format("%s is reindexing", clusterInfo(n.source()));
        var application = requirePresent(n.source().application(), "application");
        var instance = requirePresent(n.source().instance(), "instance");
        var clusterId = requirePresent(n.source().clusterId(), "clusterId");
        var zone = requirePresent(n.source().zoneId(), "zoneId");
        return new FormattedNotification(n, "Reindex", message,
                consoleUrls.clusterReindexing(ApplicationId.from(n.source().tenant(), application, instance), zone, clusterId));
    }

    private FormattedNotification feedBlock(Notification n) {
        String type = n.level() == Notification.Level.warning ? "Nearly feed blocked" : "Feed blocked";
        var message = Text.format("%s is %s", clusterInfo(n.source()), type.toLowerCase());
        return new FormattedNotification(n, type, message, notificationLink(consoleUrls, n.source()));
    }

    private String jobText(NotificationSource source) {
        var jobType = requirePresent(source.jobType(), "jobType");
        var zone = jobType.zone();
        var runNumber = source.runNumber().orElseThrow(() -> new MissingOptionalException("runNumber"));
        switch (zone.environment().value()) {
            case "production":
                return Text.format("Deployment job #%d to %s", runNumber, zone.region());
            case "test":
                return Text.format("Test job #%d to %s", runNumber, zone.region());
            case "dev":
            case "perf":
                return Text.format("Deployment job #%d to %s.%s", runNumber, zone.environment().value(), zone.region().value());
        }
        switch (jobType.jobName()) {
            case "system-test":
            case "staging-test":
        }
        return Text.format("%s #%d", jobType.jobName(), runNumber);
    }

    private String levelText(Notification.Level level, int count) {
        return switch (level) {
            case error -> "failed";
            case warning -> count > 1 ? Text.format("%d warnings", count) : "a warning";
            default -> count > 1 ? Text.format("%d messages", count) : "a message";
        };
    }

    private String clusterInfo(NotificationSource source) {
        var application = requirePresent(source.application(), "application");
        var instance = requirePresent(source.instance(), "instance");
        var zone = requirePresent(source.zoneId(), "zoneId");
        var clusterId = requirePresent(source.clusterId(), "clusterId");
        return Text.format("Cluster %s in %s.%s for %s.%s",
                clusterId.value(),
                zone.environment(), zone.region(),
                application, instance);
    }


    private static <T> T requirePresent(Optional<T> optional, String field) {
        return optional.orElseThrow(() -> new MissingOptionalException(field));
    }
}