summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@vespa.ai>2023-10-16 13:47:41 +0200
committerBjørn Christian Seime <bjorncs@vespa.ai>2023-10-16 16:03:45 +0200
commitca8117d5128433efad292025c911814bd776361f (patch)
tree2466127a138e4e9efa74941fbb4ccc676ce88ef2
parent5af33f2eb25475eabe3c8136920104e3bf25907e (diff)
Include email content when persisting notification
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notification.java37
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDb.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializer.java37
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializerTest.java11
4 files changed, 77 insertions, 9 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notification.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notification.java
index 40c24c6f339..5116ecaf053 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notification.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notification.java
@@ -2,11 +2,15 @@
package com.yahoo.vespa.hosted.controller.notification;
import java.time.Instant;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.SortedMap;
+import java.util.TreeMap;
/**
* Represents an event that we want to notify the tenant about. The message(s) should be short
@@ -78,34 +82,57 @@ public record Notification(Instant at, Notification.Type type, Notification.Leve
public static class MailContent {
private final String template;
- private final Map<String, Object> values;
+ private final SortedMap<String, Object> values;
private final String subject;
private MailContent(Builder b) {
template = Objects.requireNonNull(b.template);
- values = Map.copyOf(b.values);
+ values = new TreeMap<>(b.values);
subject = b.subject;
}
public String template() { return template; }
- public Map<String, Object> values() { return Map.copyOf(values); }
+ public SortedMap<String, Object> values() { return Collections.unmodifiableSortedMap(values); }
public Optional<String> subject() { return Optional.ofNullable(subject); }
public static Builder fromTemplate(String template) { return new Builder(template); }
public static class Builder {
private final String template;
- private final HashMap<String, Object> values = new HashMap<>();
+ private final Map<String, Object> values = new HashMap<>();
private String subject;
private Builder(String template) {
this.template = template;
}
- public Builder with(String name, Object value) { values.put(name, value); return this; }
+ public Builder with(String name, String value) { values.put(name, value); return this; }
+ public Builder with(String name, Collection<String> items) { values.put(name, List.copyOf(items)); return this; }
public Builder subject(String s) { this.subject = s; return this; }
public MailContent build() { return new MailContent(this); }
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ MailContent that = (MailContent) o;
+ return Objects.equals(template, that.template) && Objects.equals(values, that.values) && Objects.equals(subject, that.subject);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(template, values, subject);
+ }
+
+ @Override
+ public String toString() {
+ return "MailContent{" +
+ "template='" + template + '\'' +
+ ", values=" + values +
+ ", subject='" + subject + '\'' +
+ '}';
+ }
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDb.java
index e752e13eddd..a5d26feafaa 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDb.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDb.java
@@ -71,7 +71,6 @@ public class NotificationsDb {
/**
* Add a notification with given source and type. If a notification with same source and type
* already exists, it'll be replaced by this one instead.
- * Email content is not persisted here. The email dispatcher is responsible for reliable delivery.
*/
public void setNotification(NotificationSource source, Type type, Level level, List<String> messages,
Optional<MailContent> mailContent) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializer.java
index 7915a833be6..62b35d4cfd4 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializer.java
@@ -8,6 +8,7 @@ import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
+import com.yahoo.slime.ObjectTraverser;
import com.yahoo.slime.Slime;
import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
@@ -15,6 +16,7 @@ import com.yahoo.vespa.hosted.controller.notification.Notification;
import com.yahoo.vespa.hosted.controller.notification.NotificationSource;
import java.util.List;
+import java.util.Optional;
/**
* (de)serializes notifications for a tenant
@@ -60,6 +62,22 @@ public class NotificationsSerializer {
notification.source().clusterId().ifPresent(clusterId -> notificationObject.setString(clusterIdField, clusterId.value()));
notification.source().jobType().ifPresent(jobType -> notificationObject.setString(jobTypeField, jobType.serialized()));
notification.source().runNumber().ifPresent(runNumber -> notificationObject.setLong(runNumberField, runNumber));
+
+ notification.mailContent().ifPresent(mc -> {
+ notificationObject.setString("mail-template", mc.template());
+ mc.subject().ifPresent(s -> notificationObject.setString("mail-subject", s));
+ var mailParamsCursor = notificationObject.setObject("mail-params");
+ mc.values().forEach((key, value) -> {
+ if (value instanceof String str) {
+ mailParamsCursor.setString(key, str);
+ } else if (value instanceof List<?> l) {
+ var array = mailParamsCursor.setArray(key);
+ l.forEach(elem -> array.addString((String) elem));
+ } else {
+ throw new ClassCastException("Unsupported param type: " + value.getClass());
+ }
+ });
+ });
}
return slime;
@@ -92,7 +110,24 @@ public class NotificationsSerializer {
SlimeUtils.optionalString(inspector.field(clusterIdField)).map(ClusterSpec.Id::from),
SlimeUtils.optionalString(inspector.field(jobTypeField)).map(jobName -> JobType.ofSerialized(jobName)),
SlimeUtils.optionalLong(inspector.field(runNumberField))),
- SlimeUtils.entriesStream(inspector.field(messagesField)).map(Inspector::asString).toList());
+ SlimeUtils.entriesStream(inspector.field(messagesField)).map(Inspector::asString).toList(),
+ mailContentFrom(inspector));
+ }
+
+ private Optional<Notification.MailContent> mailContentFrom(final Inspector inspector) {
+ return SlimeUtils.optionalString(inspector.field("mail-template")).map(template -> {
+ var builder = Notification.MailContent.fromTemplate(template);
+ SlimeUtils.optionalString(inspector.field("mail-subject")).ifPresent(builder::subject);
+ var paramsCursor = inspector.field("mail-params");
+ inspector.field("mail-params").traverse((ObjectTraverser) (name, insp) -> {
+ switch (insp.type()) {
+ case STRING -> builder.with(name, insp.asString());
+ case ARRAY -> builder.with(name, SlimeUtils.entriesStream(insp).map(Inspector::asString).toList());
+ default -> throw new IllegalArgumentException("Unsupported param type: " + insp.type());
+ }
+ });
+ return builder.build();
+ });
}
private static String asString(Notification.Type type) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializerTest.java
index 63aa45a5a34..26eb30b6525 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializerTest.java
@@ -15,6 +15,7 @@ import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.time.Instant;
import java.util.List;
+import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -27,6 +28,8 @@ public class NotificationsSerializerTest {
void serialization_test() throws IOException {
NotificationsSerializer serializer = new NotificationsSerializer();
TenantName tenantName = TenantName.from("tenant1");
+ var mail = Notification.MailContent.fromTemplate("my-template").subject("My mail subject")
+ .with("string-param", "string-value").with("list-param", List.of("elem1", "elem2")).build();
List<Notification> notifications = List.of(
new Notification(Instant.ofEpochSecond(1234),
Notification.Type.applicationPackage,
@@ -37,7 +40,8 @@ public class NotificationsSerializerTest {
Notification.Type.deployment,
Notification.Level.error,
NotificationSource.from(new RunId(ApplicationId.from(tenantName.value(), "app1", "instance1"), DeploymentContext.systemTest, 12)),
- List.of("Failed to deploy: Node allocation failure")));
+ List.of("Failed to deploy: Node allocation failure"),
+ Optional.of(mail)));
Slime serialized = serializer.toSlime(notifications);
assertEquals("{\"notifications\":[" +
@@ -55,7 +59,10 @@ public class NotificationsSerializerTest {
"\"application\":\"app1\"," +
"\"instance\":\"instance1\"," +
"\"jobId\":\"test.us-east-1\"," +
- "\"runNumber\":12" +
+ "\"runNumber\":12," +
+ "\"mail-template\":\"my-template\"," +
+ "\"mail-subject\":\"My mail subject\"," +
+ "\"mail-params\":{\"list-param\":[\"elem1\",\"elem2\"],\"string-param\":\"string-value\"}" +
"}]}", new String(SlimeUtils.toJsonBytes(serialized)));
List<Notification> deserialized = serializer.fromSlime(tenantName, serialized);