diff options
author | Valerij Fredriksen <valerij92@gmail.com> | 2021-04-29 15:25:17 +0200 |
---|---|---|
committer | Valerij Fredriksen <valerij92@gmail.com> | 2021-04-29 21:07:37 +0200 |
commit | e1fad8e05389ffff936667a7f10fec27aa933982 (patch) | |
tree | 038a2f35a640745a8368a91b7aab4702a6f2d315 /controller-server | |
parent | f4b248296b9322bbd59f8802ffe9f5d5b56c5ef0 (diff) |
Move notification level to notification
Diffstat (limited to 'controller-server')
11 files changed, 90 insertions, 59 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java index 32063bf9ba5..5843354ac1b 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java @@ -407,8 +407,8 @@ public class ApplicationController { .distinct() .collect(Collectors.toList())) .orElseGet(List::of); - if (warnings.isEmpty()) controller.notificationsDb().removeNotification(source, Notification.Type.APPLICATION_PACKAGE_WARNING); - else controller.notificationsDb().setNotification(source, Notification.Type.APPLICATION_PACKAGE_WARNING, warnings); + if (warnings.isEmpty()) controller.notificationsDb().removeNotification(source, Notification.Type.applicationPackage); + else controller.notificationsDb().setNotification(source, Notification.Type.applicationPackage, Notification.Level.warning, warnings); lockApplicationOrThrow(applicationId, application -> store(application.with(job.application().instance(), diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java index 0458a64c5a9..5bd43dfd695 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java @@ -712,12 +712,12 @@ public class InternalStepRunner implements StepRunner { private void updateConsoleNotification(Run run) { NotificationSource source = NotificationSource.from(run.id()); - Consumer<String> updater = msg -> controller.notificationsDb().setNotification(source, Notification.Type.DEPLOYMENT_FAILURE, msg); + Consumer<String> updater = msg -> controller.notificationsDb().setNotification(source, Notification.Type.deployment, Notification.Level.error, msg); switch (run.status()) { case aborted: return; // wait and see how the next run goes. case running: case success: - controller.notificationsDb().removeNotification(source, Notification.Type.DEPLOYMENT_FAILURE); + controller.notificationsDb().removeNotification(source, Notification.Type.deployment); return; case outOfCapacity: if ( ! run.id().type().environment().isTest()) updater.accept("lack of capacity. Please contact the Vespa team to request more!"); 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 299ef3ef50d..55a769b5960 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 @@ -11,12 +11,14 @@ import java.util.Objects; public class Notification { private final Instant at; private final Type type; + private final Level level; private final NotificationSource source; private final List<String> messages; - public Notification(Instant at, Type type, NotificationSource source, List<String> messages) { + public Notification(Instant at, Type type, Level level, NotificationSource source, List<String> messages) { this.at = Objects.requireNonNull(at, "at cannot be null"); this.type = Objects.requireNonNull(type, "type cannot be null"); + this.level = Objects.requireNonNull(level, "level cannot be null"); this.source = Objects.requireNonNull(source, "source cannot be null"); this.messages = List.copyOf(Objects.requireNonNull(messages, "messages cannot be null")); if (messages.size() < 1) throw new IllegalArgumentException("messages cannot be empty"); @@ -24,6 +26,7 @@ public class Notification { public Instant at() { return at; } public Type type() { return type; } + public Level level() { return level; } public NotificationSource source() { return source; } public List<String> messages() { return messages; } @@ -32,12 +35,13 @@ public class Notification { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Notification that = (Notification) o; - return at.equals(that.at) && type == that.type && source.equals(that.source) && messages.equals(that.messages); + return at.equals(that.at) && type == that.type && level == that.level && + source.equals(that.source) && messages.equals(that.messages); } @Override public int hashCode() { - return Objects.hash(at, type, source, messages); + return Objects.hash(at, type, level, source, messages); } @Override @@ -45,30 +49,22 @@ public class Notification { return "Notification{" + "at=" + at + ", type=" + type + + ", level=" + level + ", source=" + source + ", messages=" + messages + '}'; } public enum Level { - warning, error; + warning, error } public enum Type { - /** Warnings about usage of deprecated features in application package */ - APPLICATION_PACKAGE_WARNING(Level.warning), + /** Related to contents of application package, e.g. usage of deprecated features/syntax */ + applicationPackage, - /** Failure to deploy application package */ - DEPLOYMENT_FAILURE(Level.error); - - private final Level level; - Type(Level level) { - this.level = level; - } - - public Level level() { - return level; - } + /** Related to deployment of application, e.g. system test failure, out of capacity, internal errors, etc. */ + deployment } } 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 950dddfc056..f61d59552c4 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,8 @@ package com.yahoo.vespa.hosted.controller.notification; import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.controller.Controller; +import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics; +import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; import java.time.Clock; @@ -10,6 +12,9 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import static com.yahoo.vespa.hosted.controller.notification.Notification.Level; +import static com.yahoo.vespa.hosted.controller.notification.Notification.Type; + /** * Adds, updates and removes tenant notifications in ZK * @@ -35,26 +40,26 @@ public class NotificationsDb { .collect(Collectors.toUnmodifiableList()); } - public void setNotification(NotificationSource source, Notification.Type type, String message) { - setNotification(source, type, List.of(message)); + public void setNotification(NotificationSource source, Type type, Level level, String message) { + setNotification(source, type, level, List.of(message)); } /** * 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 */ - public void setNotification(NotificationSource source, Notification.Type type, List<String> messages) { + public void setNotification(NotificationSource source, Type type, Level level, List<String> messages) { try (Lock lock = curatorDb.lockNotifications(source.tenant())) { List<Notification> notifications = curatorDb.readNotifications(source.tenant()).stream() .filter(notification -> !source.equals(notification.source()) || type != notification.type()) .collect(Collectors.toCollection(ArrayList::new)); - notifications.add(new Notification(clock.instant(), type, source, messages)); + notifications.add(new Notification(clock.instant(), type, level, source, messages)); curatorDb.writeNotifications(source.tenant(), notifications); } } /** Remove the notification with the given source and type */ - public void removeNotification(NotificationSource source, Notification.Type type) { + public void removeNotification(NotificationSource source, Type type) { try (Lock lock = curatorDb.lockNotifications(source.tenant())) { List<Notification> initial = curatorDb.readNotifications(source.tenant()); List<Notification> filtered = initial.stream() 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 dcb485b9016..1cadfd67e77 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 @@ -15,7 +15,6 @@ import com.yahoo.vespa.hosted.controller.notification.Notification; import com.yahoo.vespa.hosted.controller.notification.NotificationSource; import java.util.List; -import java.util.function.Function; import java.util.stream.Collectors; /** @@ -35,6 +34,7 @@ public class NotificationsSerializer { private static final String notificationsFieldName = "notifications"; private static final String atFieldName = "at"; private static final String typeField = "type"; + private static final String levelField = "level"; private static final String messagesField = "messages"; private static final String applicationField = "application"; private static final String instanceField = "instance"; @@ -51,6 +51,7 @@ public class NotificationsSerializer { Cursor notificationObject = notificationsArray.addObject(); notificationObject.setLong(atFieldName, notification.at().toEpochMilli()); notificationObject.setString(typeField, asString(notification.type())); + notificationObject.setString(levelField, asString(notification.level())); Cursor messagesArray = notificationObject.setArray(messagesField); notification.messages().forEach(messagesArray::addString); @@ -72,9 +73,11 @@ public class NotificationsSerializer { } private static Notification fromInspector(TenantName tenantName, Inspector inspector) { - return new Notification( + Notification.Type type = typeFrom(inspector.field(typeField)); + return new Notification( Serializers.instant(inspector.field(atFieldName)), - typeFrom(inspector.field(typeField)), + type, + levelFrom(inspector.field(levelField), type), new NotificationSource( tenantName, Serializers.optionalString(inspector.field(applicationField)).map(ApplicationName::from), @@ -88,17 +91,38 @@ public class NotificationsSerializer { private static String asString(Notification.Type type) { switch (type) { - case APPLICATION_PACKAGE_WARNING: return "APPLICATION_PACKAGE_WARNING"; - case DEPLOYMENT_FAILURE: return "DEPLOYMENT_FAILURE"; + case applicationPackage: return "applicationPackage"; + case deployment: return "deployment"; default: throw new IllegalArgumentException("No serialization defined for notification type " + type); } } private static Notification.Type typeFrom(Inspector field) { switch (field.asString()) { - case "APPLICATION_PACKAGE_WARNING": return Notification.Type.APPLICATION_PACKAGE_WARNING; - case "DEPLOYMENT_FAILURE": return Notification.Type.DEPLOYMENT_FAILURE; + case "APPLICATION_PACKAGE_WARNING": // TODO (valerijf): Remove after 7.398+ + case "applicationPackage": return Notification.Type.applicationPackage; + case "DEPLOYMENT_FAILURE": // TODO (valerijf): Remove after 7.398+ + case "deployment": return Notification.Type.deployment; default: throw new IllegalArgumentException("Unknown serialized notification type value '" + field.asString() + "'"); } } + + private static String asString(Notification.Level level) { + switch (level) { + case warning: return "warning"; + case error: return "error"; + default: throw new IllegalArgumentException("No serialization defined for notification level " + level); + } + } + + private static Notification.Level levelFrom(Inspector field, Notification.Type type) { + if (!field.valid()) // TODO (valerijf): Remove after 7.398+ + return type == Notification.Type.deployment ? Notification.Level.error : Notification.Level.warning; + + switch (field.asString()) { + case "warning": return Notification.Level.warning; + case "error": return Notification.Level.error; + default: throw new IllegalArgumentException("Unknown serialized notification level value '" + field.asString() + "'"); + } + } } 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 994dc877182..1a5d3a873fa 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 @@ -497,7 +497,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { private static void toSlime(Cursor cursor, Notification notification) { cursor.setLong("at", notification.at().toEpochMilli()); - cursor.setString("level", notificatioLevelAsString(notification.type().level())); + cursor.setString("level", notificationLevelAsString(notification.level())); cursor.setString("type", notificationTypeAsString(notification.type())); Cursor messagesArray = cursor.setArray("messages"); notification.messages().forEach(messagesArray::addString); @@ -515,13 +515,13 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { private static String notificationTypeAsString(Notification.Type type) { switch (type) { - case APPLICATION_PACKAGE_WARNING: return "APPLICATION_PACKAGE_WARNING"; - case DEPLOYMENT_FAILURE: return "DEPLOYMENT_FAILURE"; + case applicationPackage: return "applicationPackage"; + case deployment: return "deployment"; default: throw new IllegalArgumentException("No serialization defined for notification type " + type); } } - private static String notificatioLevelAsString(Notification.Level level) { + private static String notificationLevelAsString(Notification.Level level) { switch (level) { case warning: return "warning"; case error: return "error"; diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java index 90d1ecb2f20..c5e35296f04 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java @@ -32,12 +32,12 @@ public class NotificationsDbTest { private static final TenantName tenant = TenantName.from("tenant1"); private static final List<Notification> notifications = List.of( - notification(1001, Notification.Type.DEPLOYMENT_FAILURE, NotificationSource.from(tenant), "tenant msg"), - notification(1101, Notification.Type.DEPLOYMENT_FAILURE, NotificationSource.from(TenantAndApplicationId.from(tenant.value(), "app1")), "app msg"), - notification(1201, Notification.Type.DEPLOYMENT_FAILURE, NotificationSource.from(ApplicationId.from(tenant.value(), "app2", "instance2")), "instance msg"), - notification(1301, Notification.Type.DEPLOYMENT_FAILURE, NotificationSource.from(new DeploymentId(ApplicationId.from(tenant.value(), "app2", "instance2"), ZoneId.from("prod", "us-north-2"))), "deployment msg"), - notification(1401, Notification.Type.DEPLOYMENT_FAILURE, NotificationSource.from(new DeploymentId(ApplicationId.from(tenant.value(), "app1", "instance1"), ZoneId.from("dev", "us-south-1")), ClusterSpec.Id.from("cluster1")), "cluster msg"), - notification(1501, Notification.Type.DEPLOYMENT_FAILURE, NotificationSource.from(new RunId(ApplicationId.from(tenant.value(), "app1", "instance1"), JobType.devUsEast1, 4)), "run id msg")); + notification(1001, Notification.Type.deployment, NotificationSource.from(tenant), "tenant msg"), + notification(1101, Notification.Type.deployment, NotificationSource.from(TenantAndApplicationId.from(tenant.value(), "app1")), "app msg"), + notification(1201, Notification.Type.deployment, NotificationSource.from(ApplicationId.from(tenant.value(), "app2", "instance2")), "instance msg"), + notification(1301, Notification.Type.deployment, NotificationSource.from(new DeploymentId(ApplicationId.from(tenant.value(), "app2", "instance2"), ZoneId.from("prod", "us-north-2"))), "deployment msg"), + notification(1401, Notification.Type.deployment, NotificationSource.from(new DeploymentId(ApplicationId.from(tenant.value(), "app1", "instance1"), ZoneId.from("dev", "us-south-1")), ClusterSpec.Id.from("cluster1")), "cluster msg"), + notification(1501, Notification.Type.deployment, NotificationSource.from(new RunId(ApplicationId.from(tenant.value(), "app1", "instance1"), JobType.devUsEast1, 4)), "run id msg")); private final ManualClock clock = new ManualClock(Instant.ofEpochSecond(12345)); private final MockCuratorDb curatorDb = new MockCuratorDb(); @@ -55,14 +55,14 @@ public class NotificationsDbTest { @Test public void add_test() { - Notification notification1 = notification(12345, Notification.Type.DEPLOYMENT_FAILURE, NotificationSource.from(ApplicationId.from(tenant.value(), "app2", "instance2")), "instance msg #2"); - Notification notification2 = notification(12345, Notification.Type.DEPLOYMENT_FAILURE, NotificationSource.from(ApplicationId.from(tenant.value(), "app3", "instance2")), "instance msg #3"); + Notification notification1 = notification(12345, Notification.Type.deployment, NotificationSource.from(ApplicationId.from(tenant.value(), "app2", "instance2")), "instance msg #2"); + Notification notification2 = notification(12345, Notification.Type.deployment, NotificationSource.from(ApplicationId.from(tenant.value(), "app3", "instance2")), "instance msg #3"); // Replace the 3rd notification - notificationsDb.setNotification(notification1.source(), notification1.type(), notification1.messages()); + notificationsDb.setNotification(notification1.source(), notification1.type(), Notification.Level.warning, notification1.messages()); // Notification for a new app, add without replacement - notificationsDb.setNotification(notification2.source(), notification2.type(), notification2.messages()); + notificationsDb.setNotification(notification2.source(), notification2.type(), Notification.Level.warning, notification2.messages()); List<Notification> expected = notificationIndices(0, 1, 3, 4, 5); expected.addAll(List.of(notification1, notification2)); @@ -72,10 +72,10 @@ public class NotificationsDbTest { @Test public void remove_single_test() { // Remove the 3rd notification - notificationsDb.removeNotification(NotificationSource.from(ApplicationId.from(tenant.value(), "app2", "instance2")), Notification.Type.DEPLOYMENT_FAILURE); + notificationsDb.removeNotification(NotificationSource.from(ApplicationId.from(tenant.value(), "app2", "instance2")), Notification.Type.deployment); // Removing something that doesn't exist is OK - notificationsDb.removeNotification(NotificationSource.from(ApplicationId.from(tenant.value(), "app3", "instance2")), Notification.Type.DEPLOYMENT_FAILURE); + notificationsDb.removeNotification(NotificationSource.from(ApplicationId.from(tenant.value(), "app3", "instance2")), Notification.Type.deployment); assertEquals(notificationIndices(0, 1, 3, 4, 5), curatorDb.readNotifications(tenant)); } @@ -102,6 +102,6 @@ public class NotificationsDbTest { } private static Notification notification(long secondsSinceEpoch, Notification.Type type, NotificationSource source, String... messages) { - return new Notification(Instant.ofEpochSecond(secondsSinceEpoch), type, source, List.of(messages)); + return new Notification(Instant.ofEpochSecond(secondsSinceEpoch), type, Notification.Level.warning, source, List.of(messages)); } } 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 f3f2d10cfd0..f13f92dee85 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 @@ -28,11 +28,13 @@ public class NotificationsSerializerTest { TenantName tenantName = TenantName.from("tenant1"); List<Notification> notifications = List.of( new Notification(Instant.ofEpochSecond(1234), - Notification.Type.APPLICATION_PACKAGE_WARNING, + Notification.Type.applicationPackage, + Notification.Level.warning, NotificationSource.from(TenantAndApplicationId.from(tenantName.value(), "app1")), List.of("Something something deprecated...")), new Notification(Instant.ofEpochSecond(2345), - Notification.Type.DEPLOYMENT_FAILURE, + Notification.Type.deployment, + Notification.Level.error, NotificationSource.from(new RunId(ApplicationId.from(tenantName.value(), "app1", "instance1"), JobType.systemTest, 12)), List.of("Failed to deploy: Out of capacity"))); @@ -40,12 +42,14 @@ public class NotificationsSerializerTest { assertEquals("{\"notifications\":[" + "{" + "\"at\":1234000," + - "\"type\":\"APPLICATION_PACKAGE_WARNING\"," + + "\"type\":\"applicationPackage\"," + + "\"level\":\"warning\"," + "\"messages\":[\"Something something deprecated...\"]," + "\"application\":\"app1\"" + "},{" + "\"at\":2345000," + - "\"type\":\"DEPLOYMENT_FAILURE\"," + + "\"type\":\"deployment\"," + + "\"level\":\"error\"," + "\"messages\":[\"Failed to deploy: Out of capacity\"]," + "\"application\":\"app1\"," + "\"instance\":\"instance1\"," + 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 0137ea7eeba..69cc2512aef 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 @@ -1641,11 +1641,13 @@ public class ApplicationApiTest extends ControllerContainerTest { private void addNotifications(TenantName tenantName) { tester.controller().notificationsDb().setNotification( NotificationSource.from(TenantAndApplicationId.from(tenantName.value(), "app1")), - Notification.Type.APPLICATION_PACKAGE_WARNING, + Notification.Type.applicationPackage, + Notification.Level.warning, "Something something deprecated..."); tester.controller().notificationsDb().setNotification( NotificationSource.from(new RunId(ApplicationId.from(tenantName.value(), "app2", "instance1"), JobType.systemTest, 12)), - Notification.Type.DEPLOYMENT_FAILURE, + Notification.Type.deployment, + Notification.Level.error, "Failed to deploy: Out of capacity"); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1-app2.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1-app2.json index ab8262e26bd..0e0ca7405ca 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1-app2.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1-app2.json @@ -3,7 +3,7 @@ { "at": "(ignore)", "level": "error", - "type": "DEPLOYMENT_FAILURE", + "type": "deployment", "messages": [ "Failed to deploy: Out of capacity" ], diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1.json index 2b2c03bb75a..7d3dd5e672f 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1.json @@ -3,7 +3,7 @@ { "at": "(ignore)", "level": "warning", - "type": "APPLICATION_PACKAGE_WARNING", + "type": "applicationPackage", "messages": [ "Something something deprecated..." ], @@ -12,7 +12,7 @@ { "at": "(ignore)", "level": "error", - "type": "DEPLOYMENT_FAILURE", + "type": "deployment", "messages": [ "Failed to deploy: Out of capacity" ], |