diff options
author | Jon Marius Venstad <jonmv@users.noreply.github.com> | 2022-02-19 15:04:41 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-02-19 15:04:41 +0100 |
commit | 66c1ab2b3c724580062f13de905861daa02d9460 (patch) | |
tree | 4863fea61d9ef743a0c287557d9236154672a3a7 | |
parent | b9e1b6dc755d56cef4b8164abf783ec9cfe1ee63 (diff) | |
parent | fb4e9378d2ed85236b958962fdf88b9ff73fc8ff (diff) |
Merge pull request #21279 from vespa-engine/freva/notifications
Allow operators to list all notifications
6 files changed, 61 insertions, 14 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java index 3a5bb96d985..b34d113d331 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java @@ -20,7 +20,8 @@ import java.util.Set; enum PathGroup { /** Paths exclusive to operators (including read), used for system management. */ - classifiedOperator("/configserver/v1/{*}", + classifiedOperator("/application/v4/notifications", + "/configserver/v1/{*}", "/deployment/v1/{*}"), /** Paths used for system management by operators. */ 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 4fdfdfc461b..c0bd1ac03ff 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 @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.notification; import com.yahoo.collections.Pair; import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.TenantName; import com.yahoo.text.Text; import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.controller.Controller; @@ -41,6 +42,10 @@ public class NotificationsDb { this.curatorDb = curatorDb; } + public List<TenantName> listTenantsWithNotifications() { + return curatorDb.listTenantsWithNotifications(); + } + public List<Notification> listNotifications(NotificationSource source, boolean productionOnly) { return curatorDb.readNotifications(source.tenant()).stream() .filter(notification -> source.contains(notification.source()) && (!productionOnly || notification.source().isProduction())) diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java index 9b1a03039fb..6eaad63251c 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java @@ -625,7 +625,7 @@ public class CuratorDb { } - public List<TenantName> listNotifications() { + public List<TenantName> listTenantsWithNotifications() { return curator.getChildren(notificationsRoot).stream() .map(TenantName::from) .collect(Collectors.toUnmodifiableList()); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index 3b4154759ca..faa60afa176 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -144,6 +144,9 @@ import java.util.Optional; import java.util.OptionalLong; import java.util.Scanner; import java.util.StringJoiner; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -233,11 +236,12 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { private HttpResponse handleGET(Path path, HttpRequest request) { if (path.matches("/application/v4/")) return root(request); + if (path.matches("/application/v4/notifications")) return notifications(request, Optional.ofNullable(request.getProperty("tenant")), true); if (path.matches("/application/v4/tenant")) return tenants(request); if (path.matches("/application/v4/tenant/{tenant}")) return tenant(path.get("tenant"), request); if (path.matches("/application/v4/tenant/{tenant}/access/request/ssh")) return accessRequests(path.get("tenant"), request); if (path.matches("/application/v4/tenant/{tenant}/info")) return tenantInfo(path.get("tenant"), request); - if (path.matches("/application/v4/tenant/{tenant}/notifications")) return notifications(path.get("tenant"), request); + if (path.matches("/application/v4/tenant/{tenant}/notifications")) return notifications(request, Optional.of(path.get("tenant")), false); if (path.matches("/application/v4/tenant/{tenant}/secret-store/{name}/validate")) return validateSecretStore(path.get("tenant"), path.get("name"), request); if (path.matches("/application/v4/tenant/{tenant}/application")) return applications(path.get("tenant"), Optional.empty(), request); if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return application(path.get("tenant"), path.get("application"), request); @@ -542,26 +546,41 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { .withAddress(updateTenantInfoAddress(insp.field("address"), oldContact.address())); } - private HttpResponse notifications(String tenantName, HttpRequest request) { - NotificationSource notificationSource = new NotificationSource(TenantName.from(tenantName), - Optional.ofNullable(request.getProperty("application")).map(ApplicationName::from), - Optional.ofNullable(request.getProperty("instance")).map(InstanceName::from), - Optional.empty(), Optional.empty(), Optional.empty(), OptionalLong.empty()); - + private HttpResponse notifications(HttpRequest request, Optional<String> tenant, boolean includeTenantFieldInResponse) { + boolean productionOnly = showOnlyProductionInstances(request); + boolean excludeMessages = "true".equals(request.getProperty("excludeMessages")); Slime slime = new Slime(); Cursor notificationsArray = slime.setObject().setArray("notifications"); - controller.notificationsDb().listNotifications(notificationSource, showOnlyProductionInstances(request)) - .forEach(notification -> toSlime(notificationsArray.addObject(), notification)); + + tenant.map(t -> Stream.of(TenantName.from(t))) + .orElseGet(() -> controller.notificationsDb().listTenantsWithNotifications().stream()) + .flatMap(tenantName -> controller.notificationsDb().listNotifications(NotificationSource.from(tenantName), productionOnly).stream()) + .filter(notification -> + propertyEquals(request, "application", ApplicationName::from, notification.source().application()) && + propertyEquals(request, "instance", InstanceName::from, notification.source().instance()) && + propertyEquals(request, "zone", ZoneId::from, notification.source().zoneId()) && + propertyEquals(request, "job", JobType::fromJobName, notification.source().jobType()) && + propertyEquals(request, "type", Notification.Type::valueOf, Optional.of(notification.type())) && + propertyEquals(request, "level", Notification.Level::valueOf, Optional.of(notification.level()))) + .forEach(notification -> toSlime(notificationsArray.addObject(), notification, includeTenantFieldInResponse, excludeMessages)); return new SlimeJsonResponse(slime); } + private static <T> boolean propertyEquals(HttpRequest request, String property, Function<String, T> mapper, Optional<T> value) { + return Optional.ofNullable(request.getProperty(property)) + .map(propertyValue -> value.isPresent() && mapper.apply(propertyValue).equals(value.get())) + .orElse(true); + } - private static void toSlime(Cursor cursor, Notification notification) { + private static void toSlime(Cursor cursor, Notification notification, boolean includeTenantFieldInResponse, boolean excludeMessages) { cursor.setLong("at", notification.at().toEpochMilli()); cursor.setString("level", notificationLevelAsString(notification.level())); cursor.setString("type", notificationTypeAsString(notification.type())); - Cursor messagesArray = cursor.setArray("messages"); - notification.messages().forEach(messagesArray::addString); + if (!excludeMessages) { + Cursor messagesArray = cursor.setArray("messages"); + notification.messages().forEach(messagesArray::addString); + } + if (includeTenantFieldInResponse) cursor.setString("tenant", notification.source().tenant().value()); notification.source().application().ifPresent(application -> cursor.setString("application", application.value())); notification.source().instance().ifPresent(instance -> cursor.setString("instance", instance.value())); notification.source().zoneId().ifPresent(zoneId -> { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java index 516911b3c7b..61e9ea02acc 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java @@ -861,6 +861,10 @@ public class ApplicationApiTest extends ControllerContainerTest { ""); addNotifications(TenantName.from("tenant1")); + addNotifications(TenantName.from("tenant2")); + tester.assertResponse(request("/application/v4/notifications", GET) + .properties(Map.of("type", "applicationPackage", "excludeMessages", "true")).userIdentity(HOSTED_VESPA_OPERATOR), + new File("notifications-applicationPackage.json")); tester.assertResponse(request("/application/v4/tenant/tenant1/notifications", GET).userIdentity(USER_ID), new File("notifications-tenant1.json")); tester.assertResponse(request("/application/v4/tenant/tenant1/notifications", GET) diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-applicationPackage.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-applicationPackage.json new file mode 100644 index 00000000000..c0833ae0f05 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-applicationPackage.json @@ -0,0 +1,18 @@ +{ + "notifications": [ + { + "at": "(ignore)", + "level": "warning", + "type": "applicationPackage", + "tenant": "tenant1", + "application": "app1" + }, + { + "at": "(ignore)", + "level": "warning", + "type": "applicationPackage", + "tenant": "tenant2", + "application": "app1" + } + ] +} |